diff --git a/.env.sample b/.env.sample index 1285876..5a74326 100644 --- a/.env.sample +++ b/.env.sample @@ -1,21 +1,23 @@ -PORT=3000 - +#needed only for docker-compose to map your filesystem, for docker run use "/srv/data/static" volume path mapping STATIC_SERVE_FOLDER='/mnt/hdd/videos' +#needed only for docker-compose to map your filesystem, for docker run use "/srv/data/requests-log" volume path mapping LOG_FILE_REQUESTS_FOLDER='/mnt/hdd/videos' -LOG_FILE_REQUESTS_NAME="static_log.txt" - +#needed only for docker-compose to map your filesystem, for docker run use "/srv/data/db" volume path mapping DB_FOLDER='/mnt/hdd/videos/db' -DB_FILENAME="db.db" - +#needed only for docker-compose to map your filesystem, for docker run use "/srv/data/recent-searches" volume path mapping RECENT_SEARCHES_FOLDER="/mnt/hdd/videos/recent-searches" + +#actual env variables starts here +PORT=3000 +LOG_FILE_REQUESTS_NAME="static_log.txt" +DB_FILENAME="db.db" RECENT_SEARCHES_LIMIT=20 +PATTERNS_TO_IGNORE='["AOE1/"]' #file path patterns to ignore from video files scan, must be in json format +#can be VIDEO_FILES_FOLDER_1, VIDEO_FILES_FOLDER_2, etc., but must be edited in docker-compose.yml as well +#also map to same folders in your transmission file system VIDEO_FILES_FOLDER='/mnt/hdd/videos' #also torrent download dir -VIDEO_FILES_EXT='[".mkv", ".mp4", ".avi"]' - -PATTERNS_TO_IGNORE='["AOE1/"]' #file path patterns to ignore from video files scan - LINKOMANIJA_USERNAME=user LINKOMANIJA_PASSWORD=passw LINKOMANIJA_PASSKEY=123pass456key789 diff --git a/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts b/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts index e36da9f..b82f023 100644 --- a/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts +++ b/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts @@ -10,6 +10,7 @@ import { FileEntity } from '../../../Shared/Entity/file.entity'; import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; import { VideoFilesFacade } from '../../../VideoFiles/video-files.facade'; import { TorrentSeedClient } from '../../SeedClient/torrent-seed.client'; +import process, { exec } from 'child_process'; @Injectable() export class WebtorrentClient { @@ -42,9 +43,13 @@ export class WebtorrentClient { this.resumeNotStopped(); } - getNewTorrentOptions(): TorrentOptions { + async getNewTorrentOptions(): Promise { const torrentOptions: Record = {}; - torrentOptions.path = this.configService.getTorrentDownloadDir(); + //todo - risk! - torrent might be already downloaded on another folder + torrentOptions.path = await this.findPathWithMostSpace( + this.configService.getTorrentDownloadDirs(), + ); + // torrentOptions.path = this.configService.getTorrentDownloadDir(); torrentOptions.getAnnounceOpts = function () { const that = this as Torrent; @@ -136,11 +141,9 @@ export class WebtorrentClient { } if (!torrent) { + const torrentOptions = await this.getNewTorrentOptions(); return new Promise((resolve, reject) => { - const torrent = this.client.add( - torrentEntity.magnet, - this.getNewTorrentOptions(), - ); + const torrent = this.client.add(torrentEntity.magnet, torrentOptions); torrent.on('ready', () => { resolve(torrent); @@ -312,4 +315,52 @@ export class WebtorrentClient { }); }); } + + private async findPathWithMostSpace(paths: string[]): Promise { + const promises: Promise<{ path: string; free: number }>[] = []; + for (const path of paths) { + promises.push(this.getFreeSpace(path)); + } + + const results = await Promise.all(promises); + let maxSpace = 0; + let maxSpacePath = ''; + for (const result of results) { + console.log(`testing path ${result.path} with ${result.free}KB`); + if (result.free > maxSpace) { + maxSpace = result.free; + maxSpacePath = result.path; + } + } + + return maxSpacePath; + } + + private getFreeSpace(path: string): Promise<{ path: string; free: number }> { + return new Promise((resolve, reject) => { + exec(`df ${path}`, (err, data) => { + if (err) { + return reject(err); + } + const rows = data.split('\n'); + const fsData = rows[1].replace(/\s+/g, ' ').trim().split(' '); + const [ + filesystem, + sizeInKB, + usedKB, + availableKB, + usePercentage, + mountPoint, + ] = fsData; + + const availableKbNum = Number(availableKB); + + if (isNaN(availableKbNum)) { + return reject(`availableKB not a number: ${availableKB}`); + } + + resolve({ path, free: availableKbNum }); + }); + }); + } } diff --git a/src/config/video-files.config.ts b/src/config/video-files.config.ts index fdf8c6d..e6c5898 100644 --- a/src/config/video-files.config.ts +++ b/src/config/video-files.config.ts @@ -6,19 +6,26 @@ export class VideoFilesConfig { } public getVideoFilesFolders(): string[] { - return [this.configService.getEnv('VIDEO_FILES_FOLDER')]; + const folders: string[] = []; + Object.keys(process.env).forEach((key) => { + if (key.startsWith('VIDEO_FILES_FOLDER') && process.env[key]) { + folders.push(process.env[key]); + } + }); + + return folders; } public getVideoFilesExt(): string[] { - return JSON.parse(this.configService.getEnv('VIDEO_FILES_EXT')); + return ['.mkv', '.mp4', '.avi']; } public getPathPatternsToIgnore(): string[] { return JSON.parse(this.configService.getEnv('PATTERNS_TO_IGNORE')); } - public getTorrentDownloadDir(): string { - return this.configService.getEnv('VIDEO_FILES_FOLDER'); + public getTorrentDownloadDirs(): string[] { + return this.getVideoFilesFolders(); } public getTransmissionOptions() {