zookeeper + cleanup

This commit is contained in:
FTCHD
2026-07-10 14:41:22 +03:00
parent 44e71643cd
commit 02bfdb8ddf
72 changed files with 6111 additions and 5335 deletions
+79 -54
View File
@@ -258,6 +258,7 @@ dependencies = [
"sentry",
"serde",
"serde_json",
"sha2",
"sysinfo",
"tar",
"tauri",
@@ -276,11 +277,12 @@ dependencies = [
"tauri-plugin-prevent-default",
"tauri-plugin-process",
"tauri-plugin-sentry",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
"tauri-plugin-store",
"tauri-plugin-updater",
"tinyfiledialogs-rs",
"windows-sys 0.59.0",
"winreg 0.52.0",
"x11rb",
"zbus",
"zip 0.6.6",
@@ -5294,44 +5296,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shared_child"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
dependencies = [
"libc",
"sigchld",
"windows-sys 0.60.2",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "sigchld"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
dependencies = [
"libc",
"os_pipe",
"signal-hook",
]
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
@@ -6101,27 +6071,6 @@ dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "tauri-plugin-shell"
version = "2.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b"
dependencies = [
"encoding_rs",
"log",
"open",
"os_pipe",
"regex",
"schemars 0.8.22",
"serde",
"serde_json",
"shared_child",
"tauri",
"tauri-plugin",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.4.2"
@@ -7599,6 +7548,15 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -7650,6 +7608,21 @@ dependencies = [
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -7707,6 +7680,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -7725,6 +7704,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -7743,6 +7728,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -7773,6 +7764,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -7791,6 +7788,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -7809,6 +7812,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -7827,6 +7836,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@@ -7872,6 +7887,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.55.0"
+5 -1
View File
@@ -24,7 +24,6 @@ log = "0.4"
tauri = { version = "2.11.1", features = [ "tray-icon", "image-ico",
"image-png", "config-json5" ] }
tauri-plugin-log = "2.8.0"
tauri-plugin-shell = "2.3.5"
tauri-plugin-dialog = "2.7.1"
tauri-plugin-fs = "2.5.1"
tauri-plugin-opener = "2.5.4"
@@ -50,6 +49,7 @@ tinyfiledialogs = { package = "tinyfiledialogs-rs", version = "3.21.3", features
tauri-plugin-deep-link = "2.4.9"
flate2 = "1.1.9"
tar = "0.4.45"
sha2 = "0.10"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26"
@@ -59,3 +59,7 @@ objc = "0.2"
zbus = { version = "5" }
x11rb = "0.13"
gtk = "0.18"
[target.'cfg(target_os = "windows")'.dependencies]
winreg = "0.52"
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging"] }
-40
View File
@@ -168,46 +168,6 @@
"core:webview:allow-internal-toggle-devtools",
"core:webview:allow-get-all-webviews",
"clipboard-manager:allow-write-text",
"shell:default",
"shell:allow-kill",
"shell:allow-spawn",
"shell:allow-stdin-write",
"shell:allow-open",
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "rclone-system",
"cmd": "rclone",
"args": true
},
{
"name": "rclone-internal",
"cmd": "$APPLOCALDATA/rclone",
"args": true
},
{
"name": "umount",
"cmd": "umount",
"args": true
}
]
},
{
"identifier": "shell:allow-spawn",
"allow": [
{
"name": "rclone-system",
"cmd": "rclone",
"args": true
},
{
"name": "rclone-internal",
"cmd": "$APPLOCALDATA/rclone",
"args": true
}
]
},
"log:default",
"dialog:default",
{
-5
View File
@@ -18,7 +18,6 @@ pub fn make_transparent(window: &WebviewWindow) -> Result<(), tauri::Error> {
let ns_window: id = msg_send![webview_obj, window];
let bg_color = NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0.0, 0.0, 0.0, 0.0);
let _: id = msg_send![ns_window, setBackgroundColor: bg_color];
// let _: () = msg_send![ns_window, setIgnoresMouseEvents:true];
})?;
Ok(())
@@ -155,8 +154,6 @@ pub async fn open_window(
) -> Result<(), String> {
if let Some(existing) = app_handle.get_webview_window(&name) {
existing.set_focus().map_err(|e| e.to_string())?;
#[cfg(target_os = "linux")]
focus_window_linux(&app_handle, &existing);
return Ok(());
}
@@ -286,8 +283,6 @@ pub async fn open_small_window(
return Ok(());
}
let os = std::env::consts::OS;
let mut builder = WebviewWindowBuilder::new(&app_handle, &name, WebviewUrl::App(url.into()))
.title(&name)
.inner_size(800.0, 500.0)
+32 -171
View File
@@ -1,12 +1,10 @@
use machine_uid;
use sentry;
use std::fs::{self, File};
use std::path::Path;
use std::fs;
use sysinfo::System;
use tauri::{AppHandle, Emitter, Manager};
use tauri::{AppHandle, Manager};
use tauri_plugin_sentry;
use tinyfiledialogs as tfd;
use zip::ZipArchive;
#[path = "../common/shortcut.rs"]
mod shortcut;
@@ -14,6 +12,8 @@ mod shortcut;
#[path = "../common/window.rs"]
mod window;
mod zookeeper;
use shortcut::{
ensure_toolbar_window, set_toolbar_shortcut, show_toolbar_window, DEFAULT_TOOLBAR_SHORTCUT,
};
@@ -127,48 +127,7 @@ fn has_flatpak_permissions() -> bool {
false
}
#[tauri::command]
fn unzip_file(zip_path: &str, output_folder: &str) -> Result<(), String> {
// Open the zip file
let file = File::open(zip_path).map_err(|e| e.to_string())?;
// Create output directory if it doesn't exist
fs::create_dir_all(output_folder).map_err(|e| e.to_string())?;
// Create ZIP archive reader
let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
// Extract everything
for i in 0..archive.len() {
let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
let outpath = Path::new(output_folder).join(file.name());
if file.name().ends_with('/') || file.name().ends_with('\\') {
fs::create_dir_all(&outpath).map_err(|e| e.to_string())?;
} else {
if let Some(p) = outpath.parent() {
fs::create_dir_all(p).map_err(|e| e.to_string())?;
}
let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
}
// Get and set permissions (Unix only)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))
.map_err(|e| e.to_string())?;
}
}
}
Ok(())
}
#[tauri::command]
async fn stop_pid(pid: u32, timeout_ms: Option<u64>) -> Result<(), String> {
pub(crate) async fn kill_pid(pid: u32, timeout_ms: Option<u64>) -> Result<(), String> {
let timeout = timeout_ms.unwrap_or(5000);
#[cfg(any(
@@ -325,7 +284,7 @@ async fn stop_rclone_processes(timeout_ms: Option<u64>) -> Result<u32, String> {
let mut stopped: u32 = 0;
for pid in pids {
match stop_pid(pid, Some(timeout)).await {
match kill_pid(pid, Some(timeout)).await {
Ok(()) => stopped += 1,
Err(_e) => {}
}
@@ -334,10 +293,6 @@ async fn stop_rclone_processes(timeout_ms: Option<u64>) -> Result<u32, String> {
Ok(stopped)
}
async fn prompt_password(title: String, message: String) -> Result<Option<String>, String> {
prompt_text(title, message, None, Some(true)).await
}
async fn prompt_text(
title: String,
message: String,
@@ -559,6 +514,7 @@ async fn start_cloudflared_tunnel(app: tauri::AppHandle) -> Result<(u32, String)
// Start cloudflared tunnel
let mut child = SysCommand::new(&cloudflared_path)
// keep in sync with RC_PORT in lib/hosts.ts
.args(&["tunnel", "--url", "http://localhost:5572"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
@@ -602,7 +558,7 @@ async fn start_cloudflared_tunnel(app: tauri::AppHandle) -> Result<(u32, String)
}
// If we didn't get a URL, kill the process and return error
let _ = stop_pid(pid, Some(2000)).await;
let _ = kill_pid(pid, Some(2000)).await;
Err("Failed to get tunnel URL from cloudflared".to_string())
}
@@ -611,7 +567,7 @@ async fn stop_cloudflared_tunnel(pid: u32) -> Result<(), String> {
use std::time::Duration;
// Cloudflared takes ~5s to gracefully shut down, so give it enough time
match stop_pid(pid, Some(6000)).await {
match kill_pid(pid, Some(6000)).await {
Ok(()) => Ok(()),
Err(e) => {
// Wait a bit for the process to fully terminate
@@ -752,119 +708,6 @@ async fn test_proxy_connection(proxy_url: String) -> Result<String, String> {
Err(last_error.unwrap_or_else(|| "All proxy tests failed".to_string()))
}
#[tauri::command]
async fn update_system_rclone() -> Result<i32, String> {
#[cfg(target_os = "macos")]
{
use std::process::Command as SysCommand;
fn quote_posix(value: &str) -> String {
let escaped = value.replace("'", "'\\''");
format!("'{}'", escaped)
}
let mut cmdline =
String::from("PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH; ");
cmdline.push_str(&quote_posix("rclone"));
cmdline.push(' ');
cmdline.push_str(&quote_posix("selfupdate"));
// Escape for embedding inside an AppleScript string literal
let applescript_cmd = cmdline.replace('\\', "\\\\").replace('"', "\\\"");
let prompt = "Rclone UI needs permission to run rclone selfupdate.";
let script = format!(
"do shell script \"{}\" with administrator privileges with prompt \"{}\"",
applescript_cmd,
prompt.replace('"', "\\\"")
);
let status = SysCommand::new("osascript")
.arg("-e")
.arg(script)
.status()
.map_err(|e| e.to_string())?;
return Ok(status.code().unwrap_or(0));
}
#[cfg(target_os = "linux")]
{
use std::process::Command as SysCommand;
let path_env =
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin";
// Try PolicyKit first (graphical auth prompt on most desktops)
let mut pkexec_args: Vec<String> = Vec::new();
pkexec_args.push("--description".to_string());
pkexec_args.push("Rclone UI needs to run rclone selfupdate".to_string());
pkexec_args.push("env".to_string());
pkexec_args.push(path_env.to_string());
pkexec_args.push("rclone".to_string());
pkexec_args.push("selfupdate".to_string());
match SysCommand::new("pkexec").args(&pkexec_args).status() {
Ok(status) => return Ok(status.code().unwrap_or(0)),
Err(_e) => {
// Fallback to sudo with custom prompt (works if the user has NOPASSWD or cached credentials)
let mut sudo_env = std::collections::HashMap::new();
sudo_env.insert("SUDO_PROMPT", "Rclone UI needs permission to run rclone selfupdate. Please enter your password: ");
let mut sudo_args: Vec<String> = Vec::new();
sudo_args.push("-n".to_string());
sudo_args.push("env".to_string());
sudo_args.push(path_env.to_string());
sudo_args.push("rclone".to_string());
sudo_args.push("selfupdate".to_string());
let status = SysCommand::new("sudo")
.envs(&sudo_env)
.args(&sudo_args)
.status()
.map_err(|e| e.to_string())?;
return Ok(status.code().unwrap_or(0));
}
}
}
#[cfg(target_os = "windows")]
{
use std::process::Command as SysCommand;
fn quote_ps(value: &str) -> String {
// PowerShell single-quote escaping: ' -> ''
format!("'{}'", value.replace('\'', "''"))
}
let file_path = quote_ps("rclone");
let arg_list = String::from("@('selfupdate')");
let ps_script = format!(
"$p = Start-Process -Verb RunAs -WindowStyle Hidden -PassThru -FilePath {file} -ArgumentList {args}; \n\
$p.WaitForExit();\n\
exit $p.ExitCode",
file = file_path,
args = arg_list
);
let status = SysCommand::new("powershell")
.args([
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
&ps_script,
])
.status()
.map_err(|e| e.to_string())?;
return Ok(status.code().unwrap_or(0));
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
Err("Unsupported platform".to_string())
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let client = sentry::init((
@@ -887,6 +730,9 @@ pub fn run() {
}
let mut app = builder
.manage::<zookeeper::SharedDaemonState>(std::sync::Mutex::new(
zookeeper::DaemonState::default(),
))
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_sentry::init_with_no_injection(&client))
.plugin(tauri_plugin_clipboard_manager::init())
@@ -903,21 +749,17 @@ pub fn run() {
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_log::Builder::new().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_prevent_default::debug())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.invoke_handler(tauri::generate_handler![
unzip_file,
get_arch,
get_uid,
is_rclone_running,
stop_rclone_processes,
prompt,
stop_pid,
update_toolbar_shortcut,
show_toolbar,
update_system_rclone,
test_proxy_connection,
is_flatpak,
is_linux_mint,
@@ -929,7 +771,22 @@ pub fn run() {
unlock_windows,
start_cloudflared_tunnel,
stop_cloudflared_tunnel,
extract_tgz
extract_tgz,
zookeeper::exec_rclone,
zookeeper::spawn_rclone,
zookeeper::kill_rclone_daemon,
zookeeper::validate_rclone_binary,
zookeeper::rclone_config_path,
zookeeper::find_system_rclone,
zookeeper::classify_rclone_path,
zookeeper::list_downloaded_rclone_versions,
zookeeper::delete_rclone_version,
zookeeper::adopt_legacy_rclone,
zookeeper::managed_version_path,
zookeeper::download_rclone_version,
zookeeper::update_path_pointer,
zookeeper::get_rclone_path_integration,
zookeeper::set_rclone_path_integration
])
.setup(|app| {
#[cfg(target_os = "linux")]
@@ -962,6 +819,10 @@ pub fn run() {
}
}
// Reclaim leftover .tmp-* download staging dirs from an interrupted download. Runs
// once here (before any webview) so it can never race a live download.
zookeeper::sweep_versions_tmp(app.handle());
if let Err(err) = ensure_toolbar_window(&app.handle()) {
log::warn!("failed to prepare toolbar window: {}", err);
}
File diff suppressed because it is too large Load Diff