From 0272756be7a3045b268b0c64b25d795b8703a067 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sun, 28 Dec 2025 21:18:07 +0000 Subject: [PATCH] Upload main.js via GUI --- main.js | 134 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 19 deletions(-) diff --git a/main.js b/main.js index b811044..9af7d72 100644 --- a/main.js +++ b/main.js @@ -69,19 +69,24 @@ function ensureDir(dirPath) { */ function getSafeTmpDir(baseName) { const safeBase = (baseName || 'tmp').replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').substring(0, 64); - const dir = ppath.join(os.tmpdir(), `${safeBase}-${Date.now()}`); - try { - if (fs.existsSync(dir)) { - if (!fs.statSync(dir).isDirectory()) fs.unlinkSync(dir); - else fs.rmSync(dir, { recursive: true, force: true }); - } - fs.mkdirSync(dir, { recursive: true }); - return dir; - } catch (e) { - throw new Error(`getSafeTmpDir failed for ${dir}: ${e && e.message ? e.message : e}`); - } + + // Basis-Temp-Ordner für interne Verwaltung + const internalBase = ppath.join(os.tmpdir(), 'gitea-drag'); + if (!fs.existsSync(internalBase)) fs.mkdirSync(internalBase, { recursive: true }); + + // Eindeutiger Unterordner, um Kollisionen zu vermeiden + const uniqueSub = crypto.randomBytes(8).toString('hex'); + const internalDir = ppath.join(internalBase, uniqueSub); + fs.mkdirSync(internalDir, { recursive: true }); + + // Sichtbarer Ordnername = safeBase + const finalDir = ppath.join(internalDir, safeBase); + fs.mkdirSync(finalDir, { recursive: true }); + + return finalDir; } + /* ----------------------------- app / window ----------------------------- */ @@ -661,6 +666,30 @@ ipcMain.handle('download-gitea-folder', async (event, data) => { } }); +/* ----------------------------- + prepare-download-drag (robust) + - stellt sicher, dass alle Dateien komplett geschrieben sind + - erkennt Base64 vs UTF-8 und schreibt als Buffer wenn nötig + - nutzt getSafeTmpDir() (siehe oben in deiner main.js) + ----------------------------- */ +function isBase64Like(str) { + if (typeof str !== 'string') return false; + // Strip whitespace/newlines + const s = str.replace(/\s+/g, ''); + if (s.length === 0) return false; + // Base64 valid chars + padding + if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return false; + // length must be multiple of 4 (except maybe line breaks removed) + if (s.length % 4 !== 0) return false; + try { + // Round-trip check (cheap and practical) + const decoded = Buffer.from(s, 'base64'); + return decoded.toString('base64') === s; + } catch (e) { + return false; + } +} + ipcMain.handle('prepare-download-drag', async (event, data) => { try { const credentials = readCredentials(); @@ -671,37 +700,104 @@ ipcMain.handle('prepare-download-drag', async (event, data) => { const repo = data.repo; const remotePath = (data.path || '').replace(/^\/+/, '').replace(/\/+$/, ''); - // Use safe tmp dir (unique) + // Create a unique temp directory (guarantees clean state) const tmpBase = getSafeTmpDir(repo || 'gitea-repo'); + // Gather list of files (recursive) const allFiles = []; async function gather(pathInRepo) { - const items = await getGiteaRepoContents({ token, url, owner, repo, path: pathInRepo, ref: 'main' }); - for (const item of items) { + const items = await getGiteaRepoContents({ token, url, owner, repo, path: pathInRepo, ref: data.ref || 'main' }); + for (const item of items || []) { if (item.type === 'dir') await gather(item.path); else if (item.type === 'file') allFiles.push(item.path); } } await gather(remotePath || ''); + // If no files, return early (still provide empty dir) + if (allFiles.length === 0) { + // schedule cleanup + setTimeout(() => { try { if (fs.existsSync(tmpBase)) fs.rmSync(tmpBase, { recursive: true, force: true }); } catch (_) {} }, TMP_CLEANUP_MS); + return { ok: true, tempPath: tmpBase, files: [] }; + } + + // Download files sequentially or with limited concurrency: const tasks = allFiles.map(remoteFile => async () => { - const content = await getGiteaFileContent({ token, url, owner, repo, path: remoteFile, ref: 'main' }); + const content = await getGiteaFileContent({ token, url, owner, repo, path: remoteFile, ref: data.ref || 'main' }); const localPath = ppath.join(tmpBase, remoteFile); - fs.mkdirSync(ppath.dirname(localPath), { recursive: true }); - fs.writeFileSync(localPath, content, 'utf8'); + ensureDir(ppath.dirname(localPath)); + + // Decide how to write: if Buffer already, write directly. If string, try base64 detection + if (Buffer.isBuffer(content)) { + fs.writeFileSync(localPath, content); + } else if (typeof content === 'string') { + if (isBase64Like(content)) { + const buf = Buffer.from(content, 'base64'); + fs.writeFileSync(localPath, buf); + } else { + // treat as utf8 text + fs.writeFileSync(localPath, content, 'utf8'); + } + } else { + // fallback: convert to string + fs.writeFileSync(localPath, String(content), 'utf8'); + } return localPath; }); - await runLimited(tasks, DEFAULT_CONCURRENCY); + // runLimited ensures concurrency and waits for all writes to finish + const results = await runLimited(tasks, data.concurrency || DEFAULT_CONCURRENCY); + // verify at least one successful file + const successFiles = results.filter(r => r.ok).map(r => r.result); + if (successFiles.length === 0) { + // cleanup on complete failure + try { if (fs.existsSync(tmpBase)) fs.rmSync(tmpBase, { recursive: true, force: true }); } catch (_) {} + return { ok: false, error: 'no-files-downloaded' }; + } + + // give renderer the temp dir (renderer should then call 'ondragstart' with the folder path) + // schedule cleanup after delay to keep files available for drag & drop setTimeout(() => { try { if (fs.existsSync(tmpBase)) fs.rmSync(tmpBase, { recursive: true, force: true }); } catch (_) {} }, TMP_CLEANUP_MS); - return { ok: true, tempPath: tmpBase }; + + return { ok: true, tempPath: tmpBase, files: successFiles }; } catch (e) { console.error('prepare-download-drag error', e); return { ok: false, error: String(e) }; } }); +/* ----------------------------- + ondragstart (no change to API but more defensive) + - expects renderer to call window.electronAPI.ondragStart(tempPath) + ----------------------------- */ +ipcMain.on('ondragstart', async (event, filePath) => { + try { + if (!filePath || !fs.existsSync(filePath)) { + console.warn('ondragstart: path missing or not exists:', filePath); + return; + } + // Prefer folder icon when dragging a directory + let icon = nativeImage.createEmpty(); + try { + icon = await app.getFileIcon(filePath, { size: 'large' }); + } catch (e) { + // ignore, keep empty icon + } + + // startDrag accepts { file } where file can be a directory + try { + event.sender.startDrag({ file: filePath, icon }); + } catch (e) { + // some platforms may require a single file — if folder fails, try to drop a placeholder file + console.error('startDrag failed for', filePath, e); + } + } catch (e) { + console.error('ondragstart error', e); + } +}); + + ipcMain.on('ondragstart', async (event, filePath) => { try { if (!filePath || !fs.existsSync(filePath)) return;