Skip to content

Commit

Permalink
fix(clients): refactor for v6 matching and linking features (#652)
Browse files Browse the repository at this point in the history
- rename dataCategory to linkCategory
- renamed legacyLinking to flatLinking
- refactored inject() in qbittorrent to tag duplicateCat when linking
- qbittorrent injection now should use flatLinking if using any autotmm
- refactored performAction() in action.ts
- updated config.template.js for new options
- updated configSchema.ts
- change partial match recheck to always
- refactor deluge labeling ternary
- refactor deluge to use Result class
- lots of logic changes for qbittorrent (inject()) formData
- extract recheck logic into a helper function
- #648

---------

Co-authored-by: Michael Goodnow <mmgoodnow@gmail.com>
Co-authored-by: Shanary <86130442+ShanaryS@users.noreply.github.com>
Co-authored-by: ShanaryS <shanarys@gmail.com>
  • Loading branch information
4 people committed May 9, 2024
1 parent 1b8ed17 commit b47fcd7
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 348 deletions.
5 changes: 3 additions & 2 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"devDependencies": {
"@types/bencode": "^2.0.1",
"@types/lowdb": "^1.0.9",
"@types/ms": "^0.7.34",
"@types/node": "^20.10.6",
"@types/xml2js": "^0.4.9",
"@types/xmlrpc": "^1.3.6",
Expand Down
78 changes: 38 additions & 40 deletions src/action.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import chalk from "chalk";
import {
existsSync,
linkSync,
mkdirSync,
readdirSync,
statSync,
symlinkSync,
} from "fs";
import { basename, dirname, join, relative, resolve } from "path";
import { existsSync, linkSync, mkdirSync, statSync, symlinkSync } from "fs";
import { dirname, join, resolve } from "path";
import { getClient } from "./clients/TorrentClient.js";
import {
Action,
Expand Down Expand Up @@ -59,25 +52,43 @@ function logInjectionResult(
}

/**
* this may not work with subfolder content layout
* @return the root of linked file. most likely "destinationDir/name". Not necessarily a file, it can be a directory
* @return the root of linked files.
*/
function linkExactTree(
newMeta: Metafile,
destinationDir: string,
sourceRoot: string,
): string {
if (newMeta.files.length === 1) {
return fuzzyLinkOneFile(newMeta, destinationDir, sourceRoot);
}
for (const newFile of newMeta.files) {
const srcFilePath = join(dirname(sourceRoot), newFile.path);
const destFilePath = join(destinationDir, newFile.path);
mkdirSync(dirname(destFilePath), { recursive: true });
linkFile(srcFilePath, destFilePath);
}
return join(destinationDir, newMeta.name);
}

/**
* @return the root of linked file.
*/
function fuzzyLinkOneFile(
searchee: Searchee,
newMeta: Metafile,
destinationDir: string,
sourceRoot: string,
): string {
const srcFilePath = join(
sourceRoot,
relative(searchee.name, searchee.files[0].path),
);
const srcFilePath = sourceRoot;
const destFilePath = join(destinationDir, newMeta.files[0].path);
mkdirSync(dirname(destFilePath), { recursive: true });
linkFile(srcFilePath, destFilePath);
return join(destinationDir, newMeta.name);
}

/**
* @return the root of linked files.
*/
function fuzzyLinkPartial(
searchee: Searchee,
newMeta: Metafile,
Expand Down Expand Up @@ -134,7 +145,12 @@ async function linkAllFilesInMetafile(
e === "NOT_FOUND" ? "TORRENT_NOT_FOUND" : e,
);
}
sourceRoot = join(downloadDirResult.unwrapOrThrow(), searchee.name);
sourceRoot = join(
downloadDirResult.unwrapOrThrow(),
searchee.files.length === 1
? searchee.files[0].path
: searchee.name,
);
}

if (!existsSync(sourceRoot)) {
Expand All @@ -145,11 +161,9 @@ async function linkAllFilesInMetafile(
}

if (decision === Decision.MATCH) {
return resultOf(linkExactTree(sourceRoot, fullLinkDir));
return resultOf(linkExactTree(newMeta, fullLinkDir, sourceRoot));
} else if (decision === Decision.MATCH_SIZE_ONLY) {
return resultOf(
fuzzyLinkOneFile(searchee, newMeta, fullLinkDir, sourceRoot),
);
return resultOf(fuzzyLinkOneFile(newMeta, fullLinkDir, sourceRoot));
} else {
return resultOf(
fuzzyLinkPartial(searchee, newMeta, fullLinkDir, sourceRoot),
Expand Down Expand Up @@ -195,6 +209,9 @@ export async function performAction(
linkedFilesRootResult.unwrapErrOrThrow() === "MISSING_DATA"
) {
logger.warn("Falling back to non-linking.");
if (searchee.path) {
destinationDir = dirname(searchee.path);
}
} else {
logInjectionResult(
InjectionResult.FAILURE,
Expand All @@ -213,7 +230,6 @@ export async function performAction(
// should be a MATCH, as risky requires a linkDir to be set
destinationDir = dirname(searchee.path);
}

const result = await getClient().inject(
newMeta,
searchee,
Expand Down Expand Up @@ -243,24 +259,6 @@ export async function performActions(searchee, matches) {
return results;
}

/**
* @return the root of linked files.
*/

function linkExactTree(oldPath: string, dest: string): string {
const newPath = join(dest, basename(oldPath));
if (statSync(oldPath).isFile()) {
mkdirSync(dirname(newPath), { recursive: true });
linkFile(oldPath, newPath);
} else {
mkdirSync(newPath, { recursive: true });
for (const dirent of readdirSync(oldPath)) {
linkExactTree(join(oldPath, dirent), newPath);
}
}
return newPath;
}

function linkFile(oldPath: string, newPath: string) {
const { linkType } = getRuntimeConfig();
try {
Expand Down
94 changes: 59 additions & 35 deletions src/clients/Deluge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import ms from "ms";
import {
Decision,
InjectionResult,
Expand All @@ -11,8 +10,9 @@ import { Metafile } from "../parseTorrent.js";
import { getRuntimeConfig } from "../runtimeConfig.js";
import { Searchee } from "../searchee.js";
import { TorrentClient } from "./TorrentClient.js";
import { extractCredentialsFromUrl } from "../utils.js";
import { shouldRecheck, extractCredentialsFromUrl, wait } from "../utils.js";
import { Result, resultOf, resultOfErr } from "../Result.js";
import ms from "ms";
interface TorrentInfo {
complete?: boolean;
save_path: string;
Expand Down Expand Up @@ -50,6 +50,7 @@ export default class Deluge implements TorrentClient {
private delugeLabel = TORRENT_TAG;
private delugeLabelSuffix = TORRENT_CATEGORY_SUFFIX;
private isLabelEnabled: boolean;
private delugeRequestId: number = 0;

/**
* validates the login and host for deluge webui
Expand Down Expand Up @@ -133,7 +134,6 @@ export default class Deluge implements TorrentClient {
if (this.delugeCookie) headers.set("Cookie", this.delugeCookie);

let response: Response, json: DelugeJSON<ResultType>;
const id = Math.floor(Math.random() * 0x7fffffff);
const abortController = new AbortController();

setTimeout(
Expand All @@ -146,7 +146,7 @@ export default class Deluge implements TorrentClient {
body: JSON.stringify({
method,
params,
id,
id: this.delugeRequestId++,
}),
method: "POST",
headers,
Expand Down Expand Up @@ -176,7 +176,7 @@ export default class Deluge implements TorrentClient {
return this.call<ResultType>(method, params, 0);
} else {
throw new Error(
"Connection lost with Deluge. Reauthentication failed.",
"Connection lost with Deluge. Re-authentication failed.",
);
}
}
Expand Down Expand Up @@ -207,6 +207,29 @@ export default class Deluge implements TorrentClient {
: enabledPlugins.result!.includes("Label");
}

// generates the label (string) for injection based on searchee and torrentInfo
private calculateLabel(
searchee: Searchee,
torrentInfo: TorrentInfo,
): string {
const { linkCategory, duplicateCategories } = getRuntimeConfig();
if (!searchee.infoHash || !torrentInfo!.label) {
return this.delugeLabel;
}
const ogLabel = torrentInfo!.label;
if (!duplicateCategories) {
return ogLabel;
}
const shouldSuffixLabel =
!ogLabel.endsWith(this.delugeLabelSuffix) && // no .cross-seed
ogLabel !== linkCategory; // not data

return searchee.path
? linkCategory
: shouldSuffixLabel
? `${ogLabel}${this.delugeLabelSuffix}`
: ogLabel;
}
/**
* if Label plugin is loaded, adds (if necessary)
* and sets the label based on torrent hash.
Expand Down Expand Up @@ -247,7 +270,6 @@ export default class Deluge implements TorrentClient {
path?: string,
): Promise<InjectionResult> {
try {
const { duplicateCategories } = getRuntimeConfig();
let torrentInfo: TorrentInfo;
if (searchee.infoHash) {
torrentInfo = await this.getTorrentInfo(searchee);
Expand All @@ -263,43 +285,48 @@ export default class Deluge implements TorrentClient {
return InjectionResult.FAILURE;
}

const torrentFileName = `${newTorrent.getFileSystemSafeName()}.cross-seed.torrent`;
const encodedTorrentData = newTorrent.encode().toString("base64");
const torrentPath = path ? path : torrentInfo!.save_path;
const params = this.formatData(
`${newTorrent.getFileSystemSafeName()}.cross-seed.torrent`,
newTorrent.encode().toString("base64"),
path ? path : torrentInfo!.save_path,
!!searchee.infoHash,
torrentFileName,
encodedTorrentData,
torrentPath,
decision,
);

const addResult = await this.call<string>(
"core.add_torrent_file",
params,
);
if (addResult.result) {
const { linkingCategory } = getRuntimeConfig();

const addResponse =
typeof addResult.error?.message === "string"
? addResult.error
: addResult.result;

if (typeof addResponse === "string") {
await this.setLabel(
newTorrent.name,
newTorrent.infoHash,
searchee.path
? linkingCategory
: torrentInfo!.label
? duplicateCategories
? torrentInfo!.label.endsWith(
this.delugeLabelSuffix,
) || torrentInfo!.label === linkingCategory
? torrentInfo!.label
: `${torrentInfo!.label}${
this.delugeLabelSuffix
}`
: torrentInfo!.label
: this.delugeLabel,
this.calculateLabel(searchee, torrentInfo!),
);

if (shouldRecheck(decision)) {
// when paused, libtorrent doesnt start rechecking
// leaves torrent ready to download - ~99%
await wait(1000);
await this.call<string>("core.force_recheck", [
[newTorrent.infoHash],
]);
}
return InjectionResult.SUCCESS;
} else if (addResult.error!.message!.includes("already")) {
} else if (addResponse?.message!.includes("already")) {
return InjectionResult.ALREADY_EXISTS;
} else if (addResult.error!.message) {
} else if (addResponse) {
logger.debug({
label: Label.DELUGE,
message: `Injection failed: ${addResult.error!.message}`,
message: `Injection failed: ${addResponse}`,
});
return InjectionResult.FAILURE;
} else {
Expand All @@ -326,28 +353,25 @@ export default class Deluge implements TorrentClient {
filename: string,
filedump: string,
path: string,
isTorrent: boolean,
decision:
| Decision.MATCH
| Decision.MATCH_SIZE_ONLY
| Decision.MATCH_PARTIAL,
): InjectData {
const { skipRecheck } = getRuntimeConfig();
const skipRecheckTorrent =
decision === Decision.MATCH_PARTIAL ? skipRecheck : true;
const toRecheck = shouldRecheck(decision);
return [
filename,
filedump,
{
add_paused: isTorrent ? !skipRecheckTorrent : !skipRecheck,
seed_mode: isTorrent ? skipRecheckTorrent : skipRecheck,
add_paused: toRecheck,
seed_mode: !toRecheck,
download_location: path,
},
];
}

/**
* returns directory of a infohash in deluge as a string
* returns directory of an infohash in deluge as a string
*/
async getDownloadDir(
searchee: Searchee,
Expand Down

0 comments on commit b47fcd7

Please sign in to comment.