diff --git a/main.js b/main.js index 9af7d72..af70c2b 100644 --- a/main.js +++ b/main.js @@ -14,7 +14,20 @@ const { listGiteaRepos, getGiteaRepoContents, getGiteaFileContent, - uploadGiteaFile + uploadGiteaFile, + getGiteaCommits, + getGiteaCommit, + getGiteaCommitDiff, + getGiteaCommitFiles, + searchGiteaCommits, + getGiteaBranches, + listGiteaReleases, + getGiteaRelease, + createGiteaRelease, + editGiteaRelease, + deleteGiteaRelease, + uploadReleaseAsset, + deleteReleaseAsset } = require('./src/git/apiHandler.js'); const { initRepo, commitAndPush, getBranches, getCommitLogs } = require('./src/git/gitHandler.js'); @@ -329,6 +342,52 @@ ipcMain.handle('getCommitLogs', async (event, data) => { } }); +/* ---------------------------------------------------------------- + Neue/kompatible Handler: 'get-commits' (Renderer verwendet ggf. diesen) + - Unterstützt: + 1) Lokale Git-Logs via data.folder -> getCommitLogs + 2) Gitea Commits via owner+repo -> getGiteaCommits + ---------------------------------------------------------------- */ +ipcMain.handle('get-commits', async (event, data) => { + try { + // 1) Lokale Git-Logs (folder vorhanden) + if (data && data.folder) { + const logs = await getCommitLogs(data.folder, data.count || 50); + return { ok: true, logs }; + } + + // 2) Gitea-Commits (owner/repo vorhanden) + if (data && data.owner && data.repo) { + const credentials = readCredentials(); + const token = (data && data.token) || (credentials && credentials.giteaToken); + const url = (data && data.url) || (credentials && credentials.giteaURL); + if (!token || !url) return { ok: false, error: 'missing-token-or-url' }; + + // map optional params + const page = data.page || 1; + const limit = data.limit || 50; + const sha = data.sha; // optional branch/sha filter + + const commits = await getGiteaCommits({ + token, + url, + owner: data.owner, + repo: data.repo, + page, + limit, + sha + }); + + return { ok: true, commits }; + } + + return { ok: false, error: 'invalid-parameters-for-get-commits' }; + } catch (e) { + console.error('get-commits error', e); + return { ok: false, error: String(e) }; + } +}); + /* ----------------------------- Local file tree functions ----------------------------- */ @@ -387,6 +446,21 @@ ipcMain.handle('readFile', async (event, data) => { } }); +ipcMain.handle('writeFile', async (event, data) => { + try { + if (!data || !data.path) return { ok: false, error: 'invalid-path' }; + const dir = ppath.dirname(data.path); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(data.path, data.content || '', 'utf8'); + return { ok: true }; + } catch (e) { + console.error('writeFile error', e); + return { ok: false, error: String(e) }; + } +}); + ipcMain.handle('deleteFile', async (event, data) => { try { if (!data || !data.path || !fs.existsSync(data.path)) return { ok: false, error: 'file-not-found' }; @@ -458,6 +532,83 @@ ipcMain.handle('get-gitea-file-content', async (event, data) => { } }); +// Alias für Editor: read-gitea-file (für Text und Bilder) +ipcMain.handle('read-gitea-file', async (event, data) => { + try { + const credentials = readCredentials(); + const token = (data && data.token) || (credentials && credentials.giteaToken); + const url = (data && data.url) || (credentials && credentials.giteaURL); + if (!token || !url) { + console.error('Missing token or URL'); + return { ok: false, error: 'missing-token-or-url' }; + } + + const owner = data.owner; + const repo = data.repo; + const p = data.path; + const ref = data.ref || 'main'; + + console.log(`read-gitea-file: ${owner}/${repo}/${p} (ref: ${ref})`); + + // Prüfe ob es eine Bilddatei ist + const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(p); + + if (isImage) { + // Für Bilder: Lade als Base64 + console.log('Loading as image (Base64)'); + const apiUrl = `${url}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/raw/${encodeURIComponent(p)}?ref=${ref}`; + console.log('Image URL:', apiUrl); + + return new Promise((resolve) => { + try { + const protocol = url.startsWith('https') ? https : http; + protocol.get(apiUrl, { + headers: { + 'Authorization': `token ${token}`, + 'User-Agent': 'Git-Manager-GUI' + } + }, (res) => { + console.log(`Image response status: ${res.statusCode}`); + if (res.statusCode !== 200) { + resolve({ ok: false, error: `HTTP ${res.statusCode}` }); + return; + } + + const chunks = []; + res.on('data', chunk => chunks.push(chunk)); + res.on('end', () => { + try { + const buffer = Buffer.concat(chunks); + const base64 = buffer.toString('base64'); + console.log(`Image loaded: ${base64.length} bytes`); + resolve({ ok: true, content: base64 }); + } catch (e) { + console.error('Base64 conversion error:', e.message); + resolve({ ok: false, error: String(e) }); + } + }); + }).on('error', (e) => { + console.error('Image HTTP error:', e.message); + resolve({ ok: false, error: String(e) }); + }); + } catch (e) { + console.error('Image load try error:', e.message); + resolve({ ok: false, error: String(e) }); + } + }); + } else { + // Für Text: Nutze normale Funktion + console.log('Loading as text file'); + const content = await getGiteaFileContent({ token, url, owner, repo, path: p, ref }); + console.log(`Text file loaded: ${content.length} chars`); + return { ok: true, content }; + } + } catch (e) { + console.error('read-gitea-file error:', e.message, e.stack); + return { ok: false, error: String(e) }; + } +}); + ipcMain.handle('upload-gitea-file', async (event, data) => { try { const credentials = readCredentials(); @@ -515,6 +666,41 @@ ipcMain.handle('upload-gitea-file', async (event, data) => { } }); +// Alias für Editor: write-gitea-file +ipcMain.handle('write-gitea-file', async (event, data) => { + try { + const credentials = readCredentials(); + const token = (data && data.token) || (credentials && credentials.giteaToken); + const url = (data && data.url) || (credentials && credentials.giteaURL); + if (!token || !url) return { ok: false, error: 'missing-token-or-url' }; + + const owner = data.owner; + const repo = data.repo; + const path = data.path; + const content = data.content || ''; + const ref = data.ref || 'main'; + + // Konvertiere Content zu Base64 + const base64 = Buffer.from(content, 'utf8').toString('base64'); + + const uploaded = await uploadGiteaFile({ + token, + url, + owner, + repo, + path, + contentBase64: base64, + message: `Edit ${path} via Git Manager GUI`, + branch: ref + }); + + return { ok: true, uploaded }; + } catch (e) { + console.error('write-gitea-file error', e); + return { ok: false, error: String(e) }; + } +}); + ipcMain.handle('upload-local-folder-to-gitea', async (event, data) => { try { const credentials = readCredentials(); @@ -780,6 +966,7 @@ ipcMain.on('ondragstart', async (event, filePath) => { // Prefer folder icon when dragging a directory let icon = nativeImage.createEmpty(); try { + // ask platform for file icon; large size for clearer drag icon icon = await app.getFileIcon(filePath, { size: 'large' }); } catch (e) { // ignore, keep empty icon @@ -798,15 +985,6 @@ ipcMain.on('ondragstart', async (event, filePath) => { }); -ipcMain.on('ondragstart', async (event, filePath) => { - try { - if (!filePath || !fs.existsSync(filePath)) return; - let icon = nativeImage.createEmpty(); - try { icon = await app.getFileIcon(filePath); } catch (e) {} - try { event.sender.startDrag({ file: filePath, icon: icon }); } catch (e) { console.error('startDrag failed', e); } - } catch (e) { console.error('ondragstart error', e); } -}); - ipcMain.handle('delete-gitea-repo', async (event, data) => { try { const credentials = readCredentials(); @@ -1097,3 +1275,314 @@ ipcMain.handle('upload-and-push', async (event, data) => { return { ok: false, error: String(e) }; } }); +/* ================================ + RELEASE MANAGEMENT IPC HANDLERS + ================================ */ + +// List all releases for a repository +ipcMain.handle('list-releases', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const releases = await listGiteaReleases({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo + }); + + return { ok: true, releases }; + } catch (error) { + console.error('list-releases error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Get a specific release by tag +ipcMain.handle('get-release', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const release = await getGiteaRelease({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + tag: data.tag + }); + + return { ok: true, release }; + } catch (error) { + console.error('get-release error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Create a new release +ipcMain.handle('create-release', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const releaseData = { + tag_name: data.tag_name, + name: data.name, + body: data.body, + draft: data.draft, + prerelease: data.prerelease, + target_commitish: data.target_commitish + }; + + const release = await createGiteaRelease({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + data: releaseData + }); + + return { ok: true, release }; + } catch (error) { + console.error('create-release error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Edit/update a release +ipcMain.handle('edit-release', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const updateData = {}; + if (data.name !== undefined) updateData.name = data.name; + if (data.body !== undefined) updateData.body = data.body; + if (data.draft !== undefined) updateData.draft = data.draft; + if (data.prerelease !== undefined) updateData.prerelease = data.prerelease; + + const release = await editGiteaRelease({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + releaseId: data.releaseId, + data: updateData + }); + + return { ok: true, release }; + } catch (error) { + console.error('edit-release error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Delete a release +ipcMain.handle('delete-release', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + await deleteGiteaRelease({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + releaseId: data.releaseId + }); + + return { ok: true }; + } catch (error) { + console.error('delete-release error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Upload a release asset +ipcMain.handle('upload-release-asset', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const asset = await uploadReleaseAsset({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + releaseId: data.releaseId, + filePath: data.filePath, + fileName: data.fileName + }); + + return { ok: true, asset }; + } catch (error) { + console.error('upload-release-asset error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Delete a release asset +ipcMain.handle('delete-release-asset', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + await deleteReleaseAsset({ + token: credentials.giteaToken, + url: credentials.giteaURL, + owner: data.owner, + repo: data.repo, + assetId: data.assetId + }); + + return { ok: true }; + } catch (error) { + console.error('delete-release-asset error:', error); + return { ok: false, error: String(error) }; + } +}); + +// Download release archive (ZIP/TAR) +ipcMain.handle('download-release-archive', async (event, data) => { + try { + const credentials = readCredentials(); + if (!credentials) return { ok: false, error: 'no-credentials' }; + + const base = credentials.giteaURL.replace(/\/$/, ''); + const archiveUrl = `${base}/${data.owner}/${data.repo}/archive/${data.tag}.zip`; + + // Ask user where to save + const result = await dialog.showSaveDialog({ + defaultPath: `${data.repo}-${data.tag}.zip`, + filters: [{ name: 'ZIP Archive', extensions: ['zip'] }] + }); + + if (result.canceled) return { ok: false, canceled: true }; + + const savePath = result.filePath; + + // Download archive + const axios = require('axios'); + const fs = require('fs'); + const writer = fs.createWriteStream(savePath); + + const response = await axios({ + url: archiveUrl, + method: 'GET', + responseType: 'stream', + headers: { Authorization: `token ${credentials.giteaToken}` } + }); + + response.data.pipe(writer); + + return new Promise((resolve, reject) => { + writer.on('finish', () => resolve({ ok: true, savedTo: savePath })); + writer.on('error', (err) => reject(err)); + }); + + } catch (error) { + console.error('download-release-archive error:', error); + return { ok: false, error: String(error) }; + } +}); + +/* ======================== + LOCAL COMMIT HANDLERS + ======================== */ +ipcMain.handle('get-local-commits', async (event, data) => { + try { + const { getCommitLogs } = require('./src/git/gitHandler.js'); + const commits = await getCommitLogs(data.folderPath, data.branch || 'HEAD'); + return { ok: true, commits }; + } catch (error) { + console.error('get-local-commits error:', error); + return { ok: false, error: String(error) }; + } +}); + +ipcMain.handle('get-local-commit-details', async (event, data) => { + try { + const { getCommitDetails } = require('./src/git/gitHandler.js'); + const details = await getCommitDetails(data.folderPath, data.sha); + return { ok: true, ...details }; + } catch (error) { + console.error('get-local-commit-details error:', error); + return { ok: false, error: String(error) }; + } +}); + +ipcMain.handle('get-local-commit-diff', async (event, data) => { + try { + const { getCommitDiff } = require('./src/git/gitHandler.js'); + const diff = await getCommitDiff(data.folderPath, data.sha); + return { ok: true, diff }; + } catch (error) { + console.error('get-local-commit-diff error:', error); + return { ok: false, error: String(error) }; + } +}); + +ipcMain.handle('get-local-commit-files', async (event, data) => { + try { + const { getCommitDetails } = require('./src/git/gitHandler.js'); + const details = await getCommitDetails(data.folderPath, data.sha); + return { ok: true, files: details.files || [], stats: { additions: 0, deletions: 0 } }; + } catch (error) { + console.error('get-local-commit-files error:', error); + return { ok: false, error: String(error) }; + } +}); + +/* ======================== + GITEA COMMIT HANDLERS + ======================== */ +ipcMain.handle('get-commit-diff', async (event, data) => { + try { + const credentials = readCredentials(); + const token = (data && data.token) || (credentials && credentials.giteaToken); + const url = (data && data.url) || (credentials && credentials.giteaURL); + + if (!token || !url) { + return { ok: false, error: 'missing-token-or-url' }; + } + + const diff = await getGiteaCommitDiff({ + token, + url, + owner: data.owner, + repo: data.repo, + sha: data.sha + }); + + return { ok: true, diff }; + } catch (error) { + console.error('get-commit-diff error:', error); + return { ok: false, error: String(error) }; + } +}); + +ipcMain.handle('get-commit-files', async (event, data) => { + try { + const credentials = readCredentials(); + const token = (data && data.token) || (credentials && credentials.giteaToken); + const url = (data && data.url) || (credentials && credentials.giteaURL); + + if (!token || !url) { + return { ok: false, error: 'missing-token-or-url' }; + } + + const result = await getGiteaCommitFiles({ + token, + url, + owner: data.owner, + repo: data.repo, + sha: data.sha + }); + + return { ok: true, files: result.files, stats: result.stats }; + } catch (error) { + console.error('get-commit-files error:', error); + return { ok: false, error: String(error) }; + } +});