refactor: expose structs and commands in drive and sync modules

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-03-30 16:53:46 -03:00
parent 85a5e1d0de
commit 9c0afe87db
4 changed files with 123 additions and 148 deletions

View file

@ -1,119 +1,101 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tauri::{Manager, Window}; use tauri::{Emitter, Manager, Window};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct FileItem { pub struct FileItem {
name: String, name: String,
path: String, path: String,
is_dir: bool, is_dir: bool,
} }
impl Drive { #[tauri::command]
#[tauri::command] pub fn list_files(path: &str) -> Result<Vec<FileItem>, String> {
fn list_files(path: &str) -> Result<Vec<FileItem>, String> { let base_path = Path::new(path);
let base_path = Path::new(path); let mut files = Vec::new();
let mut files = Vec::new();
if !base_path.exists() { if !base_path.exists() {
return Err("Path does not exist".into()); return Err("Path does not exist".into());
} }
for entry in fs::read_dir(base_path).map_err(|e| e.to_string())? { for entry in fs::read_dir(base_path).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?; let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path(); let path = entry.path();
let name = path let name = path
.file_name() .file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.unwrap_or("") .unwrap_or("")
.to_string(); .to_string();
files.push(FileItem { files.push(FileItem {
name, name,
path: path.to_str().unwrap_or("").to_string(), path: path.to_str().unwrap_or("").to_string(),
is_dir: path.is_dir(), is_dir: path.is_dir(),
});
}
// Sort directories first, then files
files.sort_by(|a, b| {
if a.is_dir && !b.is_dir {
std::cmp::Ordering::Less
} else if !a.is_dir && b.is_dir {
std::cmp::Ordering::Greater
} else {
a.name.cmp(&b.name)
}
}); });
Ok(files)
} }
#[tauri::command] // Sort directories first, then files
async fn upload_file( files.sort_by(|a, b| {
window: Window, if a.is_dir && !b.is_dir {
src_path: String, std::cmp::Ordering::Less
dest_path: String, } else if !a.is_dir && b.is_dir {
) -> Result<(), String> { std::cmp::Ordering::Greater
use std::fs::File; } else {
use std::io::{Read, Write}; a.name.cmp(&b.name)
use tauri::api::path::home_dir;
let src = PathBuf::from(&src_path);
let dest_dir = PathBuf::from(&dest_path);
let dest = dest_dir.join(src.file_name().ok_or("Invalid source file")?);
// Create destination directory if it doesn't exist
if !dest_dir.exists() {
fs::create_dir_all(&dest_dir).map_err(|e| e.to_string())?;
} }
});
let mut source_file = File::open(&src).map_err(|e| e.to_string())?; Ok(files)
let mut dest_file = File::create(&dest).map_err(|e| e.to_string())?; }
let file_size = source_file.metadata().map_err(|e| e.to_string())?.len(); #[tauri::command]
let mut buffer = [0; 8192]; pub async fn upload_file(window: Window, src_path: String, dest_path: String) -> Result<(), String> {
let mut total_read = 0; use std::fs::File;
use std::io::{Read, Write};
loop {
let bytes_read = source_file.read(&mut buffer).map_err(|e| e.to_string())?; let src = PathBuf::from(&src_path);
if bytes_read == 0 { let dest_dir = PathBuf::from(&dest_path);
break; let dest = dest_dir.join(src.file_name().ok_or("Invalid source file")?);
}
// Create destination directory if it doesn't exist
dest_file if !dest_dir.exists() {
.write_all(&buffer[..bytes_read]) fs::create_dir_all(&dest_dir).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?; }
total_read += bytes_read as u64;
let mut source_file = File::open(&src).map_err(|e| e.to_string())?;
let progress = (total_read as f64 / file_size as f64) * 100.0; let mut dest_file = File::create(&dest).map_err(|e| e.to_string())?;
window
.emit("upload_progress", progress) let file_size = source_file.metadata().map_err(|e| e.to_string())?.len();
.map_err(|e| e.to_string())?; let mut buffer = [0; 8192];
} let mut total_read = 0;
Ok(()) loop {
} let bytes_read = source_file.read(&mut buffer).map_err(|e| e.to_string())?;
if bytes_read == 0 {
#[tauri::command] break;
fn create_folder(path: String, name: String) -> Result<(), String> { }
let full_path = Path::new(&path).join(&name);
if full_path.exists() { dest_file
return Err("Folder already exists".into()); .write_all(&buffer[..bytes_read])
} .map_err(|e| e.to_string())?;
fs::create_dir(full_path).map_err(|e| e.to_string())?; total_read += bytes_read as u64;
Ok(())
} let progress = (total_read as f64 / file_size as f64) * 100.0;
window
fn main() { .emit("upload_progress", progress)
tauri::Builder::default() .map_err(|e| e.to_string())?;
.invoke_handler(tauri::generate_handler![ }
list_files,
upload_file, Ok(())
create_folder }
])
.run(tauri::generate_context!()) #[tauri::command]
.expect("error while running tauri application"); pub fn create_folder(path: String, name: String) -> Result<(), String> {
} let full_path = Path::new(&path).join(&name);
if full_path.exists() {
return Err("Folder already exists".into());
}
fs::create_dir(full_path).map_err(|e| e.to_string())?;
Ok(())
} }

View file

@ -1,39 +1,4 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use std::env;
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Mutex;
use tauri::{Manager, Window};
pub mod drive; pub mod drive;
use drive::Drive::; pub mod sync;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(AppState {
sync_processes: Mutex::new(Vec::new()),
sync_active: Mutex::new(false),
})
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
save_config,
list_files,
start_sync,
stop_sync,
get_status
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -2,7 +2,35 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
pub mod drive; pub mod drive;
pub mod sync;
fn main() { use sync::AppState;
my_tauri_app_lib::run() use std::sync::Mutex;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
} }
#[cfg_attr(mobile, tauri::mobile_entry_point)]
fn main() {
tauri::Builder::default()
.manage(AppState {
sync_processes: Mutex::new(Vec::new()),
sync_active: Mutex::new(false),
})
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
sync::save_config,
drive::list_files,
drive::upload_file,
drive::create_folder,
sync::start_sync,
sync::stop_sync,
sync::get_status
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -9,7 +9,7 @@ use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct RcloneConfig { pub struct RcloneConfig {
name: String, name: String,
remote_path: String, remote_path: String,
local_path: String, local_path: String,
@ -18,7 +18,7 @@ struct RcloneConfig {
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct SyncStatus { pub struct SyncStatus {
name: String, name: String,
status: String, status: String,
transferred: String, transferred: String,
@ -27,13 +27,13 @@ struct SyncStatus {
last_updated: String, last_updated: String,
} }
struct AppState { pub(crate) struct AppState {
sync_processes: Mutex<Vec<std::process::Child>>, pub sync_processes: Mutex<Vec<std::process::Child>>,
sync_active: Mutex<bool>, pub sync_active: Mutex<bool>,
} }
#[tauri::command] #[tauri::command]
fn save_config(config: RcloneConfig) -> Result<(), String> { pub fn save_config(config: RcloneConfig) -> Result<(), String> {
let home_dir = env::var("HOME").map_err(|_| "HOME environment variable not set".to_string())?; let home_dir = env::var("HOME").map_err(|_| "HOME environment variable not set".to_string())?;
let config_path = Path::new(&home_dir).join(".config/rclone/rclone.conf"); let config_path = Path::new(&home_dir).join(".config/rclone/rclone.conf");
@ -54,7 +54,7 @@ fn save_config(config: RcloneConfig) -> Result<(), String> {
} }
#[tauri::command] #[tauri::command]
fn start_sync(config: RcloneConfig, state: tauri::State<AppState>) -> Result<(), String> { pub fn start_sync(config: RcloneConfig, state: tauri::State<AppState>) -> Result<(), String> {
let local_path = Path::new(&config.local_path); let local_path = Path::new(&config.local_path);
if !local_path.exists() { if !local_path.exists() {
create_dir_all(local_path).map_err(|e| format!("Failed to create local path: {}", e))?; create_dir_all(local_path).map_err(|e| format!("Failed to create local path: {}", e))?;
@ -78,7 +78,7 @@ fn start_sync(config: RcloneConfig, state: tauri::State<AppState>) -> Result<(),
} }
#[tauri::command] #[tauri::command]
fn stop_sync(state: tauri::State<AppState>) -> Result<(), String> { pub fn stop_sync(state: tauri::State<AppState>) -> Result<(), String> {
let mut processes = state.sync_processes.lock().unwrap(); let mut processes = state.sync_processes.lock().unwrap();
for child in processes.iter_mut() { for child in processes.iter_mut() {
child.kill().map_err(|e| format!("Failed to kill process: {}", e))?; child.kill().map_err(|e| format!("Failed to kill process: {}", e))?;
@ -89,7 +89,7 @@ fn stop_sync(state: tauri::State<AppState>) -> Result<(), String> {
} }
#[tauri::command] #[tauri::command]
fn get_status(remote_name: String) -> Result<SyncStatus, String> { pub fn get_status(remote_name: String) -> Result<SyncStatus, String> {
let output = Command::new("rclone") let output = Command::new("rclone")
.arg("rc") .arg("rc")
.arg("core/stats") .arg("core/stats")
@ -129,7 +129,7 @@ fn get_status(remote_name: String) -> Result<SyncStatus, String> {
}) })
} }
fn format_bytes(bytes: u64) -> String { pub fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024; const KB: u64 = 1024;
const MB: u64 = KB * 1024; const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024; const GB: u64 = MB * 1024;