Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion server/routes/api/torrents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import paths from '../../../shared/config/paths';
import type {AddTorrentByFileOptions, AddTorrentByURLOptions} from '../../../shared/schema/api/torrents';
import type {MoveTorrentsOptions, SetTorrentsTrackersOptions} from '../../../shared/types/api/torrents';
import type {TorrentContent} from '../../../shared/types/TorrentContent';
import type {TorrentList} from '../../../shared/types/Torrent';
import type {TorrentList, TorrentProperties} from '../../../shared/types/Torrent';
import type {TorrentStatus} from '../../../shared/constants/torrentStatusMap';
import type {TorrentTracker} from '../../../shared/types/TorrentTracker';

Expand Down Expand Up @@ -494,6 +494,27 @@ describe('PATCH /api/torrents/trackers', () => {
});
});

describe('GET /api/torrents/{hash}', () => {
it('Gets torrent information', (done) => {
request
.get(`/api/torrents/${torrentHashes[0]}`)
.send()
.set('Cookie', [authToken])
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) done(err);

const contents: TorrentProperties = res.body;

expect(contents.hash).toBe(torrentHashes[0]);

done();
});
});
});

describe('GET /api/torrents/{hash}/contents', () => {
it('Gets contents of torrents', (done) => {
request
Expand Down
12 changes: 11 additions & 1 deletion server/routes/api/torrents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
import {accessDeniedError, fileNotFoundError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
import {getTempPath} from '../../models/TemporaryStorage';
import {getToken} from '../../util/authUtil';
import {TorrentProperties} from '@shared/types/Torrent';

const getDestination = async (
services: Express.Request['services'],
Expand Down Expand Up @@ -608,13 +609,22 @@ router.get<{hashes: string}>(
*/

/**
* TODO: API not yet implemented
* GET /api/torrents/{hash}
* @summary Gets information of a torrent.
* @tags Torrent
* @security User
* @param {string} hash.path - Hash of a torrent
* @return {TorrentProperties} 200 - success response - application/json
* @return {Error} 500 - failure response - application/json
*/
router.get(
'/:hash',
async (req, res): Promise<Response> =>
req.services.clientGatewayService.fetchTorrent(req.params.hash).then(
(contents) => res.status(200).json(contents),
({code, message}) => res.status(500).json({code, message}),
),
);

/**
* GET /api/torrents/{hash}/contents
Expand Down
71 changes: 71 additions & 0 deletions server/services/Deluge/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,77 @@ class DelugeClientGatewayService extends ClientGatewayService {
});
}

async fetchTorrent(hash: string): Promise<TorrentProperties> {
return this.clientRequestManager
.coreGetTorrentStatus(hash, [
'active_time',
'comment',
'download_location',
'download_payload_rate',
'eta',
'finished_time',
'message',
'name',
'num_peers',
'num_seeds',
'private',
'progress',
'ratio',
'sequential_download',
'state',
'super_seeding',
'time_added',
'total_done',
'total_payload_download',
'total_payload_upload',
'total_peers',
'total_size',
'total_seeds',
'tracker_host',
'upload_payload_rate',
])
.then(this.processClientRequestSuccess, this.processClientRequestError)
.then((status) => {
const dateNowSeconds = Math.ceil(Date.now() / 1000);

const torrentProperties: TorrentProperties = {
bytesDone: status.total_done,
comment: status.comment,
dateActive: status.download_payload_rate > 0 || status.upload_payload_rate > 0 ? -1 : status.active_time,
dateAdded: status.time_added,
dateCreated: 0,
dateFinished: status.finished_time > 0 ? Math.ceil((dateNowSeconds - status.finished_time) / 10) * 10 : 0,
directory: status.download_location,
downRate: status.download_payload_rate,
downTotal: status.total_payload_download,
eta: status.eta === 0 ? -1 : status.eta,
hash: hash.toUpperCase(),
isPrivate: status.private,
isInitialSeeding: status.super_seeding,
isSequential: status.sequential_download,
message: status.message,
name: status.name,
peersConnected: status.num_peers,
peersTotal: status.total_peers < 0 ? 0 : status.total_peers,
percentComplete: status.progress,
priority: 1,
ratio: status.ratio,
seedsConnected: status.num_seeds,
seedsTotal: status.total_seeds < 0 ? 0 : status.total_seeds,
sizeBytes: status.total_size,
status: getTorrentStatusFromStatuses(status),
tags: [],
trackerURIs: [status.tracker_host],
upRate: status.upload_payload_rate,
upTotal: status.total_payload_upload,
};

this.emit('PROCESS_TORRENT', torrentProperties);

return torrentProperties;
});
}

async fetchTransferSummary(): Promise<TransferSummary> {
return this.clientRequestManager
.coreGetSessionStatus([
Expand Down
77 changes: 77 additions & 0 deletions server/services/Transmission/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,83 @@ class TransmissionClientGatewayService extends ClientGatewayService {
});
}

async fetchTorrent(hash: TorrentProperties['hash']): Promise<TorrentProperties> {
return this.clientRequestManager
.getTorrents(hash, [
'hashString',
'downloadDir',
'name',
'comment',
'haveValid',
'addedDate',
'dateCreated',
'doneDate',
'rateDownload',
'rateUpload',
'downloadedEver',
'uploadedEver',
'eta',
'isPrivate',
'error',
'errorString',
'peersGettingFromUs',
'peersSendingToUs',
'status',
'totalSize',
'trackers',
'labels',
'activityDate',
])
.then(this.processClientRequestSuccess, this.processClientRequestError)
.then((torrents) => {
const [torrent] = torrents;
if (torrent == null) {
throw new Error();
}

const percentComplete = (torrent.haveValid / torrent.totalSize) * 100;
const ratio = torrent.downloadedEver === 0 ? -1 : torrent.uploadedEver / torrent.downloadedEver;
const trackerURIs = getDomainsFromURLs(torrent.trackers.map((tracker) => tracker.announce));
const status = torrentPropertiesUtil.getTorrentStatus(torrent);

const torrentProperties: TorrentProperties = {
hash: torrent.hashString.toUpperCase(),
name: torrent.name,
comment: torrent.comment,
bytesDone: torrent.haveValid,
dateActive: torrent.rateDownload > 0 || torrent.rateUpload > 0 ? -1 : torrent.activityDate,
dateAdded: torrent.addedDate,
dateCreated: torrent.dateCreated,
dateFinished: torrent.doneDate,
directory: torrent.downloadDir,
downRate: torrent.rateDownload,
downTotal: torrent.downloadedEver,
upRate: torrent.rateUpload,
upTotal: torrent.uploadedEver,
eta: torrent.eta > 0 ? torrent.eta : -1,
isPrivate: torrent.isPrivate,
isInitialSeeding: false,
isSequential: false,
message: torrent.errorString,
peersConnected: torrent.peersGettingFromUs,
peersTotal: torrent.peersGettingFromUs,
percentComplete,
priority: TorrentPriority.NORMAL,
ratio,
seedsConnected: torrent.peersSendingToUs,
seedsTotal: torrent.peersSendingToUs,
sizeBytes: torrent.totalSize,
status,
tags: torrent.labels || [],
trackerURIs,
};

this.emit('PROCESS_TORRENT', torrentProperties);

return torrentProperties;
});
}

async fetchTransferSummary(): Promise<TransferSummary> {
return this.clientRequestManager
.getSessionStats()
Expand Down
8 changes: 8 additions & 0 deletions server/services/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ abstract class ClientGatewayService extends BaseService<ClientGatewayServiceEven
*/
abstract fetchTorrentList(): Promise<TorrentListSummary>;

/**
* Fetches a torrent
*
* @param {string} hash - Hash of torrent
* @return {Promise<TorrentProperties>} - Resolves with TorrentProperties or rejects with error.
*/
abstract fetchTorrent(hash: TorrentProperties['hash']): Promise<TorrentProperties>;

/**
* Fetches the transfer summary
*
Expand Down
50 changes: 50 additions & 0 deletions server/services/rTorrent/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,56 @@ class RTorrentClientGatewayService extends ClientGatewayService {
});
}

async fetchTorrent(hash: TorrentProperties['hash']): Promise<TorrentProperties> {
return this.clientRequestManager
.methodCall('d.multicall2', [hash, ''].concat((await this.availableMethodCalls).torrentList))
.then(this.processClientRequestSuccess, this.processRTorrentRequestError)
.then((responses: string[][]) => {
return Promise.all(
responses.map((response) => processMethodCallResponse(response, torrentListMethodCallConfigs)),
);
})
.then((processedResponses) => {
const [response] = processedResponses;

const torrentProperties: TorrentProperties = {
bytesDone: response.bytesDone,
comment: response.comment,
dateActive: response.downRate > 0 || response.upRate > 0 ? -1 : response.dateActive,
dateAdded: response.dateAdded,
dateCreated: response.dateCreated,
dateFinished: response.dateFinished,
directory: response.directory,
downRate: response.downRate,
downTotal: response.downTotal,
eta: getTorrentETAFromProperties(response),
hash: response.hash,
isPrivate: response.isPrivate,
isInitialSeeding: response.isInitialSeeding,
isSequential: response.isSequential,
message: response.message,
name: response.name,
peersConnected: response.peersConnected,
peersTotal: response.peersTotal,
percentComplete: getTorrentPercentCompleteFromProperties(response),
priority: response.priority,
ratio: response.ratio,
seedsConnected: response.seedsConnected,
seedsTotal: response.seedsTotal,
sizeBytes: response.sizeBytes,
status: getTorrentStatusFromProperties(response),
tags: response.tags,
trackerURIs: response.trackerURIs,
upRate: response.upRate,
upTotal: response.upTotal,
};

this.emit('PROCESS_TORRENT', torrentProperties);

return torrentProperties;
});
}

async fetchTransferSummary(): Promise<TransferSummary> {
const methodCalls: MultiMethodCalls = (await this.availableMethodCalls).transferSummary.map((methodCall) => {
return {
Expand Down