mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: extend crash recovery for pre-built bundle artifacts
This commit is contained in:
@@ -378,6 +378,40 @@ export function recoverInterruptedInstalls(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Delete staging-{bundleId}/ directories (incomplete extraction)
|
||||
try {
|
||||
const aiEntries = readdirSync(AI_DIR, { withFileTypes: true });
|
||||
for (const entry of aiEntries) {
|
||||
if (entry.isDirectory() && entry.name.startsWith("staging-")) {
|
||||
const stagingPath = join(AI_DIR, entry.name);
|
||||
rmSync(stagingPath, { recursive: true, force: true });
|
||||
console.info(`[feature-status] Deleted orphaned ${entry.name}/`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// AI_DIR may not exist yet
|
||||
}
|
||||
|
||||
// 7. Clean up staging/ download directory (partial downloads, orphaned tars)
|
||||
const downloadStaging = join(AI_DIR, "staging");
|
||||
if (existsSync(downloadStaging)) {
|
||||
try {
|
||||
const files = readdirSync(downloadStaging);
|
||||
for (const file of files) {
|
||||
const filePath = join(downloadStaging, file);
|
||||
if (file.endsWith(".partial") || file.endsWith(".meta")) {
|
||||
unlinkSync(filePath);
|
||||
console.info(`[feature-status] Deleted stale download file: ${file}`);
|
||||
} else if (file.endsWith(".tar.gz")) {
|
||||
unlinkSync(filePath);
|
||||
console.info(`[feature-status] Deleted orphaned archive: ${file}`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
|
||||
@@ -438,6 +438,48 @@ describe("Crash recovery - recoverInterruptedInstalls", () => {
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(mod.isFeatureInstalled("ocr")).toBe(false);
|
||||
});
|
||||
|
||||
it("deletes staging-{bundleId}/ directories", () => {
|
||||
const staging = join(aiDir, "staging-ocr");
|
||||
mkdirSync(staging, { recursive: true });
|
||||
writeFileSync(join(staging, "somefile"), "data");
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(existsSync(staging)).toBe(false);
|
||||
});
|
||||
|
||||
it("deletes multiple staging directories", () => {
|
||||
mkdirSync(join(aiDir, "staging-ocr"), { recursive: true });
|
||||
mkdirSync(join(aiDir, "staging-upscale-enhance"), { recursive: true });
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(existsSync(join(aiDir, "staging-ocr"))).toBe(false);
|
||||
expect(existsSync(join(aiDir, "staging-upscale-enhance"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does NOT delete non-staging directories", () => {
|
||||
const venvDir = join(aiDir, "venv");
|
||||
mkdirSync(venvDir, { recursive: true });
|
||||
writeFileSync(join(venvDir, "file"), "data");
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(existsSync(venvDir)).toBe(true);
|
||||
});
|
||||
|
||||
it("deletes stale download files in staging/", () => {
|
||||
const staging = join(aiDir, "staging");
|
||||
mkdirSync(staging, { recursive: true });
|
||||
writeFileSync(join(staging, "bundle.tar.gz.partial"), "partial");
|
||||
writeFileSync(join(staging, "bundle.tar.gz.meta"), '{"bytesDownloaded":0}');
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(existsSync(join(staging, "bundle.tar.gz.partial"))).toBe(false);
|
||||
expect(existsSync(join(staging, "bundle.tar.gz.meta"))).toBe(false);
|
||||
});
|
||||
|
||||
it("deletes orphaned .tar.gz in staging when bundle not installed", () => {
|
||||
const staging = join(aiDir, "staging");
|
||||
mkdirSync(staging, { recursive: true });
|
||||
writeFileSync(join(staging, "background-removal-amd64-gpu.tar.gz"), "tar-data");
|
||||
mod.recoverInterruptedInstalls();
|
||||
expect(existsSync(join(staging, "background-removal-amd64-gpu.tar.gz"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Composite state - getFeatureStates", () => {
|
||||
|
||||
Reference in New Issue
Block a user