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_END',
|
||||||
'PROCESS_TORRENT_LIST_START',
|
'PROCESS_TORRENT_LIST_START',
|
||||||
'PROCESS_TRANSFER_RATE_START',
|
'PROCESS_TRANSFER_RATE_START',
|
||||||
|
'TORRENTS_REMOVED'
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = objectUtil.createSymbolMapFromArray(
|
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) {
|
setDownloadPath(options) {
|
||||||
let hashes = this.getEnsuredArray(options.hashes);
|
let hashes = this.getEnsuredArray(options.hashes);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const clientResponseUtil = require('../util/clientResponseUtil');
|
|||||||
const clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
const clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
||||||
const ClientRequest = require('./ClientRequest');
|
const ClientRequest = require('./ClientRequest');
|
||||||
const formatUtil = require('../../shared/util/formatUtil');
|
const formatUtil = require('../../shared/util/formatUtil');
|
||||||
const scgi = require('../util/scgi');
|
|
||||||
const TemporaryStorage = require('./TemporaryStorage');
|
const TemporaryStorage = require('./TemporaryStorage');
|
||||||
const torrentFilePropsMap = require('../../shared/constants/torrentFilePropsMap');
|
const torrentFilePropsMap = require('../../shared/constants/torrentFilePropsMap');
|
||||||
const torrentPeerPropsMap = require('../../shared/constants/torrentPeerPropsMap');
|
const torrentPeerPropsMap = require('../../shared/constants/torrentPeerPropsMap');
|
||||||
@@ -78,43 +77,6 @@ var client = {
|
|||||||
request.send();
|
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) {
|
downloadFiles(hash, files, res) {
|
||||||
try {
|
try {
|
||||||
let selectedTorrent = null;
|
let selectedTorrent = null;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const multer = require('multer');
|
|||||||
|
|
||||||
const ajaxUtil = require('../util/ajaxUtil');
|
const ajaxUtil = require('../util/ajaxUtil');
|
||||||
const client = require('../models/client');
|
const client = require('../models/client');
|
||||||
|
const clientRequestService = require('../services/clientRequestService');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
@@ -65,10 +66,15 @@ router.post('/torrents/move', function(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/torrents/delete', function(req, res, next) {
|
router.post('/torrents/delete', function(req, res, next) {
|
||||||
let deleteData = req.body.deleteData;
|
const {deleteData, hash: hashes} = req.body;
|
||||||
let hashes = req.body.hash;
|
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) {
|
router.get('/torrents/taxonomy', function(req, res, next) {
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
const path = require('path');
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
|
||||||
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
||||||
|
const fileListPropMap = require('../constants/fileListPropMap');
|
||||||
|
const methodCallUtil = require('../util/methodCallUtil');
|
||||||
const scgi = require('../util/scgi');
|
const scgi = require('../util/scgi');
|
||||||
|
const torrentListPropMap = require('../constants/torrentListPropMap');
|
||||||
|
|
||||||
|
const fileListMethodCallConfig = methodCallUtil.getMethodCallConfigFromPropMap(
|
||||||
|
fileListPropMap,
|
||||||
|
['pathComponents']
|
||||||
|
);
|
||||||
|
|
||||||
class ClientRequestService extends EventEmitter {
|
class ClientRequestService extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -33,6 +43,89 @@ class ClientRequestService extends EventEmitter {
|
|||||||
this.torrentListReducers.push(reducer);
|
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.
|
* Sends a multicall request to rTorrent with the requested method calls.
|
||||||
*
|
*
|
||||||
@@ -48,15 +141,10 @@ class ClientRequestService extends EventEmitter {
|
|||||||
* with the processed client error.
|
* with the processed client error.
|
||||||
*/
|
*/
|
||||||
fetchTorrentList(options) {
|
fetchTorrentList(options) {
|
||||||
return new Promise((resolve, reject) => {
|
return scgi
|
||||||
scgi.methodCall('d.multicall2', ['', 'main'].concat(options.methodCalls))
|
.methodCall('d.multicall2', ['', 'main'].concat(options.methodCalls))
|
||||||
.then((torrentList) => {
|
.then(torrents => this.processTorrentListResponse(torrents, options))
|
||||||
resolve(this.processTorrentListResponse(torrentList, options));
|
.catch(clientError => this.processClientError(clientError));
|
||||||
})
|
|
||||||
.catch((clientError) => {
|
|
||||||
reject(this.processClientError(clientError));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchTransferSummary(options) {
|
fetchTransferSummary(options) {
|
||||||
@@ -64,15 +152,14 @@ class ClientRequestService extends EventEmitter {
|
|||||||
return {methodName, params: []};
|
return {methodName, params: []};
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return scgi
|
||||||
scgi.methodCall('system.multicall', [methodCalls])
|
.methodCall('system.multicall', [methodCalls])
|
||||||
.then((transferRate) => {
|
.then(transferRate => {
|
||||||
resolve(this.processTransferRateResponse(transferRate, options));
|
return this.processTransferRateResponse(transferRate, options);
|
||||||
})
|
})
|
||||||
.catch((clientError) => {
|
.catch(clientError => {
|
||||||
reject(this.processClientError(clientError));
|
return this.processClientError(clientError);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processClientError(error) {
|
processClientError(error) {
|
||||||
|
|||||||
@@ -6,27 +6,12 @@ const config = require('../../config');
|
|||||||
const HistoryEra = require('../models/HistoryEra');
|
const HistoryEra = require('../models/HistoryEra');
|
||||||
const historyServiceEvents = require('../constants/historyServiceEvents');
|
const historyServiceEvents = require('../constants/historyServiceEvents');
|
||||||
const historySnapshotTypes = require('../../shared/constants/historySnapshotTypes');
|
const historySnapshotTypes = require('../../shared/constants/historySnapshotTypes');
|
||||||
|
const methodCallUtil = require('../util/methodCallUtil');
|
||||||
const objectUtil = require('../../shared/util/objectUtil');
|
const objectUtil = require('../../shared/util/objectUtil');
|
||||||
const transferSummaryPropMap = require('../constants/transferSummaryPropMap');
|
const transferSummaryPropMap = require('../constants/transferSummaryPropMap');
|
||||||
|
|
||||||
const transferSummaryFetchOptions = Array
|
const transferSummaryMethodCallConfig = methodCallUtil
|
||||||
.from(transferSummaryPropMap.keys())
|
.getMethodCallConfigFromPropMap(transferSummaryPropMap);
|
||||||
.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 processData = (opts, callback, data, error) => {
|
const processData = (opts, callback, data, error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -159,7 +144,7 @@ class HistoryService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientRequestService
|
clientRequestService
|
||||||
.fetchTransferSummary(transferSummaryFetchOptions)
|
.fetchTransferSummary(transferSummaryMethodCallConfig)
|
||||||
.then(this.handleFetchTransferSummarySuccess.bind(this))
|
.then(this.handleFetchTransferSummarySuccess.bind(this))
|
||||||
.catch(this.handleFetchTransferSummaryError.bind(this));
|
.catch(this.handleFetchTransferSummaryError.bind(this));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,30 +5,15 @@ const config = require('../../config.js');
|
|||||||
const clientRequestService = require('./clientRequestService.js');
|
const clientRequestService = require('./clientRequestService.js');
|
||||||
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
const clientRequestServiceEvents = require('../constants/clientRequestServiceEvents');
|
||||||
const formatUtil = require('../../shared/util/formatUtil');
|
const formatUtil = require('../../shared/util/formatUtil');
|
||||||
|
const methodCallUtil = require('../util/methodCallUtil');
|
||||||
const notificationService = require('./notificationService.js');
|
const notificationService = require('./notificationService.js');
|
||||||
const serverEventTypes = require('../../shared/constants/serverEventTypes');
|
const serverEventTypes = require('../../shared/constants/serverEventTypes');
|
||||||
const torrentListPropMap = require('../constants/torrentListPropMap');
|
const torrentListPropMap = require('../constants/torrentListPropMap');
|
||||||
const torrentServiceEvents = require('../constants/torrentServiceEvents.js');
|
const torrentServiceEvents = require('../constants/torrentServiceEvents.js');
|
||||||
const torrentStatusMap = require('../../shared/constants/torrentStatusMap');
|
const torrentStatusMap = require('../../shared/constants/torrentStatusMap');
|
||||||
|
|
||||||
const torrentListFetchOptions = Array
|
const torrentListMethodCallConfig = methodCallUtil
|
||||||
.from(torrentListPropMap.keys())
|
.getMethodCallConfigFromPropMap(torrentListPropMap);
|
||||||
.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: []
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
class TorrentService extends EventEmitter {
|
class TorrentService extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -40,6 +25,7 @@ class TorrentService extends EventEmitter {
|
|||||||
|
|
||||||
this.fetchTorrentList = this.fetchTorrentList.bind(this);
|
this.fetchTorrentList = this.fetchTorrentList.bind(this);
|
||||||
this.handleTorrentProcessed = this.handleTorrentProcessed.bind(this);
|
this.handleTorrentProcessed = this.handleTorrentProcessed.bind(this);
|
||||||
|
this.handleTorrentsRemoved = this.handleTorrentsRemoved.bind(this);
|
||||||
|
|
||||||
clientRequestService.addTorrentListReducer({
|
clientRequestService.addTorrentListReducer({
|
||||||
key: 'status',
|
key: 'status',
|
||||||
@@ -61,6 +47,11 @@ class TorrentService extends EventEmitter {
|
|||||||
this.handleTorrentProcessed
|
this.handleTorrentProcessed
|
||||||
);
|
);
|
||||||
|
|
||||||
|
clientRequestService.on(
|
||||||
|
clientRequestServiceEvents.TORRENTS_REMOVED,
|
||||||
|
this.handleTorrentsRemoved
|
||||||
|
);
|
||||||
|
|
||||||
this.fetchTorrentList();
|
this.fetchTorrentList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +108,7 @@ class TorrentService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientRequestService
|
clientRequestService
|
||||||
.fetchTorrentList(torrentListFetchOptions)
|
.fetchTorrentList(torrentListMethodCallConfig)
|
||||||
.then(this.handleFetchTorrentListSuccess.bind(this))
|
.then(this.handleFetchTorrentListSuccess.bind(this))
|
||||||
.catch(this.handleFetchTorrentListError.bind(this));
|
.catch(this.handleFetchTorrentListError.bind(this));
|
||||||
}
|
}
|
||||||
@@ -321,6 +312,10 @@ class TorrentService extends EventEmitter {
|
|||||||
&& nextData.percentComplete === 100
|
&& nextData.percentComplete === 100
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTorrentsRemoved() {
|
||||||
|
this.fetchTorrentList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new TorrentService();
|
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