Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added comment to torrent details #541

Merged
merged 2 commits into from
Apr 19, 2022
Merged
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
38 changes: 38 additions & 0 deletions client/src/javascript/components/general/LinkedText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {FC} from 'react';

interface LinkedTextProps {
text: string;
className?: string;
}

function isValidHttpUrl(s: string) {
let url;

try {
url = new URL(s);
} catch (_) {
return false;
}

return url.protocol === 'http:' || url.protocol === 'https:';
}

const LinkedText: FC<LinkedTextProps> = ({text, className}: LinkedTextProps) => {
const nodes = text.split(/(?<=\s)(?!\s)(?:\b|\B)/).map((s) =>
isValidHttpUrl(s.trimEnd()) ? (
<a href={s.trimEnd()} target="_blank" rel="noopener noreferrer">
{s}
</a>
) : (
s
),
);

return <span className={className}>{nodes}</span>;
};

LinkedText.defaultProps = {
className: undefined,
};

export default LinkedText;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Trans, useLingui} from '@lingui/react';

import type {TorrentProperties} from '@shared/types/Torrent';

import LinkedText from '../../general/LinkedText';
import Size from '../../general/Size';
import TorrentStore from '../../../stores/TorrentStore';
import UIStore from '../../../stores/UIStore';
Expand Down Expand Up @@ -201,6 +202,14 @@ const TorrentGeneralInfo: FC = observer(() => {
: i18n._('torrents.details.general.type.public')}
</td>
</tr>
<tr className="torrent-details__detail torrent-details__detail--comment">
<td className="torrent-details__detail__label">
<Trans id="torrents.details.general.comment" />
</td>
<td className="torrent-details_detail__value">
<LinkedText text={torrent.comment} />
</td>
</tr>
<tr className="torrent-details__table__heading">
<td className="torrent-details__table__heading--tertiary" colSpan={2}>
<Trans id="torrents.details.general.heading.tracker" />
Expand Down
2 changes: 2 additions & 0 deletions server/services/Deluge/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ class DelugeClientGatewayService extends ClientGatewayService {
return this.clientRequestManager
.coreGetTorrentsStatus([
'active_time',
'comment',
'download_location',
'download_payload_rate',
'eta',
Expand Down Expand Up @@ -322,6 +323,7 @@ class DelugeClientGatewayService extends ClientGatewayService {

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,
Expand Down
2 changes: 1 addition & 1 deletion server/services/Deluge/types/DelugeCoreMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export interface DelugeCoreTorrentStatuses {
trackers: Array<DelugeCoreTorrentTrackerStatuses>;
tracker_status: unknown;
upload_payload_rate: number;
comment: unknown;
comment: string;
creator: unknown;
num_files: unknown;
num_pieces: unknown;
Expand Down
2 changes: 2 additions & 0 deletions server/services/Transmission/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ class TransmissionClientGatewayService extends ClientGatewayService {
'hashString',
'downloadDir',
'name',
'comment',
'haveValid',
'addedDate',
'dateCreated',
Expand Down Expand Up @@ -389,6 +390,7 @@ class TransmissionClientGatewayService extends ClientGatewayService {
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,
Expand Down
14 changes: 12 additions & 2 deletions server/services/qBittorrent/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ import {TorrentTrackerType} from '../../../shared/types/TorrentTracker';

class QBittorrentClientGatewayService extends ClientGatewayService {
private clientRequestManager = new ClientRequestManager(this.user.client as QBittorrentConnectionSettings);
private cachedProperties: Record<string, Pick<TorrentProperties, 'dateCreated' | 'isPrivate' | 'trackerURIs'>> = {};
private cachedProperties: Record<
string,
Pick<TorrentProperties, 'comment' | 'dateCreated' | 'isPrivate' | 'trackerURIs'>
> = {};

async addTorrentsByFile({
files,
Expand Down Expand Up @@ -358,6 +361,7 @@ class QBittorrentClientGatewayService extends ClientGatewayService {

if (properties != null && trackers != null && Array.isArray(trackers)) {
this.cachedProperties[hash] = {
comment: properties?.comment,
dateCreated: properties?.creation_date,
isPrivate: trackers[0]?.msg.includes('is private'),
trackerURIs: getDomainsFromURLs(
Expand All @@ -374,10 +378,16 @@ class QBittorrentClientGatewayService extends ClientGatewayService {
{},
...(await Promise.all(
infos.map(async (info) => {
const {dateCreated = 0, isPrivate = false, trackerURIs = []} = this.cachedProperties[info.hash] || {};
const {
comment = '',
dateCreated = 0,
isPrivate = false,
trackerURIs = [],
} = this.cachedProperties[info.hash] || {};

const torrentProperties: TorrentProperties = {
bytesDone: info.completed,
comment: comment,
dateActive: info.dlspeed > 0 || info.upspeed > 0 ? -1 : info.last_activity,
dateAdded: info.added_on,
dateCreated,
Expand Down
32 changes: 26 additions & 6 deletions server/services/rTorrent/clientGatewayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import ClientGatewayService from '../clientGatewayService';
import ClientRequestManager from './clientRequestManager';
import {fetchUrls} from '../../util/fetchUtil';
import {getMethodCalls, processMethodCallResponse} from './util/rTorrentMethodCallUtil';
import {setCompleted, setTrackers} from '../../util/torrentFileUtil';
import {getComment, setCompleted, setTrackers} from '../../util/torrentFileUtil';
import {
encodeTags,
getAddTorrentPropertiesCalls,
Expand All @@ -60,6 +60,15 @@ class RTorrentClientGatewayService extends ClientGatewayService {
clientRequestManager = new ClientRequestManager(this.user.client as RTorrentConnectionSettings);
availableMethodCalls = this.fetchAvailableMethodCalls(true);

async appendTorrentCommentCall(file: string, additionalCalls: string[]) {
const comment = await getComment(Buffer.from(file, 'base64'));
if (comment && comment.length > 0) {
// VRS24mrker is used for compatability with ruTorrent
return [...additionalCalls, `d.custom2.set="VRS24mrker${encodeURIComponent(comment)}"`];
}
return additionalCalls;
}

async addTorrentsByFile({
files,
destination,
Expand Down Expand Up @@ -102,10 +111,16 @@ class RTorrentClientGatewayService extends ClientGatewayService {
if (hasLoadThrow && this.clientRequestManager.isJSONCapable) {
await this.clientRequestManager
.methodCall('system.multicall', [
processedFiles.map((file) => ({
methodName: start ? 'load.start_throw' : 'load.throw',
params: ['', `data:applications/x-bittorrent;base64,${file}`, ...additionalCalls],
})),
await Promise.all(
processedFiles.map(async (file) => ({
methodName: start ? 'load.start_throw' : 'load.throw',
params: [
'',
`data:applications/x-bittorrent;base64,${file}`,
...(await this.appendTorrentCommentCall(file, additionalCalls)),
],
})),
),
])
.then(this.processClientRequestSuccess, this.processRTorrentRequestError)
.then((response: Array<Array<string | number>>) => {
Expand All @@ -116,7 +131,11 @@ class RTorrentClientGatewayService extends ClientGatewayService {
await Promise.all(
processedFiles.map(async (file) => {
await this.clientRequestManager
.methodCall(start ? 'load.raw_start' : 'load.raw', ['', Buffer.from(file, 'base64'), ...additionalCalls])
.methodCall(start ? 'load.raw_start' : 'load.raw', [
'',
Buffer.from(file, 'base64'),
...(await this.appendTorrentCommentCall(file, additionalCalls)),
])
.then(this.processClientRequestSuccess, this.processRTorrentRequestError);
}),
);
Expand Down Expand Up @@ -643,6 +662,7 @@ class RTorrentClientGatewayService extends ClientGatewayService {
processedResponses.map(async (response) => {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ const torrentListMethodCallConfigs = {
methodCall: 'd.state=',
transformValue: booleanTransformer,
},
comment: {
methodCall: 'd.custom2=',
transformValue: (value: unknown): string => {
// ruTorrent sets VRS24mrkr as a comment prefix, so we use it as well for compatability
if (value === '' || typeof value !== 'string' || value.indexOf('VRS24mrker') !== 0) {
return '';
}

return decodeURIComponent(value.substring(10));
},
},
isActive: {
methodCall: 'd.is_active=',
transformValue: booleanTransformer,
Expand Down
10 changes: 10 additions & 0 deletions server/util/torrentFileUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const openAndDecodeTorrent = async (torrentPath: string): Promise<TorrentFile |
return torrentData;
};

export const getComment = async (torrent: Buffer): Promise<string | undefined> => {
const torrentData: TorrentFile | null = await bencode.decode(torrent);

if (torrentData == null) {
return;
}

return torrentData.comment?.toString();
};

export const getContentSize = async (info: TorrentFile['info']): Promise<number> => {
if (info.length != null) {
// Single file torrent
Expand Down
1 change: 1 addition & 0 deletions shared/types/Torrent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum TorrentPriority {

export interface TorrentProperties {
bytesDone: number;
comment: string;
// Last time the torrent is active, -1 means currently active, 0 means data unavailable
dateActive: number;
dateAdded: number;
Expand Down