cross-seed 6.13.6 → 7.0.0-1

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.
Files changed (147) hide show
  1. package/README.md +12 -13
  2. package/dist/webui/assets/FieldInfo-Bxj_j8SJ.js +1 -0
  3. package/dist/webui/assets/Page-C3rteCZt.js +1 -0
  4. package/dist/webui/assets/array-field-DVSC6nHP.js +1 -0
  5. package/dist/webui/assets/badge-DTZMtS0e.js +1 -0
  6. package/dist/webui/assets/check-Bu3ldi63.js +1 -0
  7. package/dist/webui/assets/chevron-down-CRy8M0kJ.js +1 -0
  8. package/dist/webui/assets/clients-CW8oEZoQ.js +1 -0
  9. package/dist/webui/assets/connect-YBNsnjWT.js +1 -0
  10. package/dist/webui/assets/debug-mz8-WYZj.js +1 -0
  11. package/dist/webui/assets/directories-BSK28RgR.js +1 -0
  12. package/dist/webui/assets/duration-field-C6xoSlJg.js +1 -0
  13. package/dist/webui/assets/general-lJJxZhH7.js +1 -0
  14. package/dist/webui/assets/health-CXbsVrie.js +1 -0
  15. package/dist/webui/assets/index-Bi48hI2z.js +54 -0
  16. package/dist/webui/assets/index-C-Ul7GNg.css +1 -0
  17. package/dist/webui/assets/index-C2cH1Gst.js +1 -0
  18. package/dist/webui/assets/index-Cc5bDmJr.js +1 -0
  19. package/dist/webui/assets/jobs-CxmNab9w.js +1 -0
  20. package/dist/webui/assets/library-vaj2W8sE.js +1 -0
  21. package/dist/webui/assets/loader-circle-M0gu1gZ-.js +1 -0
  22. package/dist/webui/assets/logs-Cu9RyKS0.js +1 -0
  23. package/dist/webui/assets/search-2R5sIdT8.js +1 -0
  24. package/dist/webui/assets/select-field-BCqNLDrJ.js +1 -0
  25. package/dist/webui/assets/select-zHgqMzLj.js +1 -0
  26. package/dist/webui/assets/settings-CMYjpTbZ.js +1 -0
  27. package/dist/webui/assets/submit-button-BtcnyggQ.js +1 -0
  28. package/dist/webui/assets/switch-G0W3uJVN.js +1 -0
  29. package/dist/webui/assets/switch-field-IBd9ORNq.js +1 -0
  30. package/dist/webui/assets/table-DvgJU7Gh.js +1 -0
  31. package/dist/webui/assets/test-tube-BIwmoM45.js +1 -0
  32. package/dist/webui/assets/text-field-DruSbGhy.js +1 -0
  33. package/dist/webui/assets/time-BSMZjmyW.js +1 -0
  34. package/dist/webui/assets/trackers-D-OpAe63.js +7 -0
  35. package/dist/webui/assets/use-form-validation-context-BkAfWAh0.js +1 -0
  36. package/dist/webui/assets/use-settings-form-submit-CDRh-E9U.js +2 -0
  37. package/dist/webui/assets/useQuery-A4Hv_4uX.js +1 -0
  38. package/dist/webui/index.html +13 -0
  39. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts +261 -0
  40. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts.map +1 -0
  41. package/node_modules/@cross-seed/shared/dist/configSchema.js +53 -0
  42. package/node_modules/@cross-seed/shared/dist/configSchema.js.map +1 -0
  43. package/node_modules/@cross-seed/shared/dist/constants.d.ts +122 -0
  44. package/node_modules/@cross-seed/shared/dist/constants.d.ts.map +1 -0
  45. package/node_modules/@cross-seed/shared/dist/constants.js +127 -0
  46. package/node_modules/@cross-seed/shared/dist/constants.js.map +1 -0
  47. package/node_modules/@cross-seed/shared/dist/tsconfig.tsbuildinfo +1 -0
  48. package/node_modules/@cross-seed/shared/dist/utils.d.ts +6 -0
  49. package/node_modules/@cross-seed/shared/dist/utils.d.ts.map +1 -0
  50. package/node_modules/@cross-seed/shared/dist/utils.js +9 -0
  51. package/node_modules/@cross-seed/shared/dist/utils.js.map +1 -0
  52. package/node_modules/@cross-seed/shared/package.json +22 -0
  53. package/package.json +35 -11
  54. package/dist/Result.js +0 -64
  55. package/dist/Result.js.map +0 -1
  56. package/dist/action.js +0 -693
  57. package/dist/action.js.map +0 -1
  58. package/dist/arr.js +0 -199
  59. package/dist/arr.js.map +0 -1
  60. package/dist/auth.js +0 -25
  61. package/dist/auth.js.map +0 -1
  62. package/dist/clients/Deluge.js +0 -698
  63. package/dist/clients/Deluge.js.map +0 -1
  64. package/dist/clients/QBittorrent.js +0 -785
  65. package/dist/clients/QBittorrent.js.map +0 -1
  66. package/dist/clients/RTorrent.js +0 -654
  67. package/dist/clients/RTorrent.js.map +0 -1
  68. package/dist/clients/TorrentClient.js +0 -272
  69. package/dist/clients/TorrentClient.js.map +0 -1
  70. package/dist/clients/Transmission.js +0 -404
  71. package/dist/clients/Transmission.js.map +0 -1
  72. package/dist/cmd.js +0 -196
  73. package/dist/cmd.js.map +0 -1
  74. package/dist/config.template.cjs +0 -353
  75. package/dist/config.template.cjs.map +0 -1
  76. package/dist/configSchema.js +0 -667
  77. package/dist/configSchema.js.map +0 -1
  78. package/dist/configuration.js +0 -82
  79. package/dist/configuration.js.map +0 -1
  80. package/dist/constants.js +0 -281
  81. package/dist/constants.js.map +0 -1
  82. package/dist/dataFiles.js +0 -208
  83. package/dist/dataFiles.js.map +0 -1
  84. package/dist/db.js +0 -216
  85. package/dist/db.js.map +0 -1
  86. package/dist/decide.js +0 -553
  87. package/dist/decide.js.map +0 -1
  88. package/dist/diff.js +0 -24
  89. package/dist/diff.js.map +0 -1
  90. package/dist/errors.js +0 -16
  91. package/dist/errors.js.map +0 -1
  92. package/dist/indexers.js +0 -180
  93. package/dist/indexers.js.map +0 -1
  94. package/dist/inject.js +0 -594
  95. package/dist/inject.js.map +0 -1
  96. package/dist/jobs.js +0 -146
  97. package/dist/jobs.js.map +0 -1
  98. package/dist/logger.js +0 -143
  99. package/dist/logger.js.map +0 -1
  100. package/dist/migrations/00-initialSchema.js +0 -30
  101. package/dist/migrations/00-initialSchema.js.map +0 -1
  102. package/dist/migrations/01-jobs.js +0 -12
  103. package/dist/migrations/01-jobs.js.map +0 -1
  104. package/dist/migrations/02-timestamps.js +0 -21
  105. package/dist/migrations/02-timestamps.js.map +0 -1
  106. package/dist/migrations/03-rateLimits.js +0 -14
  107. package/dist/migrations/03-rateLimits.js.map +0 -1
  108. package/dist/migrations/04-auth.js +0 -13
  109. package/dist/migrations/04-auth.js.map +0 -1
  110. package/dist/migrations/05-caps.js +0 -16
  111. package/dist/migrations/05-caps.js.map +0 -1
  112. package/dist/migrations/06-uniqueDecisions.js +0 -29
  113. package/dist/migrations/06-uniqueDecisions.js.map +0 -1
  114. package/dist/migrations/07-limits.js +0 -12
  115. package/dist/migrations/07-limits.js.map +0 -1
  116. package/dist/migrations/08-rss.js +0 -15
  117. package/dist/migrations/08-rss.js.map +0 -1
  118. package/dist/migrations/09-clientAndDataSearchees.js +0 -34
  119. package/dist/migrations/09-clientAndDataSearchees.js.map +0 -1
  120. package/dist/migrations/10-indexerNameAudioBookCaps.js +0 -18
  121. package/dist/migrations/10-indexerNameAudioBookCaps.js.map +0 -1
  122. package/dist/migrations/11-trackers.js +0 -38
  123. package/dist/migrations/11-trackers.js.map +0 -1
  124. package/dist/migrations/migrations.js +0 -31
  125. package/dist/migrations/migrations.js.map +0 -1
  126. package/dist/parseTorrent.js +0 -128
  127. package/dist/parseTorrent.js.map +0 -1
  128. package/dist/pipeline.js +0 -527
  129. package/dist/pipeline.js.map +0 -1
  130. package/dist/preFilter.js +0 -250
  131. package/dist/preFilter.js.map +0 -1
  132. package/dist/pushNotifier.js +0 -137
  133. package/dist/pushNotifier.js.map +0 -1
  134. package/dist/runtimeConfig.js +0 -11
  135. package/dist/runtimeConfig.js.map +0 -1
  136. package/dist/searchee.js +0 -658
  137. package/dist/searchee.js.map +0 -1
  138. package/dist/server.js +0 -456
  139. package/dist/server.js.map +0 -1
  140. package/dist/startup.js +0 -203
  141. package/dist/startup.js.map +0 -1
  142. package/dist/torrent.js +0 -637
  143. package/dist/torrent.js.map +0 -1
  144. package/dist/torznab.js +0 -786
  145. package/dist/torznab.js.map +0 -1
  146. package/dist/utils.js +0 -637
  147. package/dist/utils.js.map +0 -1
@@ -1,654 +0,0 @@
1
- import { readdir, stat } from "fs/promises";
2
- import { basename, dirname, join, resolve, sep } from "path";
3
- import { inspect } from "util";
4
- import xmlrpc from "xmlrpc";
5
- import { InjectionResult, TORRENT_TAG, } from "../constants.js";
6
- import { db } from "../db.js";
7
- import { CrossSeedError } from "../errors.js";
8
- import { Label, logger } from "../logger.js";
9
- import { Metafile } from "../parseTorrent.js";
10
- import { resultOf, resultOfErr } from "../Result.js";
11
- import { getRuntimeConfig } from "../runtimeConfig.js";
12
- import { createSearcheeFromDB, parseTitle, updateSearcheeClientDB, } from "../searchee.js";
13
- import { extractCredentialsFromUrl, fromBatches, humanReadableSize, isTruthy, mapAsync, sanitizeInfoHash, wait, } from "../utils.js";
14
- import { shouldResumeFromNonRelevantFiles, clientSearcheeModified, getMaxRemainingBytes, getResumeStopTime, organizeTrackers, resumeErrSleepTime, resumeSleepTime, shouldRecheck, } from "./TorrentClient.js";
15
- const COULD_NOT_FIND_INFO_HASH = "Could not find info-hash.";
16
- async function createLibTorrentResumeTree(meta, basePath) {
17
- async function getFileResumeData(file) {
18
- const filePathWithoutFirstSegment = file.path
19
- .split(sep)
20
- .slice(1)
21
- .join(sep);
22
- const resolvedFilePath = resolve(basePath, filePathWithoutFirstSegment);
23
- const fileStat = await stat(resolvedFilePath).catch(() => ({ isFile: () => false }));
24
- if (!fileStat.isFile() || fileStat.size !== file.length) {
25
- return null;
26
- }
27
- return {
28
- completed: Math.ceil(file.length / meta.pieceLength),
29
- mtime: Math.trunc(fileStat.mtimeMs / 1000),
30
- priority: 1,
31
- };
32
- }
33
- const fileResumes = await mapAsync(meta.files, getFileResumeData);
34
- return {
35
- bitfield: Math.ceil(meta.length / meta.pieceLength),
36
- files: fileResumes.filter(isTruthy),
37
- };
38
- }
39
- export default class RTorrent {
40
- client;
41
- clientHost;
42
- clientPriority;
43
- clientType = Label.RTORRENT;
44
- readonly;
45
- label;
46
- batchSize = 500;
47
- constructor(url, clientHost, priority, readonly) {
48
- this.clientHost = clientHost;
49
- this.clientPriority = priority;
50
- this.readonly = readonly;
51
- this.label = `${this.clientType}@${this.clientHost}`;
52
- const { href, username, password } = extractCredentialsFromUrl(url).unwrapOrThrow(new CrossSeedError(`[${this.label}] rTorrent url must be percent-encoded`));
53
- const clientCreator = new URL(href).protocol === "https:"
54
- ? xmlrpc.createSecureClient
55
- : xmlrpc.createClient;
56
- const shouldUseAuth = Boolean(username && password);
57
- this.client = clientCreator({
58
- url: href,
59
- basic_auth: shouldUseAuth
60
- ? { user: username, pass: password }
61
- : undefined,
62
- });
63
- }
64
- async methodCallP(method, args) {
65
- const msg = `Calling method ${method} with params ${inspect(args, { depth: null, compact: true })}`;
66
- const message = msg.length > 1000 ? `${msg.slice(0, 1000)}...` : msg;
67
- logger.verbose({ label: this.label, message });
68
- return new Promise((resolve, reject) => {
69
- this.client.methodCall(method, args, (err, data) => {
70
- if (err)
71
- return reject(err);
72
- return resolve(data);
73
- });
74
- });
75
- }
76
- async isTorrentInClient(inputHash) {
77
- const infoHash = inputHash.toLowerCase();
78
- try {
79
- const downloadList = await this.methodCallP("download_list", []);
80
- for (const hash of downloadList) {
81
- if (hash.toLowerCase() === infoHash)
82
- return resultOf(true);
83
- }
84
- return resultOf(false);
85
- }
86
- catch (e) {
87
- return resultOfErr(e);
88
- }
89
- }
90
- async checkOriginalTorrent(infoHash, options) {
91
- const hash = infoHash.toUpperCase();
92
- let response;
93
- const args = [
94
- [
95
- {
96
- methodName: "d.name",
97
- params: [hash],
98
- },
99
- {
100
- methodName: "d.directory",
101
- params: [hash],
102
- },
103
- {
104
- methodName: "d.left_bytes",
105
- params: [hash],
106
- },
107
- {
108
- methodName: "d.hashing",
109
- params: [hash],
110
- },
111
- {
112
- methodName: "d.complete",
113
- params: [hash],
114
- },
115
- {
116
- methodName: "d.is_multi_file",
117
- params: [hash],
118
- },
119
- {
120
- methodName: "d.is_active",
121
- params: [hash],
122
- },
123
- ],
124
- ];
125
- try {
126
- response = await this.methodCallP("system.multicall", args);
127
- }
128
- catch (e) {
129
- logger.debug({ label: this.label, message: e });
130
- return resultOfErr("FAILURE");
131
- }
132
- function isFault(response) {
133
- return "faultString" in response[0];
134
- }
135
- try {
136
- if (isFault(response)) {
137
- if (response[0].faultString === COULD_NOT_FIND_INFO_HASH) {
138
- return resultOfErr("NOT_FOUND");
139
- }
140
- else {
141
- throw new Error("Unknown rTorrent fault while checking original torrent");
142
- }
143
- }
144
- const [[name], [directoryBase], [bytesLeftStr], [hashingStr], [isCompleteStr], [isMultiFileStr], [isActiveStr],] = response;
145
- const isComplete = Boolean(Number(isCompleteStr));
146
- if (options.onlyCompleted && !isComplete) {
147
- return resultOfErr("TORRENT_NOT_COMPLETE");
148
- }
149
- return resultOf({
150
- name,
151
- directoryBase,
152
- bytesLeft: Number(bytesLeftStr),
153
- hashing: Number(hashingStr),
154
- isMultiFile: Boolean(Number(isMultiFileStr)),
155
- isActive: Boolean(Number(isActiveStr)),
156
- });
157
- }
158
- catch (e) {
159
- logger.error({ label: this.label, message: e });
160
- logger.debug("Failure caused by server response below:");
161
- logger.debug(inspect(response));
162
- return resultOfErr("FAILURE");
163
- }
164
- }
165
- async getDownloadLocation(meta, searchee, options) {
166
- if (options.destinationDir) {
167
- // resolve to absolute because we send the path to rTorrent
168
- const basePath = resolve(options.destinationDir, meta.name);
169
- const directoryBase = meta.isSingleFileTorrent
170
- ? options.destinationDir
171
- : basePath;
172
- return resultOf({
173
- downloadDir: options.destinationDir,
174
- basePath,
175
- directoryBase,
176
- });
177
- }
178
- else {
179
- const result = await this.checkOriginalTorrent(searchee.infoHash, {
180
- onlyCompleted: options.onlyCompleted,
181
- });
182
- return result.mapOk(({ directoryBase }) => ({
183
- directoryBase,
184
- downloadDir: meta.isSingleFileTorrent
185
- ? directoryBase
186
- : dirname(directoryBase),
187
- basePath: meta.isSingleFileTorrent
188
- ? join(directoryBase, searchee.name)
189
- : directoryBase,
190
- }));
191
- }
192
- }
193
- async validateConfig() {
194
- const { torrentDir } = getRuntimeConfig();
195
- try {
196
- await this.methodCallP("download_list", []);
197
- }
198
- catch (e) {
199
- logger.debug({ label: this.label, message: e });
200
- throw new CrossSeedError(`[${this.label}] Failed to reach rTorrent at ${this.clientHost}: ${e.message}`);
201
- }
202
- logger.info({
203
- label: this.label,
204
- message: `Logged in successfully${this.readonly ? " (readonly)" : ""}`,
205
- });
206
- if (!torrentDir)
207
- return;
208
- if (!(await readdir(torrentDir)).some((f) => f.endsWith("_resume"))) {
209
- throw new CrossSeedError(`[${this.label}] Invalid torrentDir, if no torrents are in client set to null for now: https://www.cross-seed.org/docs/basics/options#torrentdir`);
210
- }
211
- }
212
- async getDownloadDir(meta, options) {
213
- const existsRes = await this.isTorrentInClient(meta.infoHash);
214
- if (existsRes.isErr())
215
- return resultOfErr("UNKNOWN_ERROR");
216
- if (!existsRes.unwrap())
217
- return resultOfErr("NOT_FOUND");
218
- const result = await this.checkOriginalTorrent(meta.infoHash, options);
219
- return result
220
- .mapOk(({ directoryBase, isMultiFile }) => {
221
- return isMultiFile ? dirname(directoryBase) : directoryBase;
222
- })
223
- .mapErr((error) => (error === "FAILURE" ? "UNKNOWN_ERROR" : error));
224
- }
225
- async getAllDownloadDirs(options) {
226
- const hashes = await this.methodCallP("download_list", []);
227
- function isFault(response) {
228
- return "faultString" in response[0];
229
- }
230
- let numMethods = 0;
231
- const results = await fromBatches(hashes, async (batch) => {
232
- const args = [
233
- batch.flatMap((hash) => {
234
- const arg = [
235
- {
236
- methodName: "d.directory",
237
- params: [hash],
238
- },
239
- {
240
- methodName: "d.is_multi_file",
241
- params: [hash],
242
- },
243
- {
244
- methodName: "d.complete",
245
- params: [hash],
246
- },
247
- ];
248
- numMethods = arg.length;
249
- return arg;
250
- }),
251
- ];
252
- try {
253
- const res = await this.methodCallP("system.multicall", args);
254
- if (isFault(res)) {
255
- logger.error({
256
- label: this.label,
257
- message: "Fault while getting download directories for all torrents",
258
- });
259
- logger.debug(inspect(res));
260
- return [];
261
- }
262
- return res;
263
- }
264
- catch (e) {
265
- logger.error({
266
- label: this.label,
267
- message: `Failed to get download directories for all torrents: ${e.message}`,
268
- });
269
- logger.debug(e);
270
- return [];
271
- }
272
- }, { batchSize: this.batchSize });
273
- if (!results.length || results.length !== hashes.length * numMethods) {
274
- logger.error({
275
- label: this.label,
276
- message: `Unexpected number of results: ${results.length} for ${hashes.length} hashes`,
277
- });
278
- return new Map();
279
- }
280
- try {
281
- return hashes.reduce((infoHashSavePathMap, hash, index) => {
282
- const directory = results[index * numMethods][0];
283
- const isMultiFile = Boolean(Number(results[index * numMethods + 1][0]));
284
- const isComplete = Boolean(Number(results[index * numMethods + 2][0]));
285
- if (!options.onlyCompleted || isComplete) {
286
- infoHashSavePathMap.set(hash, isMultiFile ? dirname(directory) : directory);
287
- }
288
- return infoHashSavePathMap;
289
- }, new Map());
290
- }
291
- catch (e) {
292
- logger.error({
293
- label: this.label,
294
- message: `Error parsing response for all torrents: ${e.message}`,
295
- });
296
- logger.debug(e);
297
- return new Map();
298
- }
299
- }
300
- async isTorrentComplete(infoHash) {
301
- try {
302
- const response = await this.methodCallP("d.complete", [
303
- infoHash,
304
- ]);
305
- if (response.length === 0) {
306
- return resultOfErr("NOT_FOUND");
307
- }
308
- return resultOf(Boolean(Number(response[0])));
309
- }
310
- catch (e) {
311
- return resultOfErr("NOT_FOUND");
312
- }
313
- }
314
- async isTorrentChecking(infoHash) {
315
- try {
316
- const response = await this.methodCallP("d.hashing", [
317
- infoHash,
318
- ]);
319
- if (response.length === 0) {
320
- return resultOfErr("NOT_FOUND");
321
- }
322
- return resultOf(Boolean(Number(response[0])));
323
- }
324
- catch (e) {
325
- return resultOfErr("NOT_FOUND");
326
- }
327
- }
328
- async getAllTorrents() {
329
- const hashes = await this.methodCallP("download_list", []);
330
- function isFault(response) {
331
- return "faultString" in response[0];
332
- }
333
- const results = await fromBatches(hashes, async (batch) => {
334
- const args = [
335
- batch.map((hash) => {
336
- return {
337
- methodName: "d.custom1",
338
- params: [hash],
339
- };
340
- }),
341
- ];
342
- try {
343
- const res = await this.methodCallP("system.multicall", args);
344
- if (isFault(res)) {
345
- logger.error({
346
- label: this.label,
347
- message: "Fault while getting torrent info for all torrents",
348
- });
349
- logger.debug(inspect(res));
350
- return [];
351
- }
352
- return res;
353
- }
354
- catch (e) {
355
- logger.error({
356
- label: this.label,
357
- message: `Failed to get torrent info for all torrents: ${e.message}`,
358
- });
359
- logger.debug(e);
360
- return [];
361
- }
362
- }, { batchSize: this.batchSize });
363
- if (results.length !== hashes.length) {
364
- logger.error({
365
- label: this.label,
366
- message: `Unexpected number of results: ${results.length} for ${hashes.length} hashes`,
367
- });
368
- return [];
369
- }
370
- try {
371
- // response: [ [tag1], [tag2], ... ], assuming infoHash order is preserved
372
- return hashes.map((hash, index) => ({
373
- infoHash: hash.toLowerCase(),
374
- tags: results[index].length !== 1
375
- ? results[index]
376
- : results[index][0].length
377
- ? decodeURIComponent(results[index][0])
378
- .split(",")
379
- .map((tag) => tag.trim())
380
- : [],
381
- }));
382
- }
383
- catch (e) {
384
- logger.error({
385
- label: this.label,
386
- message: `Error parsing response for all torrents: ${e.message}`,
387
- });
388
- logger.debug(e);
389
- return [];
390
- }
391
- }
392
- async getClientSearchees(options) {
393
- const searchees = [];
394
- const newSearchees = [];
395
- const hashes = await this.methodCallP("download_list", []);
396
- function isFault(response) {
397
- return "faultString" in response[0];
398
- }
399
- let numMethods = 0;
400
- const results = await fromBatches(hashes, async (batch) => {
401
- const args = [
402
- batch.flatMap((hash) => {
403
- const arg = [
404
- {
405
- methodName: "d.name",
406
- params: [hash],
407
- },
408
- {
409
- methodName: "d.size_bytes",
410
- params: [hash],
411
- },
412
- {
413
- methodName: "d.directory",
414
- params: [hash],
415
- },
416
- {
417
- methodName: "d.is_multi_file",
418
- params: [hash],
419
- },
420
- {
421
- methodName: "d.custom1",
422
- params: [hash],
423
- },
424
- {
425
- methodName: "f.multicall",
426
- params: [hash, "", "f.path=", "f.size_bytes="],
427
- },
428
- {
429
- methodName: "t.multicall",
430
- params: [hash, "", "t.url=", "t.group="],
431
- },
432
- ];
433
- numMethods = arg.length;
434
- return arg;
435
- }),
436
- ];
437
- try {
438
- const res = await this.methodCallP("system.multicall", args);
439
- if (isFault(res)) {
440
- logger.error({
441
- label: this.label,
442
- message: "Fault while getting client torrents",
443
- });
444
- logger.debug(inspect(res));
445
- return [];
446
- }
447
- return res;
448
- }
449
- catch (e) {
450
- logger.error({
451
- label: this.label,
452
- message: `Failed to get client torrents: ${e.message}`,
453
- });
454
- logger.debug(e);
455
- return [];
456
- }
457
- }, { batchSize: this.batchSize });
458
- if (!results.length || results.length !== hashes.length * numMethods) {
459
- logger.error({
460
- label: this.label,
461
- message: `Unexpected number of results: ${results.length} for ${hashes.length} hashes`,
462
- });
463
- return { searchees, newSearchees };
464
- }
465
- const infoHashes = new Set();
466
- for (let i = 0; i < hashes.length; i++) {
467
- const infoHash = hashes[i].toLowerCase();
468
- infoHashes.add(infoHash);
469
- const dbTorrent = await db("client_searchee")
470
- .where("info_hash", infoHash)
471
- .where("client_host", this.clientHost)
472
- .first();
473
- const name = results[i * numMethods][0];
474
- const directory = results[i * numMethods + 2][0];
475
- const isMultiFile = Boolean(Number(results[i * numMethods + 3][0]));
476
- const labels = results[i * numMethods + 4][0];
477
- const savePath = isMultiFile ? dirname(directory) : directory;
478
- const tags = labels.length
479
- ? decodeURIComponent(labels)
480
- .split(",")
481
- .map((tag) => tag.trim())
482
- : [];
483
- const modified = clientSearcheeModified(this.label, dbTorrent, name, savePath, {
484
- tags,
485
- });
486
- const refresh = options?.refresh === undefined
487
- ? false
488
- : options.refresh.length === 0
489
- ? true
490
- : options.refresh.includes(infoHash);
491
- if (!modified && !refresh) {
492
- if (!options?.newSearcheesOnly) {
493
- searchees.push(createSearcheeFromDB(dbTorrent));
494
- }
495
- continue;
496
- }
497
- const length = Number(results[i * numMethods + 1][0]);
498
- const files = results[i * numMethods + 5][0].map((arr) => ({
499
- name: basename(arr[0]),
500
- path: isMultiFile ? join(basename(directory), arr[0]) : arr[0],
501
- length: Number(arr[1]),
502
- }));
503
- if (!files.length) {
504
- logger.verbose({
505
- label: this.label,
506
- message: `No files found for ${name} [${sanitizeInfoHash(infoHash)}]: skipping`,
507
- });
508
- continue;
509
- }
510
- const trackers = organizeTrackers(results[i * numMethods + 6][0].map((arr) => ({
511
- url: arr[0],
512
- tier: Number(arr[1]),
513
- })));
514
- const title = parseTitle(name, files) ?? name;
515
- const searchee = {
516
- infoHash,
517
- name,
518
- title,
519
- files,
520
- length,
521
- clientHost: this.clientHost,
522
- savePath,
523
- tags,
524
- trackers,
525
- };
526
- newSearchees.push(searchee);
527
- searchees.push(searchee);
528
- }
529
- await updateSearcheeClientDB(this.clientHost, newSearchees, infoHashes);
530
- return { searchees, newSearchees };
531
- }
532
- async recheckTorrent(infoHash) {
533
- // Pause first as it may resume after recheck automatically
534
- await this.methodCallP("d.pause", [infoHash]);
535
- await this.methodCallP("d.check_hash", [infoHash]);
536
- }
537
- async resumeInjection(meta, decision, options) {
538
- const infoHash = meta.infoHash;
539
- let sleepTime = resumeSleepTime;
540
- const stopTime = getResumeStopTime();
541
- let stop = false;
542
- while (Date.now() < stopTime) {
543
- if (options.checkOnce) {
544
- if (stop)
545
- return;
546
- stop = true;
547
- }
548
- await wait(sleepTime);
549
- const torrentInfoRes = await this.checkOriginalTorrent(infoHash, {
550
- onlyCompleted: false,
551
- });
552
- if (torrentInfoRes.isErr()) {
553
- sleepTime = resumeErrSleepTime; // Dropping connections or restart
554
- continue;
555
- }
556
- const torrentInfo = torrentInfoRes.unwrap();
557
- if (torrentInfo.hashing) {
558
- continue;
559
- }
560
- const torrentLog = `${torrentInfo.name} [${sanitizeInfoHash(infoHash)}]`;
561
- if (torrentInfo.isActive) {
562
- logger.warn({
563
- label: this.label,
564
- message: `Will not resume torrent ${torrentLog}: active`,
565
- });
566
- return;
567
- }
568
- const maxRemainingBytes = getMaxRemainingBytes(meta, decision, {
569
- torrentLog,
570
- label: this.label,
571
- });
572
- if (torrentInfo.bytesLeft > maxRemainingBytes) {
573
- if (!shouldResumeFromNonRelevantFiles(meta, torrentInfo.bytesLeft, decision, { torrentLog, label: this.label })) {
574
- logger.warn({
575
- label: this.label,
576
- message: `autoResumeMaxDownload will not resume ${torrentLog}: remainingSize ${humanReadableSize(torrentInfo.bytesLeft, { binary: true })} > ${humanReadableSize(maxRemainingBytes, { binary: true })} limit`,
577
- });
578
- return;
579
- }
580
- }
581
- logger.info({
582
- label: this.label,
583
- message: `Resuming torrent ${torrentLog}`,
584
- });
585
- await this.methodCallP("d.resume", [infoHash]);
586
- }
587
- logger.warn({
588
- label: this.label,
589
- message: `Will not resume torrent ${infoHash}: timeout`,
590
- });
591
- }
592
- async inject(meta, searchee, decision, options) {
593
- const existsRes = await this.isTorrentInClient(meta.infoHash);
594
- if (existsRes.isErr())
595
- return InjectionResult.FAILURE;
596
- if (existsRes.unwrap())
597
- return InjectionResult.ALREADY_EXISTS;
598
- const result = await this.getDownloadLocation(meta, searchee, options);
599
- if (result.isErr()) {
600
- switch (result.unwrapErr()) {
601
- case "NOT_FOUND":
602
- return InjectionResult.FAILURE;
603
- case "TORRENT_NOT_COMPLETE":
604
- return InjectionResult.TORRENT_NOT_COMPLETE;
605
- case "FAILURE":
606
- return InjectionResult.FAILURE;
607
- }
608
- }
609
- const { directoryBase, basePath } = result.unwrap();
610
- const rawWithLibtorrentResume = {
611
- ...meta.raw,
612
- libtorrent_resume: await createLibTorrentResumeTree(meta, basePath),
613
- };
614
- const toRecheck = shouldRecheck(meta, decision);
615
- const loadType = toRecheck ? "load.raw" : "load.raw_start";
616
- const retries = 5;
617
- for (let i = 0; i < retries; i++) {
618
- try {
619
- await this.methodCallP(loadType, [
620
- "",
621
- new Metafile(rawWithLibtorrentResume).encode(),
622
- `d.directory_base.set="${directoryBase}"`,
623
- `d.custom1.set="${TORRENT_TAG}"`,
624
- `d.custom.set=addtime,${Math.round(Date.now() / 1000)}`,
625
- toRecheck
626
- ? `d.check_hash=${meta.infoHash.toUpperCase()}`
627
- : null,
628
- ].filter((e) => e !== null));
629
- if (toRecheck) {
630
- void this.resumeInjection(meta, decision, {
631
- checkOnce: false,
632
- });
633
- }
634
- break;
635
- }
636
- catch (e) {
637
- logger.verbose({
638
- label: this.label,
639
- message: `Failed to inject torrent ${meta.name} on attempt ${i + 1}/${retries}: ${e.message}`,
640
- });
641
- logger.debug(e);
642
- await wait(1000 * Math.pow(2, i));
643
- }
644
- }
645
- for (let i = 0; i < 5; i++) {
646
- if ((await this.isTorrentInClient(meta.infoHash)).orElse(false)) {
647
- return InjectionResult.SUCCESS;
648
- }
649
- await wait(100 * Math.pow(2, i));
650
- }
651
- return InjectionResult.FAILURE;
652
- }
653
- }
654
- //# sourceMappingURL=RTorrent.js.map