Removed downlload delay setting for subscriptions

Subscription downloads already queued are now not requeued on the next check

Headers in download queue table are now sortable

Added button to clear all finished downloads in the downloads manager
download-manager
Isaac Abadi 4 years ago
parent 71bb91b6e6
commit 09b3c752d9

@ -42,8 +42,7 @@
"allow_subscriptions": true, "allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/", "subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "300", "subscriptions_check_interval": "300",
"redownload_fresh_uploads": false, "redownload_fresh_uploads": false
"download_delay": ""
}, },
"Users": { "Users": {
"base_path": "users/", "base_path": "users/",

@ -217,8 +217,7 @@ const DEFAULT_CONFIG = {
"allow_subscriptions": true, "allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/", "subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "86400", "subscriptions_check_interval": "86400",
"redownload_fresh_uploads": false, "redownload_fresh_uploads": false
"download_delay": ""
}, },
"Users": { "Users": {
"base_path": "users/", "base_path": "users/",

@ -48,6 +48,7 @@ exports.createDownload = async (url, type, options, user_uid = null, sub_id = nu
uid: uuid(), uid: uuid(),
step_index: 0, step_index: 0,
paused: false, paused: false,
running: false,
finished_step: true, finished_step: true,
error: null, error: null,
percent_complete: null, percent_complete: null,
@ -71,7 +72,7 @@ exports.pauseDownload = async (download_uid) => {
return false; return false;
} }
return await db_api.updateRecord('download_queue', {uid: download_uid}, {paused: true}); return await db_api.updateRecord('download_queue', {uid: download_uid}, {paused: true, running: false});
} }
exports.resumeDownload = async (download_uid) => { exports.resumeDownload = async (download_uid) => {
@ -106,7 +107,7 @@ exports.cancelDownload = async (download_uid) => {
logger.info(`Download ${download_uid} could not be cancelled before completing.`); logger.info(`Download ${download_uid} could not be cancelled before completing.`);
return false; return false;
} }
return await db_api.updateRecord('download_queue', {uid: download_uid}, {cancelled: true}); return await db_api.updateRecord('download_queue', {uid: download_uid}, {cancelled: true, running: false});
} }
exports.clearDownload = async (download_uid) => { exports.clearDownload = async (download_uid) => {
@ -127,7 +128,7 @@ async function fixDownloadState() {
const running_downloads = downloads.filter(download => !download['finished'] && !download['error']); const running_downloads = downloads.filter(download => !download['finished'] && !download['error']);
for (let i = 0; i < running_downloads.length; i++) { for (let i = 0; i < running_downloads.length; i++) {
const running_download = running_downloads[i]; const running_download = running_downloads[i];
const update_obj = {finished_step: true, paused: true}; const update_obj = {finished_step: true, paused: true, running: false};
if (running_download['step_index'] > 0) { if (running_download['step_index'] > 0) {
update_obj['step_index'] = running_download['step_index'] - 1; update_obj['step_index'] = running_download['step_index'] - 1;
} }
@ -151,18 +152,19 @@ async function checkDownloads() {
return; return;
}); });
let running_downloads_count = downloads.filter(download => download['running']).length;
const waiting_downloads = downloads.filter(download => !download['paused'] && download['finished_step'] && !download['finished']); const waiting_downloads = downloads.filter(download => !download['paused'] && download['finished_step'] && !download['finished']);
for (let i = 0; i < waiting_downloads.length; i++) { for (let i = 0; i < waiting_downloads.length; i++) {
const running_download = waiting_downloads[i]; const waiting_download = waiting_downloads[i];
if (i === 5/*config_api.getConfigItem('ytdl_max_concurrent_downloads')*/) break; if (running_downloads_count >= 5/*config_api.getConfigItem('ytdl_max_concurrent_downloads')*/) break;
if (running_download['finished_step'] && !running_download['finished']) { if (waiting_download['finished_step'] && !waiting_download['finished']) {
// move to next step // move to next step
running_downloads_count++;
if (running_download['step_index'] === 0) { if (waiting_download['step_index'] === 0) {
collectInfo(running_download['uid']); collectInfo(waiting_download['uid']);
} else if (running_download['step_index'] === 1) { } else if (waiting_download['step_index'] === 1) {
downloadQueuedFile(running_download['uid']); downloadQueuedFile(waiting_download['uid']);
} }
} }
} }
@ -174,7 +176,7 @@ async function collectInfo(download_uid) {
return; return;
} }
logger.verbose(`Collecting info for download ${download_uid}`); logger.verbose(`Collecting info for download ${download_uid}`);
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 1, finished_step: false}); await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 1, finished_step: false, running: true});
const url = download['url']; const url = download['url'];
const type = download['type']; const type = download['type'];
@ -194,7 +196,7 @@ async function collectInfo(download_uid) {
if (!info) { if (!info) {
// info failed, record error and pause download // info failed, record error and pause download
const error = 'Failed to get info, see server logs for specific error.'; const error = 'Failed to get info, see server logs for specific error.';
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true}); await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true, running: false});
return; return;
} }
@ -227,6 +229,7 @@ async function collectInfo(download_uid) {
const playlist_title = Array.isArray(info) ? info[0]['playlist_title'] || info[0]['playlist'] : null; const playlist_title = Array.isArray(info) ? info[0]['playlist_title'] || info[0]['playlist'] : null;
await db_api.updateRecord('download_queue', {uid: download_uid}, {args: args, await db_api.updateRecord('download_queue', {uid: download_uid}, {args: args,
finished_step: true, finished_step: true,
running: false,
options: options, options: options,
files_to_check_for_progress: files_to_check_for_progress, files_to_check_for_progress: files_to_check_for_progress,
expected_file_size: expected_file_size, expected_file_size: expected_file_size,
@ -243,7 +246,7 @@ async function downloadQueuedFile(download_uid) {
return new Promise(async resolve => { return new Promise(async resolve => {
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path'); const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path'); const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 2, finished_step: false}); await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 2, finished_step: false, running: true});
const url = download['url']; const url = download['url'];
const type = download['type']; const type = download['type'];
@ -355,7 +358,7 @@ async function downloadQueuedFile(download_uid) {
} }
const file_uids = file_objs.map(file_obj => file_obj.uid); const file_uids = file_objs.map(file_obj => file_obj.uid);
await db_api.updateRecord('download_queue', {uid: download_uid}, {finished_step: true, finished: true, step_index: 3, percent_complete: 100, file_uids: file_uids, container: container}); await db_api.updateRecord('download_queue', {uid: download_uid}, {finished_step: true, finished: true, running: false, step_index: 3, percent_complete: 100, file_uids: file_uids, container: container});
resolve(); resolve();
} }
}); });
@ -544,7 +547,7 @@ async function getVideoInfoByURL(url, args = [], download_uid = null) {
logger.error(`Error while retrieving info on video with URL ${url} with the following message: output JSON could not be parsed. Output JSON: ${output}`); logger.error(`Error while retrieving info on video with URL ${url} with the following message: output JSON could not be parsed. Output JSON: ${output}`);
if (download_uid) { if (download_uid) {
const error = 'Failed to get info, see server logs for specific error.'; const error = 'Failed to get info, see server logs for specific error.';
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true}); await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true, running: false});
} }
resolve(null); resolve(null);
} }
@ -555,7 +558,7 @@ async function getVideoInfoByURL(url, args = [], download_uid = null) {
} }
if (download_uid) { if (download_uid) {
const error = 'Failed to get info, see server logs for specific error.'; const error = 'Failed to get info, see server logs for specific error.';
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true}); await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error, paused: true, running: false});
} }
resolve(null); resolve(null);
} }

@ -414,15 +414,6 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
downloadConfig.push('--write-thumbnail'); downloadConfig.push('--write-thumbnail');
} }
const download_delay = config_api.getConfigItem('ytdl_subscriptions_download_delay');
if (download_delay && downloadConfig.indexOf('--sleep-interval') === -1) {
if (!(+download_delay)) {
logger.warn(`Invalid download delay of ${download_delay}, please remember to use non-zero numbers.`);
} else {
downloadConfig.push('--sleep-interval', +download_delay);
}
}
const rate_limit = config_api.getConfigItem('ytdl_download_rate_limit'); const rate_limit = config_api.getConfigItem('ytdl_download_rate_limit');
if (rate_limit && downloadConfig.indexOf('-r') === -1 && downloadConfig.indexOf('--limit-rate') === -1) { if (rate_limit && downloadConfig.indexOf('-r') === -1 && downloadConfig.indexOf('--limit-rate') === -1) {
downloadConfig.push('-r', rate_limit); downloadConfig.push('-r', rate_limit);
@ -440,7 +431,7 @@ async function getFilesToDownload(sub, output_jsons) {
const files_to_download = []; const files_to_download = [];
for (let i = 0; i < output_jsons.length; i++) { for (let i = 0; i < output_jsons.length; i++) {
const output_json = output_jsons[i]; const output_json = output_jsons[i];
const file_missing = !(await db_api.getRecord('files', {sub_id: sub.id, url: output_json['webpage_url']})); const file_missing = !(await db_api.getRecord('files', {sub_id: sub.id, url: output_json['webpage_url']})) && !(await db_api.getRecord('download_queue', {sub_id: sub.id, url: output_json['webpage_url'], error: null}));
if (file_missing) { if (file_missing) {
const file_with_path_exists = await db_api.getRecord('files', {sub_id: sub.id, path: output_json['_filename']}); const file_with_path_exists = await db_api.getRecord('files', {sub_id: sub.id, path: output_json['_filename']});
if (file_with_path_exists) { if (file_with_path_exists) {

@ -1,16 +1,16 @@
<div *ngIf="downloads && downloads.length > 0"> <div [hidden]="!(downloads && downloads.length > 0)">
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<mat-table [dataSource]="dataSource"> <mat-table matSort [dataSource]="dataSource">
<!-- Date Column --> <!-- Date Column -->
<ng-container matColumnDef="date"> <ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Date">Date</ng-container> </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Date">Date</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.timestamp_start | date: 'short'}} </mat-cell> <mat-cell *matCellDef="let element"> {{element.timestamp_start | date: 'short'}} </mat-cell>
</ng-container> </ng-container>
<!-- Title Column --> <!-- Title Column -->
<ng-container matColumnDef="title"> <ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Title">Title</ng-container> </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> <mat-cell *matCellDef="let element">
<span class="one-line" [matTooltip]="element.title ? element.title : null"> <span class="one-line" [matTooltip]="element.title ? element.title : null">
{{element.title}} {{element.title}}
@ -20,7 +20,7 @@
<!-- Subscription Column --> <!-- Subscription Column -->
<ng-container matColumnDef="subscription"> <ng-container matColumnDef="subscription">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Subscription">Subscription</ng-container> </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Subscription">Subscription</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> <mat-cell *matCellDef="let element">
<ng-container *ngIf="element.sub_name"> <ng-container *ngIf="element.sub_name">
{{element.sub_name}} {{element.sub_name}}
@ -33,13 +33,13 @@
<!-- Stage Column --> <!-- Stage Column -->
<ng-container matColumnDef="stage"> <ng-container matColumnDef="stage">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Stage">Stage</ng-container> </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Stage">Stage</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> {{STEP_INDEX_TO_LABEL[element.step_index]}} </mat-cell> <mat-cell *matCellDef="let element"> {{STEP_INDEX_TO_LABEL[element.step_index]}} </mat-cell>
</ng-container> </ng-container>
<!-- Progress Column --> <!-- Progress Column -->
<ng-container matColumnDef="progress"> <ng-container matColumnDef="progress">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Progress">Progress</ng-container> </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Progress">Progress</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> <mat-cell *matCellDef="let element">
<ng-container *ngIf="element.percent_complete"> <ng-container *ngIf="element.percent_complete">
{{+(element.percent_complete) > 100 ? '100' : element.percent_complete}}% {{+(element.percent_complete) > 100 ? '100' : element.percent_complete}}%
@ -78,6 +78,9 @@
aria-label="Select page of downloads"> aria-label="Select page of downloads">
</mat-paginator> </mat-paginator>
</div> </div>
<div style="margin-top: 10px; margin-left: 5px;">
<button mat-stroked-button (click)="clearFinishedDownloads()"><ng-container i18n="Clear finished downloads">Clear finished downloads</ng-container></button>
</div>
</div> </div>
<div *ngIf="(!downloads || downloads.length === 0) && downloads_retrieved"> <div *ngIf="(!downloads || downloads.length === 0) && downloads_retrieved">

@ -4,6 +4,9 @@ import { trigger, transition, animateChild, stagger, query, style, animate } fro
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
import { MatSort } from '@angular/material/sort';
@Component({ @Component({
selector: 'app-downloads', selector: 'app-downloads',
@ -57,13 +60,14 @@ export class DownloadsComponent implements OnInit, OnDestroy {
downloads_retrieved = false; downloads_retrieved = false;
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
sort_downloads = (a, b) => { sort_downloads = (a, b) => {
const result = b.timestamp_start - a.timestamp_start; const result = b.timestamp_start - a.timestamp_start;
return result; return result;
} }
constructor(public postsService: PostsService, private router: Router) { } constructor(public postsService: PostsService, private router: Router, private dialog: MatDialog) { }
ngOnInit(): void { ngOnInit(): void {
if (this.postsService.initialized) { if (this.postsService.initialized) {
@ -103,6 +107,7 @@ export class DownloadsComponent implements OnInit, OnDestroy {
this.downloads.sort(this.sort_downloads); this.downloads.sort(this.sort_downloads);
this.dataSource = new MatTableDataSource<Download>(this.downloads); this.dataSource = new MatTableDataSource<Download>(this.downloads);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
} else { } else {
// failed to get downloads // failed to get downloads
} }
@ -110,12 +115,24 @@ export class DownloadsComponent implements OnInit, OnDestroy {
} }
clearFinishedDownloads(): void { clearFinishedDownloads(): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
dialogTitle: $localize`Clear finished downloads`,
dialogText: $localize`Would you like to clear your finished downloads?`,
submitText: $localize`Clear`,
warnSubmitColor: true
}
});
dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) {
this.postsService.clearFinishedDownloads().subscribe(res => { this.postsService.clearFinishedDownloads().subscribe(res => {
if (!res['success']) { if (!res['success']) {
this.postsService.openSnackBar('Failed to clear finished downloads!'); this.postsService.openSnackBar('Failed to clear finished downloads!');
} }
}); });
} }
});
}
pauseDownload(download_uid: string): void { pauseDownload(download_uid: string): void {
this.postsService.pauseDownload(download_uid).subscribe(res => { this.postsService.pauseDownload(download_uid).subscribe(res => {

@ -58,12 +58,6 @@
<mat-hint><ng-container i18n="Check interval setting input hint">Unit is seconds, only include numbers.</ng-container></mat-hint> <mat-hint><ng-container i18n="Check interval setting input hint">Unit is seconds, only include numbers.</ng-container></mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-12 mt-2">
<mat-form-field class="text-field" color="accent">
<input [(ngModel)]="new_config['Subscriptions']['download_delay']" matInput placeholder="Download delay" i18n-placeholder="Download delay input placeholder">
<mat-hint><ng-container i18n="Download delay input hint">Units is seconds, will force youtube-dl to sleep between videos in a subscription by the specified number of seconds. Only include numbers.</ng-container></mat-hint>
</mat-form-field>
</div>
<div class="col-12 mt-4 mb-3"> <div class="col-12 mt-4 mb-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['Subscriptions']['redownload_fresh_uploads']" matTooltip="Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day." i18n-matTooltip="Redownload fresh uploads tooltip"><ng-container i18n="Redownload fresh uploads">Redownload fresh uploads</ng-container></mat-checkbox> <mat-checkbox color="accent" [(ngModel)]="new_config['Subscriptions']['redownload_fresh_uploads']" matTooltip="Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day." i18n-matTooltip="Redownload fresh uploads tooltip"><ng-container i18n="Redownload fresh uploads">Redownload fresh uploads</ng-container></mat-checkbox>
</div> </div>

Loading…
Cancel
Save