gbclient/src-tauri/src/lib.rs

173 lines
5.3 KiB
Rust
Raw Normal View History

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use tauri::{Manager, Window};
use std::sync::Mutex;
use std::process::{Command, Stdio};
use std::path::Path;
use std::fs::{File, OpenOptions, create_dir_all};
use std::io::Write;
use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct RcloneConfig {
name: String,
remote_path: String,
local_path: String,
access_key: String,
secret_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SyncStatus {
name: String,
status: String,
transferred: String,
bytes: String,
errors: usize,
last_updated: String,
}
struct AppState {
sync_processes: Mutex<Vec<std::process::Child>>,
sync_active: Mutex<bool>,
}
#[tauri::command]
fn save_config(config: RcloneConfig) -> Result<(), 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 mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&config_path)
.map_err(|e| format!("Failed to open config file: {}", e))?;
writeln!(file, "[{}]", config.name)
.and_then(|_| writeln!(file, "type = s3"))
.and_then(|_| writeln!(file, "provider = Other"))
.and_then(|_| writeln!(file, "access_key_id = {}", config.access_key))
.and_then(|_| writeln!(file, "secret_access_key = {}", config.secret_key))
.and_then(|_| writeln!(file, "endpoint = https://drive-api.pragmatismo.com.br"))
.and_then(|_| writeln!(file, "acl = private"))
.map_err(|e| format!("Failed to write config: {}", e))
}
#[tauri::command]
fn start_sync(config: RcloneConfig, state: tauri::State<AppState>) -> Result<(), String> {
let local_path = Path::new(&config.local_path);
if !local_path.exists() {
create_dir_all(local_path).map_err(|e| format!("Failed to create local path: {}", e))?;
}
let child = Command::new("rclone")
.arg("sync")
.arg(&config.remote_path)
.arg(&config.local_path)
.arg("--no-check-certificate")
.arg("--verbose")
.arg("--rc")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|e| format!("Failed to start rclone: {}", e))?;
state.sync_processes.lock().unwrap().push(child);
*state.sync_active.lock().unwrap() = true;
Ok(())
}
#[tauri::command]
fn stop_sync(state: tauri::State<AppState>) -> Result<(), String> {
let mut processes = state.sync_processes.lock().unwrap();
for child in processes.iter_mut() {
child.kill().map_err(|e| format!("Failed to kill process: {}", e))?;
}
processes.clear();
*state.sync_active.lock().unwrap() = false;
Ok(())
}
#[tauri::command]
fn get_status(remote_name: String) -> Result<SyncStatus, String> {
let output = Command::new("rclone")
.arg("rc")
.arg("core/stats")
.arg("--json")
.output()
.map_err(|e| format!("Failed to execute rclone rc: {}", e))?;
if !output.status.success() {
return Err(format!("rclone rc failed: {}", String::from_utf8_lossy(&output.stderr)));
}
let json = String::from_utf8_lossy(&output.stdout);
let value: serde_json::Value = serde_json::from_str(&json)
.map_err(|e| format!("Failed to parse rclone status: {}", e))?;
let transferred = value.get("bytes").and_then(|v| v.as_u64()).unwrap_or(0);
let errors = value.get("errors").and_then(|v| v.as_u64()).unwrap_or(0);
let speed = value.get("speed").and_then(|v| v.as_f64()).unwrap_or(0.0);
let status = if errors > 0 {
"Error occurred".to_string()
} else if speed > 0.0 {
"Transferring".to_string()
} else if transferred > 0 {
"Completed".to_string()
} else {
"Initializing".to_string()
};
Ok(SyncStatus {
name: remote_name,
status,
transferred: format_bytes(transferred),
bytes: format!("{}/s", format_bytes(speed as u64)),
errors: errors as usize,
last_updated: chrono::Local::now().format("%H:%M:%S").to_string(),
})
}
fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
// 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,
start_sync,
stop_sync,
get_status])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}