Vanalite 5e57caee43 Merge remote-tracking branch 'origin/dev' into mobile/dev
# Conflicts:
#	extensions/yarn.lock
#	package.json
#	src-tauri/plugins/tauri-plugin-hardware/src/vendor/vulkan.rs
#	src-tauri/src/lib.rs
#	yarn.lock
2025-09-29 22:22:00 +07:00

255 lines
8.8 KiB
Rust

// 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<R: Runtime>(app_handle: tauri::AppHandle<R>, args: Vec<String>) -> 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<R: Runtime>(app_handle: tauri::AppHandle<R>, args: Vec<String>) -> 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<R: Runtime>(app_handle: tauri::AppHandle<R>, args: Vec<String>) -> 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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: Vec<String>,
) -> Result<String, String> {
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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: Vec<String>,
) -> Result<bool, String> {
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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: String,
) -> Result<FileStat, String> {
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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: Vec<String>,
) -> Result<String, String> {
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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: Vec<String>,
) -> 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<R: Runtime>(
app_handle: tauri::AppHandle<R>,
args: Vec<String>,
) -> Result<Vec<String>, 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<String> = 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<impl Runtime>,
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<R: Runtime>(app: tauri::AppHandle<R>, path: &str) -> Result<serde_json::Value, String> {
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<R: Runtime>(app: tauri::AppHandle<R>, 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(())
}