fix(Ui): Fix err with deleting files

This commit is contained in:
Black_Raven committed 2026-03-29 14:18:34 +03:00
1 parent e318699a36
commit 43a5995036
1 file changed
+187 -102
+187 -102
View File
@@ -1,12 +1,14 @@
//! Графический интерфейс для операций rclone. //! Графический интерфейс для операций rclone.
use crate::operations::{
self, CopyOptions, DeleteOptions, FileInfo, FindOptions, MoveOptions, Remote,
};
use crate::rclone_install::RcloneApp;
use eframe::egui; use eframe::egui;
use egui::{CentralPanel, ScrollArea, Window, Align2, Color32, Spinner}; use egui::{Align2, CentralPanel, Color32, ScrollArea, Spinner, Window};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use crate::rclone_install::RcloneApp;
use crate::operations::{self, FileInfo, Remote, CopyOptions, MoveOptions, DeleteOptions, FindOptions};
/// Состояние приложения. /// Состояние приложения.
#[derive(Clone)] #[derive(Clone)]
@@ -153,8 +155,12 @@ impl RcloneUI {
Ok(types) => self.available_remote_types = types, Ok(types) => self.available_remote_types = types,
Err(_) => { Err(_) => {
self.available_remote_types = vec![ self.available_remote_types = vec![
"drive".to_string(), "s3".to_string(), "dropbox".to_string(), "drive".to_string(),
"local".to_string(), "onedrive".to_string(), "webdav".to_string(), "s3".to_string(),
"dropbox".to_string(),
"local".to_string(),
"onedrive".to_string(),
"webdav".to_string(),
]; ];
} }
} }
@@ -268,7 +274,12 @@ impl RcloneUI {
rt.block_on(async { rt.block_on(async {
operations::sync::move_files(&rclone, &source, &dest, &options) operations::sync::move_files(&rclone, &source, &dest, &options)
}) })
.map(|stats| format!("Перемещено {} файлов, {} байт", stats.files, stats.transferred)) .map(|stats| {
format!(
"Перемещено {} файлов, {} байт",
stats.files, stats.transferred
)
})
.map_err(|e| format!("Ошибка перемещения: {}", e)) .map_err(|e| format!("Ошибка перемещения: {}", e))
}); });
} }
@@ -287,11 +298,9 @@ impl RcloneUI {
let dest = dest.to_string(); let dest = dest.to_string();
self.start_background_operation(move || { self.start_background_operation(move || {
let rt = Runtime::new().unwrap(); let rt = Runtime::new().unwrap();
rt.block_on(async { rt.block_on(async { operations::sync::copy(&rclone, &source, &dest, &options) })
operations::sync::copy(&rclone, &source, &dest, &options) .map(|stats| format!("Скопировано {} файлов", stats.files))
}) .map_err(|e| format!("Ошибка копирования: {}", e))
.map(|stats| format!("Скопировано {} файлов", stats.files))
.map_err(|e| format!("Ошибка копирования: {}", e))
}); });
} }
} }
@@ -328,27 +337,46 @@ impl RcloneUI {
} }
fn validate_remote_name(name: &str) -> bool { fn validate_remote_name(name: &str) -> bool {
if name.is_empty() || name.starts_with(' ') || name.starts_with('-') || name.ends_with(' ') { if name.is_empty() || name.starts_with(' ') || name.starts_with('-') || name.ends_with(' ')
{
return false; return false;
} }
name.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' || ch == '.' || ch == '+' || ch == '@' || ch == ' ') name.chars().all(|ch| {
ch.is_ascii_alphanumeric()
|| ch == '_'
|| ch == '-'
|| ch == '.'
|| ch == '+'
|| ch == '@'
|| ch == ' '
})
} }
fn format_size(bytes: u64) -> String { fn format_size(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;
if bytes >= GB { format!("{:.2} ГБ", bytes as f64 / GB as f64) } if bytes >= GB {
else if bytes >= MB { format!("{:.2} МБ", bytes as f64 / MB as f64) } format!("{:.2} ГБ", bytes as f64 / GB as f64)
else if bytes >= KB { format!("{:.2} КБ", bytes as f64 / KB as f64) } } else if bytes >= MB {
else { format!("{} Б", bytes) } format!("{:.2} МБ", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} КБ", bytes as f64 / KB as f64)
} else {
format!("{} Б", bytes)
}
} }
fn format_speed(speed: f64) -> String { fn format_speed(speed: f64) -> String {
if speed < 1024.0 { format!("{:.0} Б/с", speed) } if speed < 1024.0 {
else if speed < 1024.0 * 1024.0 { format!("{:.1} КБ/с", speed / 1024.0) } format!("{:.0} Б/с", speed)
else if speed < 1024.0 * 1024.0 * 1024.0 { format!("{:.1} МБ/с", speed / (1024.0 * 1024.0)) } } else if speed < 1024.0 * 1024.0 {
else { format!("{:.2} ГБ/с", speed / (1024.0 * 1024.0 * 1024.0)) } format!("{:.1} КБ/с", speed / 1024.0)
} else if speed < 1024.0 * 1024.0 * 1024.0 {
format!("{:.1} МБ/с", speed / (1024.0 * 1024.0))
} else {
format!("{:.2} ГБ/с", speed / (1024.0 * 1024.0 * 1024.0))
}
} }
} }
@@ -364,7 +392,9 @@ impl eframe::App for RcloneUI {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::menu::bar(ui, |ui| { egui::menu::bar(ui, |ui| {
ui.menu_button("Файл", |ui| { ui.menu_button("Файл", |ui| {
if ui.button("Выход").clicked() { std::process::exit(0); } if ui.button("Выход").clicked() {
std::process::exit(0);
}
}); });
ui.menu_button("Хранилища", |ui| { ui.menu_button("Хранилища", |ui| {
if ui.button("Добавить новое хранилище").clicked() { if ui.button("Добавить новое хранилище").clicked() {
@@ -376,13 +406,24 @@ impl eframe::App for RcloneUI {
}); });
ui.menu_button("Настройки", |ui| { ui.menu_button("Настройки", |ui| {
ui.checkbox(&mut self.settings.show_hidden, "Показывать скрытые файлы"); ui.checkbox(&mut self.settings.show_hidden, "Показывать скрытые файлы");
ui.checkbox(&mut self.settings.confirm_before_transfer, "Подтверждать перед передачей"); ui.checkbox(
ui.add(egui::Slider::new(&mut self.settings.max_concurrent_transfers, 1..=10).text("Макс. одновременных передач")); &mut self.settings.confirm_before_transfer,
"Подтверждать перед передачей",
);
ui.add(
egui::Slider::new(&mut self.settings.max_concurrent_transfers, 1..=10)
.text("Макс. одновременных передач"),
);
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Ограничение скорости:"); ui.label("Ограничение скорости:");
let mut limit_str = self.settings.bandwidth_limit.clone().unwrap_or_default(); let mut limit_str =
self.settings.bandwidth_limit.clone().unwrap_or_default();
if ui.text_edit_singleline(&mut limit_str).changed() { if ui.text_edit_singleline(&mut limit_str).changed() {
self.settings.bandwidth_limit = if limit_str.is_empty() { None } else { Some(limit_str) }; self.settings.bandwidth_limit = if limit_str.is_empty() {
None
} else {
Some(limit_str)
};
} }
}); });
}); });
@@ -395,10 +436,18 @@ impl eframe::App for RcloneUI {
}); });
} else { } else {
match &self.state { match &self.state {
AppState::Ready => { ui.colored_label(Color32::GREEN, "● Готов"); } AppState::Ready => {
AppState::Initializing => { ui.colored_label(Color32::YELLOW, "● Инициализация..."); } ui.colored_label(Color32::GREEN, "● Готов");
AppState::Error(_) => { ui.colored_label(Color32::RED, "● Ошибка"); } }
_ => { ui.colored_label(Color32::BLUE, "● Занят"); } AppState::Initializing => {
ui.colored_label(Color32::YELLOW, "● Инициализация...");
}
AppState::Error(_) => {
ui.colored_label(Color32::RED, "● Ошибка");
}
_ => {
ui.colored_label(Color32::BLUE, "● Занят");
}
} }
}; };
if let Some(rclone) = &self.rclone { if let Some(rclone) = &self.rclone {
@@ -427,7 +476,8 @@ impl eframe::App for RcloneUI {
if ui.button(format!("📡 {}", remote.name)).clicked() { if ui.button(format!("📡 {}", remote.name)).clicked() {
self.pending_load_path = Some(format!("{}:", remote.name)); self.pending_load_path = Some(format!("{}:", remote.name));
} }
if ui.button("🗑️").on_hover_text("Удалить хранилище").clicked() { if ui.button("🗑️").on_hover_text("Удалить хранилище").clicked()
{
self.remote_to_delete = Some(remote.name.clone()); self.remote_to_delete = Some(remote.name.clone());
self.show_delete_remote_dialog = true; self.show_delete_remote_dialog = true;
} }
@@ -445,7 +495,11 @@ impl eframe::App for RcloneUI {
let mut new_search = search_pattern.clone(); let mut new_search = search_pattern.clone();
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("📍 Путь:"); ui.label("📍 Путь:");
ui.label(if current_path.is_empty() { "Выберите хранилище" } else { &current_path }); ui.label(if current_path.is_empty() {
"Выберите хранилище"
} else {
&current_path
});
ui.separator(); ui.separator();
ui.label("🔍 Поиск:"); ui.label("🔍 Поиск:");
if ui.text_edit_singleline(&mut new_search).changed() { if ui.text_edit_singleline(&mut new_search).changed() {
@@ -469,7 +523,7 @@ impl eframe::App for RcloneUI {
ui.add_space(10.0); ui.add_space(10.0);
ui.colored_label( ui.colored_label(
egui::Color32::LIGHT_BLUE, egui::Color32::LIGHT_BLUE,
"⟳ Выполняется операция... Пожалуйста, подождите." "⟳ Выполняется операция... Пожалуйста, подождите.",
); );
}); });
ctx.request_repaint_after(Duration::from_millis(16)); ctx.request_repaint_after(Duration::from_millis(16));
@@ -507,7 +561,9 @@ impl eframe::App for RcloneUI {
let current_files = self.current_files.clone(); let current_files = self.current_files.clone();
let current_path = self.current_path.clone(); let current_path = self.current_path.clone();
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
if current_path.contains('/') && ui.button("📁 .. (Родительская папка)").clicked() { if current_path.contains('/')
&& ui.button("📁 .. (Родительская папка)").clicked()
{
let parent = current_path.rsplit_once('/').map(|(p, _)| p).unwrap_or(""); let parent = current_path.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
self.pending_load_path = Some(parent.to_string()); self.pending_load_path = Some(parent.to_string());
} }
@@ -569,7 +625,20 @@ impl eframe::App for RcloneUI {
if ui.button("🗑️ Удалить").clicked() { if ui.button("🗑️ Удалить").clicked() {
if let Some(rclone) = self.rclone.clone() { if let Some(rclone) = self.rclone.clone() {
let path = self.selected_paths[0].clone(); let path = self.selected_paths[0].clone();
let options = DeleteOptions { recursive: true, verbose: true, dry_run: false }; // Определяем, является ли выбранный путь директорией
let is_dir = self.current_files.iter().any(|f| {
let full_path = if self.current_path.ends_with(':') {
format!("{}{}", self.current_path, f.name)
} else {
format!("{}/{}", self.current_path, f.name)
};
full_path == path && f.is_dir
});
let options = DeleteOptions {
recursive: is_dir,
verbose: true,
dry_run: false,
};
self.start_background_operation(move || { self.start_background_operation(move || {
operations::sync::delete(&rclone, &path, &options) operations::sync::delete(&rclone, &path, &options)
.map(|_| format!("Удалено: {}", path)) .map(|_| format!("Удалено: {}", path))
@@ -594,31 +663,35 @@ impl eframe::App for RcloneUI {
if self.show_transfer_dialog { if self.show_transfer_dialog {
let source = self.transfer_source.clone(); let source = self.transfer_source.clone();
let dest = self.transfer_dest.clone(); let dest = self.transfer_dest.clone();
Window::new("Передача файлов").collapsible(false).resizable(false).anchor(Align2::CENTER_CENTER, [0.0, 0.0]).show(ctx, |ui| { Window::new("Передача файлов")
ui.label(format!("Источник: {}", source)); .collapsible(false)
ui.label("Назначение:"); .resizable(false)
let mut dest_edit = dest.clone(); .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
if ui.text_edit_singleline(&mut dest_edit).changed() { .show(ctx, |ui| {
self.transfer_dest = dest_edit; ui.label(format!("Источник: {}", source));
} ui.label("Назначение:");
ui.separator(); let mut dest_edit = dest.clone();
let current_dest = self.transfer_dest.clone(); if ui.text_edit_singleline(&mut dest_edit).changed() {
ui.horizontal(|ui| { self.transfer_dest = dest_edit;
if ui.button("✅ Копировать").clicked() {
self.perform_copy(&source, &current_dest);
self.show_transfer_dialog = false;
self.selected_paths.clear();
}
if ui.button("✂️ Переместить").clicked() {
self.perform_move(&source, &current_dest);
self.show_transfer_dialog = false;
self.selected_paths.clear();
}
if ui.button("❌ Отмена").clicked() {
self.show_transfer_dialog = false;
} }
ui.separator();
let current_dest = self.transfer_dest.clone();
ui.horizontal(|ui| {
if ui.button("✅ Копировать").clicked() {
self.perform_copy(&source, &current_dest);
self.show_transfer_dialog = false;
self.selected_paths.clear();
}
if ui.button("✂️ Переместить").clicked() {
self.perform_move(&source, &current_dest);
self.show_transfer_dialog = false;
self.selected_paths.clear();
}
if ui.button("❌ Отмена").clicked() {
self.show_transfer_dialog = false;
}
});
}); });
});
} }
// Диалог добавления хранилища // Диалог добавления хранилища
@@ -664,62 +737,74 @@ impl eframe::App for RcloneUI {
// Диалог предупреждения о браузере // Диалог предупреждения о браузере
if self.show_browser_warning { if self.show_browser_warning {
Window::new("Внимание").collapsible(false).resizable(false).anchor(Align2::CENTER_CENTER, [0.0, 0.0]).show(ctx, |ui| { Window::new("Внимание")
ui.colored_label(Color32::YELLOW, "🌐 Будет открыт браузер для авторизации"); .collapsible(false)
ui.label("После завершения авторизации в браузере вернитесь в приложение."); .resizable(false)
ui.label("Операция выполняется в фоне, интерфейс не зависнет."); .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
ui.separator(); .show(ctx, |ui| {
if ui.button("Продолжить").clicked() { ui.colored_label(Color32::YELLOW, "🌐 Будет открыт браузер для авторизации");
if let Some((name, typ)) = self.pending_remote_creation.take() { ui.label("После завершения авторизации в браузере вернитесь в приложение.");
self.execute_remote_creation(&name, &typ); ui.label("Операция выполняется в фоне, интерфейс не зависнет.");
ui.separator();
if ui.button("Продолжить").clicked() {
if let Some((name, typ)) = self.pending_remote_creation.take() {
self.execute_remote_creation(&name, &typ);
}
self.show_browser_warning = false;
} }
self.show_browser_warning = false; if ui.button("Отмена").clicked() {
} self.pending_remote_creation = None;
if ui.button("Отмена").clicked() { self.show_browser_warning = false;
self.pending_remote_creation = None; }
self.show_browser_warning = false; });
}
});
} }
// Диалог подтверждения удаления хранилища // Диалог подтверждения удаления хранилища
if self.show_delete_remote_dialog { if self.show_delete_remote_dialog {
let remote_name = self.remote_to_delete.clone(); let remote_name = self.remote_to_delete.clone();
Window::new("Удаление хранилища").collapsible(false).resizable(false).anchor(Align2::CENTER_CENTER, [0.0, 0.0]).show(ctx, |ui| { Window::new("Удаление хранилища")
if let Some(ref name) = remote_name { .collapsible(false)
ui.label(format!("Удалить хранилище '{}'?", name)); .resizable(false)
ui.label("Это действие не удаляет данные, только конфигурацию."); .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
let name_clone = name.clone(); .show(ctx, |ui| {
ui.horizontal(|ui| { if let Some(ref name) = remote_name {
if ui.button("✅ Удалить").clicked() { ui.label(format!("Удалить хранилище '{}'?", name));
self.delete_remote(&name_clone); ui.label("Это действие не удаляет данные, только конфигурацию.");
self.show_delete_remote_dialog = false; let name_clone = name.clone();
self.remote_to_delete = None; ui.horizontal(|ui| {
} if ui.button("✅ Удалить").clicked() {
if ui.button("❌ Отмена").clicked() { self.delete_remote(&name_clone);
self.show_delete_remote_dialog = false; self.show_delete_remote_dialog = false;
self.remote_to_delete = None; self.remote_to_delete = None;
} }
}); if ui.button("❌ Отмена").clicked() {
} else { self.show_delete_remote_dialog = false;
self.show_delete_remote_dialog = false; self.remote_to_delete = None;
} }
}); });
} else {
self.show_delete_remote_dialog = false;
}
});
} }
// Диалог ошибок // Диалог ошибок
if let Some(error) = &self.error_message { if let Some(error) = &self.error_message {
let error_clone = error.clone(); let error_clone = error.clone();
Window::new("Ошибка").collapsible(false).resizable(false).anchor(Align2::CENTER_CENTER, [0.0, 0.0]).show(ctx, |ui| { Window::new("Ошибка")
ui.colored_label(Color32::RED, "⚠️ Произошла ошибка:"); .collapsible(false)
ui.label(&error_clone); .resizable(false)
if ui.button("OK").clicked() { .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
self.error_message = None; .show(ctx, |ui| {
if let AppState::Error(_) = &self.state { ui.colored_label(Color32::RED, "⚠️ Произошла ошибка:");
self.state = AppState::Ready; ui.label(&error_clone);
if ui.button("OK").clicked() {
self.error_message = None;
if let AppState::Error(_) = &self.state {
self.state = AppState::Ready;
}
} }
} });
});
} }
ctx.request_repaint(); ctx.request_repaint();