@psychout98/tadaima 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +91 -0
- package/dist/daemon.js.map +1 -0
- package/dist/download-handler.d.ts +15 -0
- package/dist/download-handler.d.ts.map +1 -0
- package/dist/download-handler.js +203 -0
- package/dist/download-handler.js.map +1 -0
- package/dist/downloader.d.ts +11 -0
- package/dist/downloader.d.ts.map +1 -0
- package/dist/downloader.js +65 -0
- package/dist/downloader.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +74 -0
- package/dist/logger.js.map +1 -0
- package/dist/organizer.d.ts +12 -0
- package/dist/organizer.d.ts.map +1 -0
- package/dist/organizer.js +42 -0
- package/dist/organizer.js.map +1 -0
- package/dist/rd-client.d.ts +25 -0
- package/dist/rd-client.d.ts.map +1 -0
- package/dist/rd-client.js +129 -0
- package/dist/rd-client.js.map +1 -0
- package/dist/service.d.ts +3 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +186 -0
- package/dist/service.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +92 -0
- package/dist/setup.js.map +1 -0
- package/dist/status-writer.d.ts +20 -0
- package/dist/status-writer.d.ts.map +1 -0
- package/dist/status-writer.js +34 -0
- package/dist/status-writer.js.map +1 -0
- package/dist/tui.d.ts +14 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +73 -0
- package/dist/tui.js.map +1 -0
- package/dist/updater.d.ts +27 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +191 -0
- package/dist/updater.js.map +1 -0
- package/dist/ws-client.d.ts +26 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +155 -0
- package/dist/ws-client.js.map +1 -0
- package/package.json +62 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
export interface AgentConfig {
|
|
3
|
+
relay: string;
|
|
4
|
+
deviceToken: string;
|
|
5
|
+
deviceId: string;
|
|
6
|
+
deviceName: string;
|
|
7
|
+
profileName: string;
|
|
8
|
+
directories: {
|
|
9
|
+
movies: string;
|
|
10
|
+
tv: string;
|
|
11
|
+
staging: string;
|
|
12
|
+
};
|
|
13
|
+
realDebrid: {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
};
|
|
16
|
+
maxConcurrentDownloads: number;
|
|
17
|
+
rdPollInterval: number;
|
|
18
|
+
lastUpdateCheck: string;
|
|
19
|
+
updateChannel: "stable";
|
|
20
|
+
previousBinaryPath: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const config: Conf<AgentConfig>;
|
|
23
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,QAAQ,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,MAAM,mBAsBjB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
export const config = new Conf({
|
|
3
|
+
projectName: "tadaima",
|
|
4
|
+
defaults: {
|
|
5
|
+
relay: "",
|
|
6
|
+
deviceToken: "",
|
|
7
|
+
deviceId: "",
|
|
8
|
+
deviceName: "",
|
|
9
|
+
profileName: "",
|
|
10
|
+
directories: {
|
|
11
|
+
movies: "",
|
|
12
|
+
tv: "",
|
|
13
|
+
staging: "",
|
|
14
|
+
},
|
|
15
|
+
realDebrid: {
|
|
16
|
+
apiKey: "",
|
|
17
|
+
},
|
|
18
|
+
maxConcurrentDownloads: 2,
|
|
19
|
+
rdPollInterval: 30,
|
|
20
|
+
lastUpdateCheck: "",
|
|
21
|
+
updateChannel: "stable",
|
|
22
|
+
previousBinaryPath: "",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAuBxB,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAc;IAC1C,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE;QACR,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,EAAE;QACd,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,MAAM,EAAE,EAAE;YACV,EAAE,EAAE,EAAE;YACN,OAAO,EAAE,EAAE;SACZ;QACD,UAAU,EAAE;YACV,MAAM,EAAE,EAAE;SACX;QACD,sBAAsB,EAAE,CAAC;QACzB,cAAc,EAAE,EAAE;QAClB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,QAAQ;QACvB,kBAAkB,EAAE,EAAE;KACvB;CACF,CAAC,CAAC"}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAoBA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAsClC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAuBjC;AAED,wBAAgB,eAAe,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAqBpE"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, createWriteStream, } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
function getPidPath() {
|
|
6
|
+
return join(dirname(config.path), "tadaima.pid");
|
|
7
|
+
}
|
|
8
|
+
function getLogDir() {
|
|
9
|
+
return join(dirname(config.path), "logs");
|
|
10
|
+
}
|
|
11
|
+
export function getLogPath() {
|
|
12
|
+
return join(getLogDir(), "tadaima.log");
|
|
13
|
+
}
|
|
14
|
+
export function startDaemon() {
|
|
15
|
+
const pidPath = getPidPath();
|
|
16
|
+
if (existsSync(pidPath)) {
|
|
17
|
+
const oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
18
|
+
if (Number.isNaN(oldPid)) {
|
|
19
|
+
console.log("Stale PID file (invalid contents). Removing.");
|
|
20
|
+
unlinkSync(pidPath);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
try {
|
|
24
|
+
process.kill(oldPid, 0);
|
|
25
|
+
console.log(`Agent already running (PID ${oldPid})`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
unlinkSync(pidPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const logDir = getLogDir();
|
|
34
|
+
mkdirSync(logDir, { recursive: true });
|
|
35
|
+
const logPath = getLogPath();
|
|
36
|
+
const logStream = createWriteStream(logPath, { flags: "a" });
|
|
37
|
+
const child = spawn(process.execPath, [process.argv[1], "start"], {
|
|
38
|
+
detached: true,
|
|
39
|
+
stdio: ["ignore", logStream, logStream],
|
|
40
|
+
env: { ...process.env, TADAIMA_DAEMON: "1" },
|
|
41
|
+
});
|
|
42
|
+
child.unref();
|
|
43
|
+
if (child.pid) {
|
|
44
|
+
writeFileSync(pidPath, String(child.pid));
|
|
45
|
+
console.log(`Agent started in background (PID ${child.pid})`);
|
|
46
|
+
console.log(`Logs: ${logPath}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function stopDaemon() {
|
|
50
|
+
const pidPath = getPidPath();
|
|
51
|
+
if (!existsSync(pidPath)) {
|
|
52
|
+
console.log("Agent is not running.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const pid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
56
|
+
if (Number.isNaN(pid)) {
|
|
57
|
+
console.log("Stale PID file (invalid contents). Removing.");
|
|
58
|
+
unlinkSync(pidPath);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, "SIGTERM");
|
|
63
|
+
console.log(`Sent SIGTERM to PID ${pid}`);
|
|
64
|
+
unlinkSync(pidPath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
console.log(`Process ${pid} not found. Cleaning up PID file.`);
|
|
68
|
+
unlinkSync(pidPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function getDaemonStatus() {
|
|
72
|
+
const pidPath = getPidPath();
|
|
73
|
+
if (!existsSync(pidPath)) {
|
|
74
|
+
return { running: false };
|
|
75
|
+
}
|
|
76
|
+
const pid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
|
|
77
|
+
if (Number.isNaN(pid)) {
|
|
78
|
+
console.log("Stale PID file (invalid contents). Removing.");
|
|
79
|
+
unlinkSync(pidPath);
|
|
80
|
+
return { running: false };
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
process.kill(pid, 0);
|
|
84
|
+
return { running: true, pid };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
unlinkSync(pidPath);
|
|
88
|
+
return { running: false };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentWebSocket } from "./ws-client.js";
|
|
2
|
+
export declare class DownloadHandler {
|
|
3
|
+
private ws;
|
|
4
|
+
private activeJobs;
|
|
5
|
+
private semaphore;
|
|
6
|
+
constructor(ws: AgentWebSocket);
|
|
7
|
+
get activeCount(): number;
|
|
8
|
+
handleRequest(msg: Record<string, unknown>): Promise<void>;
|
|
9
|
+
handleCancel(msg: Record<string, unknown>): void;
|
|
10
|
+
handleCacheCheck(msg: Record<string, unknown>): Promise<void>;
|
|
11
|
+
private executeDownload;
|
|
12
|
+
private sendProgress;
|
|
13
|
+
private sendMessage;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=download-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-handler.d.ts","sourceRoot":"","sources":["../src/download-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAsBrD,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,cAAc;IAK9B,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EhE,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAS1C,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAcrD,eAAe;IA0G7B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,WAAW;CAQpB"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { rm } from "node:fs/promises";
|
|
3
|
+
import { createMessageId, createTimestamp } from "@tadaima/shared";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { rdClient } from "./rd-client.js";
|
|
6
|
+
import { downloadFile } from "./downloader.js";
|
|
7
|
+
import { organizeFile } from "./organizer.js";
|
|
8
|
+
export class DownloadHandler {
|
|
9
|
+
ws;
|
|
10
|
+
activeJobs = new Map();
|
|
11
|
+
semaphore;
|
|
12
|
+
constructor(ws) {
|
|
13
|
+
this.ws = ws;
|
|
14
|
+
this.semaphore = config.get("maxConcurrentDownloads");
|
|
15
|
+
}
|
|
16
|
+
get activeCount() {
|
|
17
|
+
return this.activeJobs.size;
|
|
18
|
+
}
|
|
19
|
+
async handleRequest(msg) {
|
|
20
|
+
if (typeof msg.id !== "string" || !msg.id) {
|
|
21
|
+
console.error("download:request missing valid msg.id, ignoring:", msg);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (msg.payload == null || typeof msg.payload !== "object") {
|
|
25
|
+
console.error("download:request missing valid msg.payload, ignoring:", msg.id);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const payload = msg.payload;
|
|
29
|
+
const requestId = msg.id;
|
|
30
|
+
if (typeof payload.magnet !== "string" || !payload.magnet) {
|
|
31
|
+
console.error("download:request missing required payload.magnet, ignoring:", requestId);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (typeof payload.title !== "string" || !payload.title) {
|
|
35
|
+
console.error("download:request missing required payload.title, ignoring:", requestId);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (this.activeJobs.size >= this.semaphore) {
|
|
39
|
+
this.sendMessage("download:rejected", {
|
|
40
|
+
requestId,
|
|
41
|
+
reason: "queue_full",
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const jobId = createMessageId();
|
|
46
|
+
const abortController = new AbortController();
|
|
47
|
+
const meta = {
|
|
48
|
+
tmdbId: payload.tmdbId,
|
|
49
|
+
imdbId: payload.imdbId,
|
|
50
|
+
title: payload.title,
|
|
51
|
+
year: payload.year,
|
|
52
|
+
mediaType: payload.mediaType,
|
|
53
|
+
season: payload.season,
|
|
54
|
+
episode: payload.episode,
|
|
55
|
+
episodeTitle: payload.episodeTitle,
|
|
56
|
+
magnet: payload.magnet,
|
|
57
|
+
torrentName: payload.torrentName,
|
|
58
|
+
expectedSize: payload.expectedSize,
|
|
59
|
+
};
|
|
60
|
+
const job = {
|
|
61
|
+
jobId,
|
|
62
|
+
requestId,
|
|
63
|
+
abortController,
|
|
64
|
+
phase: "adding",
|
|
65
|
+
meta,
|
|
66
|
+
};
|
|
67
|
+
this.activeJobs.set(jobId, job);
|
|
68
|
+
this.ws.setActiveJobs(this.activeJobs.size);
|
|
69
|
+
this.sendMessage("download:accepted", { jobId, requestId });
|
|
70
|
+
try {
|
|
71
|
+
await this.executeDownload(job);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
const retryable = !errMsg.includes("Cancelled");
|
|
76
|
+
this.sendMessage("download:failed", {
|
|
77
|
+
jobId,
|
|
78
|
+
error: errMsg,
|
|
79
|
+
phase: job.phase,
|
|
80
|
+
retryable,
|
|
81
|
+
_meta: meta,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
this.activeJobs.delete(jobId);
|
|
86
|
+
this.ws.setActiveJobs(this.activeJobs.size);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
handleCancel(msg) {
|
|
90
|
+
const payload = msg.payload;
|
|
91
|
+
const jobId = payload.jobId;
|
|
92
|
+
const job = this.activeJobs.get(jobId);
|
|
93
|
+
if (job) {
|
|
94
|
+
job.abortController.abort();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async handleCacheCheck(msg) {
|
|
98
|
+
const payload = msg.payload;
|
|
99
|
+
const requestId = payload.requestId;
|
|
100
|
+
const infoHashes = payload.infoHashes;
|
|
101
|
+
try {
|
|
102
|
+
const cached = await rdClient.checkCache(infoHashes);
|
|
103
|
+
this.sendMessage("cache:result", { requestId, cached });
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.warn("Cache check failed", err);
|
|
107
|
+
this.sendMessage("cache:result", { requestId, cached: {} });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async executeDownload(job) {
|
|
111
|
+
const { meta, abortController } = job;
|
|
112
|
+
const signal = abortController.signal;
|
|
113
|
+
// Phase: adding
|
|
114
|
+
job.phase = "adding";
|
|
115
|
+
this.sendProgress(job.jobId, "adding", 0);
|
|
116
|
+
console.log(`[${job.jobId}] Adding magnet to RD...`);
|
|
117
|
+
const { id: torrentId } = await rdClient.addMagnet(meta.magnet);
|
|
118
|
+
if (signal.aborted)
|
|
119
|
+
throw new Error("Cancelled");
|
|
120
|
+
await rdClient.selectFiles(torrentId);
|
|
121
|
+
if (signal.aborted)
|
|
122
|
+
throw new Error("Cancelled");
|
|
123
|
+
// Phase: waiting
|
|
124
|
+
job.phase = "waiting";
|
|
125
|
+
this.sendProgress(job.jobId, "waiting", 0);
|
|
126
|
+
console.log(`[${job.jobId}] Waiting for RD to process...`);
|
|
127
|
+
const links = await rdClient.pollUntilReady(torrentId, undefined, undefined, (progress) => this.sendProgress(job.jobId, "waiting", progress), signal);
|
|
128
|
+
// Phase: unrestricting
|
|
129
|
+
job.phase = "unrestricting";
|
|
130
|
+
this.sendProgress(job.jobId, "unrestricting", 0);
|
|
131
|
+
console.log(`[${job.jobId}] Unrestricting ${links.length} links...`);
|
|
132
|
+
const unrestricted = await rdClient.unrestrictAll(links);
|
|
133
|
+
if (signal.aborted)
|
|
134
|
+
throw new Error("Cancelled");
|
|
135
|
+
// Phase: downloading
|
|
136
|
+
job.phase = "downloading";
|
|
137
|
+
console.log(`[${job.jobId}] Downloading files...`);
|
|
138
|
+
const stagingDir = config.get("directories.staging") || "/tmp/tadaima/staging";
|
|
139
|
+
const downloadedFiles = [];
|
|
140
|
+
let totalSize = 0;
|
|
141
|
+
for (const file of unrestricted) {
|
|
142
|
+
if (signal.aborted)
|
|
143
|
+
throw new Error("Cancelled");
|
|
144
|
+
const destPath = join(stagingDir, job.jobId, file.filename);
|
|
145
|
+
console.log(`[${job.jobId}] Downloading: ${file.filename}`);
|
|
146
|
+
const size = await downloadFile(file.url, destPath, (progress) => {
|
|
147
|
+
const pct = progress.totalBytes > 0
|
|
148
|
+
? Math.round((progress.downloadedBytes / progress.totalBytes) * 100)
|
|
149
|
+
: 0;
|
|
150
|
+
this.sendMessage("download:progress", {
|
|
151
|
+
jobId: job.jobId,
|
|
152
|
+
phase: "downloading",
|
|
153
|
+
progress: pct,
|
|
154
|
+
downloadedBytes: progress.downloadedBytes,
|
|
155
|
+
totalBytes: progress.totalBytes,
|
|
156
|
+
speedBps: progress.speedBps,
|
|
157
|
+
eta: progress.eta,
|
|
158
|
+
});
|
|
159
|
+
}, signal);
|
|
160
|
+
downloadedFiles.push(destPath);
|
|
161
|
+
totalSize += size;
|
|
162
|
+
}
|
|
163
|
+
// Phase: organizing
|
|
164
|
+
job.phase = "organizing";
|
|
165
|
+
this.sendProgress(job.jobId, "organizing", 0);
|
|
166
|
+
console.log(`[${job.jobId}] Organizing files...`);
|
|
167
|
+
let finalPath = "";
|
|
168
|
+
for (const filePath of downloadedFiles) {
|
|
169
|
+
finalPath = await organizeFile({
|
|
170
|
+
title: meta.title,
|
|
171
|
+
year: meta.year,
|
|
172
|
+
tmdbId: meta.tmdbId,
|
|
173
|
+
mediaType: meta.mediaType,
|
|
174
|
+
season: meta.season,
|
|
175
|
+
episode: meta.episode,
|
|
176
|
+
episodeTitle: meta.episodeTitle,
|
|
177
|
+
sourcePath: filePath,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Clean staging
|
|
181
|
+
await rm(join(stagingDir, job.jobId), { recursive: true, force: true }).catch(() => { });
|
|
182
|
+
// Done!
|
|
183
|
+
console.log(`[${job.jobId}] Complete: ${finalPath}`);
|
|
184
|
+
this.sendMessage("download:completed", {
|
|
185
|
+
jobId: job.jobId,
|
|
186
|
+
filePath: finalPath,
|
|
187
|
+
finalSize: totalSize,
|
|
188
|
+
_meta: meta,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
sendProgress(jobId, phase, progress) {
|
|
192
|
+
this.sendMessage("download:progress", { jobId, phase, progress });
|
|
193
|
+
}
|
|
194
|
+
sendMessage(type, payload) {
|
|
195
|
+
this.ws.send({
|
|
196
|
+
id: createMessageId(),
|
|
197
|
+
type,
|
|
198
|
+
timestamp: createTimestamp(),
|
|
199
|
+
payload,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=download-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-handler.js","sourceRoot":"","sources":["../src/download-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface DownloadProgress {
|
|
2
|
+
downloadedBytes: number;
|
|
3
|
+
totalBytes: number;
|
|
4
|
+
speedBps: number;
|
|
5
|
+
eta: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Download a file via HTTP with progress reporting.
|
|
9
|
+
*/
|
|
10
|
+
export declare function downloadFile(url: string, destPath: string, onProgress?: (progress: DownloadProgress) => void, signal?: AbortSignal, baseDir?: string): Promise<number>;
|
|
11
|
+
//# sourceMappingURL=downloader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,EACjD,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAqEjB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { pipeline } from "node:stream/promises";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
/**
|
|
7
|
+
* Download a file via HTTP with progress reporting.
|
|
8
|
+
*/
|
|
9
|
+
export async function downloadFile(url, destPath, onProgress, signal, baseDir) {
|
|
10
|
+
if (baseDir) {
|
|
11
|
+
const resolvedDest = resolve(destPath);
|
|
12
|
+
const resolvedBase = resolve(baseDir);
|
|
13
|
+
if (!resolvedDest.startsWith(resolvedBase + "/") && resolvedDest !== resolvedBase) {
|
|
14
|
+
throw new Error(`Path traversal detected: ${destPath} escapes base directory ${baseDir}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
18
|
+
const res = await fetch(url, { signal });
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new Error(`Download failed: HTTP ${res.status}`);
|
|
21
|
+
if (!res.body)
|
|
22
|
+
throw new Error("No response body");
|
|
23
|
+
const totalBytes = parseInt(res.headers.get("content-length") ?? "0", 10);
|
|
24
|
+
let downloadedBytes = 0;
|
|
25
|
+
let lastReport = Date.now();
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
const reader = res.body.getReader();
|
|
28
|
+
const fileStream = createWriteStream(destPath);
|
|
29
|
+
const readable = new Readable({
|
|
30
|
+
async read() {
|
|
31
|
+
try {
|
|
32
|
+
const { done, value } = await reader.read();
|
|
33
|
+
if (done) {
|
|
34
|
+
this.push(null);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
downloadedBytes += value.byteLength;
|
|
38
|
+
// Throttle progress to 1/sec
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (now - lastReport >= 1000 && onProgress) {
|
|
41
|
+
const elapsed = (now - startTime) / 1000;
|
|
42
|
+
const speedBps = elapsed > 0 ? Math.round(downloadedBytes / elapsed) : 0;
|
|
43
|
+
const remaining = speedBps > 0 ? (totalBytes - downloadedBytes) / speedBps : 0;
|
|
44
|
+
onProgress({
|
|
45
|
+
downloadedBytes,
|
|
46
|
+
totalBytes,
|
|
47
|
+
speedBps,
|
|
48
|
+
eta: Math.round(remaining),
|
|
49
|
+
});
|
|
50
|
+
lastReport = now;
|
|
51
|
+
}
|
|
52
|
+
this.push(Buffer.from(value));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
this.destroy(err);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
await pipeline(readable, fileStream);
|
|
60
|
+
if (totalBytes > 0 && downloadedBytes !== totalBytes) {
|
|
61
|
+
throw new Error(`Download incomplete: expected ${totalBytes} bytes but got ${downloadedBytes}`);
|
|
62
|
+
}
|
|
63
|
+
return downloadedBytes;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=downloader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloader.js","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|