diff --git a/frontend/src/api/file-api-service.ts b/frontend/src/api/file-api-service.ts index ac1f2c900..828f3e662 100644 --- a/frontend/src/api/file-api-service.ts +++ b/frontend/src/api/file-api-service.ts @@ -1,6 +1,6 @@ import ApiService, {DefaultApiService} from "./api-service"; import {Observable, throwError} from "rxjs"; -import {FileDownloadInfo, FileDownloadRequest, FileDownloadResponse} from "../model/file-download"; +import {FileDownloadInfo, FileDownloadRequest} from "../model/file-download"; import {first} from "rxjs/operators"; //const FILE_API_PATH = 'file'; @@ -8,14 +8,14 @@ const FILE_DOWNLOAD_API_PATH = 'file/download'; const FILE_DOWNLOAD_INFO_API_PATH =FILE_DOWNLOAD_API_PATH + '/info'; export default interface FileApiService extends ApiService { - download(req: FileDownloadRequest): Observable; + download(req: FileDownloadRequest): Observable; getDownloadInfo(): Observable; } export class DefaultFileApiService extends DefaultApiService implements FileApiService { - download(req: FileDownloadRequest): Observable { + download(req: FileDownloadRequest): Observable { if (req.url) { - return this.post(FILE_DOWNLOAD_API_PATH, req); + return this.post(FILE_DOWNLOAD_API_PATH, req); } return throwError(() => new Error('Invalid arguments')); } diff --git a/frontend/src/component/file-download/file-download.tsx b/frontend/src/component/file-download/file-download.tsx index 20b7eccb0..5e8c080b2 100644 --- a/frontend/src/component/file-download/file-download.tsx +++ b/frontend/src/component/file-download/file-download.tsx @@ -73,7 +73,16 @@ export default function FileDownload(props: FileDownloadProps) { }, []); useEffect(() => { - const sub = services.file().subscribeDownloadNotification(() => startPoll()); + const sub = services.file().subscribeDownloadNotification((info: FileDownloadInfo) => { + setDownloads((downloads: any) => { + if (!downloads[info.uuid]) { + downloads[info.uuid] = info; + return {...downloads}; + } + return downloads; + }); + startPoll(); + }); return () => sub.unsubscribe(); }, [services, startPoll]) diff --git a/frontend/src/component/playlist-tree/playlist-tree.tsx b/frontend/src/component/playlist-tree/playlist-tree.tsx index 00e06e337..73067305a 100644 --- a/frontend/src/component/playlist-tree/playlist-tree.tsx +++ b/frontend/src/component/playlist-tree/playlist-tree.tsx @@ -8,7 +8,7 @@ import {useSnackbar} from "notistack"; import {getIconByName} from "../../icons/icons"; import {useServices} from "../../provider/service-provider"; import ServerConfig from "../../model/server-config"; -import {FileDownloadResponse} from "../../model/file-download"; +import {FileDownloadInfo} from "../../model/file-download"; const VALID_VIDEO_FILES = ['mkv', 'mp4', 'avi']; @@ -97,7 +97,7 @@ export default function PlaylistTree(props: PlaylistTreeProps) { url: item.header.url, filename: filename + '.' + ext }).pipe(first()).subscribe({ - next: (_: FileDownloadResponse) => {}, + next: (_: FileDownloadInfo) => {}, error: _ => enqueueSnackbar("Download failed!", {variant: 'error'}), complete: noop, }); diff --git a/frontend/src/model/file-download.ts b/frontend/src/model/file-download.ts index 2a59c4415..5b65c370f 100644 --- a/frontend/src/model/file-download.ts +++ b/frontend/src/model/file-download.ts @@ -3,10 +3,6 @@ export interface FileDownloadRequest { filename: string; } -export interface FileDownloadResponse { - success: boolean; -} - export interface DownloadErrorInfo { uuid: string; filename: string; diff --git a/frontend/src/service/file-service.ts b/frontend/src/service/file-service.ts index 44dd5fa8c..da176d815 100755 --- a/frontend/src/service/file-service.ts +++ b/frontend/src/service/file-service.ts @@ -1,12 +1,12 @@ import {PlaylistItem, PlaylistGroup} from "../model/playlist"; import FileSaver from "file-saver"; -import {Observer, Observable, Subject, Subscription, tap} from "rxjs"; +import {Observable, Subject, Subscription, tap} from "rxjs"; import FileApiService, {DefaultFileApiService} from "../api/file-api-service"; -import {FileDownloadInfo, FileDownloadRequest, FileDownloadResponse} from "../model/file-download"; +import {FileDownloadInfo, FileDownloadRequest} from "../model/file-download"; export default class FileService { - private downloadNotification = new Subject(); + private downloadNotification = new Subject(); constructor(private fileApiService: FileApiService = new DefaultFileApiService()) { } @@ -14,8 +14,8 @@ export default class FileService { return this.downloadNotification.subscribe(observer as any); } - private notifyDownload() { - this.downloadNotification.next(true); + private notifyDownload(info: FileDownloadInfo) { + this.downloadNotification.next(info); } save(playlist: PlaylistGroup[]) { @@ -30,8 +30,8 @@ export default class FileService { FileSaver.saveAs(blob, "playlist.m3u"); } - download(req: FileDownloadRequest): Observable { - return this.fileApiService.download(req).pipe(tap((result) => result?.success && this.notifyDownload() )); + download(req: FileDownloadRequest): Observable { + return this.fileApiService.download(req).pipe(tap((result) => this.notifyDownload(result) )); } getDownloadInfo(): Observable { diff --git a/src/api/api_model.rs b/src/api/api_model.rs index 672ca39ee..8468981ff 100644 --- a/src/api/api_model.rs +++ b/src/api/api_model.rs @@ -55,15 +55,25 @@ impl FileDownload { Ok(url) => { let filename_re = download_cfg._re_filename.as_ref().unwrap(); let tmp_filename = filename_re.replace_all(&unidecode(req_filename) - .replace(' ', "_"), "").to_string(); - let filname_path = Path::new(&tmp_filename); - let file_stem = filname_path.file_stem().and_then(OsStr::to_str).unwrap_or(""); - let file_ext = filname_path.extension().and_then(OsStr::to_str).unwrap_or(""); + .replace(' ', "_"), "") + .replace("__", "_") + .replace("_-_", "-").to_string(); + let filename_path = Path::new(&tmp_filename); + let file_stem = filename_path.file_stem().and_then(OsStr::to_str).unwrap_or("").trim_matches(FILENAME_TRIM_PATTERNS); + let file_ext = filename_path.extension().and_then(OsStr::to_str).unwrap_or(""); - let filename = format!("{}.{}", file_stem.trim_matches(FILENAME_TRIM_PATTERNS), file_ext); + let mut filename = format!("{}.{}", file_stem, file_ext); let file_dir = get_download_directory(download_cfg, file_stem); let mut file_path: PathBuf = file_dir.clone(); file_path.push(&filename); + let mut x: usize = 1; + while file_path.is_file() { + filename = format!("{}_{}.{}", file_stem, x, file_ext); + file_path = file_dir.clone(); + file_path.push(&filename); + x += 1; + } + file_path.to_str()?; Some(FileDownload { diff --git a/src/api/download_api.rs b/src/api/download_api.rs index 5deec5679..83b8351a3 100644 --- a/src/api/download_api.rs +++ b/src/api/download_api.rs @@ -4,7 +4,7 @@ use std::io::{ErrorKind, Write}; use std::ops::Deref; use std::sync::{Arc, RwLock}; use actix_web::{HttpResponse, web}; -use serde_json::{json}; +use serde_json::{json, Value}; use crate::api::api_model::{AppState, DownloadErrorInfo, DownloadQueue, FileDownload, FileDownloadRequest}; use crate::model::config::{VideoDownloadConfig}; use crate::utils::{bytes_to_megabytes, get_request_headers}; @@ -93,6 +93,12 @@ fn run_download_queue(download_cfg: &VideoDownloadConfig, download_queue: Arc { + HttpResponse::Ok().json(json!({"uuid": $file_download.uuid, "filename": $file_download.filename, "filesize": $file_download.size, "errors": $errors})) + } +} + pub(crate) async fn queue_download_file( req: web::Json, _app_state: web::Data, @@ -103,7 +109,7 @@ pub(crate) async fn queue_download_file( } match FileDownload::new(req.url.as_str(), req.filename.as_str(), download_cfg) { Some(file_download) => { - let file_name = file_download.filename.to_owned(); + let response = download_info_response!(file_download, Value::Null); _app_state.downloads.queue.lock().unwrap().push(file_download); if _app_state.downloads.active.read().unwrap().is_none() { match run_download_queue(download_cfg, Arc::clone(&_app_state.downloads)) { @@ -111,7 +117,7 @@ pub(crate) async fn queue_download_file( Err(err) => return HttpResponse::InternalServerError().json(json!({"error": err})), } } - HttpResponse::Ok().json(json!({"success": file_name})) + response } None => HttpResponse::BadRequest().json(json!({"error": "Invalid Arguments"})), } @@ -120,7 +126,6 @@ pub(crate) async fn queue_download_file( } } - pub(crate) async fn download_file_info( _app_state: web::Data, ) -> HttpResponse { @@ -133,6 +138,6 @@ pub(crate) async fn download_file_info( match &*_app_state.downloads.active.read().unwrap() { None => HttpResponse::Ok().json(json!({"finished": true, "errors": errors})), Some(file_download) => - HttpResponse::Ok().json(json!({"uuid": file_download.uuid, "filename": file_download.filename, "filesize": file_download.size, "errors": errors})) + download_info_response!(file_download, errors) } } \ No newline at end of file