metube/ui/src/app/downloads.service.ts
Elad Levi 2141db22a8 Add UI and backend support for download configuration
Introduces UI controls to set download mode and max concurrent downloads, with backend API to update these settings dynamically. The configuration is now synchronized between frontend and backend, allowing users to adjust download scheduling directly from the UI.
2025-08-02 13:01:32 +03:00

168 lines
5.7 KiB
TypeScript

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, of, Subject } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { MeTubeSocket } from './metube-socket';
export interface Status {
status: string;
msg?: string;
}
export interface Download {
id: string;
title: string;
url: string;
quality: string;
format: string;
folder: string;
custom_name_prefix: string;
playlist_strict_mode: boolean;
playlist_item_limit: number;
status: string;
msg: string;
percent: number;
speed: number;
eta: number;
filename: string;
checked?: boolean;
deleting?: boolean;
}
@Injectable({
providedIn: 'root'
})
export class DownloadsService {
loading = true;
queue = new Map<string, Download>();
done = new Map<string, Download>();
queueChanged = new Subject();
doneChanged = new Subject();
customDirsChanged = new Subject();
ytdlOptionsChanged = new Subject();
configurationChanged = new Subject();
updated = new Subject();
configuration = {};
customDirs = {};
constructor(private http: HttpClient, private socket: MeTubeSocket) {
socket.fromEvent('all').subscribe((strdata: string) => {
this.loading = false;
let data: [[[string, Download]], [[string, Download]]] = JSON.parse(strdata);
this.queue.clear();
data[0].forEach(entry => this.queue.set(...entry));
this.done.clear();
data[1].forEach(entry => this.done.set(...entry));
this.queueChanged.next(null);
this.doneChanged.next(null);
});
socket.fromEvent('added').subscribe((strdata: string) => {
let data: Download = JSON.parse(strdata);
this.queue.set(data.url, data);
this.queueChanged.next(null);
});
socket.fromEvent('updated').subscribe((strdata: string) => {
let data: Download = JSON.parse(strdata);
let dl: Download = this.queue.get(data.url);
data.checked = dl.checked;
data.deleting = dl.deleting;
this.queue.set(data.url, data);
this.updated.next(null);
});
socket.fromEvent('completed').subscribe((strdata: string) => {
let data: Download = JSON.parse(strdata);
this.queue.delete(data.url);
this.done.set(data.url, data);
this.queueChanged.next(null);
this.doneChanged.next(null);
});
socket.fromEvent('canceled').subscribe((strdata: string) => {
let data: string = JSON.parse(strdata);
this.queue.delete(data);
this.queueChanged.next(null);
});
socket.fromEvent('cleared').subscribe((strdata: string) => {
let data: string = JSON.parse(strdata);
this.done.delete(data);
this.doneChanged.next(null);
});
socket.fromEvent('configuration').subscribe((strdata: string) => {
let data = JSON.parse(strdata);
console.debug("got configuration:", data);
this.configuration = data;
this.configurationChanged.next(data);
});
socket.fromEvent('custom_dirs').subscribe((strdata: string) => {
let data = JSON.parse(strdata);
console.debug("got custom_dirs:", data);
this.customDirs = data;
this.customDirsChanged.next(data);
});
socket.fromEvent('ytdl_options_changed').subscribe((strdata: string) => {
let data = JSON.parse(strdata);
this.ytdlOptionsChanged.next(data);
});
}
handleHTTPError(error: HttpErrorResponse) {
var msg = error.error instanceof ErrorEvent ? error.error.message : error.error;
return of({status: 'error', msg: msg})
}
public add(url: string, quality: string, format: string, folder: string, customNamePrefix: string, playlistStrictMode: boolean, playlistItemLimit: number, autoStart: boolean) {
return this.http.post<Status>('add', {url: url, quality: quality, format: format, folder: folder, custom_name_prefix: customNamePrefix, playlist_strict_mode: playlistStrictMode, playlist_item_limit: playlistItemLimit, auto_start: autoStart}).pipe(
catchError(this.handleHTTPError)
);
}
public startById(ids: string[]) {
return this.http.post('start', {ids: ids});
}
public delById(where: string, ids: string[]) {
ids.forEach(id => this[where].get(id).deleting = true);
return this.http.post('delete', {where: where, ids: ids});
}
public startByFilter(where: string, filter: (dl: Download) => boolean) {
let ids: string[] = [];
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
return this.startById(ids);
}
public delByFilter(where: string, filter: (dl: Download) => boolean) {
let ids: string[] = [];
this[where].forEach((dl: Download) => { if (filter(dl)) ids.push(dl.url) });
return this.delById(where, ids);
}
public addDownloadByUrl(url: string): Promise<any> {
const defaultQuality = 'best';
const defaultFormat = 'mp4';
const defaultFolder = '';
const defaultCustomNamePrefix = '';
const defaultPlaylistStrictMode = false;
const defaultPlaylistItemLimit = 0;
const defaultAutoStart = true;
return new Promise((resolve, reject) => {
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart)
.subscribe(
response => resolve(response),
error => reject(error)
);
});
}
public exportQueueUrls(): string[] {
return Array.from(this.queue.values()).map(download => download.url);
}
public updateDownloadConfig(downloadMode: string, maxConcurrentDownloads: number): Observable<Status> {
return this.http.post<Status>('update_download_config', {
download_mode: downloadMode,
max_concurrent_downloads: maxConcurrentDownloads
}).pipe(
catchError(this.handleHTTPError)
);
}
}