// WARNING: These APIs will be deprecated soon due to removing FS API access from frontend. // It's added to ensure the legacy implementation from frontend still functions before removal. use super::helpers::resolve_path; use super::models::FileStat; use std::fs; use tauri::Runtime; #[tauri::command] pub fn rm(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { if args.is_empty() || args[0].is_empty() { return Err("rm error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); if path.is_file() { fs::remove_file(&path).map_err(|e| e.to_string())?; } else if path.is_dir() { fs::remove_dir_all(&path).map_err(|e| e.to_string())?; } else { return Err("rm error: Path does not exist".to_string()); } Ok(()) } #[tauri::command] pub fn mkdir(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { if args.is_empty() || args[0].is_empty() { return Err("mkdir error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); fs::create_dir_all(&path).map_err(|e| e.to_string()) } #[tauri::command] pub fn mv(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { if args.len() < 2 || args[0].is_empty() || args[1].is_empty() { return Err("mv error: Invalid argument - source and destination required".to_string()); } let source = resolve_path(app_handle.clone(), &args[0]); let destination = resolve_path(app_handle, &args[1]); if !source.exists() { return Err("mv error: Source path does not exist".to_string()); } fs::rename(&source, &destination).map_err(|e| e.to_string()) } #[tauri::command] pub fn join_path( app_handle: tauri::AppHandle, args: Vec, ) -> Result { if args.is_empty() { return Err("join_path error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); let joined_path = args[1..].iter().fold(path, |acc, part| acc.join(part)); Ok(joined_path.to_string_lossy().to_string()) } #[tauri::command] pub fn exists_sync( app_handle: tauri::AppHandle, args: Vec, ) -> Result { if args.is_empty() || args[0].is_empty() { return Err("exist_sync error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); Ok(path.exists()) } #[tauri::command] pub fn file_stat( app_handle: tauri::AppHandle, args: String, ) -> Result { if args.is_empty() { return Err("file_stat error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args); let metadata = fs::metadata(&path).map_err(|e| e.to_string())?; let is_directory = metadata.is_dir(); let size = if is_directory { 0 } else { metadata.len() }; let file_stat = FileStat { is_directory, size }; Ok(file_stat) } #[tauri::command] pub fn read_file_sync( app_handle: tauri::AppHandle, args: Vec, ) -> Result { if args.is_empty() || args[0].is_empty() { return Err("read_file_sync error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); fs::read_to_string(&path).map_err(|e| e.to_string()) } #[tauri::command] pub fn write_file_sync( app_handle: tauri::AppHandle, args: Vec, ) -> Result<(), String> { if args.len() < 2 || args[0].is_empty() || args[1].is_empty() { return Err("write_file_sync error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); let content = &args[1]; fs::write(&path, content).map_err(|e| e.to_string()) } #[tauri::command] pub fn readdir_sync( app_handle: tauri::AppHandle, args: Vec, ) -> Result, String> { if args.is_empty() || args[0].is_empty() { return Err("read_dir_sync error: Invalid argument".to_string()); } let path = resolve_path(app_handle, &args[0]); let entries = fs::read_dir(&path).map_err(|e| e.to_string())?; let paths: Vec = entries .filter_map(|entry| entry.ok()) .map(|entry| entry.path().to_string_lossy().to_string()) .collect(); Ok(paths) } #[tauri::command] pub fn write_yaml( app: tauri::AppHandle, data: serde_json::Value, save_path: &str, ) -> Result<(), String> { // TODO: have an internal function to check scope let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone()); let save_path = jan_utils::normalize_path(&jan_data_folder.join(save_path)); if !save_path.starts_with(&jan_data_folder) { return Err(format!( "Error: save path {} is not under jan_data_folder {}", save_path.to_string_lossy(), jan_data_folder.to_string_lossy(), )); } let file = fs::File::create(&save_path).map_err(|e| e.to_string())?; let mut writer = std::io::BufWriter::new(file); serde_yaml::to_writer(&mut writer, &data).map_err(|e| e.to_string())?; Ok(()) } #[tauri::command] pub fn read_yaml(app: tauri::AppHandle, path: &str) -> Result { let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone()); let path = jan_utils::normalize_path(&jan_data_folder.join(path)); if !path.starts_with(&jan_data_folder) { return Err(format!( "Error: path {} is not under jan_data_folder {}", path.to_string_lossy(), jan_data_folder.to_string_lossy(), )); } let file = fs::File::open(&path).map_err(|e| e.to_string())?; let reader = std::io::BufReader::new(file); let data: serde_json::Value = serde_yaml::from_reader(reader).map_err(|e| e.to_string())?; Ok(data) } #[tauri::command] pub fn decompress(app: tauri::AppHandle, path: &str, output_dir: &str) -> Result<(), String> { let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone()); let path_buf = jan_utils::normalize_path(&jan_data_folder.join(path)); let output_dir_buf = jan_utils::normalize_path(&jan_data_folder.join(output_dir)); if !output_dir_buf.starts_with(&jan_data_folder) { return Err(format!( "Error: output directory {} is not under jan_data_folder {}", output_dir_buf.to_string_lossy(), jan_data_folder.to_string_lossy(), )); } // Ensure output directory exists fs::create_dir_all(&output_dir_buf).map_err(|e| { format!( "Failed to create output directory {}: {}", output_dir_buf.to_string_lossy(), e ) })?; // Use short path on Windows to handle paths with spaces #[cfg(windows)] let file = { if let Some(short_path) = jan_utils::path::get_short_path(&path_buf) { fs::File::open(&short_path).map_err(|e| e.to_string())? } else { fs::File::open(&path_buf).map_err(|e| e.to_string())? } }; #[cfg(not(windows))] let file = fs::File::open(&path_buf).map_err(|e| e.to_string())?; if path.ends_with(".tar.gz") { let tar = flate2::read::GzDecoder::new(file); let mut archive = tar::Archive::new(tar); archive.unpack(&output_dir_buf).map_err(|e| e.to_string())?; } else if path.ends_with(".zip") { let mut zip = zip::ZipArchive::new(file).map_err(|e| e.to_string())?; for i in 0..zip.len() { let mut entry = zip.by_index(i).map_err(|e| e.to_string())?; let outpath = output_dir_buf.join( entry .enclosed_name() .ok_or_else(|| "Invalid zip entry path".to_string())?, ); if entry.name().ends_with('/') { std::fs::create_dir_all(&outpath).map_err(|e| e.to_string())?; } else { if let Some(parent) = outpath.parent() { std::fs::create_dir_all(parent).map_err(|e| e.to_string())?; } let mut outfile = std::fs::File::create(&outpath).map_err(|e| e.to_string())?; std::io::copy(&mut entry, &mut outfile).map_err(|e| e.to_string())?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = entry.unix_mode() { let _ = std::fs::set_permissions( &outpath, std::fs::Permissions::from_mode(mode), ); } } } } } else { return Err("Unsupported file format. Only .tar.gz and .zip are supported.".to_string()); } Ok(()) }