1- use async_compat:: Compat ;
1+ use std:: { path:: Path , time:: Duration } ;
2+
23use directories:: ProjectDirs ;
34use slint:: PlatformError ;
45use 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}
0 commit comments