server: fetch torrents from http/https URLs directly from Flood

This allows more control over the fetching process.

Specifically, it allows us to add a feature to attach
Cookies to requests in a later commit.
This commit is contained in:
Jesse Chan
2020-10-20 21:28:48 +08:00
parent 8d2959edfc
commit b0add0db99
4 changed files with 80 additions and 18 deletions
+1
View File
@@ -1,6 +1,7 @@
module.exports = {
preset: 'ts-jest/presets/js-with-babel',
rootDir: './../',
testEnvironment: 'node',
testMatch: ['<rootDir>/routes/api/auth.test.ts'],
setupFilesAfterEnv: ['<rootDir>/.jest/auth.setup.js'],
globals: {
+1
View File
@@ -1,6 +1,7 @@
module.exports = {
preset: 'ts-jest/presets/js-with-babel',
rootDir: './../',
testEnvironment: 'node',
testPathIgnorePatterns: ['auth.test.ts'],
setupFilesAfterEnv: ['<rootDir>/.jest/test.setup.js'],
globals: {
@@ -1,4 +1,3 @@
import crypto from 'crypto';
import fs from 'fs';
import geoip from 'geoip-country';
import {moveSync} from 'fs-extra';
@@ -32,7 +31,7 @@ import ClientGatewayService from '../interfaces/clientGatewayService';
import ClientRequestManager from './clientRequestManager';
import scgiUtil from './util/scgiUtil';
import {getMethodCalls, processMethodCallResponse} from './util/rTorrentMethodCallUtil';
import {getTempPath} from '../../models/TemporaryStorage';
import {fetchURLToTempFile, saveBufferToTempFile} from '../../util/tempFileUtil';
import torrentFileUtil from '../../util/torrentFileUtil';
import {
encodeTags,
@@ -57,24 +56,12 @@ class RTorrentClientGatewayService extends ClientGatewayService {
clientRequestManager = new ClientRequestManager(this.user.client as RTorrentConnectionSettings);
async addTorrentsByFile({files, destination, tags, isBasePath, start}: AddTorrentByFileOptions): Promise<void> {
const tempPath = path.join(
getTempPath(this.user._id),
'torrents',
`${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
);
await createDirectory(tempPath);
const torrentPaths = await Promise.all(
files.map(async (file, index) => {
const torrentPath = path.join(tempPath, `${index}.torrent`);
fs.writeFileSync(torrentPath, Buffer.from(file, 'base64'), {});
return torrentPath;
files.map(async (file) => {
return saveBufferToTempFile(Buffer.from(file, 'base64'), 'torrent');
}),
);
// Delete temp files after 5 minutes. This is more than enough.
setTimeout(() => fs.rmdirSync(tempPath, {recursive: true}), 1000 * 60 * 5);
return this.addTorrentsByURL({urls: torrentPaths, destination, tags, isBasePath, start});
}
@@ -87,7 +74,24 @@ class RTorrentClientGatewayService extends ClientGatewayService {
await createDirectory(destinationPath);
const methodCalls: MultiMethodCalls = urls.map((url) => {
const torrentPaths = await Promise.all(
urls.map(async (url) => {
if (/^(http|https):\/\//.test(url)) {
// TODO: properly handle error and let frontend know
const torrentPath = await fetchURLToTempFile(url, 'torrent').catch((e) => console.error(e));
if (typeof torrentPath === 'string') {
return torrentPath;
}
}
// TODO: handle potential other types of downloads
return url;
}),
);
const methodCalls: MultiMethodCalls = torrentPaths.map((torrentPath) => {
const additionalCalls: Array<string> = [];
additionalCalls.push(`${isBasePath ? 'd.directory_base.set' : 'd.directory.set'}="${destinationPath}"`);
@@ -100,7 +104,7 @@ class RTorrentClientGatewayService extends ClientGatewayService {
return {
methodName: start ? 'load.start' : 'load.normal',
params: ['', url].concat(additionalCalls),
params: ['', torrentPath].concat(additionalCalls),
};
}, []);
+56
View File
@@ -0,0 +1,56 @@
import axios, {AxiosResponse} from 'axios';
import crypto from 'crypto';
import fs from 'fs';
import {getTempPath} from '../models/TemporaryStorage';
/**
* Gets a randomly generated file path for temp file.
*
* @return {string} - path
*/
const getTempFilePath = (extension = 'tmp'): string => {
return getTempPath(`${Date.now()}-${crypto.randomBytes(8).toString('hex')}.${extension}`);
};
/**
* Saves buffer to temporary storage as a file.
*
* @param {Buffer} buffer - buffer
* @param {string} extension - file extension of temp file
* @return {string} - path of saved temporary file. deleted after 5 minutes.
*/
export const saveBufferToTempFile = async (buffer: Buffer, extension?: string): Promise<string> => {
const tempPath = getTempFilePath(extension);
fs.writeFileSync(tempPath, buffer);
setTimeout(() => fs.unlinkSync(tempPath), 1000 * 60 * 5);
return tempPath;
};
/**
* Fetches from URL to temporary storage.
*
* @param {string} url - URL
* @param {string} extension - file extension of temp file
* @return {string} - path of saved temporary file. deleted after 5 minutes.
*/
export const fetchURLToTempFile = async (url: string, extension?: string): Promise<string> => {
const tempPath = getTempFilePath(extension);
await new Promise((resolve) => {
axios({
method: 'GET',
url,
responseType: 'stream',
}).then((res: AxiosResponse) => {
res.data.pipe(fs.createWriteStream(tempPath)).on('finish', () => resolve());
});
});
setTimeout(() => fs.unlinkSync(tempPath), 1000 * 60 * 5);
return tempPath;
};