Compare commits
2 Commits
d73b446d68
...
ce573fe7b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce573fe7b6 | ||
|
|
39fbc54818 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.227",
|
"version": "1.7.228",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -180,21 +180,13 @@ export class AppController {
|
|||||||
if (this.settings.autoResumeOnStart) {
|
if (this.settings.autoResumeOnStart) {
|
||||||
const snapshot = this.manager.getSnapshot();
|
const snapshot = this.manager.getSnapshot();
|
||||||
const hasPending = Object.values(snapshot.session.items).some((item) => item.status === "queued" || item.status === "reconnect_wait");
|
const hasPending = Object.values(snapshot.session.items).some((item) => item.status === "queued" || item.status === "reconnect_wait");
|
||||||
if (hasPending) {
|
if (hasPending && this.hasAnyProviderToken(this.settings)) {
|
||||||
void this.manager.getStartConflicts().then((conflicts) => {
|
|
||||||
const hasConflicts = conflicts.length > 0;
|
|
||||||
if (this.hasAnyProviderToken(this.settings) && !hasConflicts) {
|
|
||||||
if (this.onStateHandler) {
|
if (this.onStateHandler) {
|
||||||
logger.info("Auto-Resume beim Start aktiviert (nach Konflikt-Check)");
|
this.beginAutoResume();
|
||||||
void this.manager.start().catch((err) => logger.warn(`Auto-Resume Start Fehler: ${String(err)}`));
|
|
||||||
} else {
|
} else {
|
||||||
this.autoResumePending = true;
|
this.autoResumePending = true;
|
||||||
logger.info("Auto-Resume beim Start vorgemerkt");
|
logger.info("Auto-Resume beim Start vorgemerkt");
|
||||||
}
|
}
|
||||||
} else if (hasConflicts) {
|
|
||||||
logger.info("Auto-Resume übersprungen: Start-Konflikte erkannt");
|
|
||||||
}
|
|
||||||
}).catch((err) => logger.warn(`getStartConflicts Fehler (constructor): ${String(err)}`));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,14 +243,27 @@ export class AppController {
|
|||||||
handler(this.manager.getSnapshot());
|
handler(this.manager.getSnapshot());
|
||||||
if (this.autoResumePending) {
|
if (this.autoResumePending) {
|
||||||
this.autoResumePending = false;
|
this.autoResumePending = false;
|
||||||
void this.manager.start().catch((err) => logger.warn(`Auto-Resume Start Fehler: ${String(err)}`));
|
this.beginAutoResume();
|
||||||
logger.info("Auto-Resume beim Start aktiviert");
|
|
||||||
} else {
|
} else {
|
||||||
this.manager.triggerIdleExtractions();
|
this.manager.triggerIdleExtractions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private beginAutoResume(): void {
|
||||||
|
void this.manager.getStartConflicts().then((conflicts) => {
|
||||||
|
const excludePackageIds = new Set(conflicts.map((conflict) => conflict.packageId));
|
||||||
|
if (excludePackageIds.size > 0) {
|
||||||
|
const names = conflicts.map((conflict) => conflict.packageName).join(", ");
|
||||||
|
logger.info(`Auto-Resume: ${excludePackageIds.size} Paket(e) mit Start-Konflikt zurückgehalten (${names}); übrige Pakete starten`);
|
||||||
|
} else {
|
||||||
|
logger.info("Auto-Resume beim Start aktiviert (keine Start-Konflikte)");
|
||||||
|
}
|
||||||
|
void this.manager.start(excludePackageIds.size > 0 ? { excludePackageIds } : undefined)
|
||||||
|
.catch((err) => logger.warn(`Auto-Resume Start Fehler: ${String(err)}`));
|
||||||
|
}).catch((err) => logger.warn(`Auto-Resume Konflikt-Check Fehler: ${String(err)}`));
|
||||||
|
}
|
||||||
|
|
||||||
public getSnapshot(): UiSnapshot {
|
public getSnapshot(): UiSnapshot {
|
||||||
return this.manager.getSnapshot();
|
return this.manager.getSnapshot();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3011,6 +3011,14 @@ export class DownloadManager extends EventEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasOwnCompletedOutput = pkg.itemIds.some((itemId) => {
|
||||||
|
const item = this.session.items[itemId];
|
||||||
|
return Boolean(item && item.status === "completed");
|
||||||
|
});
|
||||||
|
if (hasOwnCompletedOutput) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isPackageSpecificExtractDir(pkg)) {
|
if (!this.isPackageSpecificExtractDir(pkg)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -5562,7 +5570,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(options?: { excludePackageIds?: ReadonlySet<string> }): Promise<void> {
|
||||||
if (this.session.running) {
|
if (this.session.running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -5603,6 +5611,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (item.status !== "queued" && item.status !== "reconnect_wait") {
|
if (item.status !== "queued" && item.status !== "reconnect_wait") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (options?.excludePackageIds?.has(item.packageId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const pkg = this.session.packages[item.packageId];
|
const pkg = this.session.packages[item.packageId];
|
||||||
return Boolean(pkg && !pkg.cancelled && pkg.enabled);
|
return Boolean(pkg && !pkg.cancelled && pkg.enabled);
|
||||||
});
|
});
|
||||||
@ -5670,6 +5681,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.itemContributedBytes.clear();
|
this.itemContributedBytes.clear();
|
||||||
this.reservedTargetPaths.clear();
|
this.reservedTargetPaths.clear();
|
||||||
this.claimedTargetPathByItem.clear();
|
this.claimedTargetPathByItem.clear();
|
||||||
|
if (options?.excludePackageIds) {
|
||||||
|
for (const excluded of options.excludePackageIds) {
|
||||||
|
this.runPackageIds.delete(excluded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.session.running = true;
|
this.session.running = true;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
|
|||||||
@ -12244,3 +12244,121 @@ describe("download manager", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("start conflict guard + selective resume", () => {
|
||||||
|
function makeItem(id: string, packageId: string, status: string, fileName: string): any {
|
||||||
|
return {
|
||||||
|
id, packageId, url: `https://hoster.example/${id}`, provider: "realdebrid",
|
||||||
|
status, retries: 0, speedBps: 0, downloadedBytes: status === "completed" ? 100 : 0,
|
||||||
|
totalBytes: status === "completed" ? 100 : null, progressPercent: status === "completed" ? 100 : 0,
|
||||||
|
fileName, targetPath: "", resumable: true, attempts: 0, lastError: "", fullStatus: "",
|
||||||
|
createdAt: Date.now(), updatedAt: Date.now()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("does not flag a partially-downloaded package whose extract dir holds its own completed output", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-startconflict-own-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
const storagePaths = createStoragePaths(path.join(root, "state"));
|
||||||
|
initPackageLogs(storagePaths.baseDir);
|
||||||
|
initItemLogs(storagePaths.baseDir);
|
||||||
|
|
||||||
|
const session = emptySession();
|
||||||
|
const packageId = "pkg-own-output";
|
||||||
|
const extractDir = path.join(root, "extract", "OwnOutput");
|
||||||
|
fs.mkdirSync(extractDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(extractDir, "episode01.mkv"), Buffer.alloc(64, 7));
|
||||||
|
session.packageOrder = [packageId];
|
||||||
|
session.packages[packageId] = {
|
||||||
|
id: packageId, name: "OwnOutput",
|
||||||
|
outputDir: path.join(root, "downloads", "OwnOutput"), extractDir,
|
||||||
|
status: "queued", itemIds: ["own-done", "own-pending"], cancelled: false, enabled: true,
|
||||||
|
createdAt: Date.now(), updatedAt: Date.now()
|
||||||
|
} as any;
|
||||||
|
session.items["own-done"] = makeItem("own-done", packageId, "completed", "done.rar");
|
||||||
|
session.items["own-pending"] = makeItem("own-pending", packageId, "queued", "pending.rar");
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{ ...defaultSettings(), token: "rd-token", outputDir: path.join(root, "downloads"), extractDir: path.join(root, "extract") },
|
||||||
|
session, storagePaths
|
||||||
|
);
|
||||||
|
|
||||||
|
const conflicts = await manager.getStartConflicts();
|
||||||
|
expect(conflicts.map((c) => c.packageId)).not.toContain(packageId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flags a fresh package when its package-specific extract dir already holds files and it has no completed items", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-startconflict-fresh-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
const storagePaths = createStoragePaths(path.join(root, "state"));
|
||||||
|
initPackageLogs(storagePaths.baseDir);
|
||||||
|
initItemLogs(storagePaths.baseDir);
|
||||||
|
|
||||||
|
const session = emptySession();
|
||||||
|
const packageId = "pkg-fresh-conflict";
|
||||||
|
const extractDir = path.join(root, "extract", "FreshConflict");
|
||||||
|
fs.mkdirSync(extractDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(extractDir, "old-from-previous-run.mkv"), Buffer.alloc(64, 9));
|
||||||
|
session.packageOrder = [packageId];
|
||||||
|
session.packages[packageId] = {
|
||||||
|
id: packageId, name: "FreshConflict",
|
||||||
|
outputDir: path.join(root, "downloads", "FreshConflict"), extractDir,
|
||||||
|
status: "queued", itemIds: ["fresh-pending"], cancelled: false, enabled: true,
|
||||||
|
createdAt: Date.now(), updatedAt: Date.now()
|
||||||
|
} as any;
|
||||||
|
session.items["fresh-pending"] = makeItem("fresh-pending", packageId, "queued", "fresh.rar");
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{ ...defaultSettings(), token: "rd-token", outputDir: path.join(root, "downloads"), extractDir: path.join(root, "extract") },
|
||||||
|
session, storagePaths
|
||||||
|
);
|
||||||
|
|
||||||
|
const conflicts = await manager.getStartConflicts();
|
||||||
|
expect(conflicts.map((c) => c.packageId)).toContain(packageId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("start() holds excluded packages out of the run set and runs the rest", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-selective-resume-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
const storagePaths = createStoragePaths(path.join(root, "state"));
|
||||||
|
initPackageLogs(storagePaths.baseDir);
|
||||||
|
initItemLogs(storagePaths.baseDir);
|
||||||
|
|
||||||
|
const session = emptySession();
|
||||||
|
const runId = "pkg-run";
|
||||||
|
const holdId = "pkg-hold";
|
||||||
|
fs.mkdirSync(path.join(root, "downloads", "RunMe"), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(root, "downloads", "HoldMe"), { recursive: true });
|
||||||
|
session.packageOrder = [runId, holdId];
|
||||||
|
session.packages[runId] = {
|
||||||
|
id: runId, name: "RunMe",
|
||||||
|
outputDir: path.join(root, "downloads", "RunMe"), extractDir: path.join(root, "extract", "RunMe"),
|
||||||
|
status: "queued", itemIds: ["run-item"], cancelled: false, enabled: true,
|
||||||
|
createdAt: Date.now(), updatedAt: Date.now()
|
||||||
|
} as any;
|
||||||
|
session.packages[holdId] = {
|
||||||
|
id: holdId, name: "HoldMe",
|
||||||
|
outputDir: path.join(root, "downloads", "HoldMe"), extractDir: path.join(root, "extract", "HoldMe"),
|
||||||
|
status: "queued", itemIds: ["hold-item"], cancelled: false, enabled: true,
|
||||||
|
createdAt: Date.now(), updatedAt: Date.now()
|
||||||
|
} as any;
|
||||||
|
session.items["run-item"] = makeItem("run-item", runId, "queued", "run.rar");
|
||||||
|
session.items["hold-item"] = makeItem("hold-item", holdId, "queued", "hold.rar");
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{ ...defaultSettings(), token: "rd-token", maxParallel: 2, outputDir: path.join(root, "downloads"), extractDir: path.join(root, "extract") },
|
||||||
|
session, storagePaths
|
||||||
|
);
|
||||||
|
(manager as any).debridService.unrestrictLink = () => new Promise(() => {});
|
||||||
|
|
||||||
|
await manager.start({ excludePackageIds: new Set([holdId]) });
|
||||||
|
|
||||||
|
expect((manager as any).runPackageIds.has(runId)).toBe(true);
|
||||||
|
expect((manager as any).runPackageIds.has(holdId)).toBe(false);
|
||||||
|
expect((manager as any).runItemIds.has("run-item")).toBe(true);
|
||||||
|
expect((manager as any).runItemIds.has("hold-item")).toBe(false);
|
||||||
|
expect(session.items["hold-item"].status).toBe("queued");
|
||||||
|
|
||||||
|
manager.stop();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user