From 498f4b27d074777d801d5b2ca5290ad3e1ca186d Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Sun, 7 Apr 2024 23:29:10 +0200 Subject: [PATCH] implement file move, fix #440 --- ffplayout-api/src/utils/files.rs | 171 ++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 58 deletions(-) diff --git a/ffplayout-api/src/utils/files.rs b/ffplayout-api/src/utils/files.rs index bf9963d0..0cdcdbe1 100644 --- a/ffplayout-api/src/utils/files.rs +++ b/ffplayout-api/src/utils/files.rs @@ -1,5 +1,4 @@ use std::{ - fs, io::Write, path::{Path, PathBuf}, }; @@ -12,6 +11,7 @@ use rand::{distributions::Alphanumeric, Rng}; use relative_path::RelativePath; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Sqlite}; +use tokio::fs; use simplelog::*; @@ -22,6 +22,7 @@ use ffplayout_lib::utils::{file_extension, MediaProbe}; pub struct PathObject { pub source: String, parent: Option, + parent_folders: Option>, folders: Option>, files: Option>, #[serde(default)] @@ -33,6 +34,7 @@ impl PathObject { Self { source, parent, + parent_folders: Some(vec![]), folders: Some(vec![]), files: Some(vec![]), folders_only: false, @@ -108,59 +110,98 @@ pub async fn browser( .split(',') .map(|e| e.to_string()) .collect::>(); + let mut parent_folders = vec![]; let mut extensions = config.storage.extensions; extensions.append(&mut channel_extensions); let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source); + + let parent_path = if !path_component.is_empty() { + path.parent().unwrap() + } else { + &config.storage.path + }; + let mut obj = PathObject::new(path_component, Some(parent)); obj.folders_only = path_obj.folders_only; - let mut paths: Vec = match fs::read_dir(path) { - Ok(p) => p.filter_map(|r| r.ok()).map(|p| p.path()).collect(), - Err(e) => { - error!("{e} in {}", path_obj.source); - return Err(ServiceError::NoContent(e.to_string())); - } - }; + if path != parent_path && !path_obj.folders_only { + let mut parents = fs::read_dir(&parent_path).await?; + + while let Some(child) = parents.next_entry().await? { + if child.metadata().await?.is_dir() { + parent_folders.push( + child + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ); + } + } + + parent_folders.path_sort(natural_lexical_cmp); + + obj.parent_folders = Some(parent_folders); + } + + let mut paths_obj = fs::read_dir(path).await?; - paths.path_sort(natural_lexical_cmp); let mut files = vec![]; let mut folders = vec![]; - for path in paths { + while let Some(child) = paths_obj.next_entry().await? { + let f_meta = child.metadata().await?; + // ignore hidden files/folders on unix - if path.display().to_string().contains("/.") { + if child.path().to_string_lossy().to_string().contains("/.") { continue; } - if path.is_dir() { - folders.push(path.file_name().unwrap().to_string_lossy().to_string()); - } else if path.is_file() && !path_obj.folders_only { - if let Some(ext) = file_extension(&path) { + if f_meta.is_dir() { + folders.push( + child + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ); + } else if f_meta.is_file() && !path_obj.folders_only { + if let Some(ext) = file_extension(&child.path()) { if extensions.contains(&ext.to_string().to_lowercase()) { - match MediaProbe::new(&path.display().to_string()) { - Ok(probe) => { - let mut duration = 0.0; - - if let Some(dur) = probe.format.duration { - duration = dur.parse().unwrap_or_default() - } - - let video = VideoFile { - name: path.file_name().unwrap().to_string_lossy().to_string(), - duration, - }; - files.push(video); - } - Err(e) => error!("{e:?}"), - }; + files.push(child.path()) } } } } + folders.path_sort(natural_lexical_cmp); + files.path_sort(natural_lexical_cmp); + let mut media_files = vec![]; + + for file in files { + match MediaProbe::new(&file.to_string_lossy().to_string()) { + Ok(probe) => { + let mut duration = 0.0; + + if let Some(dur) = probe.format.duration { + duration = dur.parse().unwrap_or_default() + } + + let video = VideoFile { + name: file.file_name().unwrap().to_string_lossy().to_string(), + duration, + }; + media_files.push(video); + } + Err(e) => error!("{e:?}"), + }; + } + obj.folders = Some(folders); - obj.files = Some(files); + obj.files = Some(media_files); Ok(obj) } @@ -173,36 +214,50 @@ pub async fn create_directory( let (config, _) = playout_config(conn, &id).await?; let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source); - if let Err(e) = fs::create_dir_all(&path) { + if let Err(e) = fs::create_dir_all(&path).await { return Err(ServiceError::BadRequest(e.to_string())); } - info!("create folder: {}", path.display()); + info!( + "create folder: {}", + path.to_string_lossy() + ); Ok(HttpResponse::Ok().into()) } -// fn copy_and_delete(source: &PathBuf, target: &PathBuf) -> Result { -// match fs::copy(&source, &target) { -// Ok(_) => { -// if let Err(e) = fs::remove_file(source) { -// error!("{e}"); -// return Err(ServiceError::BadRequest( -// "Removing File not possible!".into(), -// )); -// }; +async fn copy_and_delete(source: &PathBuf, target: &PathBuf) -> Result { + match fs::copy(&source, &target).await { + Ok(_) => { + if let Err(e) = fs::remove_file(source).await { + error!("{e}"); + return Err(ServiceError::BadRequest( + "Removing File not possible!".into(), + )); + }; -// return Ok(PathObject::new(target.display().to_string())); -// } -// Err(e) => { -// error!("{e}"); -// Err(ServiceError::BadRequest("Error in file copy!".into())) -// } -// } -// } + return Ok(MoveObject { + source: source + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + target: target + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + }); + } + Err(e) => { + error!("{e}"); + Err(ServiceError::BadRequest("Error in file copy!".into())) + } + } +} -fn rename(source: &PathBuf, target: &PathBuf) -> Result { - match fs::rename(source, target) { +async fn rename(source: &PathBuf, target: &PathBuf) -> Result { + match fs::rename(source, target).await { Ok(_) => Ok(MoveObject { source: source .file_name() @@ -217,7 +272,7 @@ fn rename(source: &PathBuf, target: &PathBuf) -> Result { error!("{e}"); - Err(ServiceError::BadRequest("Rename failed!".into())) + copy_and_delete(source, target).await } } } @@ -237,7 +292,7 @@ pub async fn rename_file( if (source_path.is_dir() || source_path.is_file()) && source_path.parent() == Some(&target_path) { - return rename(&source_path, &target_path); + return rename(&source_path, &target_path).await; } if target_path.is_dir() { @@ -251,7 +306,7 @@ pub async fn rename_file( } if source_path.is_file() && target_path.parent().is_some() { - return rename(&source_path, &target_path); + return rename(&source_path, &target_path).await; } Err(ServiceError::InternalServerError) @@ -270,7 +325,7 @@ pub async fn remove_file_or_folder( } if source.is_dir() { - match fs::remove_dir(source) { + match fs::remove_dir(source).await { Ok(_) => return Ok(()), Err(e) => { error!("{e}"); @@ -282,7 +337,7 @@ pub async fn remove_file_or_folder( } if source.is_file() { - match fs::remove_file(source) { + match fs::remove_file(source).await { Ok(_) => return Ok(()), Err(e) => { error!("{e}");