mirror of
https://github.com/zoriya/flood.git
synced 2025-12-20 06:05:15 +00:00
257 lines
8.7 KiB
JavaScript
257 lines
8.7 KiB
JavaScript
'use strict';
|
|
const EventEmitter = require('events');
|
|
const path = require('path');
|
|
const rimraf = require('rimraf');
|
|
|
|
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
|
const fileListPropMap = require('../constants/fileListPropMap');
|
|
const methodCallUtil = require('../util/methodCallUtil');
|
|
const scgi = require('../util/scgi');
|
|
const torrentListPropMap = require('../constants/torrentListPropMap');
|
|
|
|
const fileListMethodCallConfig = methodCallUtil.getMethodCallConfigFromPropMap(
|
|
fileListPropMap,
|
|
['pathComponents']
|
|
);
|
|
|
|
class ClientRequestService extends EventEmitter {
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
this.torrentListReducers = [];
|
|
}
|
|
|
|
/**
|
|
* Adds a reducer to be applied when processing the torrent list.
|
|
*
|
|
* @param {Object} reducer - The reducer object
|
|
* @param {string} reducer.key - The key of the reducer, to be applied to the
|
|
* torrent list object.
|
|
* @param {function} reducer.reduce - The actual reducer. This will recevie
|
|
* the entire processed torrent list response and it should return it own
|
|
* processed value, to be assigned to the provided key.
|
|
*/
|
|
addTorrentListReducer(reducer = {}) {
|
|
if (typeof reducer.key !== 'string') {
|
|
throw new Error('reducer.key must be a string.');
|
|
}
|
|
|
|
if (typeof reducer.reduce !== 'function') {
|
|
throw new Error('reducer.reduce must be a function.');
|
|
}
|
|
|
|
this.torrentListReducers.push(reducer);
|
|
}
|
|
|
|
removeTorrents(options = {hashes: [], deleteData: false}) {
|
|
const methodCalls = options.hashes.reduce(
|
|
(accumulator, hash, index) => {
|
|
let eraseFileMethodCallIndex = index;
|
|
|
|
// If we're deleting files, we grab each torrents' file list before we
|
|
// remove them.
|
|
if (options.deleteData) {
|
|
// We offset the indices of these method calls so that we know exactly
|
|
// where to retrieve them in the future.
|
|
const directoryBaseMethodCallIndex = index + options.hashes.length;
|
|
eraseFileMethodCallIndex = index + options.hashes.length * 2;
|
|
|
|
accumulator[index] = {
|
|
methodName: 'f.multicall',
|
|
params: [hash, ''].concat(fileListMethodCallConfig.methodCalls)
|
|
};
|
|
|
|
accumulator[directoryBaseMethodCallIndex] = {
|
|
methodName: 'd.directory_base',
|
|
params: [hash]
|
|
};
|
|
}
|
|
|
|
accumulator[eraseFileMethodCallIndex] = {
|
|
methodName: 'd.erase',
|
|
params: [hash]
|
|
};
|
|
|
|
return accumulator;
|
|
},
|
|
[]
|
|
);
|
|
|
|
return scgi
|
|
.methodCall('system.multicall', [methodCalls])
|
|
.then((response) => {
|
|
if (options.deleteData) {
|
|
const torrentCount = options.hashes.length;
|
|
const filesToDelete = options.hashes.reduce(
|
|
(accumulator, hash, hashIndex) => {
|
|
const fileList = response[hashIndex][0];
|
|
const directoryBase = response[hashIndex + torrentCount][0];
|
|
|
|
const filesToDelete = fileList.reduce(
|
|
(fileListAccumulator, file) => {
|
|
// We only look at the first path component returned because
|
|
// if it's a directory within the torrent, then we'll remove
|
|
// the entire directory.
|
|
const filePath = path.join(directoryBase, file[0][0]);
|
|
|
|
// filePath might be a directory, so it may have already been
|
|
// added. If not, we add it.
|
|
if (!fileListAccumulator.includes(filePath)) {
|
|
fileListAccumulator.push(filePath);
|
|
}
|
|
|
|
return fileListAccumulator;
|
|
},
|
|
[]
|
|
);
|
|
|
|
return accumulator.concat(filesToDelete);
|
|
},
|
|
[]
|
|
);
|
|
|
|
filesToDelete.forEach(file => {
|
|
rimraf(file, {disableGlob: true}, error => {
|
|
if (error) {
|
|
console.error(`Error deleting file: ${file}\n${error}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
this.emit(clientRequestServiceEvents.TORRENTS_REMOVED, options);
|
|
|
|
return response;
|
|
})
|
|
.catch(clientError => this.processClientError(clientError));
|
|
}
|
|
|
|
/**
|
|
* Sends a multicall request to rTorrent with the requested method calls.
|
|
*
|
|
* @param {Object} options - An object of options...
|
|
* @param {Array} options.methodCalls - An array of strings representing
|
|
* method calls, which the client uses to retrieve details.
|
|
* @param {Array} options.propLabels - An array of strings that are used as
|
|
* keys for the transformed torrent details.
|
|
* @param {Array} options.valueTransformations - An array of functions that
|
|
* will be called with the values as returned by the client. These return
|
|
* values will be assigned to the key from the propLabels array.
|
|
* @return {Promise} - Resolves with the processed client response or rejects
|
|
* with the processed client error.
|
|
*/
|
|
fetchTorrentList(options) {
|
|
return scgi
|
|
.methodCall('d.multicall2', ['', 'main'].concat(options.methodCalls))
|
|
.then(torrents => this.processTorrentListResponse(torrents, options))
|
|
.catch(clientError => this.processClientError(clientError));
|
|
}
|
|
|
|
fetchTransferSummary(options) {
|
|
const methodCalls = options.methodCalls.map(methodName => {
|
|
return {methodName, params: []};
|
|
});
|
|
|
|
return scgi
|
|
.methodCall('system.multicall', [methodCalls])
|
|
.then(transferRate => {
|
|
return this.processTransferRateResponse(transferRate, options);
|
|
})
|
|
.catch(clientError => {
|
|
return this.processClientError(clientError);
|
|
});
|
|
}
|
|
|
|
processClientError(error) {
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* After rTorrent responds with the requested torrent details, we construct
|
|
* an object with hashes as keys and processed details as values.
|
|
*
|
|
* @param {Array} response - The array of all torrents and their details.
|
|
* @param {Object} options - An object of options that instruct us how to
|
|
* process the client's response.
|
|
* @param {Array} options.propLabels - An array of strings that map to the
|
|
* method call. These are the keys of the torrent details.
|
|
* @param {Array} options.valueTransformations - An array of functions that
|
|
* transform the detail from the client's response.
|
|
* @return {Object} - An object that represents all torrents with hashes as
|
|
* keys, each value being an object of detail labels and values.
|
|
*/
|
|
processTorrentListResponse(torrentList, options) {
|
|
this.emit(clientRequestServiceEvents.PROCESS_TORRENT_LIST_START);
|
|
|
|
// We map the array of details to objects with sensibly named keys. We want
|
|
// to return an object with torrent hashes as keys and an object of torrent
|
|
// details as values.
|
|
const processedTorrentList = torrentList.reduce(
|
|
(listAccumulator, torrentDetailValues) => {
|
|
// Transform the array of torrent detail values to an object with
|
|
// sensibly named keys.
|
|
const processedTorrentDetailValues = torrentDetailValues.reduce(
|
|
(valueAccumulator, value, valueIndex) => {
|
|
const key = options.propLabels[valueIndex];
|
|
const transformValue = options.valueTransformations[valueIndex];
|
|
|
|
valueAccumulator[key] = transformValue(value);
|
|
return valueAccumulator;
|
|
},
|
|
{}
|
|
);
|
|
|
|
// Assign values from external reducers to the torrent list object.
|
|
this.torrentListReducers.forEach(reducer => {
|
|
const {key, reduce} = reducer;
|
|
|
|
processedTorrentDetailValues[key] = reduce(
|
|
processedTorrentDetailValues
|
|
);
|
|
});
|
|
|
|
listAccumulator.torrents[processedTorrentDetailValues.hash] =
|
|
processedTorrentDetailValues;
|
|
|
|
this.emit(
|
|
clientRequestServiceEvents.PROCESS_TORRENT,
|
|
processedTorrentDetailValues
|
|
);
|
|
|
|
return listAccumulator;
|
|
},
|
|
{torrents: {}}
|
|
);
|
|
|
|
// Provide the number of torrents.
|
|
processedTorrentList.length = torrentList.length;
|
|
// Provide a unique ID for this specific torrent list.
|
|
processedTorrentList.id = Date.now();
|
|
|
|
this.emit(
|
|
clientRequestServiceEvents.PROCESS_TORRENT_LIST_END,
|
|
processedTorrentList
|
|
);
|
|
|
|
return processedTorrentList;
|
|
}
|
|
|
|
processTransferRateResponse(transferRate = [], options) {
|
|
this.emit(clientRequestServiceEvents.PROCESS_TRANSFER_RATE_START);
|
|
|
|
return transferRate.reduce(
|
|
(accumulator, value, index) => {
|
|
const key = options.propLabels[index];
|
|
const transformValue = options.valueTransformations[index];
|
|
|
|
accumulator[key] = transformValue(value);
|
|
|
|
return accumulator;
|
|
},
|
|
{}
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = new ClientRequestService();
|