diff --git a/server/services/feedService.ts b/server/services/feedService.ts index 6d559cc3..32021d5a 100644 --- a/server/services/feedService.ts +++ b/server/services/feedService.ts @@ -247,7 +247,7 @@ class FeedService extends BaseService { itemsToDownload.map( async (item): Promise> => { const {urls, destination, start, tags, ruleID} = item; - await this?.services?.clientGatewayService + await this.services?.clientGatewayService ?.addTorrentsByURL({ urls, destination, diff --git a/server/services/qBittorrent/clientGatewayService.ts b/server/services/qBittorrent/clientGatewayService.ts index e4008060..756241ad 100644 --- a/server/services/qBittorrent/clientGatewayService.ts +++ b/server/services/qBittorrent/clientGatewayService.ts @@ -46,111 +46,130 @@ class QBittorrentClientGatewayService extends ClientGatewayService { // TODO: qBittorrent does not have capability to add tags during add torrents. - return this.clientRequestManager.torrentsAddFiles(fileBuffers, { - savepath: destination, - paused: !start, - root_folder: !isBasePath, - }); + return this.clientRequestManager + .torrentsAddFiles(fileBuffers, { + savepath: destination, + paused: !start, + root_folder: !isBasePath, + }) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async addTorrentsByURL({urls, destination, isBasePath, start}: AddTorrentByURLOptions): Promise { // TODO: qBittorrent does not have capability to add tags during add torrents. - return this.clientRequestManager.torrentsAddURLs(urls, { - savepath: destination, - paused: !start, - root_folder: !isBasePath, - }); + return this.clientRequestManager + .torrentsAddURLs(urls, { + savepath: destination, + paused: !start, + root_folder: !isBasePath, + }) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async checkTorrents({hashes}: CheckTorrentsOptions): Promise { - return this.clientRequestManager.torrentsRecheck(hashes); + return this.clientRequestManager + .torrentsRecheck(hashes) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async getTorrentContents(hash: TorrentProperties['hash']): Promise> { - return this.clientRequestManager.getTorrentContents(hash).then((contents) => { - return contents.map((content, index) => { - let priority = TorrentContentPriority.NORMAL; + return this.clientRequestManager + .getTorrentContents(hash) + .then(this.processClientRequestSuccess, this.processClientRequestError) + .then((contents) => { + return contents.map((content, index) => { + let priority = TorrentContentPriority.NORMAL; - switch (content.priority) { - case QBittorrentTorrentContentPriority.DO_NOT_DOWNLOAD: - priority = TorrentContentPriority.DO_NOT_DOWNLOAD; - break; - case QBittorrentTorrentContentPriority.HIGH: - case QBittorrentTorrentContentPriority.MAXIMUM: - priority = TorrentContentPriority.HIGH; - break; - default: - break; - } + switch (content.priority) { + case QBittorrentTorrentContentPriority.DO_NOT_DOWNLOAD: + priority = TorrentContentPriority.DO_NOT_DOWNLOAD; + break; + case QBittorrentTorrentContentPriority.HIGH: + case QBittorrentTorrentContentPriority.MAXIMUM: + priority = TorrentContentPriority.HIGH; + break; + default: + break; + } - return { - index, - path: content.name, - filename: content.name.split('/').pop() || '', - percentComplete: Math.trunc(content.progress * 100), - priority, - sizeBytes: content.size, - }; + return { + index, + path: content.name, + filename: content.name.split('/').pop() || '', + percentComplete: Math.trunc(content.progress * 100), + priority, + sizeBytes: content.size, + }; + }); }); - }); } async getTorrentPeers(hash: TorrentProperties['hash']): Promise> { - return this.clientRequestManager.syncTorrentPeers(hash).then((peers) => { - return Object.keys(peers).reduce((accumulator: Array, ip_and_port) => { - const peer = peers[ip_and_port]; + return this.clientRequestManager + .syncTorrentPeers(hash) + .then(this.processClientRequestSuccess, this.processClientRequestError) + .then((peers) => { + return Object.keys(peers).reduce((accumulator: Array, ip_and_port) => { + const peer = peers[ip_and_port]; + + // Only displays connected peers + if (!peer.flags.includes('D') && !peer.flags.includes('U')) { + return accumulator; + } + + const properties = getTorrentPeerPropertiesFromFlags(peer.flags); + accumulator.push({ + country: peer.country_code, + address: peer.ip, + completedPercent: Math.trunc(peer.progress * 100), + clientVersion: peer.client, + downloadRate: peer.dl_speed, + downloadTotal: peer.downloaded, + uploadRate: peer.up_speed, + uploadTotal: peer.uploaded, + id: crypto.createHash('sha1').update(ip_and_port).digest('base64'), + peerRate: 0, + peerTotal: 0, + isEncrypted: properties.isEncrypted, + isIncoming: properties.isIncoming, + }); - // Only displays connected peers - if (!peer.flags.includes('D') && !peer.flags.includes('U')) { return accumulator; - } - - const properties = getTorrentPeerPropertiesFromFlags(peer.flags); - accumulator.push({ - country: peer.country_code, - address: peer.ip, - completedPercent: Math.trunc(peer.progress * 100), - clientVersion: peer.client, - downloadRate: peer.dl_speed, - downloadTotal: peer.downloaded, - uploadRate: peer.up_speed, - uploadTotal: peer.uploaded, - id: crypto.createHash('sha1').update(ip_and_port).digest('base64'), - peerRate: 0, - peerTotal: 0, - isEncrypted: properties.isEncrypted, - isIncoming: properties.isIncoming, - }); - - return accumulator; - }, []); - }); + }, []); + }); } async getTorrentTrackers(hash: TorrentProperties['hash']): Promise> { - return this.clientRequestManager.getTorrentTrackers(hash).then((trackers) => { - return trackers.map((tracker, index) => { - return { - index, - id: crypto.createHash('sha1').update(tracker.url).digest('base64'), - url: tracker.url, - type: getTorrentTrackerTypeFromURL(tracker.url), - group: tracker.tier, - minInterval: 0, - normalInterval: 0, - isEnabled: tracker.status !== QBittorrentTorrentTrackerStatus.DISABLED, - }; + return this.clientRequestManager + .getTorrentTrackers(hash) + .then(this.processClientRequestSuccess, this.processClientRequestError) + .then((trackers) => { + return trackers.map((tracker, index) => { + return { + index, + id: crypto.createHash('sha1').update(tracker.url).digest('base64'), + url: tracker.url, + type: getTorrentTrackerTypeFromURL(tracker.url), + group: tracker.tier, + minInterval: 0, + normalInterval: 0, + isEnabled: tracker.status !== QBittorrentTorrentTrackerStatus.DISABLED, + }; + }); }); - }); } async moveTorrents({hashes, destination}: MoveTorrentsOptions): Promise { - return this.clientRequestManager.torrentsSetLocation(hashes, destination); + return this.clientRequestManager + .torrentsSetLocation(hashes, destination) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async removeTorrents({hashes, deleteData}: DeleteTorrentsOptions): Promise { - return this.clientRequestManager.torrentsDelete(hashes, deleteData || false); + return this.clientRequestManager + .torrentsDelete(hashes, deleteData || false) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async setTorrentsPriority({hashes, priority}: SetTorrentsPriorityOptions): Promise { @@ -159,16 +178,22 @@ class QBittorrentClientGatewayService extends ClientGatewayService { case TorrentPriority.DO_NOT_DOWNLOAD: return this.stopTorrents({hashes}); case TorrentPriority.LOW: - return this.clientRequestManager.torrentsSetBottomPrio(hashes); + return this.clientRequestManager + .torrentsSetBottomPrio(hashes) + .then(this.processClientRequestSuccess, this.processClientRequestError); case TorrentPriority.HIGH: - return this.clientRequestManager.torrentsSetTopPrio(hashes); + return this.clientRequestManager + .torrentsSetTopPrio(hashes) + .then(this.processClientRequestSuccess, this.processClientRequestError); default: return undefined; } } async setTorrentsTags({hashes, tags}: SetTorrentsTagsOptions): Promise { - return this.clientRequestManager.torrentsAddTags(hashes, tags); + return this.clientRequestManager + .torrentsAddTags(hashes, tags) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async setTorrentsTrackers({hashes, trackers}: SetTorrentsTrackersOptions): Promise { @@ -176,6 +201,7 @@ class QBittorrentClientGatewayService extends ClientGatewayService { hashes.map((hash) => { return this.clientRequestManager .torrentsAddTrackers(hash, trackers) + .then(this.processClientRequestSuccess, this.processClientRequestError) .then(() => delete this.cachedTrackerURIs[hash]); }), ); @@ -198,15 +224,21 @@ class QBittorrentClientGatewayService extends ClientGatewayService { break; } - return this.clientRequestManager.torrentsFilePrio(hash, indices, qbFilePriority); + return this.clientRequestManager + .torrentsFilePrio(hash, indices, qbFilePriority) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async startTorrents({hashes}: StartTorrentsOptions): Promise { - return this.clientRequestManager.torrentsResume(hashes); + return this.clientRequestManager + .torrentsResume(hashes) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async stopTorrents({hashes}: StopTorrentsOptions): Promise { - return this.clientRequestManager.torrentsPause(hashes); + return this.clientRequestManager + .torrentsPause(hashes) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async fetchTorrentList(): Promise { @@ -215,6 +247,7 @@ class QBittorrentClientGatewayService extends ClientGatewayService { .then(this.processClientRequestSuccess, this.processClientRequestError) .then(async (infos) => { this.emit('PROCESS_TORRENT_LIST_START'); + const torrentList: TorrentList = Object.assign( {}, ...(await Promise.all( @@ -333,29 +366,31 @@ class QBittorrentClientGatewayService extends ClientGatewayService { } async setClientSettings(settings: SetClientSettingsOptions): Promise { - return this.clientRequestManager.setAppPreferences({ - dht: settings.dht, - save_path: settings.directoryDefault, - max_connec: settings.networkHttpMaxOpen, - announce_ip: settings.networkLocalAddress ? settings.networkLocalAddress[0] : undefined, - random_port: settings.networkPortRandom, - listen_port: settings.networkPortRange ? Number(settings.networkPortRange?.split('-')[0]) : undefined, - pex: settings.protocolPex, - dl_limit: settings.throttleGlobalDownMax, - up_limit: settings.throttleGlobalUpMax, - max_uploads_per_torrent: settings.throttleMaxUploads, - max_uploads: settings.throttleMaxUploadsGlobal, - }); + return this.clientRequestManager + .setAppPreferences({ + dht: settings.dht, + save_path: settings.directoryDefault, + max_connec: settings.networkHttpMaxOpen, + announce_ip: settings.networkLocalAddress ? settings.networkLocalAddress[0] : undefined, + random_port: settings.networkPortRandom, + listen_port: settings.networkPortRange ? Number(settings.networkPortRange?.split('-')[0]) : undefined, + pex: settings.protocolPex, + dl_limit: settings.throttleGlobalDownMax, + up_limit: settings.throttleGlobalUpMax, + max_uploads_per_torrent: settings.throttleMaxUploads, + max_uploads: settings.throttleMaxUploadsGlobal, + }) + .then(this.processClientRequestSuccess, this.processClientRequestError); } async testGateway(clientSettings?: ClientConnectionSettings): Promise { if (clientSettings != null && clientSettings.client !== 'qBittorrent') { - return; - } - - if (!(await this.clientRequestManager.authenticate(clientSettings))) { throw new Error(); } + + return this.clientRequestManager + .updateAuthCookie(clientSettings) + .then(() => this.processClientRequestSuccess(undefined), this.processClientRequestError); } } diff --git a/server/services/qBittorrent/clientRequestManager.ts b/server/services/qBittorrent/clientRequestManager.ts index 54467480..1ac24c2f 100644 --- a/server/services/qBittorrent/clientRequestManager.ts +++ b/server/services/qBittorrent/clientRequestManager.ts @@ -15,43 +15,42 @@ import type { } from './types/QBittorrentTorrentsMethods'; class ClientRequestManager { - connectionSettings: QBittorrentConnectionSettings; - apiBase: string; - authCookie?: Promise; + private connectionSettings: QBittorrentConnectionSettings; + private apiBase: string; + private authCookie?: Promise; - async authenticate(connectionSettings?: QBittorrentConnectionSettings): Promise { - let {url, username, password} = this.connectionSettings; + async authenticate(connectionSettings = this.connectionSettings): Promise { + const {url, username, password} = connectionSettings; - if (connectionSettings != null) { - url = connectionSettings.url; - username = connectionSettings.username; - password = connectionSettings.password; - } + return axios.get(`${url}/api/v2/auth/login?username=${username}&password=${password}`).then((res) => { + const cookies: Array = res.headers['set-cookie']; - this.authCookie = axios.get(`${url}/api/v2/auth/login?username=${username}&password=${password}`).then( - (res) => { - const cookies: Array = res.headers['set-cookie']; + if (Array.isArray(cookies)) { + return cookies.filter((cookie) => cookie.includes('SID='))[0]; + } - if (Array.isArray(cookies)) { - return cookies.filter((cookie) => cookie.includes('SID='))[0]; - } + return undefined; + }); + } - return undefined; - }, - () => { - return undefined; - }, - ); + async updateAuthCookie(connectionSettings?: QBittorrentConnectionSettings): Promise { + let authFailed = false; + + this.authCookie = new Promise((resolve) => { + this.authenticate(connectionSettings).then( + (authCookie) => { + resolve(authCookie); + }, + () => { + authFailed = true; + resolve(undefined); + }, + ); + }); await this.authCookie; - if (this.authCookie != null) { - return true; - } - - setTimeout(this.authenticate, 5000); - - return false; + return authFailed ? Promise.reject() : Promise.resolve(); } async getAppPreferences(): Promise { @@ -262,8 +261,7 @@ class ClientRequestManager { constructor(connectionSettings: QBittorrentConnectionSettings) { this.connectionSettings = connectionSettings; this.apiBase = `${connectionSettings.url}/api/v2`; - - this.authenticate(); + this.updateAuthCookie().catch(() => undefined); } } diff --git a/server/services/rTorrent/clientGatewayService.ts b/server/services/rTorrent/clientGatewayService.ts index 41cba69f..df521bdb 100644 --- a/server/services/rTorrent/clientGatewayService.ts +++ b/server/services/rTorrent/clientGatewayService.ts @@ -676,26 +676,27 @@ class RTorrentClientGatewayService extends ClientGatewayService { if (clientSettings == null) { return this.clientRequestManager .methodCall('system.methodExist', ['system.multicall']) - .then(this.processClientRequestSuccess) - .catch(this.processClientRequestError); + .then(() => this.processClientRequestSuccess(undefined), this.processClientRequestError); } if (clientSettings.client !== 'rTorrent') { return Promise.reject(); } - return scgiUtil.methodCall( - clientSettings.type === 'socket' - ? { - socketPath: clientSettings.socket, - } - : { - host: clientSettings.host, - port: clientSettings.port, - }, - 'system.methodExist', - ['system.multicall'], - ); + return scgiUtil + .methodCall( + clientSettings.type === 'socket' + ? { + socketPath: clientSettings.socket, + } + : { + host: clientSettings.host, + port: clientSettings.port, + }, + 'system.methodExist', + ['system.multicall'], + ) + .then(() => this.processClientRequestSuccess(undefined), this.processClientRequestError); } }