diff --git a/server/routes/api/torrents.ts b/server/routes/api/torrents.ts index 7f92b1d4..d57ed8c0 100644 --- a/server/routes/api/torrents.ts +++ b/server/routes/api/torrents.ts @@ -20,6 +20,7 @@ import type { MoveTorrentsOptions, SetTorrentContentsPropertiesOptions, SetTorrentsPriorityOptions, + SetTorrentsSequentialOptions, SetTorrentsTrackersOptions, StartTorrentsOptions, StopTorrentsOptions, @@ -120,7 +121,7 @@ router.post('/add-urls', async (req, r return; } - const {urls, cookies, destination, tags, isBasePath, isCompleted, start} = parsedResult.data; + const {urls, cookies, destination, tags, isBasePath, isCompleted, isSequential, start} = parsedResult.data; const finalDestination = await getDestination(req.services, { destination, @@ -140,6 +141,7 @@ router.post('/add-urls', async (req, r tags: tags ?? [], isBasePath: isBasePath ?? false, isCompleted: isCompleted ?? false, + isSequential: isSequential ?? false, start: start ?? false, }) .then((response) => { @@ -171,7 +173,7 @@ router.post('/add-files', async (req, return; } - const {files, destination, tags, isBasePath, isCompleted, start} = parsedResult.data; + const {files, destination, tags, isBasePath, isCompleted, isSequential, start} = parsedResult.data; const finalDestination = await getDestination(req.services, { destination, @@ -190,6 +192,7 @@ router.post('/add-files', async (req, tags: tags ?? [], isBasePath: isBasePath ?? false, isCompleted: isCompleted ?? false, + isSequential: isSequential ?? false, start: start ?? false, }) .then((response) => { @@ -261,6 +264,7 @@ router.post('/create', async (req, res) tags: tags ?? [], isBasePath: true, isCompleted: true, + isSequential: false, start: start || false, }) .catch(() => { @@ -431,6 +435,27 @@ router.patch('/priority', (req, re }); }); +/** + * PATCH /api/torrents/sequential + * @summary Sets sequential mode of torrents. + * @tags Torrent + * @security User + * @param {SetTorrentsSequentialOptions} request.body.required - options - application/json + * @return {object} 200 - success response - application/json + * @return {Error} 500 - failure response - application/json + */ +router.patch('/sequential', (req, res) => { + req.services?.clientGatewayService?.setTorrentsSequential(req.body).then( + (response) => { + req.services?.torrentService.fetchTorrentList(); + res.status(200).json(response); + }, + (err) => { + res.status(500).json(err); + }, + ); +}); + /** * PATCH /api/torrents/tags * @summary Sets tags of torrents. diff --git a/server/services/Transmission/clientGatewayService.ts b/server/services/Transmission/clientGatewayService.ts index 6f8223a0..c56ffc83 100644 --- a/server/services/Transmission/clientGatewayService.ts +++ b/server/services/Transmission/clientGatewayService.ts @@ -244,6 +244,11 @@ class TransmissionClientGatewayService extends ClientGatewayService { .then(this.processClientRequestSuccess, this.processClientRequestError); } + async setTorrentsSequential(): Promise { + // Transmission maintainers rejected the feature. + throw new Error('Transmission does not support this feature.'); + } + async setTorrentsTags({hashes, tags}: SetTorrentsTagsOptions): Promise { return this.clientRequestManager .setTorrentsProperties({ids: hashes, labels: tags}) @@ -363,6 +368,7 @@ class TransmissionClientGatewayService extends ClientGatewayService { upTotal: torrent.uploadedEver, eta: torrent.eta, isPrivate: torrent.isPrivate, + isSequential: false, message: torrent.errorString, peersConnected: torrent.peersGettingFromUs, peersTotal: torrent.peersGettingFromUs, diff --git a/server/services/feedService.ts b/server/services/feedService.ts index 42564f00..4b2b9edd 100644 --- a/server/services/feedService.ts +++ b/server/services/feedService.ts @@ -256,6 +256,7 @@ class FeedService extends BaseService { start, isBasePath: false, isCompleted: false, + isSequential: false, }) .then(() => { this.db.update({_id: feedID}, {$inc: {count: 1}}, {upsert: true}); diff --git a/server/services/interfaces/clientGatewayService.ts b/server/services/interfaces/clientGatewayService.ts index fbc34172..5161d5e5 100644 --- a/server/services/interfaces/clientGatewayService.ts +++ b/server/services/interfaces/clientGatewayService.ts @@ -9,6 +9,7 @@ import type { MoveTorrentsOptions, SetTorrentContentsPropertiesOptions, SetTorrentsPriorityOptions, + SetTorrentsSequentialOptions, SetTorrentsTrackersOptions, StartTorrentsOptions, StopTorrentsOptions, @@ -107,6 +108,14 @@ abstract class ClientGatewayService extends BaseService; + /** + * Sets sequential mode of torrents + * + * @param {SetTorrentsSequentialOptions} options - An object of options... + * @return {Promise} - Rejects with error. + */ + abstract setTorrentsSequential(options: SetTorrentsSequentialOptions): Promise; + /** * Sets tags of torrents * diff --git a/server/services/qBittorrent/clientGatewayService.ts b/server/services/qBittorrent/clientGatewayService.ts index 8ca9960e..213098a0 100644 --- a/server/services/qBittorrent/clientGatewayService.ts +++ b/server/services/qBittorrent/clientGatewayService.ts @@ -193,6 +193,11 @@ class QBittorrentClientGatewayService extends ClientGatewayService { } } + async setTorrentsSequential(): Promise { + // TODO: not implemented + throw new Error(); + } + async setTorrentsTags({hashes, tags}: SetTorrentsTagsOptions): Promise { return this.clientRequestManager.torrentsRemoveTags(hashes).then(() => { this.clientRequestManager @@ -294,6 +299,7 @@ class QBittorrentClientGatewayService extends ClientGatewayService { eta: info.eta >= 8640000 ? -1 : info.eta, hash: info.hash, isPrivate, + isSequential: false, // TODO: not implemented message: '', // in tracker method name: info.name, peersConnected: info.num_leechs, diff --git a/server/services/rTorrent/clientGatewayService.ts b/server/services/rTorrent/clientGatewayService.ts index a2b02707..2ccb8e58 100644 --- a/server/services/rTorrent/clientGatewayService.ts +++ b/server/services/rTorrent/clientGatewayService.ts @@ -63,6 +63,7 @@ class RTorrentClientGatewayService extends ClientGatewayService { tags, isBasePath, isCompleted, + isSequential, start, }: Required): Promise { const torrentPaths = await Promise.all( @@ -81,6 +82,7 @@ class RTorrentClientGatewayService extends ClientGatewayService { tags, isBasePath, isCompleted, + isSequential, start, }); } @@ -433,6 +435,11 @@ class RTorrentClientGatewayService extends ClientGatewayService { ); } + async setTorrentsSequential(): Promise { + // TODO: not implemented + throw new Error(); + } + async setTorrentsTags({hashes, tags}: SetTorrentsTagsOptions): Promise { const methodCalls = hashes.reduce((accumulator: MultiMethodCalls, hash) => { accumulator.push({ @@ -601,6 +608,7 @@ class RTorrentClientGatewayService extends ClientGatewayService { processedResponses.map(async (response) => { const torrentProperties: TorrentProperties = { ...response, + isSequential: false, // TODO: not implemented status: getTorrentStatusFromProperties(response), percentComplete: getTorrentPercentCompleteFromProperties(response), eta: getTorrentETAFromProperties(response), diff --git a/shared/schema/api/torrents.ts b/shared/schema/api/torrents.ts index 54cd6f80..8b67da91 100644 --- a/shared/schema/api/torrents.ts +++ b/shared/schema/api/torrents.ts @@ -21,6 +21,8 @@ export const addTorrentByURLSchema = object({ isBasePath: boolean().optional(), // Whether destination contains completed contents [default: false] isCompleted: boolean().optional(), + // Whether contents of a torrent should be downloaded sequentially [default: false] + isSequential: boolean().optional(), // Whether to start torrent [default: false] start: boolean().optional(), }); @@ -39,6 +41,8 @@ export const addTorrentByFileSchema = object({ isBasePath: boolean().optional(), // Whether destination contains completed contents [default: false] isCompleted: boolean().optional(), + // Whether contents of a torrent should be downloaded sequentially [default: false] + isSequential: boolean().optional(), // Whether to start torrent [default: false] start: boolean().optional(), }); diff --git a/shared/types/Torrent.ts b/shared/types/Torrent.ts index 13cad44e..26f48f14 100644 --- a/shared/types/Torrent.ts +++ b/shared/types/Torrent.ts @@ -27,6 +27,8 @@ export interface TorrentProperties { eta: number; hash: string; isPrivate: boolean; + // If sequential download is enabled + isSequential: boolean; message: string; name: string; peersConnected: number; diff --git a/shared/types/api/torrents.ts b/shared/types/api/torrents.ts index 266a55e0..4ab3ec8e 100644 --- a/shared/types/api/torrents.ts +++ b/shared/types/api/torrents.ts @@ -71,6 +71,14 @@ export interface SetTorrentsPriorityOptions { priority: TorrentPriority; } +// PATCH /api/torrents/sequential +export interface SetTorrentsSequentialOptions { + // An array of string representing hashes of torrents to operate on + hashes: Array; + // If sequential download is enabled + isSequential: boolean; +} + // PATCH /api/torrents/trackers export interface SetTorrentsTrackersOptions { // An array of string representing hashes of torrents to operate on