use reqwest; use std::fs; use std::io::Cursor; use std::path::PathBuf; use std::process::Command; use super::RcloneApp; impl RcloneApp { pub async fn new() -> Result> { log::info!("Инициализация RcloneApp"); // Сначала проверяем системный rclone if let Some(system_rclone) = Self::check_system_rclone() { log::info!("Найден системный rclone: {:?}", system_rclone); return Ok(Self { rclone_path: system_rclone, using_system_rclone: true, }); } // Проверяем в директории приложения let app_dir = dirs::data_dir() .ok_or("Не удалось найти папку для данных")? .join("rclone-ui"); let rclone_path = if cfg!(windows) { app_dir.join("rclone.exe") } else { app_dir.join("rclone") }; if rclone_path.exists() { log::info!("Найден rclone в директории приложения: {:?}", rclone_path); return Ok(Self { rclone_path, using_system_rclone: false, }); } // Если ничего не найдено - устанавливаем log::warn!("rclone не найден. Начинаю установку..."); Self::install_latest_rclone().await } // Публичный конструктор для тестов и создания экземпляра с заданными параметрами pub fn new_with_path(rclone_path: PathBuf, using_system_rclone: bool) -> Self { log::debug!("Создание RcloneApp с путем {:?}, системный: {}", rclone_path, using_system_rclone); Self { rclone_path, using_system_rclone, } } // Геттеры pub fn get_rclone_path(&self) -> &PathBuf { &self.rclone_path } pub fn is_using_system_rclone(&self) -> bool { self.using_system_rclone } // Сеттеры pub fn set_rclone_path(&mut self, path: PathBuf) { log::debug!("Изменение пути rclone с {:?} на {:?}", self.rclone_path, path); self.rclone_path = path; } pub fn set_using_system_rclone(&mut self, value: bool) { log::debug!("Изменение флага using_system_rclone с {} на {}", self.using_system_rclone, value); self.using_system_rclone = value; } pub fn check_system_rclone() -> Option { log::debug!("Проверка системного rclone"); if let Ok(path) = which::which("rclone") { log::debug!("Найден системный rclone по пути: {:?}", path); return Some(path); } #[cfg(windows)] { if let Ok(path) = which::which("rclone.exe") { log::debug!("Найден системный rclone.exe по пути: {:?}", path); return Some(path); } } log::debug!("Системный rclone не найден"); None } async fn install_latest_rclone() -> Result> { log::info!("Начинаю установку последней версии rclone..."); // Определяем ОС и архитектуру let os = if cfg!(target_os = "linux") { "linux" } else if cfg!(target_os = "macos") { "osx" } else if cfg!(target_os = "windows") { "windows" } else { return Err("Неподдерживаемая операционная система".into()); }; let arch = if cfg!(target_arch = "x86_64") { "amd64" } else if cfg!(target_arch = "aarch64") { "arm64" } else if cfg!(target_arch = "arm") { "arm" } else { return Err("Неподдерживаемая архитектура".into()); }; log::info!("ОС: {}, архитектура: {}", os, arch); // Получаем информацию о последнем релизе let client = reqwest::Client::new(); log::debug!("Запрос последней версии rclone с GitHub API"); let latest_release: serde_json::Value = client .get("https://api.github.com/repos/rclone/rclone/releases/latest") .header("User-Agent", "rclone-ui-installer") .send() .await? .json() .await?; let version = latest_release["tag_name"] .as_str() .ok_or("Не удалось получить версию")? .trim_start_matches('v'); log::info!("Найдена последняя версия rclone: {}", version); // Формируем URL для скачивания let download_url = format!( "https://github.com/rclone/rclone/releases/download/v{}/rclone-v{}-{}-{}.zip", version, version, os, arch ); log::info!("Скачиваю rclone с: {}", download_url); // Скачиваем архив let response = client.get(&download_url).send().await?; if !response.status().is_success() { let error_msg = format!("Ошибка скачивания: HTTP {}", response.status()); log::error!("{}", error_msg); return Err(error_msg.into()); } let bytes = response.bytes().await?; log::info!("Архив скачан, размер: {} байт", bytes.len()); // Создаем директорию приложения let app_dir = dirs::data_dir() .ok_or("Не удалось найти папку для данных")? .join("rclone-ui"); fs::create_dir_all(&app_dir)?; log::debug!("Директория приложения: {:?}", app_dir); // Определяем имя исполняемого файла let rclone_filename = if cfg!(windows) { "rclone.exe" } else { "rclone" }; let rclone_path = app_dir.join(rclone_filename); // Распаковываем архив use zip::read::ZipArchive; let cursor = Cursor::new(bytes); let mut archive = ZipArchive::new(cursor)?; let mut extracted = false; for i in 0..archive.len() { let mut file = archive.by_index(i)?; let file_name = file.name().to_string(); if file_name.ends_with("rclone") || file_name.ends_with("rclone.exe") { log::debug!("Найден исполняемый файл в архиве: {}", file_name); let mut contents = Vec::new(); std::io::copy(&mut file, &mut contents)?; fs::write(&rclone_path, contents)?; extracted = true; log::info!("Распакован: {} в {:?}", file_name, rclone_path); break; } } if !extracted { log::error!("Не удалось найти исполняемый файл rclone в архиве"); return Err("Не удалось найти исполняемый файл rclone в архиве".into()); } // Устанавливаем права на выполнение для Unix систем #[cfg(not(windows))] { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&rclone_path)?.permissions(); perms.set_mode(0o755); fs::set_permissions(&rclone_path, perms)?; log::debug!("Установлены права на выполнение для {:?}", rclone_path); } log::info!("rclone успешно установлен в {:?}", rclone_path); // Проверяем установку let output = Command::new(&rclone_path) .arg("version") .output() .map_err(|e| format!("Ошибка проверки установки: {}", e))?; if output.status.success() { let version_output = String::from_utf8_lossy(&output.stdout); log::info!("Установленная версия rclone:\n{}", version_output); } else { log::warn!("Не удалось проверить версию установленного rclone"); } Ok(Self { rclone_path, using_system_rclone: false, }) } pub fn run_command(&self, args: &[&str]) -> Result { log::debug!("Выполнение команды rclone: {} {:?}", self.rclone_path.display(), args); let output = Command::new(&self.rclone_path) .args(args) .output() .map_err(|e| { log::error!("Ошибка запуска rclone: {}", e); format!("Ошибка запуска: {}", e) })?; if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout).to_string(); log::debug!("Команда выполнена успешно, вывод: {} байт", stdout.len()); Ok(stdout) } else { let stderr = String::from_utf8_lossy(&output.stderr).to_string(); log::error!("Ошибка выполнения команды: {}", stderr); Err(stderr) } } pub fn version(&self) -> Result { log::debug!("Получение версии rclone"); self.run_command(&["version"]) } }