mirror of
https://github.com/zoriya/flood.git
synced 2025-12-19 13:45:15 +00:00
Ensure that only files belonging to torrent are deleted
This commit is contained in:
@@ -7,6 +7,7 @@ const clientRequestServiceEvents = [
|
||||
'PROCESS_TORRENT_LIST_END',
|
||||
'PROCESS_TORRENT_LIST_START',
|
||||
'PROCESS_TRANSFER_RATE_START',
|
||||
'TORRENTS_REMOVED'
|
||||
];
|
||||
|
||||
module.exports = objectUtil.createSymbolMapFromArray(
|
||||
|
||||
53
server/constants/fileListPropMap.js
Normal file
53
server/constants/fileListPropMap.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
const fileListPropMap = new Map();
|
||||
const defaultTransformer = value => value;
|
||||
|
||||
fileListPropMap.set(
|
||||
'path',
|
||||
{
|
||||
methodCall: 'f.path=',
|
||||
transformValue: defaultTransformer
|
||||
}
|
||||
);
|
||||
|
||||
fileListPropMap.set(
|
||||
'pathComponents',
|
||||
{
|
||||
methodCall: 'f.path_components=',
|
||||
transformValue: defaultTransformer
|
||||
}
|
||||
);
|
||||
|
||||
fileListPropMap.set(
|
||||
'priority',
|
||||
{
|
||||
methodCall: 'f.priority=',
|
||||
transformValue: defaultTransformer
|
||||
}
|
||||
);
|
||||
|
||||
fileListPropMap.set(
|
||||
'sizeBytes',
|
||||
{
|
||||
methodCall: 'f.size_bytes=',
|
||||
transformValue: Number
|
||||
}
|
||||
);
|
||||
|
||||
fileListPropMap.set(
|
||||
'sizeChunks',
|
||||
{
|
||||
methodCall: 'f.size_chunks=',
|
||||
transformValue: Number
|
||||
}
|
||||
);
|
||||
|
||||
fileListPropMap.set(
|
||||
'completedChunks',
|
||||
{
|
||||
methodCall: 'f.completed_chunks=',
|
||||
transformValue: Number
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = fileListPropMap;
|
||||
@@ -260,14 +260,6 @@ class ClientRequest {
|
||||
});
|
||||
}
|
||||
|
||||
removeTorrents(options) {
|
||||
let hashes = this.getEnsuredArray(options.hashes);
|
||||
|
||||
hashes.forEach((hash) => {
|
||||
this.requests.push(this.getMethodCall('d.erase', [hash]));
|
||||
});
|
||||
}
|
||||
|
||||
setDownloadPath(options) {
|
||||
let hashes = this.getEnsuredArray(options.hashes);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ const clientResponseUtil = require('../util/clientResponseUtil');
|
||||
const clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
||||
const ClientRequest = require('./ClientRequest');
|
||||
const formatUtil = require('../../shared/util/formatUtil');
|
||||
const scgi = require('../util/scgi');
|
||||
const TemporaryStorage = require('./TemporaryStorage');
|
||||
const torrentFilePropsMap = require('../../shared/constants/torrentFilePropsMap');
|
||||
const torrentPeerPropsMap = require('../../shared/constants/torrentPeerPropsMap');
|
||||
@@ -78,43 +77,6 @@ var client = {
|
||||
request.send();
|
||||
},
|
||||
|
||||
deleteTorrents: (options, callback) => {
|
||||
let filesToDelete = null;
|
||||
let eraseTorrentsRequest = new ClientRequest();
|
||||
|
||||
eraseTorrentsRequest.removeTorrents({hashes: options.hashes});
|
||||
eraseTorrentsRequest.onComplete((response, error) => {
|
||||
if (options.deleteData) {
|
||||
const torrents = torrentCollection.torrents;
|
||||
|
||||
options.hashes.forEach(hash => {
|
||||
let fileToDelete = null;
|
||||
const torrent = torrents[hash];
|
||||
|
||||
if (torrent.isMultiFile && torrent.directory != null) {
|
||||
fileToDelete = torrent.directory;
|
||||
} else if (torrent.directory != null && torrent.name != null) {
|
||||
fileToDelete = path.join(torrent.directory, torrent.name);
|
||||
}
|
||||
|
||||
if (fileToDelete != null) {
|
||||
rimraf(fileToDelete, {disableGlob: true}, error => {
|
||||
if (error) {
|
||||
console.error(`Error deleting file: ${fileToDelete}\n${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
torrentService.fetchTorrentList();
|
||||
|
||||
callback(response, error);
|
||||
});
|
||||
|
||||
eraseTorrentsRequest.send();
|
||||
},
|
||||
|
||||
downloadFiles(hash, files, res) {
|
||||
try {
|
||||
let selectedTorrent = null;
|
||||
|
||||
@@ -4,6 +4,7 @@ const multer = require('multer');
|
||||
|
||||
const ajaxUtil = require('../util/ajaxUtil');
|
||||
const client = require('../models/client');
|
||||
const clientRequestService = require('../services/clientRequestService');
|
||||
const router = express.Router();
|
||||
|
||||
const upload = multer({
|
||||
@@ -65,10 +66,15 @@ router.post('/torrents/move', function(req, res, next) {
|
||||
});
|
||||
|
||||
router.post('/torrents/delete', function(req, res, next) {
|
||||
let deleteData = req.body.deleteData;
|
||||
let hashes = req.body.hash;
|
||||
const {deleteData, hash: hashes} = req.body;
|
||||
const callback = ajaxUtil.getResponseFn(res);
|
||||
|
||||
client.deleteTorrents({hashes, deleteData}, ajaxUtil.getResponseFn(res));
|
||||
clientRequestService
|
||||
.removeTorrents({hashes, deleteData})
|
||||
.then(callback)
|
||||
.catch((err) => {
|
||||
callback(null, response);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/torrents/taxonomy', function(req, res, next) {
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
'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() {
|
||||
@@ -33,6 +43,89 @@ class ClientRequestService extends EventEmitter {
|
||||
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.
|
||||
*
|
||||
@@ -48,15 +141,10 @@ class ClientRequestService extends EventEmitter {
|
||||
* with the processed client error.
|
||||
*/
|
||||
fetchTorrentList(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
scgi.methodCall('d.multicall2', ['', 'main'].concat(options.methodCalls))
|
||||
.then((torrentList) => {
|
||||
resolve(this.processTorrentListResponse(torrentList, options));
|
||||
})
|
||||
.catch((clientError) => {
|
||||
reject(this.processClientError(clientError));
|
||||
});
|
||||
});
|
||||
return scgi
|
||||
.methodCall('d.multicall2', ['', 'main'].concat(options.methodCalls))
|
||||
.then(torrents => this.processTorrentListResponse(torrents, options))
|
||||
.catch(clientError => this.processClientError(clientError));
|
||||
}
|
||||
|
||||
fetchTransferSummary(options) {
|
||||
@@ -64,15 +152,14 @@ class ClientRequestService extends EventEmitter {
|
||||
return {methodName, params: []};
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
scgi.methodCall('system.multicall', [methodCalls])
|
||||
.then((transferRate) => {
|
||||
resolve(this.processTransferRateResponse(transferRate, options));
|
||||
return scgi
|
||||
.methodCall('system.multicall', [methodCalls])
|
||||
.then(transferRate => {
|
||||
return this.processTransferRateResponse(transferRate, options);
|
||||
})
|
||||
.catch((clientError) => {
|
||||
reject(this.processClientError(clientError));
|
||||
.catch(clientError => {
|
||||
return this.processClientError(clientError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
processClientError(error) {
|
||||
|
||||
@@ -6,27 +6,12 @@ const config = require('../../config');
|
||||
const HistoryEra = require('../models/HistoryEra');
|
||||
const historyServiceEvents = require('../constants/historyServiceEvents');
|
||||
const historySnapshotTypes = require('../../shared/constants/historySnapshotTypes');
|
||||
const methodCallUtil = require('../util/methodCallUtil');
|
||||
const objectUtil = require('../../shared/util/objectUtil');
|
||||
const transferSummaryPropMap = require('../constants/transferSummaryPropMap');
|
||||
|
||||
const transferSummaryFetchOptions = Array
|
||||
.from(transferSummaryPropMap.keys())
|
||||
.reduce(
|
||||
(accumulator, key) => {
|
||||
const {methodCall, transformValue} = transferSummaryPropMap.get(key);
|
||||
|
||||
accumulator.methodCalls.push(methodCall);
|
||||
accumulator.propLabels.push(key);
|
||||
accumulator.valueTransformations.push(transformValue);
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{
|
||||
methodCalls: [],
|
||||
propLabels: [],
|
||||
valueTransformations: []
|
||||
}
|
||||
);
|
||||
const transferSummaryMethodCallConfig = methodCallUtil
|
||||
.getMethodCallConfigFromPropMap(transferSummaryPropMap);
|
||||
|
||||
const processData = (opts, callback, data, error) => {
|
||||
if (error) {
|
||||
@@ -159,7 +144,7 @@ class HistoryService extends EventEmitter {
|
||||
}
|
||||
|
||||
clientRequestService
|
||||
.fetchTransferSummary(transferSummaryFetchOptions)
|
||||
.fetchTransferSummary(transferSummaryMethodCallConfig)
|
||||
.then(this.handleFetchTransferSummarySuccess.bind(this))
|
||||
.catch(this.handleFetchTransferSummaryError.bind(this));
|
||||
}
|
||||
|
||||
@@ -5,30 +5,15 @@ const config = require('../../config.js');
|
||||
const clientRequestService = require('./clientRequestService.js');
|
||||
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
||||
const formatUtil = require('../../shared/util/formatUtil');
|
||||
const methodCallUtil = require('../util/methodCallUtil');
|
||||
const notificationService = require('./notificationService.js');
|
||||
const serverEventTypes = require('../../shared/constants/serverEventTypes');
|
||||
const torrentListPropMap = require('../constants/torrentListPropMap');
|
||||
const torrentServiceEvents = require('../constants/torrentServiceEvents.js');
|
||||
const torrentStatusMap = require('../../shared/constants/torrentStatusMap');
|
||||
|
||||
const torrentListFetchOptions = Array
|
||||
.from(torrentListPropMap.keys())
|
||||
.reduce(
|
||||
(accumulator, key) => {
|
||||
const {methodCall, transformValue} = torrentListPropMap.get(key);
|
||||
|
||||
accumulator.methodCalls.push(methodCall);
|
||||
accumulator.propLabels.push(key);
|
||||
accumulator.valueTransformations.push(transformValue);
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{
|
||||
methodCalls: [],
|
||||
propLabels: [],
|
||||
valueTransformations: []
|
||||
}
|
||||
);
|
||||
const torrentListMethodCallConfig = methodCallUtil
|
||||
.getMethodCallConfigFromPropMap(torrentListPropMap);
|
||||
|
||||
class TorrentService extends EventEmitter {
|
||||
constructor() {
|
||||
@@ -40,6 +25,7 @@ class TorrentService extends EventEmitter {
|
||||
|
||||
this.fetchTorrentList = this.fetchTorrentList.bind(this);
|
||||
this.handleTorrentProcessed = this.handleTorrentProcessed.bind(this);
|
||||
this.handleTorrentsRemoved = this.handleTorrentsRemoved.bind(this);
|
||||
|
||||
clientRequestService.addTorrentListReducer({
|
||||
key: 'status',
|
||||
@@ -61,6 +47,11 @@ class TorrentService extends EventEmitter {
|
||||
this.handleTorrentProcessed
|
||||
);
|
||||
|
||||
clientRequestService.on(
|
||||
clientRequestServiceEvents.TORRENTS_REMOVED,
|
||||
this.handleTorrentsRemoved
|
||||
);
|
||||
|
||||
this.fetchTorrentList();
|
||||
}
|
||||
|
||||
@@ -117,7 +108,7 @@ class TorrentService extends EventEmitter {
|
||||
}
|
||||
|
||||
clientRequestService
|
||||
.fetchTorrentList(torrentListFetchOptions)
|
||||
.fetchTorrentList(torrentListMethodCallConfig)
|
||||
.then(this.handleFetchTorrentListSuccess.bind(this))
|
||||
.catch(this.handleFetchTorrentListError.bind(this));
|
||||
}
|
||||
@@ -321,6 +312,10 @@ class TorrentService extends EventEmitter {
|
||||
&& nextData.percentComplete === 100
|
||||
);
|
||||
}
|
||||
|
||||
handleTorrentsRemoved() {
|
||||
this.fetchTorrentList();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new TorrentService();
|
||||
|
||||
30
server/util/methodCallUtil.js
Normal file
30
server/util/methodCallUtil.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const methodCallUtil = {
|
||||
getMethodCallConfigFromPropMap(map = new Map(), requestedKeys) {
|
||||
let desiredKeys = Array.from(map.keys());
|
||||
|
||||
if (requestedKeys != null) {
|
||||
desiredKeys = desiredKeys.filter(key => requestedKeys.includes(key));
|
||||
}
|
||||
|
||||
return desiredKeys.reduce(
|
||||
(accumulator, key) => {
|
||||
const {methodCall, transformValue} = map.get(key);
|
||||
|
||||
accumulator.methodCalls.push(methodCall);
|
||||
accumulator.propLabels.push(key);
|
||||
accumulator.valueTransformations.push(transformValue);
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{
|
||||
methodCalls: [],
|
||||
propLabels: [],
|
||||
valueTransformations: []
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = methodCallUtil;
|
||||
Reference in New Issue
Block a user