Upload via Git Manager GUI
This commit is contained in:
53
main.js
53
main.js
@@ -283,7 +283,7 @@ function tryReadWrappedCredentials(raw) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'safeStorage-unavailable',
|
reason: 'safeStorage-unavailable',
|
||||||
message: 'Gespeicherte Zugangsdaten koennen in dieser Sitzung nicht gelesen werden. Bitte neu anmelden.'
|
message: 'Gespeicherte Zugangsdaten können in dieser Sitzung nicht gelesen werden. Bitte neu anmelden.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ function tryReadWrappedCredentials(raw) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'safeStorage-decrypt-failed',
|
reason: 'safeStorage-decrypt-failed',
|
||||||
message: 'Gespeicherte Zugangsdaten konnten nicht entschluesselt werden. Bitte Token neu eingeben.'
|
message: 'Gespeicherte Zugangsdaten konnten nicht entschlüsselt werden. Bitte Token neu eingeben.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ function readCredentialsFromFile(filePath) {
|
|||||||
credentials: null,
|
credentials: null,
|
||||||
needsRewrite: false,
|
needsRewrite: false,
|
||||||
reason: 'legacy-decrypt-failed',
|
reason: 'legacy-decrypt-failed',
|
||||||
message: 'Gespeicherte Zugangsdaten waren unlesbar und wurden zurueckgesetzt. Bitte Zugangsdaten neu eingeben.',
|
message: 'Gespeicherte Zugangsdaten waren unlesbar und wurden zurückgesetzt. Bitte Zugangsdaten neu eingeben.',
|
||||||
filePath
|
filePath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -678,7 +678,7 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Externe Fenster immer blockieren und nur erlaubte URLs explizit im System-Browser oeffnen.
|
// Externe Fenster immer blockieren und nur erlaubte URLs explizit im System-Browser öffnen.
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
if (isAllowedExternalUrl(url)) {
|
if (isAllowedExternalUrl(url)) {
|
||||||
shell.openExternal(url).catch(() => {});
|
shell.openExternal(url).catch(() => {});
|
||||||
@@ -833,7 +833,7 @@ function mapIpcError(errorLike) {
|
|||||||
if (raw.includes('timeout') || raw.includes('econnaborted')) {
|
if (raw.includes('timeout') || raw.includes('econnaborted')) {
|
||||||
return 'Zeitüberschreitung bei der Verbindung. Bitte Netzwerk oder Server prüfen.';
|
return 'Zeitüberschreitung bei der Verbindung. Bitte Netzwerk oder Server prüfen.';
|
||||||
}
|
}
|
||||||
if (raw.includes('http://') || raw.includes('https://') || raw.includes('ungueltige gitea url') || raw.includes('ungültige gitea url') || raw.includes('invalid')) {
|
if (raw.includes('http://') || raw.includes('https://') || raw.includes('ungültige gitea url') || raw.includes('ungültige gitea url') || raw.includes('invalid')) {
|
||||||
return 'Ungültige URL. Beispiel für IPv6: http://[2001:db8::1]:3000';
|
return 'Ungültige URL. Beispiel für IPv6: http://[2001:db8::1]:3000';
|
||||||
}
|
}
|
||||||
return String(errorLike && errorLike.message ? errorLike.message : errorLike);
|
return String(errorLike && errorLike.message ? errorLike.message : errorLike);
|
||||||
@@ -1714,7 +1714,7 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeDeleteBatch(files) {
|
async function executeDeleteBatch(files, concurrency = 2) {
|
||||||
const deleteOps = files.map(f =>
|
const deleteOps = files.map(f =>
|
||||||
async () => {
|
async () => {
|
||||||
const res = await giteaDeleteFile(f.path, f.sha);
|
const res = await giteaDeleteFile(f.path, f.sha);
|
||||||
@@ -1722,8 +1722,8 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Gitea: mit Concurrency=2 + Retry-Logik bei 409-Konflikten für bessere Performance
|
// Concurrency kann dynamisch reduziert werden, um API-Konflikte bei Nachloeschungen zu vermeiden
|
||||||
const deleteResults = await runParallel(deleteOps, 2);
|
const deleteResults = await runParallel(deleteOps, concurrency);
|
||||||
const failedEntries = deleteResults.filter(r => !r.ok || !(r.result && r.result.ok));
|
const failedEntries = deleteResults.filter(r => !r.ok || !(r.result && r.result.ok));
|
||||||
return { deleteResults, failedEntries };
|
return { deleteResults, failedEntries };
|
||||||
}
|
}
|
||||||
@@ -1741,38 +1741,31 @@ ipcMain.handle('deleteFile', async (event, data) => {
|
|||||||
return { ok: true, deleted: 0 };
|
return { ok: true, deleted: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete attempt
|
// Delete attempt (schnell) + sequenzieller Fallback bei Konflikten
|
||||||
let { failedEntries } = await executeDeleteBatch(filesToDelete);
|
let { failedEntries } = await executeDeleteBatch(filesToDelete, 2);
|
||||||
|
|
||||||
|
// Bei Teilfehlern sofort sequenziellen Retry fahren (stabiler bei parallelen Commits)
|
||||||
if (failedEntries.length > 0) {
|
if (failedEntries.length > 0) {
|
||||||
const failedFiles = failedEntries
|
const failedFiles = failedEntries
|
||||||
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
||||||
.join(', ');
|
.join(', ');
|
||||||
logger.error('deleteFile', `Failed to delete ${failedEntries.length} files`, { files: failedFiles });
|
logger.warn('deleteFile', `Initial delete had ${failedEntries.length} failures, retrying sequentially`, { files: failedFiles });
|
||||||
return { ok: false, error: `${failedEntries.length} von ${filesToDelete.length} Dateien konnten nicht gelöscht werden` };
|
}
|
||||||
|
|
||||||
|
// Verification + bis zu zwei sequenzielle Nachlöschversuche auf verbleibende Dateien
|
||||||
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||||||
|
if (!(await pathStillExists(filePath))) break;
|
||||||
|
const remaining = await collectAllFiles(filePath);
|
||||||
|
if (remaining.length === 0) break;
|
||||||
|
({ failedEntries } = await executeDeleteBatch(remaining, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verification + one automatic retry on leftovers
|
|
||||||
if (await pathStillExists(filePath)) {
|
if (await pathStillExists(filePath)) {
|
||||||
const remaining = await collectAllFiles(filePath);
|
const remaining = await collectAllFiles(filePath);
|
||||||
if (remaining.length > 0) {
|
const remainingPreview = remaining.slice(0, 10).map(f => f.path).join(', ');
|
||||||
({ failedEntries } = await executeDeleteBatch(remaining));
|
|
||||||
if (failedEntries.length > 0) {
|
|
||||||
const failedFiles = failedEntries
|
|
||||||
.map(r => (r.result && r.result.path) ? r.result.path : 'unknown')
|
|
||||||
.join(', ');
|
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: `Nachloeschung fehlgeschlagen. ${failedEntries.length} Dateien konnten nicht gelöscht werden (${failedFiles}).`
|
error: `Löschen unvollständig: ${remaining.length} Dateien sind noch vorhanden (${remainingPreview}${remaining.length > 10 ? ', ...' : ''}).`
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await pathStillExists(filePath)) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: 'Löschen unvollständig: Der Ordnerpfad ist nach zwei Versuchen noch vorhanden.'
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3844,7 +3837,7 @@ ipcMain.handle('import-settings-bundle', async () => {
|
|||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
|
|
||||||
if (!parsed || parsed.bundleType !== 'git-manager-settings-backup') {
|
if (!parsed || parsed.bundleType !== 'git-manager-settings-backup') {
|
||||||
return { ok: false, error: 'Ungueltige Backup-Datei.' };
|
return { ok: false, error: 'Ungültige Backup-Datei.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = readCredentials() || {};
|
const existing = readCredentials() || {};
|
||||||
|
|||||||
Reference in New Issue
Block a user