fix: Single-Instance per User (Terminalserver-kompatibel)

Lock-File liegt in %APPDATA% (Windows) bzw ~/.config (Linux) -
das ist pro User verschieden. Auf Terminalservern kann jeder
User seine eigene Instanz haben.

Verbesserungen:
- Prueft ob der Prozess aus dem Lock-File noch lebt (PID-Check)
  statt nur ob die Datei existiert
- Windows: tasklist /FI "PID eq X"
- Linux: /proc/PID existiert?
- Stale Lock-Files (Prozess abgestuerzt) werden ueberschrieben
- Ohne .cloud Argument + andere Instanz laeuft -> sofort beenden
- Mit .cloud Argument + andere Instanz -> delegieren und beenden

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-12 01:28:39 +02:00
parent 4cc8de8a1a
commit b653f9657a
+39 -11
View File
@@ -526,6 +526,8 @@ fn start_background_sync(
// --- App Setup --- // --- App Setup ---
/// Check if another instance is running. If yes, pass the .cloud file to it and exit. /// Check if another instance is running. If yes, pass the .cloud file to it and exit.
/// Single instance per user. On terminal servers each user gets their own
/// instance because the lock file is in %APPDATA% (user-specific).
fn handle_single_instance() { fn handle_single_instance() {
let config_dir = dirs::config_dir() let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(".")) .unwrap_or_else(|| PathBuf::from("."))
@@ -535,22 +537,48 @@ fn handle_single_instance() {
let lock_file = config_dir.join("instance.lock"); let lock_file = config_dir.join("instance.lock");
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
// If we have a .cloud file argument and another instance is running // Check if another instance of THIS USER is running
if args.len() > 1 && args[1].ends_with(".cloud") { let other_running = if lock_file.exists() {
if lock_file.exists() { if let Ok(pid_str) = std::fs::read_to_string(&lock_file) {
// Write the .cloud path to a request file for the running instance let pid = pid_str.trim().parse::<u32>().unwrap_or(0);
let request_file = config_dir.join("open_request.txt"); if pid > 0 && pid != std::process::id() {
std::fs::write(&request_file, &args[1]).ok(); // Check if that process is still alive
eprintln!("[SingleInstance] Passing {} to running instance", args[1]); #[cfg(target_os = "windows")]
std::process::exit(0); {
} std::process::Command::new("tasklist")
.args(["/FI", &format!("PID eq {}", pid), "/NH"])
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).contains(&pid.to_string()))
.unwrap_or(false)
}
#[cfg(not(target_os = "windows"))]
{
std::path::Path::new(&format!("/proc/{}", pid)).exists()
}
} else { false }
} else { false }
} else { false };
// If .cloud file argument and another instance runs -> delegate and exit
if args.len() > 1 && args[1].ends_with(".cloud") && other_running {
let request_file = config_dir.join("open_request.txt");
std::fs::write(&request_file, &args[1]).ok();
eprintln!("[SingleInstance] Delegated {} to running instance (PID in lock)", args[1]);
std::process::exit(0);
} }
// Write our lock file // If no .cloud argument but another instance runs -> just bring it to front and exit
std::fs::write(&lock_file, std::process::id().to_string()).ok(); if other_running && args.len() <= 1 {
eprintln!("[SingleInstance] Already running, exiting");
std::process::exit(0);
}
// We are the main instance - write our PID
std::fs::write(&lock_file, std::process::id().to_string()).ok();
} }
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
handle_single_instance(); handle_single_instance();