import type { AddTorrentByFileOptions, AddTorrentByURLOptions, ReannounceTorrentsOptions, SetTorrentsTagsOptions, } from '@shared/schema/api/torrents'; import type {UserInDatabase} from '@shared/schema/Auth'; import type {SetClientSettingsOptions} from '@shared/types/api/client'; import type { CheckTorrentsOptions, DeleteTorrentsOptions, MoveTorrentsOptions, SetTorrentContentsPropertiesOptions, SetTorrentsInitialSeedingOptions, SetTorrentsPriorityOptions, SetTorrentsSequentialOptions, SetTorrentsTrackersOptions, StartTorrentsOptions, StopTorrentsOptions, } from '@shared/types/api/torrents'; import type {ClientSettings} from '@shared/types/ClientSettings'; import type {TorrentListSummary, TorrentProperties} from '@shared/types/Torrent'; import type {TorrentContent} from '@shared/types/TorrentContent'; import type {TorrentPeer} from '@shared/types/TorrentPeer'; import type {TorrentTracker} from '@shared/types/TorrentTracker'; import type {TransferSummary} from '@shared/types/TransferData'; import config from '../../config'; import BaseService from './BaseService'; type ClientGatewayServiceEvents = { CLIENT_CONNECTION_STATE_CHANGE: (isConnected: boolean) => void; PROCESS_TORRENT_LIST_START: () => void; PROCESS_TORRENT_LIST_END: (torrentListSummary: TorrentListSummary) => void; PROCESS_TORRENT: (torrentProperties: TorrentProperties) => void; }; abstract class ClientGatewayService extends BaseService { errorCount = 0; retryTimer?: NodeJS.Timeout | null = null; /** * Adds torrents by file * * @param options - An object of options... * @return - Resolves with an array of hashes of added torrents or rejects with error. */ abstract addTorrentsByFile(options: Required): Promise; /** * Adds torrents by URL * * @param options - An object of options... * @return - Resolves with an array of hashes of added torrents or rejects with error. */ abstract addTorrentsByURL(options: Required): Promise; /** * Checks torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract checkTorrents({hashes}: CheckTorrentsOptions): Promise; /** * Gets the list of contents of a torrent. * * @param hash - Hash of torrent * @return - Resolves with TorrentContentTree or rejects with error. */ abstract getTorrentContents(hash: TorrentProperties['hash']): Promise>; /** * Gets the list of peers of a torrent. * * @param hash - Hash of torrent * @return - Resolves with an array of TorrentPeer or rejects with error. */ abstract getTorrentPeers(hash: TorrentProperties['hash']): Promise>; /** * Gets the list of trackers of a torrent. * * @param hash - Hash of torrent * @return - Resolves with an array of TorrentTracker or rejects with error. */ abstract getTorrentTrackers(hash: TorrentProperties['hash']): Promise>; /** * Moves torrents to specified destination path. * * @param options - An object of options... * @return - Rejects with error. */ abstract moveTorrents(options: MoveTorrentsOptions): Promise; /** * Reannounces torrents to trackers * * @param options - An object of options... * @return - Rejects with error. */ abstract reannounceTorrents({hashes}: ReannounceTorrentsOptions): Promise; /** * Removes torrents. Optionally deletes data of torrents. * * @param options - An object of options... * @return - Rejects with error. */ abstract removeTorrents(options: DeleteTorrentsOptions): Promise; /** * Sets initial seeding mode of torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentsInitialSeeding(options: SetTorrentsInitialSeedingOptions): Promise; /** * Sets priority of torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentsPriority(options: SetTorrentsPriorityOptions): Promise; /** * Sets sequential mode of torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentsSequential(options: SetTorrentsSequentialOptions): Promise; /** * Sets tags of torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentsTags(options: SetTorrentsTagsOptions): Promise; /** * Sets trackers of torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentsTrackers(options: SetTorrentsTrackersOptions): Promise; /** * Sets priority of contents of a torrent * * @param hash - Hash of the torrent. * @param options - An object of options... * @return - Rejects with error. */ abstract setTorrentContentsPriority(hash: string, options: SetTorrentContentsPropertiesOptions): Promise; /** * Starts torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract startTorrents(options: StartTorrentsOptions): Promise; /** * Stops torrents * * @param options - An object of options... * @return - Rejects with error. */ abstract stopTorrents(options: StopTorrentsOptions): Promise; /** * Fetches the list of torrents * * @return - Resolves with TorrentListSummary or rejects with error. */ abstract fetchTorrentList(): Promise; /** * Fetches the transfer summary * * @return - Resolves with TransferSummary or rejects with error. */ abstract fetchTransferSummary(): Promise; /** * Gets session directory (where .torrent files are stored) of the torrent client * * @return - Resolves with path of session directory or rejects with error. */ abstract getClientSessionDirectory(): Promise<{path: string; case: 'lower' | 'upper'}>; /** * Gets settings of the torrent client * * @return - Resolves with ClientSettings or rejects with error. */ abstract getClientSettings(): Promise; /** * Sets settings of the torrent client * * @param settings - Settings to be set. * @return - Rejects with error. */ abstract setClientSettings(settings: SetClientSettingsOptions): Promise; abstract testGateway(): Promise; constructor(user: UserInDatabase) { super(user); this.testGateway() .then(this.processClientRequestSuccess, this.processClientRequestError) .catch(() => undefined); } destroyTimer() { if (this.retryTimer != null) { clearTimeout(this.retryTimer); this.retryTimer = null; } } destroy(drop: boolean) { this.destroyTimer(); this.retryTimer = undefined; return super.destroy(drop); } startTimer() { if (this.retryTimer === null) { this.retryTimer = setTimeout(() => { this.errorCount += 1; this.destroyTimer(); this.testGateway().catch(() => undefined); }, config.torrentClientPollInterval * this.errorCount); } } processClientRequestSuccess = (response: T): T => { this.destroyTimer(); if (this.errorCount !== 0) { this.errorCount = 0; this.emit('CLIENT_CONNECTION_STATE_CHANGE', true); } return response; }; processClientRequestError = (error: Error) => { if (this.errorCount === 0) { this.errorCount += 1; this.emit('CLIENT_CONNECTION_STATE_CHANGE', false); } this.startTimer(); throw error; }; } export default ClientGatewayService;