Skip to content

Commit 05fdcf2

Browse files
YesecCopilotReverier-Xu
authored
🐛 fix stale desktop lock recovery (#33)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Reverier Xu <reverier.xu@xdsec.club>
1 parent 346bd8f commit 05fdcf2

2 files changed

Lines changed: 94 additions & 40 deletions

File tree

crates/desktop/src/launcher.rs

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use async_compat::Compat;
1+
use std::{path::Path, time::Duration};
2+
23
use directories::ProjectDirs;
34
use slint::PlatformError;
45
use tracing::info;
@@ -17,33 +18,7 @@ pub fn setup() -> Result<MainWindow, PlatformError> {
1718
};
1819
let lock_file = proj_dirs.data_local_dir().join(".rx.is.alive");
1920

20-
if lock_file.exists() {
21-
eprintln!("Another instance of the application is already running.");
22-
let api_port = std::fs::read_to_string(&lock_file).unwrap_or_else(|_| {
23-
eprintln!("Failed to read lock file");
24-
std::fs::remove_file(&lock_file).unwrap_or_else(|_| {
25-
eprintln!("Failed to remove lock file");
26-
});
27-
std::process::exit(1);
28-
});
29-
eprintln!("Notify the other instance to raise...");
30-
slint::spawn_local(Compat::new(async move {})).expect("Failed to spawn thread");
31-
let client = reqwest::blocking::Client::new();
32-
match client
33-
.post(format!("http://127.0.0.1:{api_port}/popup"))
34-
.header("User-Agent", format!("wsrx/{}", env!("CARGO_PKG_VERSION")))
35-
.send()
36-
{
37-
Ok(_) => {
38-
eprintln!("Notification sent.");
39-
}
40-
Err(e) => {
41-
eprintln!("Failed to send notification: {e}, removing lock file.");
42-
std::fs::remove_file(&lock_file).unwrap_or_else(|_| {
43-
eprintln!("Failed to remove lock file");
44-
});
45-
}
46-
}
21+
if lock_file.exists() && try_focus_existing_instance(&lock_file) {
4722
std::process::exit(0);
4823
}
4924

@@ -64,10 +39,82 @@ pub fn setup() -> Result<MainWindow, PlatformError> {
6439
Ok(ui)
6540
}
6641

67-
pub fn shutdown(ui: &slint::Weak<MainWindow>) {
68-
let window = ui.upgrade().unwrap();
69-
bridges::settings::save_config(&window);
70-
daemon::save_scopes(ui);
42+
fn try_focus_existing_instance(lock_file: &Path) -> bool {
43+
eprintln!("Detected existing instance lock file. Trying to notify running app...");
44+
45+
let Some(api_port) = read_lock_file_port(lock_file) else {
46+
return false;
47+
};
48+
49+
match notify_existing_instance(api_port) {
50+
Ok(()) => {
51+
eprintln!("Notification sent.");
52+
true
53+
}
54+
Err(err) => {
55+
eprintln!("Failed to notify existing app: {err}. Removing stale lock file.");
56+
remove_lock_file(lock_file);
57+
false
58+
}
59+
}
60+
}
61+
62+
fn remove_lock_file(lock_file: &Path) {
63+
std::fs::remove_file(lock_file).unwrap_or_else(|err| {
64+
if err.kind() != std::io::ErrorKind::NotFound {
65+
eprintln!("Failed to remove lock file: {err}");
66+
}
67+
});
68+
}
69+
70+
fn read_lock_file_port(lock_file: &Path) -> Option<u16> {
71+
match std::fs::read_to_string(lock_file) {
72+
Ok(port) => match port.trim().parse::<u16>() {
73+
Ok(port) => Some(port),
74+
Err(err) => {
75+
eprintln!("Invalid lock file content: {err}. Removing stale lock file.");
76+
remove_lock_file(lock_file);
77+
None
78+
}
79+
},
80+
Err(err) => {
81+
eprintln!("Failed to read lock file: {err}. Removing stale lock file.");
82+
remove_lock_file(lock_file);
83+
None
84+
}
85+
}
86+
}
87+
88+
fn notify_existing_instance(api_port: u16) -> Result<(), String> {
89+
let client = reqwest::blocking::Client::builder()
90+
.no_proxy()
91+
.timeout(Duration::from_secs(2))
92+
.build()
93+
.unwrap_or_else(|err| {
94+
eprintln!(
95+
"Failed to create loopback HTTP client: {err}. Falling back to default client."
96+
);
97+
reqwest::blocking::Client::new()
98+
});
99+
100+
let response = client
101+
.post(format!("http://127.0.0.1:{api_port}/popup"))
102+
.header("User-Agent", format!("wsrx/{}", env!("CARGO_PKG_VERSION")))
103+
.send()
104+
.map_err(|err| err.to_string())?;
105+
106+
if response.status().is_success() {
107+
Ok(())
108+
} else {
109+
Err(format!("unexpected response status {}", response.status()))
110+
}
111+
}
112+
113+
pub fn cleanup_runtime_state(ui: &slint::Weak<MainWindow>) {
114+
if let Some(window) = ui.upgrade() {
115+
bridges::settings::save_config(&window);
116+
daemon::save_scopes(ui);
117+
}
71118

72119
let proj_dirs = match ProjectDirs::from("org", "xdsec", "wsrx") {
73120
Some(dirs) => dirs,
@@ -77,15 +124,20 @@ pub fn shutdown(ui: &slint::Weak<MainWindow>) {
77124
}
78125
};
79126

80-
let log_dir = proj_dirs.data_local_dir().join("logs");
127+
cleanup_runtime_files(proj_dirs.data_local_dir());
128+
}
129+
130+
pub fn shutdown(ui: &slint::Weak<MainWindow>) {
131+
cleanup_runtime_state(ui);
132+
std::process::exit(0);
133+
}
134+
135+
fn cleanup_runtime_files(data_local_dir: &Path) {
136+
let log_dir = data_local_dir.join("logs");
81137
std::fs::remove_dir_all(log_dir).unwrap_or_else(|_| {
82138
eprintln!("Failed to remove log directory");
83139
});
84140

85-
let lock_file = proj_dirs.data_local_dir().join(".rx.is.alive");
86-
std::fs::remove_file(lock_file).unwrap_or_else(|_| {
87-
eprintln!("Failed to remove lock file");
88-
});
89-
90-
std::process::exit(0);
141+
let lock_file = data_local_dir.join(".rx.is.alive");
142+
remove_lock_file(&lock_file);
91143
}

crates/desktop/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ fn main() -> Result<(), Box<dyn Error>> {
3131

3232
// Create the main window.
3333
let ui = launcher::setup()?;
34+
let ui_weak = ui.as_weak();
3435
ui.run().ok();
35-
drop(console_guard);
36+
launcher::cleanup_runtime_state(&ui_weak);
3637
drop(file_guard);
38+
drop(console_guard);
3739
drop(ui);
3840

3941
Ok(())

0 commit comments

Comments
 (0)