fix(Ui): Fix err with deleting files
This commit is contained in:
1 parent
e318699a36
commit
43a5995036
1 file changed
+224
-139
@@ -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)]
|
||||||
@@ -42,28 +44,28 @@ pub struct RcloneUI {
|
|||||||
rclone: Option<Arc<RcloneApp>>,
|
rclone: Option<Arc<RcloneApp>>,
|
||||||
state: AppState,
|
state: AppState,
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
|
|
||||||
current_path: String,
|
current_path: String,
|
||||||
remote_list: Vec<Remote>,
|
remote_list: Vec<Remote>,
|
||||||
current_files: Vec<FileInfo>,
|
current_files: Vec<FileInfo>,
|
||||||
selected_paths: Vec<String>,
|
selected_paths: Vec<String>,
|
||||||
|
|
||||||
transfer_source: String,
|
transfer_source: String,
|
||||||
transfer_dest: String,
|
transfer_dest: String,
|
||||||
transfer_progress: Option<TransferProgress>,
|
transfer_progress: Option<TransferProgress>,
|
||||||
|
|
||||||
search_pattern: String,
|
search_pattern: String,
|
||||||
search_results: Vec<FileInfo>,
|
search_results: Vec<FileInfo>,
|
||||||
|
|
||||||
show_transfer_dialog: bool,
|
show_transfer_dialog: bool,
|
||||||
show_new_remote_dialog: bool,
|
show_new_remote_dialog: bool,
|
||||||
new_remote_name: String,
|
new_remote_name: String,
|
||||||
new_remote_type: String,
|
new_remote_type: String,
|
||||||
available_remote_types: Vec<String>,
|
available_remote_types: Vec<String>,
|
||||||
new_remote_config: std::collections::HashMap<String, String>,
|
new_remote_config: std::collections::HashMap<String, String>,
|
||||||
|
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
|
|
||||||
background_working: bool,
|
background_working: bool,
|
||||||
operation_result: Arc<Mutex<OperationResult>>,
|
operation_result: Arc<Mutex<OperationResult>>,
|
||||||
operation_id: u32,
|
operation_id: u32,
|
||||||
@@ -71,7 +73,7 @@ pub struct RcloneUI {
|
|||||||
remote_to_delete: Option<String>,
|
remote_to_delete: Option<String>,
|
||||||
show_browser_warning: bool,
|
show_browser_warning: bool,
|
||||||
pending_remote_creation: Option<(String, String)>,
|
pending_remote_creation: Option<(String, String)>,
|
||||||
|
|
||||||
// Отложенные действия
|
// Отложенные действия
|
||||||
pending_load_path: Option<String>,
|
pending_load_path: Option<String>,
|
||||||
pending_search_pattern: Option<String>,
|
pending_search_pattern: Option<String>,
|
||||||
@@ -129,7 +131,7 @@ impl RcloneUI {
|
|||||||
pending_search_pattern: None,
|
pending_search_pattern: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_rclone(&mut self) {
|
fn init_rclone(&mut self) {
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
let rclone_result = rt.block_on(async { RcloneApp::new().await });
|
let rclone_result = rt.block_on(async { RcloneApp::new().await });
|
||||||
@@ -146,21 +148,25 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_remote_types(&mut self) {
|
fn load_remote_types(&mut self) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
match operations::info::backends(rclone) {
|
match operations::info::backends(rclone) {
|
||||||
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(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_remotes(&mut self) {
|
fn load_remotes(&mut self) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
match operations::remotes::list(rclone) {
|
match operations::remotes::list(rclone) {
|
||||||
@@ -175,7 +181,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_files(&mut self, path: &str) {
|
fn load_files(&mut self, path: &str) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
match operations::files::list(rclone, path) {
|
match operations::files::list(rclone, path) {
|
||||||
@@ -191,7 +197,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_background_operation<F>(&mut self, f: F) -> u32
|
fn start_background_operation<F>(&mut self, f: F) -> u32
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Result<String, String> + Send + 'static,
|
F: FnOnce() -> Result<String, String> + Send + 'static,
|
||||||
@@ -210,7 +216,7 @@ impl RcloneUI {
|
|||||||
});
|
});
|
||||||
op_id
|
op_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_background_operation(&mut self) {
|
fn poll_background_operation(&mut self) {
|
||||||
if !self.background_working {
|
if !self.background_working {
|
||||||
return;
|
return;
|
||||||
@@ -241,7 +247,7 @@ impl RcloneUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_remote(&mut self, name: &str) {
|
fn delete_remote(&mut self, name: &str) {
|
||||||
if let Some(rclone) = self.rclone.clone() {
|
if let Some(rclone) = self.rclone.clone() {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
@@ -252,7 +258,7 @@ impl RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_move(&mut self, source: &str, dest: &str) {
|
fn perform_move(&mut self, source: &str, dest: &str) {
|
||||||
if let Some(rclone) = self.rclone.clone() {
|
if let Some(rclone) = self.rclone.clone() {
|
||||||
self.state = AppState::Moving;
|
self.state = AppState::Moving;
|
||||||
@@ -268,12 +274,17 @@ 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))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_copy(&mut self, source: &str, dest: &str) {
|
fn perform_copy(&mut self, source: &str, dest: &str) {
|
||||||
if let Some(rclone) = self.rclone.clone() {
|
if let Some(rclone) = self.rclone.clone() {
|
||||||
self.state = AppState::Copying;
|
self.state = AppState::Copying;
|
||||||
@@ -287,20 +298,18 @@ 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))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_remote_with_warning(&mut self, name: &str, r#type: &str) {
|
fn create_remote_with_warning(&mut self, name: &str, r#type: &str) {
|
||||||
self.pending_remote_creation = Some((name.to_string(), r#type.to_string()));
|
self.pending_remote_creation = Some((name.to_string(), r#type.to_string()));
|
||||||
self.show_browser_warning = true;
|
self.show_browser_warning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_remote_creation(&mut self, name: &str, r#type: &str) {
|
fn execute_remote_creation(&mut self, name: &str, r#type: &str) {
|
||||||
if let Some(rclone) = self.rclone.clone() {
|
if let Some(rclone) = self.rclone.clone() {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
@@ -313,7 +322,7 @@ impl RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_files(&mut self, pattern: &str) {
|
fn search_files(&mut self, pattern: &str) {
|
||||||
if let Some(rclone) = &self.rclone {
|
if let Some(rclone) = &self.rclone {
|
||||||
let options = FindOptions {
|
let options = FindOptions {
|
||||||
@@ -326,45 +335,66 @@ 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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for RcloneUI {
|
impl eframe::App for RcloneUI {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
self.poll_background_operation();
|
self.poll_background_operation();
|
||||||
|
|
||||||
if matches!(self.state, AppState::Initializing) && self.rclone.is_none() {
|
if matches!(self.state, AppState::Initializing) && self.rclone.is_none() {
|
||||||
self.init_rclone();
|
self.init_rclone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Верхняя панель меню
|
// Верхняя панель меню
|
||||||
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 {
|
||||||
@@ -407,7 +456,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Левая панель со списком хранилищ
|
// Левая панель со списком хранилищ
|
||||||
egui::SidePanel::left("remotes_panel")
|
egui::SidePanel::left("remotes_panel")
|
||||||
.default_width(220.0)
|
.default_width(220.0)
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -437,7 +487,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Центральная панель
|
// Центральная панель
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
let current_path = self.current_path.clone();
|
let current_path = self.current_path.clone();
|
||||||
@@ -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 { ¤t_path });
|
ui.label(if current_path.is_empty() {
|
||||||
|
"Выберите хранилище"
|
||||||
|
} else {
|
||||||
|
¤t_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() {
|
||||||
@@ -461,7 +515,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
// Анимированная загрузка в центральной панели
|
// Анимированная загрузка в центральной панели
|
||||||
if self.background_working {
|
if self.background_working {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
@@ -469,20 +523,20 @@ 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));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let AppState::Error(msg) = &self.state {
|
if let AppState::Error(msg) = &self.state {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.colored_label(Color32::RED, format!("Ошибка: {}", msg));
|
ui.colored_label(Color32::RED, format!("Ошибка: {}", msg));
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.search_results.is_empty() {
|
if !self.search_results.is_empty() {
|
||||||
let search_results = self.search_results.clone();
|
let search_results = self.search_results.clone();
|
||||||
let current_path = self.current_path.clone();
|
let current_path = self.current_path.clone();
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
@@ -553,7 +609,7 @@ impl eframe::App for RcloneUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if !self.selected_paths.is_empty() {
|
if !self.selected_paths.is_empty() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -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))
|
||||||
@@ -581,7 +650,7 @@ impl eframe::App for RcloneUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка отложенных действий
|
// Обработка отложенных действий
|
||||||
if let Some(path) = self.pending_load_path.take() {
|
if let Some(path) = self.pending_load_path.take() {
|
||||||
self.load_files(&path);
|
self.load_files(&path);
|
||||||
@@ -589,38 +658,42 @@ impl eframe::App for RcloneUI {
|
|||||||
if let Some(pattern) = self.pending_search_pattern.take() {
|
if let Some(pattern) = self.pending_search_pattern.take() {
|
||||||
self.search_files(&pattern);
|
self.search_files(&pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Диалог передачи файлов
|
// Диалог передачи файлов
|
||||||
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, ¤t_dest);
|
|
||||||
self.show_transfer_dialog = false;
|
|
||||||
self.selected_paths.clear();
|
|
||||||
}
|
|
||||||
if ui.button("✂️ Переместить").clicked() {
|
|
||||||
self.perform_move(&source, ¤t_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, ¤t_dest);
|
||||||
|
self.show_transfer_dialog = false;
|
||||||
|
self.selected_paths.clear();
|
||||||
|
}
|
||||||
|
if ui.button("✂️ Переместить").clicked() {
|
||||||
|
self.perform_move(&source, ¤t_dest);
|
||||||
|
self.show_transfer_dialog = false;
|
||||||
|
self.selected_paths.clear();
|
||||||
|
}
|
||||||
|
if ui.button("❌ Отмена").clicked() {
|
||||||
|
self.show_transfer_dialog = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Диалог добавления хранилища
|
// Диалог добавления хранилища
|
||||||
if self.show_new_remote_dialog {
|
if self.show_new_remote_dialog {
|
||||||
let mut name = self.new_remote_name.clone();
|
let mut name = self.new_remote_name.clone();
|
||||||
@@ -661,67 +734,79 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user