Files
flood/server/models/ClientRequest.js
2020-09-17 02:36:24 +08:00

388 lines
10 KiB
JavaScript

/**
* This file is deprecated in favor of clientGatewayService.
*/
import mv from 'mv';
import path from 'path';
import util from 'util';
import {clientSettings, clientSettingsMap} from '../../shared/constants/clientSettingsMap';
import rTorrentPropMap from '../util/rTorrentPropMap';
import torrentStatusMap from '../../shared/constants/torrentStatusMap';
const addTagsToRequest = (tagsArr, requestParameters) => {
if (tagsArr && tagsArr.length) {
const tags = tagsArr
.reduce((accumulator, currentTag) => {
const tag = encodeURIComponent(currentTag.trim());
if (tag !== '' && accumulator.indexOf(tag) === -1) {
accumulator.push(tag);
}
return accumulator;
}, [])
.join(',');
requestParameters.push(`d.custom1.set="${tags}"`);
}
return requestParameters;
};
const getEnsuredArray = (item) => {
if (!util.isArray(item)) {
return [item];
}
return item;
};
const getMethodCall = (methodName, params) => {
params = params || [];
return {methodName, params};
};
class ClientRequest {
constructor(user, services, options) {
options = options || {};
this.services = services;
this.user = user;
this.clientRequestManager = this.services.clientRequestManager;
this.onCompleteFn = null;
this.postProcessFn = null;
this.requests = [];
if (options.onComplete) {
this.onCompleteFn = options.onComplete;
}
if (options.postProcess) {
this.postProcessFn = options.postProcess;
}
if (options.name) {
this.name = options.name;
}
}
clearRequestQueue() {
this.requests = [];
}
handleError(error) {
if (error.code === 'ECONNREFUSED') {
console.error(
`Connection refused at ${error.address}${error.port ? `:${error.port}` : ''}. ` +
'Check these values in config.js and ensure that rTorrent is running.',
);
}
this.clearRequestQueue();
if (this.onCompleteFn) {
this.onCompleteFn(null, error);
}
}
handleSuccess(data) {
let response = data;
this.clearRequestQueue();
if (this.postProcessFn) {
response = this.postProcessFn(data);
}
if (this.onCompleteFn) {
this.onCompleteFn(response);
}
}
onComplete(fn) {
this.onCompleteFn = fn;
}
postProcess(fn) {
this.postProcessFn = fn;
}
send() {
const handleSuccess = this.handleSuccess.bind(this);
const handleError = this.handleError.bind(this);
this.clientRequestManager.methodCall('system.multicall', [this.requests]).then(handleSuccess).catch(handleError);
}
// TODO: Separate these and add support for additional clients.
// rTorrent method calls.
addFiles(options) {
const files = getEnsuredArray(options.files);
const {path: destinationPath, isBasePath, start, tags: tagsArr} = options;
files.forEach((file) => {
let methodCall = 'load.raw_start';
let parameters = ['', file.buffer];
const timeAdded = Math.floor(Date.now() / 1000);
if (destinationPath) {
if (isBasePath) {
parameters.push(`d.directory_base.set="${destinationPath}"`);
} else {
parameters.push(`d.directory.set="${destinationPath}"`);
}
}
parameters = addTagsToRequest(tagsArr, parameters);
parameters.push(`d.custom.set=x-filename,${file.originalname}`);
parameters.push(`d.custom.set=addtime,${timeAdded}`);
// The start value is a string because it was appended to a FormData
// object.
if (start === 'false') {
methodCall = 'load.raw';
}
this.requests.push(getMethodCall(methodCall, parameters));
});
}
addURLs(options) {
const {path: destinationPath, isBasePath, start, tags: tagsArr} = options;
const urls = getEnsuredArray(options.urls);
urls.forEach((url) => {
let methodCall = 'load.start';
let parameters = ['', url];
const timeAdded = Math.floor(Date.now() / 1000);
if (destinationPath) {
if (isBasePath) {
parameters.push(`d.directory_base.set="${destinationPath}"`);
} else {
parameters.push(`d.directory.set="${destinationPath}"`);
}
}
parameters = addTagsToRequest(tagsArr, parameters);
parameters.push(`d.custom.set=addtime,${timeAdded}`);
if (!start) {
methodCall = 'load.normal';
}
this.requests.push(getMethodCall(methodCall, parameters));
});
}
checkHash(options) {
const {torrentService} = this.services;
const hashes = getEnsuredArray(options.hashes);
const stoppedHashes = hashes.filter((hash) =>
torrentService.getTorrent(hash).status.includes(torrentStatusMap.stopped),
);
const hashesToStart = [];
this.stopTorrents({hashes});
hashes.forEach((hash) => {
this.requests.push(getMethodCall('d.check_hash', [hash]));
if (!stoppedHashes.includes(hash)) {
hashesToStart.push(hash);
}
});
if (hashesToStart.length) {
this.startTorrents({hashes: hashesToStart});
}
}
fetchSettings(options) {
let {requestedSettings} = options;
if (requestedSettings == null) {
requestedSettings = Object.keys(clientSettings).map((settingsKey) => clientSettingsMap[settingsKey]);
}
// Ensure client's response gets mapped to the correct requested keys.
if (options.setRequestedKeysArr) {
options.setRequestedKeysArr(requestedSettings);
}
requestedSettings.forEach((settingsKey) => {
this.requests.push(getMethodCall(settingsKey));
});
}
getTorrentDetails(options) {
const peerParams = [options.hash, ''].concat(options.peerProps);
const fileParams = [options.hash, ''].concat(options.fileProps);
const trackerParams = [options.hash, ''].concat(options.trackerProps);
this.requests.push(getMethodCall('p.multicall', peerParams));
this.requests.push(getMethodCall('f.multicall', fileParams));
this.requests.push(getMethodCall('t.multicall', trackerParams));
}
getTorrentList(options) {
this.requests.push(getMethodCall('d.multicall2', options.props));
}
getTransferData() {
Object.keys(rTorrentPropMap.transferData).forEach((key) => {
this.requests.push(getMethodCall(rTorrentPropMap.transferData[key]));
});
}
listMethods(options) {
const args = getEnsuredArray(options.args);
this.requests.push(getMethodCall(options.method, [args]));
}
moveTorrents(options) {
const {destinationPath} = options;
const filenames = getEnsuredArray(options.filenames);
const sourcePaths = getEnsuredArray(options.sourcePaths);
sourcePaths.forEach((source, index) => {
let callback = () => {
// empty initializer
};
const destination = `${destinationPath}${path.sep}${filenames[index]}`;
const isLastRequest = index + 1 === sourcePaths.length;
if (isLastRequest) {
callback = this.handleSuccess.bind(this);
}
if (source !== destination) {
mv(source, destination, {mkdirp: true}, callback);
} else if (isLastRequest) {
callback();
}
});
}
setDownloadPath(options) {
const hashes = getEnsuredArray(options.hashes);
let pathMethod;
if (options.isBasePath) {
pathMethod = 'd.directory_base.set';
} else {
pathMethod = 'd.directory.set';
}
hashes.forEach((hash) => {
this.requests.push(getMethodCall(pathMethod, [hash, options.path]));
this.requests.push(getMethodCall('d.open', [hash]));
this.requests.push(getMethodCall('d.close', [hash]));
});
}
setFilePriority(options) {
const fileIndices = getEnsuredArray(options.fileIndices);
const hashes = getEnsuredArray(options.hashes);
hashes.forEach((hash) => {
fileIndices.forEach((fileIndex) => {
this.requests.push(getMethodCall('f.priority.set', [`${hash}:f${fileIndex}`, options.priority]));
});
this.requests.push(getMethodCall('d.update_priorities', [hash]));
});
}
setPriority(options) {
const hashes = getEnsuredArray(options.hashes);
hashes.forEach((hash) => {
this.requests.push(getMethodCall('d.priority.set', [hash, options.priority]));
this.requests.push(getMethodCall('d.update_priorities', [hash]));
});
}
setSettings(options) {
const settings = getEnsuredArray(options.settings);
settings.forEach((setting) => {
this.requests.push(getMethodCall(`${clientSettingsMap[setting.id]}.set`, ['', setting.data]));
});
}
setTaxonomy(options) {
const methodName = 'd.custom1.set';
const tags = options.tags
.reduce((memo, currentTag) => {
const tag = encodeURIComponent(currentTag.trim());
if (tag !== '' && memo.indexOf(tag) === -1) {
memo.push(tag);
}
return memo;
}, [])
.join(',');
getEnsuredArray(options.hashes).forEach((hash) => {
this.requests.push(getMethodCall(methodName, [hash, tags]));
});
}
setTracker(options) {
const existingTrackerIndex = 0;
const {tracker} = options;
getEnsuredArray(options.hashes).forEach((hash) => {
// Disable existing tracker
this.requests.push(getMethodCall('t.disable', [`${hash}:t${existingTrackerIndex}`]));
// Insert new tracker
this.requests.push(getMethodCall('d.tracker.insert', [hash, `${existingTrackerIndex}`, tracker]));
// Save full session to apply tracker change
this.requests.push(getMethodCall('d.save_full_session', [hash]));
});
}
setThrottle(options) {
let methodName = 'throttle.global_down.max_rate.set';
if (options.direction === 'upload') {
methodName = 'throttle.global_up.max_rate.set';
}
this.requests.push(getMethodCall(methodName, ['', options.throttle]));
}
startTorrents(options) {
if (!options.hashes) {
console.error("startTorrents requires key 'hashes'.");
return;
}
getEnsuredArray(options.hashes).forEach((hash) => {
this.requests.push(getMethodCall('d.open', [hash]));
this.requests.push(getMethodCall('d.start', [hash]));
});
}
stopTorrents(options) {
if (!options.hashes) {
console.error("stopTorrents requires key 'hashes'.");
return;
}
getEnsuredArray(options.hashes).forEach((hash) => {
this.requests.push(getMethodCall('d.stop', [hash]));
this.requests.push(getMethodCall('d.close', [hash]));
});
}
getSessionPath() {
this.requests.push(getMethodCall('session.path'));
}
}
export default ClientRequest;