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
1 changed files with 39 additions and 11 deletions

View File

@ -526,6 +526,8 @@ fn start_background_sync(
// --- App Setup ---
/// 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() {
let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
@ -535,22 +537,48 @@ fn handle_single_instance() {
let lock_file = config_dir.join("instance.lock");
let args: Vec<String> = std::env::args().collect();
// If we have a .cloud file argument and another instance is running
if args.len() > 1 && args[1].ends_with(".cloud") {
if lock_file.exists() {
// Write the .cloud path to a request file for the running instance
let request_file = config_dir.join("open_request.txt");
std::fs::write(&request_file, &args[1]).ok();
eprintln!("[SingleInstance] Passing {} to running instance", args[1]);
std::process::exit(0);
}
// Check if another instance of THIS USER is running
let other_running = if lock_file.exists() {
if let Ok(pid_str) = std::fs::read_to_string(&lock_file) {
let pid = pid_str.trim().parse::<u32>().unwrap_or(0);
if pid > 0 && pid != std::process::id() {
// Check if that process is still alive
#[cfg(target_os = "windows")]
{
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
std::fs::write(&lock_file, std::process::id().to_string()).ok();
// If no .cloud argument but another instance runs -> just bring it to front and exit
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)]
pub fn run() {
handle_single_instance();