diff --git a/backend/app.js b/backend/app.js index 02daddf..3eba4ec 100644 --- a/backend/app.js +++ b/backend/app.js @@ -970,6 +970,7 @@ app.post('/api/downloadFile', optionalJwt, async function(req, res) { req.setTimeout(0); // remove timeout in case of long videos const url = req.body.url; const type = req.body.type; + const user_uid = req.isAuthenticated() ? req.user.uid : null; var options = { customArgs: req.body.customArgs, customOutput: req.body.customOutput, @@ -978,11 +979,10 @@ app.post('/api/downloadFile', optionalJwt, async function(req, res) { youtubeUsername: req.body.youtubeUsername, youtubePassword: req.body.youtubePassword, ui_uid: req.body.ui_uid, - user: req.isAuthenticated() ? req.user.uid : null, cropFileSettings: req.body.cropFileSettings } - const download = await downloader_api.createDownload(url, type, options); + const download = await downloader_api.createDownload(url, type, options, user_uid); if (download) { res.send({download: download}); @@ -1740,7 +1740,8 @@ app.get('/api/thumbnail/:path', optionalJwt, async (req, res) => { // Downloads management app.get('/api/downloads', optionalJwt, async (req, res) => { - const downloads = await db_api.getRecords('download_queue'); + const user_uid = req.isAuthenticated() ? req.user.uid : null; + const downloads = await db_api.getRecords('download_queue', {user_uid: user_uid}); res.send({downloads: downloads}); }); @@ -1757,7 +1758,8 @@ app.post('/api/download', optionalJwt, async (req, res) => { }); app.post('/api/clearFinishedDownloads', optionalJwt, async (req, res) => { - const success = db_api.removeAllRecords('download_queue', {finished: true}); + const user_uid = req.isAuthenticated() ? req.user.uid : null; + const success = db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid}); res.send({success: success}); }); diff --git a/backend/appdata/default.json b/backend/appdata/default.json index 713a92d..c25bdce 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -20,7 +20,7 @@ "file_manager_enabled": true, "allow_quality_select": true, "download_only_mode": false, - "allow_multi_download_mode": true, + "allow_autoplay": true, "enable_downloads_manager": true, "allow_playlist_categorization": true }, @@ -68,8 +68,8 @@ "multi_user_mode": false, "allow_advanced_download": false, "use_cookies": false, - "jwt_expiration": 86400, + "jwt_expiration": 86400, "logger_level": "info" } } -} +} \ No newline at end of file diff --git a/backend/config.js b/backend/config.js index c8a8aaa..90d3525 100644 --- a/backend/config.js +++ b/backend/config.js @@ -195,7 +195,7 @@ const DEFAULT_CONFIG = { "file_manager_enabled": true, "allow_quality_select": true, "download_only_mode": false, - "allow_multi_download_mode": true, + "allow_autoplay": true, "enable_downloads_manager": true, "allow_playlist_categorization": true }, diff --git a/backend/consts.js b/backend/consts.js index 28ee62c..5392b4e 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -64,9 +64,9 @@ exports.CONFIG_ITEMS = { 'key': 'ytdl_download_only_mode', 'path': 'YoutubeDLMaterial.Extra.download_only_mode' }, - 'ytdl_allow_multi_download_mode': { - 'key': 'ytdl_allow_multi_download_mode', - 'path': 'YoutubeDLMaterial.Extra.allow_multi_download_mode' + 'ytdl_allow_autoplay': { + 'key': 'ytdl_allow_autoplay', + 'path': 'YoutubeDLMaterial.Extra.allow_autoplay' }, 'ytdl_enable_downloads_manager': { 'key': 'ytdl_enable_downloads_manager', diff --git a/backend/downloader.js b/backend/downloader.js index b5daff9..3528168 100644 --- a/backend/downloader.js +++ b/backend/downloader.js @@ -29,12 +29,13 @@ exports.initialize = (input_db_api) => { setupDownloads(); } -exports.createDownload = async (url, type, options) => { +exports.createDownload = async (url, type, options, user_uid = null) => { return await mutex.runExclusive(async () => { const download = { url: url, type: type, title: '', + user_uid: user_uid, options: options, uid: uuid(), step_index: 0, @@ -82,7 +83,7 @@ exports.resumeDownload = async (download_uid) => { exports.restartDownload = async (download_uid) => { const download = await db_api.getRecord('download_queue', {uid: download_uid}); await exports.clearDownload(download_uid); - const success = !!(await exports.createDownload(download['url'], download['type'], download['options'])); + const success = !!(await exports.createDownload(download['url'], download['type'], download['options'], download['user_uid'])); should_check_downloads = true; return success; @@ -171,13 +172,13 @@ async function collectInfo(download_uid) { const type = download['type']; const options = download['options']; - if (options.user && !options.customFileFolderPath) { + if (download['user_uid'] && !options.customFileFolderPath) { let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - const user_path = path.join(usersFileFolder, options.user, type); + const user_path = path.join(usersFileFolder, download['user_uid'], type); options.customFileFolderPath = user_path + path.sep; } - let args = await generateArgs(url, type, options); + let args = await generateArgs(url, type, options, download['user_uid']); // get video info prior to download let info = await getVideoInfoByURL(url, args, download_uid); @@ -198,7 +199,7 @@ async function collectInfo(download_uid) { if (category && category['custom_output']) { options.customOutput = category['custom_output']; options.noRelativePath = true; - args = await generateArgs(url, type, options); + args = await generateArgs(url, type, options, download['user_uid']); info = await getVideoInfoByURL(url, args, download_uid); } @@ -294,7 +295,7 @@ async function downloadQueuedFile(download_uid) { && config_api.getConfigItem('ytdl_use_twitch_api') && config_api.getConfigItem('ytdl_twitch_auto_download_chat')) { let vodId = url.split('twitch.tv/videos/')[1]; vodId = vodId.split('?')[0]; - twitch_api.downloadTwitchChatByVODID(vodId, file_name, type, options.user); + twitch_api.downloadTwitchChatByVODID(vodId, file_name, type, download['user_uid']); } // renames file if necessary due to bug @@ -321,7 +322,7 @@ async function downloadQueuedFile(download_uid) { } // registers file in DB - const file_obj = await db_api.registerFileDB2(full_file_path, type, options.user, category, null, options.cropFileSettings); + const file_obj = await db_api.registerFileDB2(full_file_path, type, download['user_uid'], category, null, options.cropFileSettings); file_objs.push(file_obj); } @@ -329,7 +330,7 @@ async function downloadQueuedFile(download_uid) { if (options.merged_string !== null && options.merged_string !== undefined) { let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8'); let diff = current_merged_archive.replace(options.merged_string, ''); - const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`); + const archive_path = download['user_uid'] ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`); fs.appendFileSync(archive_path, diff); } @@ -338,7 +339,7 @@ async function downloadQueuedFile(download_uid) { if (file_objs.length > 1) { // create playlist const playlist_name = file_objs.map(file_obj => file_obj.title).join(', '); - container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, options.user); + container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, download['user_uid']); } else if (file_objs.length === 1) { container = file_objs[0]; } else { @@ -355,7 +356,7 @@ async function downloadQueuedFile(download_uid) { // helper functions -async function generateArgs(url, type, options) { +async function generateArgs(url, type, options, user_uid = null) { const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path'); const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path'); @@ -436,13 +437,13 @@ async function generateArgs(url, type, options) { let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { - const archive_folder = options.user ? path.join(fileFolderPath, 'archives') : archivePath; + const archive_folder = user_uid ? path.join(fileFolderPath, 'archives') : archivePath; const archive_path = path.join(archive_folder, `archive_${type}.txt`); await fs.ensureDir(archive_folder); await fs.ensureFile(archive_path); - let blacklist_path = options.user ? path.join(fileFolderPath, 'archives', `blacklist_${type}.txt`) : path.join(archivePath, `blacklist_${type}.txt`); + let blacklist_path = user_uid ? path.join(fileFolderPath, 'archives', `blacklist_${type}.txt`) : path.join(archivePath, `blacklist_${type}.txt`); await fs.ensureFile(blacklist_path); let merged_path = path.join(fileFolderPath, `merged_${type}.txt`); diff --git a/src/app/components/downloads/downloads.component.html b/src/app/components/downloads/downloads.component.html index bbb953c..50f2762 100644 --- a/src/app/components/downloads/downloads.component.html +++ b/src/app/components/downloads/downloads.component.html @@ -1,4 +1,4 @@ -
+
@@ -63,4 +63,8 @@ aria-label="Select page of downloads">
+
+ +
+

No downloads available!

\ No newline at end of file diff --git a/src/app/components/downloads/downloads.component.ts b/src/app/components/downloads/downloads.component.ts index ba0eee5..380b77c 100644 --- a/src/app/components/downloads/downloads.component.ts +++ b/src/app/components/downloads/downloads.component.ts @@ -54,6 +54,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { displayedColumns: string[] = ['date', 'title', 'stage', 'progress', 'actions']; dataSource = null; // new MatTableDataSource(); + downloads_retrieved = false; @ViewChild(MatPaginator) paginator: MatPaginator; @@ -93,10 +94,12 @@ export class DownloadsComponent implements OnInit, OnDestroy { getCurrentDownloads(): void { this.postsService.getCurrentDownloads().subscribe(res => { + this.downloads_retrieved = true; if (res['downloads'] !== null && res['downloads'] !== undefined && JSON.stringify(this.downloads) !== JSON.stringify(res['downloads'])) { - this.downloads = res['downloads']; + this.downloads = this.combineDownloads(this.downloads, res['downloads']); + // this.downloads = res['downloads']; this.downloads.sort(this.sort_downloads); this.dataSource = new MatTableDataSource(this.downloads); this.dataSource.paginator = this.paginator; @@ -114,7 +117,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - pauseDownload(download_uid) { + pauseDownload(download_uid: string): void { this.postsService.pauseDownload(download_uid).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to pause download! See server logs for more info.'); @@ -122,7 +125,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - resumeDownload(download_uid) { + resumeDownload(download_uid: string): void { this.postsService.resumeDownload(download_uid).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to resume download! See server logs for more info.'); @@ -130,7 +133,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - restartDownload(download_uid) { + restartDownload(download_uid: string): void { this.postsService.restartDownload(download_uid).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to restart download! See server logs for more info.'); @@ -138,7 +141,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - cancelDownload(download_uid) { + cancelDownload(download_uid: string): void { this.postsService.cancelDownload(download_uid).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to cancel download! See server logs for more info.'); @@ -146,7 +149,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - clearDownload(download_uid) { + clearDownload(download_uid: string): void { this.postsService.clearDownload(download_uid).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to pause download! See server logs for more info.'); @@ -154,7 +157,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } - watchContent(download) { + watchContent(download): void { const container = download['container']; localStorage.setItem('player_navigator', this.router.url.split(';')[0]); const is_playlist = container['uids']; // hacky, TODO: fix @@ -165,6 +168,26 @@ export class DownloadsComponent implements OnInit, OnDestroy { } } + combineDownloads(downloads_old, downloads_new) { + // only keeps downloads that exist in the new set + downloads_old = downloads_old.filter(download_old => downloads_new.some(download_new => download_new.uid === download_old.uid)); + + // add downloads from the new set that the old one doesn't have + const downloads_to_add = downloads_new.filter(download_new => !downloads_old.some(download_old => download_new.uid === download_old.uid)); + downloads_old.push(...downloads_to_add); + downloads_old.forEach(download_old => { + const download_new = downloads_new.find(download_to_check => download_old.uid === download_to_check.uid); + Object.keys(download_new).forEach(key => { + download_old[key] = download_new[key]; + }); + + Object.keys(download_old).forEach(key => { + if (!download_new[key]) delete download_old[key]; + }); + }); + + return downloads_old; + } } export interface Download { diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 604f9ce..84ae4f7 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -65,9 +65,9 @@ Only Audio - - - Multi-download Mode + + + Autoplay @@ -169,18 +169,6 @@
-
- -
-
- - - - -
-
-
-

diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 934f484..fb4a5ce 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -46,7 +46,7 @@ export class MainComponent implements OnInit { determinateProgress = false; downloadingfile = false; audioOnly: boolean; - multiDownloadMode = false; + autoplay = false; customArgsEnabled = false; customArgs = null; customOutputEnabled = false; @@ -68,7 +68,7 @@ export class MainComponent implements OnInit { fileManagerEnabled = false; allowQualitySelect = false; downloadOnlyMode = false; - allowMultiDownloadMode = false; + allowAutoplay = false; audioFolderPath; videoFolderPath; use_youtubedl_archive = false; @@ -232,7 +232,7 @@ export class MainComponent implements OnInit { this.fileManagerEnabled = this.postsService.config['Extra']['file_manager_enabled'] && this.postsService.hasPermission('filemanager'); this.downloadOnlyMode = this.postsService.config['Extra']['download_only_mode']; - this.allowMultiDownloadMode = this.postsService.config['Extra']['allow_multi_download_mode']; + this.allowAutoplay = this.postsService.config['Extra']['allow_autoplay']; this.audioFolderPath = this.postsService.config['Downloader']['path-audio']; this.videoFolderPath = this.postsService.config['Downloader']['path-video']; this.use_youtubedl_archive = this.postsService.config['Downloader']['use_youtubedl_archive']; @@ -314,8 +314,8 @@ export class MainComponent implements OnInit { this.audioOnly = localStorage.getItem('audioOnly') === 'true'; } - if (localStorage.getItem('multiDownloadMode') !== null) { - this.multiDownloadMode = localStorage.getItem('multiDownloadMode') === 'true'; + if (localStorage.getItem('autoplay') !== null) { + this.autoplay = localStorage.getItem('autoplay') === 'true'; } // check if params exist @@ -374,10 +374,9 @@ export class MainComponent implements OnInit { } // download helpers - downloadHelper(container, type, is_playlist = false, force_view = false, navigate_mode = false) { this.downloadingfile = false; - if (this.multiDownloadMode && !this.downloadOnlyMode && !navigate_mode) { + if (!this.autoplay && !this.downloadOnlyMode && !navigate_mode) { // do nothing this.reloadRecentVideos(); } else { @@ -398,9 +397,6 @@ export class MainComponent implements OnInit { } } } - - // // remove download from current downloads - // this.removeDownloadFromCurrentDownloads(new_download); } // download click handler @@ -432,8 +428,6 @@ export class MainComponent implements OnInit { } const type = this.audioOnly ? 'audio' : 'video'; - // this.downloads.push(new_download); - // if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download }; this.downloadingfile = true; const customQualityConfiguration = type === 'audio' ? this.getSelectedAudioFormat() : this.getSelectedVideoFormat(); @@ -449,22 +443,17 @@ export class MainComponent implements OnInit { this.postsService.downloadFile(this.url, type, (this.selectedQuality === '' ? null : this.selectedQuality), customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, cropFileSettings).subscribe(res => { - console.log(res); this.current_download = res['download']; this.downloadingfile = true; }, error => { // can't access server this.downloadingfile = false; this.current_download = null; - // new_download['downloading'] = false; - // // removes download from list of downloads - // const downloads_index = this.downloads.indexOf(new_download); - // if (downloads_index !== -1) { - // this.downloads.splice(downloads_index) - // } this.openSnackBar('Download failed!', 'OK.'); }); - if (this.multiDownloadMode) { + if (!this.autoplay) { + const download_queued_message = $localize`Download for ${this.url}:url has been queued!`; + this.postsService.openSnackBar(download_queued_message); this.url = ''; this.downloadingfile = false; } @@ -755,8 +744,8 @@ export class MainComponent implements OnInit { localStorage.setItem('audioOnly', new_val.checked.toString()); } - multiDownloadModeChanged(new_val) { - localStorage.setItem('multiDownloadMode', new_val.checked.toString()); + autoplayChanged(new_val) { + localStorage.setItem('autoplay', new_val.checked.toString()); } customArgsEnabledChanged(new_val) { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index bad9daf..43d0579 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -225,7 +225,7 @@ Download only mode
- Allow multi-download mode + Allow autoplay