Merge pull request 'dev/stricts' (#2) from dev/stricts into main
Reviewed-on: https://git.rustprogersteam.ru/Black_Raven/UIrclone/pulls/2
This commit is contained in:
commit
51758c21bf
9 files changed
+563
-1
No files matched your search
@@ -0,0 +1,120 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// === Модуль для работы с удаленными хранилищами ===
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Remote {
|
||||||
|
pub name: String,
|
||||||
|
pub r#type: String,
|
||||||
|
pub config: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod remotes;
|
||||||
|
|
||||||
|
/// === Модуль для работы с файлами и директориями ===
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
pub size: u64,
|
||||||
|
pub is_dir: bool,
|
||||||
|
pub modified: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileInfo {
|
||||||
|
pub fn format_size(&self) -> String {
|
||||||
|
let bytes = self.size;
|
||||||
|
if bytes < 1024 {
|
||||||
|
format!("{} B", bytes)
|
||||||
|
} else if bytes < 1024 * 1024 {
|
||||||
|
format!("{:.1} KB", bytes as f64 / 1024.0)
|
||||||
|
} else if bytes < 1024 * 1024 * 1024 {
|
||||||
|
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
|
||||||
|
} else {
|
||||||
|
format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> &'static str {
|
||||||
|
if self.is_dir { "📁" } else { "📄" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod files;
|
||||||
|
|
||||||
|
/// === Модуль для операций синхронизации ===
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SyncStats {
|
||||||
|
pub transferred: u64,
|
||||||
|
pub files: u64,
|
||||||
|
pub errors: u64,
|
||||||
|
pub checks: u64,
|
||||||
|
pub elapsed_time: f64,
|
||||||
|
pub transfer_speed: f64, // в байтах в секунду
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncStats {
|
||||||
|
pub fn format_speed(&self) -> String {
|
||||||
|
let speed = self.transfer_speed;
|
||||||
|
if speed < 1024.0 {
|
||||||
|
format!("{:.0} B/s", speed)
|
||||||
|
} else if speed < 1024.0 * 1024.0 {
|
||||||
|
format!("{:.1} KB/s", speed / 1024.0)
|
||||||
|
} else if speed < 1024.0 * 1024.0 * 1024.0 {
|
||||||
|
format!("{:.1} MB/s", speed / (1024.0 * 1024.0))
|
||||||
|
} else {
|
||||||
|
format!("{:.2} GB/s", speed / (1024.0 * 1024.0 * 1024.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct CopyOptions {
|
||||||
|
pub verbose: bool,
|
||||||
|
pub dry_run: bool,
|
||||||
|
pub bandwidth_limit: Option<String>,
|
||||||
|
pub no_traverse: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SyncOptions {
|
||||||
|
pub verbose: bool,
|
||||||
|
pub dry_run: bool,
|
||||||
|
pub delete_excluded: bool,
|
||||||
|
pub bandwidth_limit: Option<String>,
|
||||||
|
pub existing_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct MoveOptions {
|
||||||
|
pub verbose: bool,
|
||||||
|
pub dry_run: bool,
|
||||||
|
pub delete_empty_src_dirs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DeleteOptions {
|
||||||
|
pub verbose: bool,
|
||||||
|
pub dry_run: bool,
|
||||||
|
pub recursive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod sync;
|
||||||
|
|
||||||
|
/// === Модуль для поиска ===
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct FindOptions {
|
||||||
|
pub recursive: bool,
|
||||||
|
pub max_results: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod search;
|
||||||
|
|
||||||
|
pub mod check;
|
||||||
|
pub mod info;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod operations_tests;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Проверить разницу между двумя директориями
|
||||||
|
pub fn diff(app: &RcloneApp, source: &str, dest: &str) -> Result<Vec<String>, String> {
|
||||||
|
let output = app.run_command(&["check", source, dest, "--combined", "-"])?;
|
||||||
|
|
||||||
|
let mut differences = Vec::new();
|
||||||
|
for line in output.lines() {
|
||||||
|
if !line.is_empty() {
|
||||||
|
differences.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(differences)
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Получить содержимое директории
|
||||||
|
pub fn list(app: &RcloneApp, remote_path: &str) -> Result<Vec<FileInfo>, String> {
|
||||||
|
let output = app.run_command(&["lsjson", remote_path])?;
|
||||||
|
|
||||||
|
let files: Vec<Value> = serde_json::from_str(&output)
|
||||||
|
.map_err(|e| format!("Ошибка парсинга списка файлов: {}", e))?;
|
||||||
|
|
||||||
|
let mut file_infos = Vec::new();
|
||||||
|
for item in files {
|
||||||
|
let name = item["Name"].as_str().unwrap_or("").to_string();
|
||||||
|
let path = item["Path"].as_str().unwrap_or("").to_string();
|
||||||
|
let size = item["Size"].as_u64().unwrap_or(0);
|
||||||
|
let is_dir = item["IsDir"].as_bool().unwrap_or(false);
|
||||||
|
let modified = item["ModTime"].as_str().map(String::from);
|
||||||
|
|
||||||
|
file_infos.push(FileInfo {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
size,
|
||||||
|
is_dir,
|
||||||
|
modified,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(file_infos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить размер и количество файлов
|
||||||
|
pub fn size(app: &RcloneApp, path: &str) -> Result<(u64, u64), String> {
|
||||||
|
let output = app.run_command(&["size", path, "--json"])?;
|
||||||
|
|
||||||
|
let size_info: Value =
|
||||||
|
serde_json::from_str(&output).map_err(|e| format!("Ошибка парсинга размера: {}", e))?;
|
||||||
|
|
||||||
|
let count = size_info["count"].as_u64().unwrap_or(0);
|
||||||
|
let bytes = size_info["bytes"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
Ok((bytes, count))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить информацию о хранилище (общий объем, использовано, свободно)
|
||||||
|
pub fn about(app: &RcloneApp, remote: &str) -> Result<HashMap<String, u64>, String> {
|
||||||
|
let output = app.run_command(&["about", remote, "--json"])?;
|
||||||
|
|
||||||
|
let about_info: Value = serde_json::from_str(&output)
|
||||||
|
.map_err(|e| format!("Ошибка парсинга информации о хранилище: {}", e))?;
|
||||||
|
|
||||||
|
let mut info = HashMap::new();
|
||||||
|
if let Some(obj) = about_info.as_object() {
|
||||||
|
for (key, value) in obj {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
info.insert(key.clone(), num);
|
||||||
|
} else if let Some(num) = value.as_f64() {
|
||||||
|
info.insert(key.clone(), num as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Получить версию в удобочитаемом формате
|
||||||
|
pub fn version(app: &RcloneApp) -> Result<HashMap<String, String>, String> {
|
||||||
|
let output = app.run_command(&["version"])?;
|
||||||
|
let mut version_info = HashMap::new();
|
||||||
|
|
||||||
|
for line in output.lines() {
|
||||||
|
if let Some((key, value)) = line.split_once(':') {
|
||||||
|
version_info.insert(key.trim().to_string(), value.trim().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(version_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить статистику текущих операций
|
||||||
|
pub fn stats(app: &RcloneApp) -> Result<Value, String> {
|
||||||
|
let output = app.run_command(&["core", "stats"])?;
|
||||||
|
let stats: Value =
|
||||||
|
serde_json::from_str(&output).map_err(|e| format!("Ошибка парсинга статистики: {}", e))?;
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use crate::operations::{FileInfo, SyncStats};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_info_formatting() {
|
||||||
|
let file = FileInfo {
|
||||||
|
name: "test.txt".to_string(),
|
||||||
|
path: "/path/test.txt".to_string(),
|
||||||
|
size: 1500,
|
||||||
|
is_dir: false,
|
||||||
|
modified: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(file.format_size(), "1.5 KB");
|
||||||
|
assert_eq!(file.icon(), "📄");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sync_stats_formatting() {
|
||||||
|
let stats = SyncStats {
|
||||||
|
transferred: 1024,
|
||||||
|
files: 10,
|
||||||
|
errors: 0,
|
||||||
|
checks: 100,
|
||||||
|
elapsed_time: 5.0,
|
||||||
|
transfer_speed: 2_500_000.0, // Добавлен .0 для f64
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(stats.format_speed(), "2.4 MB/s");
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Получить список всех удаленных хранилищ
|
||||||
|
pub fn list(app: &RcloneApp) -> Result<Vec<Remote>, String> {
|
||||||
|
let output = app.run_command(&["listremotes"])?;
|
||||||
|
|
||||||
|
let mut remotes = Vec::new();
|
||||||
|
for line in output.lines() {
|
||||||
|
let name = line.trim_end_matches(':');
|
||||||
|
if !name.is_empty() {
|
||||||
|
// Получаем детальную информацию о каждом remote
|
||||||
|
if let Ok(config) = get_config(app, name) {
|
||||||
|
remotes.push(config);
|
||||||
|
} else {
|
||||||
|
// Если не удалось получить конфиг, добавляем базовую информацию
|
||||||
|
remotes.push(Remote {
|
||||||
|
name: name.to_string(),
|
||||||
|
r#type: "unknown".to_string(),
|
||||||
|
config: HashMap::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(remotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить конфигурацию конкретного remote
|
||||||
|
pub fn get_config(app: &RcloneApp, remote_name: &str) -> Result<Remote, String> {
|
||||||
|
let output = app.run_command(&["config", "dump"])?;
|
||||||
|
|
||||||
|
let configs: Value = serde_json::from_str(&output)
|
||||||
|
.map_err(|e| format!("Ошибка парсинга конфигурации: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(remote_config) = configs.get(remote_name) {
|
||||||
|
let r#type = remote_config
|
||||||
|
.get("type")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut config_map = HashMap::new();
|
||||||
|
if let Some(obj) = remote_config.as_object() {
|
||||||
|
for (key, value) in obj {
|
||||||
|
if key != "type" {
|
||||||
|
if let Some(val_str) = value.as_str() {
|
||||||
|
config_map.insert(key.clone(), val_str.to_string());
|
||||||
|
} else {
|
||||||
|
config_map.insert(key.clone(), value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Remote {
|
||||||
|
name: remote_name.to_string(),
|
||||||
|
r#type,
|
||||||
|
config: config_map,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(format!("Remote '{}' не найден", remote_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Создать новое удаленное хранилище
|
||||||
|
pub fn create(
|
||||||
|
app: &RcloneApp,
|
||||||
|
name: &str,
|
||||||
|
r#type: &str,
|
||||||
|
config: &HashMap<String, String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut args = vec!["config", "create", name, r#type];
|
||||||
|
|
||||||
|
for (key, value) in config {
|
||||||
|
args.push(key.as_str());
|
||||||
|
args.push(value.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
app.run_command(&args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обновить конфигурацию remote
|
||||||
|
pub fn update(app: &RcloneApp, name: &str, config: &HashMap<String, String>) -> Result<(), String> {
|
||||||
|
let mut args = vec!["config", "update", name];
|
||||||
|
|
||||||
|
for (key, value) in config {
|
||||||
|
args.push(key.as_str());
|
||||||
|
args.push(value.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
app.run_command(&args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Удалить remote
|
||||||
|
pub fn delete(app: &RcloneApp, name: &str) -> Result<(), String> {
|
||||||
|
app.run_command(&["config", "delete", name])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверить доступность remote
|
||||||
|
pub fn check(app: &RcloneApp, remote_name: &str) -> Result<bool, String> {
|
||||||
|
let result = app.run_command(&["lsd", remote_name, "--max-depth", "1"]);
|
||||||
|
match result {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(e) => {
|
||||||
|
if e.contains("directory not found") {
|
||||||
|
Ok(true) // Remote существует, но пустой
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Найти файлы по имени
|
||||||
|
pub fn by_name(
|
||||||
|
app: &RcloneApp,
|
||||||
|
path: &str,
|
||||||
|
pattern: &str,
|
||||||
|
options: &FindOptions,
|
||||||
|
) -> Result<Vec<FileInfo>, String> {
|
||||||
|
let mut args = vec!["lsjson", path];
|
||||||
|
|
||||||
|
if options.recursive {
|
||||||
|
args.push("-R");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = app.run_command(&args)?;
|
||||||
|
let files: Vec<Value> =
|
||||||
|
serde_json::from_str(&output).map_err(|e| format!("Ошибка парсинга: {}", e))?;
|
||||||
|
|
||||||
|
let pattern = pattern.to_lowercase();
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for item in files {
|
||||||
|
let name = item["Name"].as_str().unwrap_or("");
|
||||||
|
let path = item["Path"].as_str().unwrap_or("");
|
||||||
|
|
||||||
|
if name.to_lowercase().contains(&pattern) || path.to_lowercase().contains(&pattern) {
|
||||||
|
results.push(FileInfo {
|
||||||
|
name: name.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
size: item["Size"].as_u64().unwrap_or(0),
|
||||||
|
is_dir: item["IsDir"].as_bool().unwrap_or(false),
|
||||||
|
modified: item["ModTime"].as_str().map(String::from),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if results.len() >= options.max_results {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::rclone_install::RcloneApp;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Скопировать файлы/директории
|
||||||
|
pub fn copy(
|
||||||
|
app: &RcloneApp,
|
||||||
|
source: &str,
|
||||||
|
dest: &str,
|
||||||
|
options: &CopyOptions,
|
||||||
|
) -> Result<SyncStats, String> {
|
||||||
|
let mut args = vec!["copy", source, dest, "--progress", "--stats-one-line"];
|
||||||
|
|
||||||
|
if options.verbose {
|
||||||
|
args.push("-v");
|
||||||
|
}
|
||||||
|
if options.dry_run {
|
||||||
|
args.push("--dry-run");
|
||||||
|
}
|
||||||
|
if let Some(bandwidth) = &options.bandwidth_limit {
|
||||||
|
args.push("--bwlimit");
|
||||||
|
args.push(bandwidth.as_str());
|
||||||
|
}
|
||||||
|
if options.no_traverse {
|
||||||
|
args.push("--no-traverse");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = app.run_command(&args)?;
|
||||||
|
parse_sync_stats(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Синхронизировать директории (dest станет идентичной source)
|
||||||
|
pub fn sync(
|
||||||
|
app: &RcloneApp,
|
||||||
|
source: &str,
|
||||||
|
dest: &str,
|
||||||
|
options: &SyncOptions,
|
||||||
|
) -> Result<SyncStats, String> {
|
||||||
|
let mut args = vec!["sync", source, dest, "--progress", "--stats-one-line"];
|
||||||
|
|
||||||
|
if options.verbose {
|
||||||
|
args.push("-v");
|
||||||
|
}
|
||||||
|
if options.dry_run {
|
||||||
|
args.push("--dry-run");
|
||||||
|
}
|
||||||
|
if options.delete_excluded {
|
||||||
|
args.push("--delete-excluded");
|
||||||
|
}
|
||||||
|
if let Some(bandwidth) = &options.bandwidth_limit {
|
||||||
|
args.push("--bwlimit");
|
||||||
|
args.push(bandwidth.as_str());
|
||||||
|
}
|
||||||
|
if options.existing_only {
|
||||||
|
args.push("--existing-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = app.run_command(&args)?;
|
||||||
|
parse_sync_stats(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Переместить файлы/директории
|
||||||
|
pub fn move_files(
|
||||||
|
app: &RcloneApp,
|
||||||
|
source: &str,
|
||||||
|
dest: &str,
|
||||||
|
options: &MoveOptions,
|
||||||
|
) -> Result<SyncStats, String> {
|
||||||
|
let mut args = vec!["move", source, dest, "--progress", "--stats-one-line"];
|
||||||
|
|
||||||
|
if options.verbose {
|
||||||
|
args.push("-v");
|
||||||
|
}
|
||||||
|
if options.dry_run {
|
||||||
|
args.push("--dry-run");
|
||||||
|
}
|
||||||
|
if options.delete_empty_src_dirs {
|
||||||
|
args.push("--delete-empty-src-dirs");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = app.run_command(&args)?;
|
||||||
|
parse_sync_stats(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Удалить файлы/директории
|
||||||
|
pub fn delete(app: &RcloneApp, path: &str, options: &DeleteOptions) -> Result<SyncStats, String> {
|
||||||
|
let command = if options.recursive { "purge" } else { "delete" };
|
||||||
|
|
||||||
|
let mut args = vec![command, path, "--progress"];
|
||||||
|
|
||||||
|
if options.verbose {
|
||||||
|
args.push("-v");
|
||||||
|
}
|
||||||
|
if options.dry_run {
|
||||||
|
args.push("--dry-run");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = app.run_command(&args)?;
|
||||||
|
parse_sync_stats(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Парсинг статистики синхронизации из вывода
|
||||||
|
fn parse_sync_stats(output: &str) -> Result<SyncStats, String> {
|
||||||
|
let mut stats = SyncStats {
|
||||||
|
transferred: 0,
|
||||||
|
files: 0,
|
||||||
|
errors: 0,
|
||||||
|
checks: 0,
|
||||||
|
elapsed_time: 0.0,
|
||||||
|
transfer_speed: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Парсим последнюю строку со статистикой
|
||||||
|
for line in output.lines().rev() {
|
||||||
|
if line.contains("Transferred:") {
|
||||||
|
// Пример: "Transferred: 5 / 5, 100%, 1.234 MB/s, ETA 0s"
|
||||||
|
if let Some(speed_part) = line.split(',').nth(2) {
|
||||||
|
if speed_part.contains("MB/s") {
|
||||||
|
let speed_str = speed_part.trim().replace("MB/s", "").trim().to_string();
|
||||||
|
if let Ok(speed_mb) = speed_str.parse::<f64>() {
|
||||||
|
stats.transfer_speed = speed_mb * 1024.0 * 1024.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить список файлов, которые будут затронуты при синхронизации (dry-run)
|
||||||
|
pub fn preview(app: &RcloneApp, source: &str, dest: &str) -> Result<Vec<String>, String> {
|
||||||
|
let output = app.run_command(&["copy", source, dest, "--dry-run", "--progress"])?;
|
||||||
|
|
||||||
|
let mut files = Vec::new();
|
||||||
|
for line in output.lines() {
|
||||||
|
if line.contains(":") && !line.contains("Transferred:") && !line.contains("Errors:") {
|
||||||
|
files.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
@@ -206,7 +206,7 @@ impl RcloneApp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_command(&self, args: &[&str]) -> Result<String, String> {
|
pub fn run_command(&self, args: &[&str]) -> Result<String, String> {
|
||||||
let output = Command::new(&self.rclone_path)
|
let output = Command::new(&self.rclone_path)
|
||||||
.args(args)
|
.args(args)
|
||||||
.output()
|
.output()
|
||||||
|
|||||||
Reference in new issue
Block a user