From 0a263b26dc1b48141f2a9ae0f009bc69f80597d7 Mon Sep 17 00:00:00 2001 From: 0xb-s <145866191+0xb-s@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:32:35 -0800 Subject: [PATCH 1/3] Update ui.rs --- src/ui.rs | 692 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 491 insertions(+), 201 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 8be19bf..3b073fe 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,27 +1,295 @@ use crate::ssh::SSHConnection; use eframe::egui; use serde::{Deserialize, Serialize}; -use std::path::Path; +use std::{ + path::Path, + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread, +}; +/// The file where connections are stored const CONNECTIONS_FILE: &str = "saved_connections.json"; +/// Represents a saved SSH connection configuration +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SSHConnectionData { + /// The hostname/IP address of the SSH server + pub hostname: String, + /// The username to authenticate with + pub username: String, + /// The port number of the SSH server + pub port: u16, +} + +/// Load saved SSH connections from a JSON file +fn load_saved_connections() -> Vec { + if Path::new(CONNECTIONS_FILE).exists() { + let content = std::fs::read_to_string(CONNECTIONS_FILE).unwrap_or_default(); + serde_json::from_str(&content).unwrap_or_default() + } else { + Vec::new() + } +} + +/// Save SSH connections to a JSON file +fn save_connections(connections: &Vec) { + let content = serde_json::to_string(connections).unwrap(); + std::fs::write(CONNECTIONS_FILE, content).unwrap(); +} + +/// Represents tasks that can be performed on the SSH connection. +enum Task { + /// Connect to the SSH server (hostname, username, password, port) + Connect(String, String, String, u16), + /// List the directory contents of the given path + ListDirectory(String), + /// Create a directory at the specified path + CreateDirectory(String), + /// Create an empty file at the specified path + CreateFile(String), + /// Download a file from remote to local + DownloadFile(String, String), + /// Upload a file from local to remote + UploadFile(String, String), + /// Delete a file + DeleteFile(String), + /// Rename a file (old_path, new_path) + RenameFile(String, String), + /// Read a file from the remote server + ReadFile(String), + /// Write file content to the remote server + WriteFile(String, String), + /// Disconnect the active connection + Disconnect, +} + +/// Represents the result of executing a Task. +/// The UI thread will receive these results and update the UI state accordingly. +enum TaskResult { + /// The result of the connect attempt + ConnectResult(Result<(), String>), + /// The result of listing a directory (Vec<(filename, is_dir)> or error) + ListDirectoryResult(Result, String>), + /// Generic success message for directory creation + CreateDirectoryResult(Result<(), String>), + /// Generic success message for file creation + CreateFileResult(Result<(), String>), + /// Generic success message for file download + DownloadFileResult(Result<(), String>), + /// Generic success message for file upload + UploadFileResult(Result<(), String>), + /// Generic success message for file deletion + DeleteFileResult(Result<(), String>), + /// Generic success message for file renaming + RenameFileResult(Result<(), String>), + /// The result of reading a file + ReadFileResult(Result), + /// The result of writing a file + WriteFileResult(Result<(), String>), + /// The result of disconnecting + DisconnectResult, +} + +/// BackgroundWorker handles asynchronous tasks to avoid blocking the UI. +/// Communicates with the UI via channels. +struct BackgroundWorker { + /// Sender to send tasks from the UI thread to the worker thread + task_sender: Sender, + /// Receiver on the UI side to receive the results from the worker thread + result_receiver: Receiver, + /// Holds the active SSH connection if connected + #[allow(dead_code)] + connection: Option, +} + +impl BackgroundWorker { + /// Create a new BackgroundWorker and start the worker thread + fn new() -> Self { + let (task_sender, task_receiver) = mpsc::channel(); + let (result_sender, result_receiver) = mpsc::channel(); + + // Spawn the worker thread + thread::spawn(move || { + let mut connection: Option = None; + while let Ok(task) = task_receiver.recv() { + match task { + Task::Connect(hostname, username, password, port) => { + let mut conn = SSHConnection::new(&hostname, &username, &password, port); + let connect_result = conn.connect(); + + let send_result = match connect_result { + Ok(_) => { + connection = Some(conn); + Ok(()) + } + Err(e) => Err(format!("Failed to connect: {}", e)), + }; + + let _ = result_sender.send(TaskResult::ConnectResult(send_result)); + } + + Task::ListDirectory(path) => { + if let Some(conn) = connection.as_ref() { + let result = conn.list_directory(&path).map_err(|e| e); + let _ = result_sender.send(TaskResult::ListDirectoryResult(result)); + } else { + let _ = result_sender + .send(TaskResult::ListDirectoryResult(Err("Not connected".into()))); + } + } + Task::CreateDirectory(path) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .create_directory(&path) + .map_err(|e| format!("Failed to create directory: {}", e)); + let _ = result_sender.send(TaskResult::CreateDirectoryResult(result)); + } else { + let _ = result_sender.send(TaskResult::CreateDirectoryResult(Err( + "Not connected".into(), + ))); + } + } + Task::CreateFile(path) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .create_file(&path) + .map_err(|e| format!("Failed to create file: {}", e)); + let _ = result_sender.send(TaskResult::CreateFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::CreateFileResult(Err("Not connected".into()))); + } + } + Task::DownloadFile(remote, local) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .download_file(&remote, &local) + .map_err(|e| format!("Failed to download: {}", e)); + let _ = result_sender.send(TaskResult::DownloadFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::DownloadFileResult(Err("Not connected".into()))); + } + } + Task::UploadFile(local, remote) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .upload_file(&local, &remote) + .map_err(|e| format!("Failed to upload: {}", e)); + let _ = result_sender.send(TaskResult::UploadFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::UploadFileResult(Err("Not connected".into()))); + } + } + Task::DeleteFile(path) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .delete_file(&path) + .map_err(|e| format!("Failed to delete: {}", e)); + let _ = result_sender.send(TaskResult::DeleteFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::DeleteFileResult(Err("Not connected".into()))); + } + } + Task::RenameFile(old, new) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .rename(&old, &new) + .map_err(|e| format!("Failed to rename: {}", e)); + let _ = result_sender.send(TaskResult::RenameFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::RenameFileResult(Err("Not connected".into()))); + } + } + Task::ReadFile(path) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .read_file(&path) + .map_err(|e| format!("Failed to read file: {}", e)); + let _ = result_sender.send(TaskResult::ReadFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::ReadFileResult(Err("Not connected".into()))); + } + } + Task::WriteFile(path, content) => { + if let Some(conn) = connection.as_ref() { + let result = conn + .write_file(&path, &content) + .map_err(|e| format!("Failed to write file: {}", e)); + let _ = result_sender.send(TaskResult::WriteFileResult(result)); + } else { + let _ = result_sender + .send(TaskResult::WriteFileResult(Err("Not connected".into()))); + } + } + Task::Disconnect => { + if let Some(mut conn) = connection.take() { + conn.disconnect(); + } + let _ = result_sender.send(TaskResult::DisconnectResult); + } + } + } + }); + + Self { + task_sender, + result_receiver, + connection: None, + } + } + + /// Send a task to the worker thread + fn send_task(&self, task: Task) { + let _ = self.task_sender.send(task); + } +} + +/// Represents the UI state pub struct UIState { + /// The SSH hostname pub hostname: String, + /// The SSH username pub username: String, + /// The SSH password pub password: String, + /// The SSH port pub port: u16, + /// Whether currently connected or not pub connected: bool, + /// The current remote directory path pub current_path: String, + /// List of files in the current directory pub files: Vec<(String, bool)>, + /// Any error or status message to display pub error_message: Option, + /// Whether dark mode is enabled pub dark_mode: bool, + /// A list of saved connections pub saved_connections: Vec, + /// If we are editing a file, store its remote path pub editing_file: Option, + /// The content of the file currently being edited pub file_content: String, + /// If we are renaming a file, store its name pub renaming_file: Option, + /// The new name for the file/directory being renamed pub new_name: String, + /// The name for new directories pub new_directory_name: String, + /// The name for new files pub new_file_name: String, + /// The background worker to run tasks asynchronously + worker: Arc>, + /// Shows if an operation is in progress to provide feedback to the user + pub operation_in_progress: bool, } impl Default for UIState { @@ -43,16 +311,19 @@ impl Default for UIState { new_name: String::new(), new_directory_name: String::new(), new_file_name: String::new(), + worker: Arc::new(Mutex::new(BackgroundWorker::new())), + operation_in_progress: false, } } } -pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option) { - // Apply the selected theme +/// Render the UI and handle events +pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, _connection: &mut Option) { let ctx = ui.ctx(); apply_theme(ctx, state.dark_mode); - // Theme Toggle + poll_worker(state); + ui.horizontal(|ui| { ui.label("Theme:"); if ui @@ -67,6 +338,10 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option } }); + if state.operation_in_progress { + ui.label("Operation in progress..."); + } + if !state.connected { ui.heading("Connect to SSH Server"); @@ -125,24 +400,16 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option } if ui.button("Connect").clicked() { - let mut ssh_conn = SSHConnection::new( - &state.hostname, - &state.username, - &state.password, - state.port, - ); - match ssh_conn.connect() { - Ok(()) => { - state.connected = true; - state.current_path = "/".to_string(); - match ssh_conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - *connection = Some(ssh_conn); - } - Err(e) => state.error_message = Some(format!("Failed to connect: {}", e)), - } + state.operation_in_progress = true; + let worker = state.worker.clone(); + let hostname = state.hostname.clone(); + let username = state.username.clone(); + let password = state.password.clone(); + let port = state.port; + worker + .lock() + .unwrap() + .send_task(Task::Connect(hostname, username, password, port)); } if let Some(error) = &state.error_message { @@ -158,12 +425,10 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option .lost_focus() && ui.input(|state| state.key_pressed(egui::Key::Enter)) { - if let Some(conn) = connection { - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } + state.operation_in_progress = true; + let worker = state.worker.clone(); + let path = state.current_path.clone(); + worker.lock().unwrap().send_task(Task::ListDirectory(path)); } }); @@ -172,26 +437,14 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option ui.text_edit_singleline(&mut state.new_directory_name); if ui.button("Create").clicked() { if !state.new_directory_name.is_empty() { - if let Some(conn) = connection { - let full_path = - format!("{}/{}", state.current_path, state.new_directory_name); - match conn.create_directory(&full_path) { - Ok(_) => { - state.error_message = - Some("Directory created successfully.".to_string()); - state.new_directory_name.clear(); // Clear the input field && - // Refresh directory listing - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } - Err(e) => { - state.error_message = - Some(format!("Failed to create directory: {}", e)); - } - } - } + let full_path = format!("{}/{}", state.current_path, state.new_directory_name); + state.operation_in_progress = true; + state.new_directory_name.clear(); + let worker = state.worker.clone(); + worker + .lock() + .unwrap() + .send_task(Task::CreateDirectory(full_path)); } else { state.error_message = Some("Directory name cannot be empty.".to_string()); } @@ -203,24 +456,14 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option ui.text_edit_singleline(&mut state.new_file_name); if ui.button("Create").clicked() { if !state.new_file_name.is_empty() { - if let Some(conn) = connection { - let full_path = format!("{}/{}", state.current_path, state.new_file_name); - match conn.create_file(&full_path) { - Ok(_) => { - state.error_message = - Some("File created successfully.".to_string()); - state.new_file_name.clear(); - - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } - Err(e) => { - state.error_message = Some(format!("Failed to create file: {}", e)); - } - } - } + let full_path = format!("{}/{}", state.current_path, state.new_file_name); + state.operation_in_progress = true; + state.new_file_name.clear(); + let worker = state.worker.clone(); + worker + .lock() + .unwrap() + .send_task(Task::CreateFile(full_path)); } else { state.error_message = Some("File name cannot be empty.".to_string()); } @@ -234,30 +477,23 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option if state.current_path.is_empty() { state.current_path = "/".to_string(); } - if let Some(conn) = connection { - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } + state.operation_in_progress = true; + let worker = state.worker.clone(); + let path = state.current_path.clone(); + worker.lock().unwrap().send_task(Task::ListDirectory(path)); } } if ui.button("Home").clicked() { state.current_path = "/".to_string(); - if let Some(conn) = connection { - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } + state.operation_in_progress = true; + let worker = state.worker.clone(); + let path = state.current_path.clone(); + worker.lock().unwrap().send_task(Task::ListDirectory(path)); } if ui.button("Disconnect").clicked() { - state.connected = false; - if let Some(mut conn) = connection.take() { - conn.disconnect(); - } - state.files.clear(); - state.current_path = "/".to_string(); + state.operation_in_progress = true; + let worker = state.worker.clone(); + worker.lock().unwrap().send_task(Task::Disconnect); } }); @@ -268,23 +504,16 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option if renaming_file == &name { ui.text_edit_singleline(&mut state.new_name); if ui.button("Save").clicked() { - if let Some(conn) = connection { - let old_path = format!("{}/{}", state.current_path, name); - let new_path = - format!("{}/{}", state.current_path, state.new_name); - match conn.rename(&old_path, &new_path) { - Ok(_) => { - state.renaming_file = None; - state.new_name.clear(); - - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } - Err(e) => state.error_message = Some(e), - } - } + let old_path = format!("{}/{}", state.current_path, name); + let new_path = format!("{}/{}", state.current_path, state.new_name); + state.operation_in_progress = true; + state.renaming_file = None; + state.new_name.clear(); + let worker = state.worker.clone(); + worker + .lock() + .unwrap() + .send_task(Task::RenameFile(old_path, new_path)); } if ui.button("Cancel").clicked() { state.renaming_file = None; @@ -299,74 +528,48 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option state.current_path.trim_end_matches('/'), name ); - if let Some(conn) = connection { - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } + state.operation_in_progress = true; + let worker = state.worker.clone(); + let path = state.current_path.clone(); + worker.lock().unwrap().send_task(Task::ListDirectory(path)); } } else { ui.label(format!("📄 {}", name)); } if !is_dir && ui.button("Download").clicked() { - if let Some(conn) = connection { - if let Some(local_path) = rfd::FileDialog::new() - .set_file_name(name.clone()) - .save_file() - { - match conn.download_file( - &format!("{}/{}", state.current_path, name), - local_path.to_str().unwrap(), - ) { - Ok(_) => { - state.error_message = - Some("Download successful".to_string()); - } - Err(e) => { - state.error_message = - Some(format!("Failed to download: {}", e)); - } - }; - } + if let Some(local_path) = rfd::FileDialog::new() + .set_file_name(name.clone()) + .save_file() + { + let remote_path = format!("{}/{}", state.current_path, name); + let worker = state.worker.clone(); + state.operation_in_progress = true; + worker.lock().unwrap().send_task(Task::DownloadFile( + remote_path, + local_path.to_str().unwrap().to_string(), + )); } } if ui.button("Delete").clicked() { - if let Some(conn) = connection { - let remote_path = format!("{}/{}", state.current_path, name); - match conn.delete_file(&remote_path) { - Ok(_) => { - state.error_message = - Some("File deleted successfully.".to_string()); - // Refresh directory listing - match conn.list_directory(&state.current_path) { - Ok(files) => state.files = files, - Err(e) => state.error_message = Some(e), - } - } - Err(e) => { - state.error_message = - Some(format!("Failed to delete: {}", e)) - } - } - } + let remote_path = format!("{}/{}", state.current_path, name); + let worker = state.worker.clone(); + state.operation_in_progress = true; + worker + .lock() + .unwrap() + .send_task(Task::DeleteFile(remote_path)); } + if !is_dir && ui.button("Modify").clicked() { - if let Some(conn) = connection { - let remote_path = format!("{}/{}", state.current_path, name); - match conn.read_file(&remote_path) { - Ok(content) => { - state.editing_file = Some(remote_path); - state.file_content = content; - } - Err(e) => { - state.error_message = - Some(format!("Failed to read file: {}", e)); - } - } - } + let remote_path = format!("{}/{}", state.current_path, name); + let worker = state.worker.clone(); + state.operation_in_progress = true; + worker + .lock() + .unwrap() + .send_task(Task::ReadFile(remote_path)); } if ui.button("Rename").clicked() { @@ -389,19 +592,14 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option ui.horizontal(|ui| { if ui.button("Save").clicked() { - if let Some(conn) = connection { - match conn.write_file(&editing_file_clone, &state.file_content) { - Ok(_) => { - state.error_message = - Some("File saved successfully.".to_string()); - state.editing_file = None; - } - Err(e) => { - state.error_message = - Some(format!("Failed to save file: {}", e)) - } - } - } + let worker = state.worker.clone(); + state.operation_in_progress = true; + let path = editing_file_clone.clone(); + let content = state.file_content.clone(); + worker + .lock() + .unwrap() + .send_task(Task::WriteFile(path, content)); } if ui.button("Cancel").clicked() { state.editing_file = None; @@ -411,18 +609,18 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option } if ui.button("Upload File").clicked() { - if let Some(conn) = connection { - if let Some(local_path) = rfd::FileDialog::new().pick_file() { - let remote_path = format!( - "{}/{}", - state.current_path, - local_path.file_name().unwrap().to_str().unwrap() - ); - match conn.upload_file(local_path.to_str().unwrap(), &remote_path) { - Ok(_) => state.error_message = Some("Upload successful".to_string()), - Err(e) => state.error_message = Some(format!("Failed to upload: {}", e)), - }; - }; + if let Some(local_path) = rfd::FileDialog::new().pick_file() { + let remote_path = format!( + "{}/{}", + state.current_path, + local_path.file_name().unwrap().to_str().unwrap() + ); + let worker = state.worker.clone(); + state.operation_in_progress = true; + worker.lock().unwrap().send_task(Task::UploadFile( + local_path.to_str().unwrap().to_string(), + remote_path, + )); } } @@ -432,34 +630,126 @@ pub fn render_ui(ui: &mut egui::Ui, state: &mut UIState, connection: &mut Option } } +/// Apply the chosen theme (dark or light mode) fn apply_theme(ctx: &egui::Context, dark_mode: bool) { let mut style = (*ctx.style()).clone(); - if dark_mode { style.visuals = egui::Visuals::dark(); } else { style.visuals = egui::Visuals::light(); } - ctx.set_style(style); } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct SSHConnectionData { - pub hostname: String, - pub username: String, - pub port: u16, -} -fn load_saved_connections() -> Vec { - if Path::new(CONNECTIONS_FILE).exists() { - let content = std::fs::read_to_string(CONNECTIONS_FILE).unwrap_or_default(); - serde_json::from_str(&content).unwrap_or_default() - } else { - Vec::new() +/// Poll the background worker for results and update the UI state accordingly +fn poll_worker(state: &mut UIState) { + let worker = state.worker.clone(); + let worker = worker.lock().unwrap(); + while let Ok(result) = worker.result_receiver.try_recv() { + state.operation_in_progress = false; + match result { + TaskResult::ConnectResult(res) => { + match res { + Ok(_) => { + state.connected = true; + state.current_path = "/".to_string(); + // Once connected, immediately list the directory + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => { + state.error_message = Some(e); + state.connected = false; + } + } + } + TaskResult::ListDirectoryResult(res) => match res { + Ok(files) => { + state.files = files; + state.error_message = None; + } + Err(e) => { + state.error_message = Some(e); + } + }, + TaskResult::CreateDirectoryResult(res) => match res { + Ok(_) => { + state.error_message = Some("Directory created successfully.".to_string()); + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => { + state.error_message = Some(e); + } + }, + TaskResult::CreateFileResult(res) => match res { + Ok(_) => { + state.error_message = Some("File created successfully.".to_string()); + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => { + state.error_message = Some(e); + } + }, + TaskResult::DownloadFileResult(res) => match res { + Ok(_) => state.error_message = Some("Download successful".to_string()), + Err(e) => state.error_message = Some(e), + }, + TaskResult::UploadFileResult(res) => match res { + Ok(_) => { + state.error_message = Some("Upload successful".to_string()); + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => state.error_message = Some(e), + }, + TaskResult::DeleteFileResult(res) => match res { + Ok(_) => { + state.error_message = Some("File deleted successfully.".to_string()); + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => state.error_message = Some(e), + }, + TaskResult::RenameFileResult(res) => match res { + Ok(_) => { + state.error_message = Some("File renamed successfully.".to_string()); + state.operation_in_progress = true; + let path = state.current_path.clone(); + worker.send_task(Task::ListDirectory(path)); + } + Err(e) => state.error_message = Some(e), + }, + TaskResult::ReadFileResult(res) => match res { + Ok(content) => { + state.file_content = content; + state.error_message = Some("File content loaded.".to_string()); + } + Err(e) => { + state.error_message = Some(e); + } + }, + TaskResult::WriteFileResult(res) => match res { + Ok(_) => { + state.error_message = Some("File saved successfully.".to_string()); + state.editing_file = None; + } + Err(e) => { + state.error_message = Some(e); + } + }, + TaskResult::DisconnectResult => { + state.connected = false; + state.files.clear(); + state.current_path = "/".to_string(); + state.error_message = Some("Disconnected".to_string()); + } + } } } - -fn save_connections(connections: &Vec) { - let content = serde_json::to_string(connections).unwrap(); - std::fs::write(CONNECTIONS_FILE, content).unwrap(); -} From f926294b1f6c7e68fb6f032463ffbee744835491 Mon Sep 17 00:00:00 2001 From: 0xb-s <145866191+0xb-s@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:36:04 -0800 Subject: [PATCH 2/3] fix clippy --- src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui.rs b/src/ui.rs index 3b073fe..5920fe7 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -133,7 +133,7 @@ impl BackgroundWorker { Task::ListDirectory(path) => { if let Some(conn) = connection.as_ref() { - let result = conn.list_directory(&path).map_err(|e| e); + let result = conn.list_directory(&path); let _ = result_sender.send(TaskResult::ListDirectoryResult(result)); } else { let _ = result_sender From be40fe7c99f395187e05887ec57e3c93b0656ff1 Mon Sep 17 00:00:00 2001 From: 0xb-s <145866191+0xb-s@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:40:07 -0800 Subject: [PATCH 3/3] fix clippy x2 --- src/ui.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui.rs b/src/ui.rs index 5920fe7..7568000 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -68,6 +68,7 @@ enum Task { /// Represents the result of executing a Task. /// The UI thread will receive these results and update the UI state accordingly. +#[allow(clippy::enum_variant_names)] enum TaskResult { /// The result of the connect attempt ConnectResult(Result<(), String>),