zng_ext_single_instance/
lib.rs1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9
10use std::{
11 io::{Read, Write},
12 time::Duration,
13};
14
15use zng_app::{
16 AppExtension,
17 event::{event, event_args},
18 handler::{async_hn, clmv},
19};
20use zng_ext_fs_watcher::WATCHER;
21use zng_txt::{ToTxt, Txt};
22
23#[derive(Default)]
31#[non_exhaustive]
32pub struct SingleInstanceManager {}
33impl AppExtension for SingleInstanceManager {
34 fn init(&mut self) {
35 let args: Box<[_]> = std::env::args().map(Txt::from).collect();
36 APP_INSTANCE_EVENT.notify(AppInstanceArgs::now(args, 0usize));
37
38 let name = match SINGLE_INSTANCE.lock().as_ref().map(|l| l.name.clone()) {
39 Some(n) => n,
40 None => return, };
42
43 let args_file = std::env::temp_dir().join(name);
44 let mut count = 1usize;
45 WATCHER
46 .on_file_changed(
47 &args_file,
48 async_hn!(args_file, |_| {
49 let args = zng_task::wait(clmv!(args_file, || {
50 for i in 0..5 {
51 if i > 0 {
52 std::thread::sleep(Duration::from_millis(200));
53 }
54
55 match std::fs::File::options().read(true).write(true).open(&args_file) {
58 Ok(mut file) => {
59 let mut s = String::new();
60 if let Err(e) = file.read_to_string(&mut s) {
61 tracing::error!("error reading args (retry {i}), {e}");
62 continue;
63 }
64 file.set_len(0).unwrap();
65 return s;
66 }
67 Err(e) => {
68 if e.kind() == std::io::ErrorKind::NotFound {
69 return String::new();
70 }
71 tracing::error!("error reading args (retry {i}), {e}")
72 }
73 }
74 }
75 String::new()
76 }))
77 .await;
78
79 for line in args.lines() {
81 let line = line.trim();
82 if line.is_empty() {
83 continue;
84 }
85
86 let args = match serde_json::from_str::<Box<[Txt]>>(line) {
87 Ok(args) => args,
88 Err(e) => {
89 tracing::error!("invalid args, {e}");
90 Box::new([])
91 }
92 };
93
94 APP_INSTANCE_EVENT.notify(AppInstanceArgs::now(args, count));
95
96 count += 1;
97 }
98 }),
99 )
100 .perm();
101 }
102}
103
104event_args! {
105 pub struct AppInstanceArgs {
107 pub args: Box<[Txt]>,
111
112 pub count: usize,
115
116 ..
117
118 fn delivery_list(&self, _list: &mut UpdateDeliveryList) {}
119 }
120}
121impl AppInstanceArgs {
122 pub fn is_current(&self) -> bool {
126 self.count == 0
127 }
128}
129
130event! {
131 pub static APP_INSTANCE_EVENT: AppInstanceArgs;
136}
137
138zng_env::on_process_start!(|args| {
139 if args.next_handlers_count > 0 && args.yield_count < zng_env::ProcessStartArgs::MAX_YIELD_COUNT {
140 return args.yield_once();
142 }
143
144 let mut lock = SINGLE_INSTANCE.lock();
145 assert!(lock.is_none(), "single_instance already called in this process");
146
147 let name = std::env::current_exe()
148 .and_then(dunce::canonicalize)
149 .expect("current exe is required")
150 .display()
151 .to_txt();
152 let name: String = name
153 .chars()
154 .map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '_' })
155 .collect();
156 let mut name = name.as_str();
157 if name.len() > 128 {
158 name = &name[name.len() - 128..];
159 }
160 let name = zng_txt::formatx!("zng-si-{name}");
161
162 let l = single_instance::SingleInstance::new(&name).expect("failed to create single instance lock");
163
164 if l.is_single() {
165 *lock = Some(SingleInstanceData { _lock: l, name });
166 } else {
167 tracing::info!("another instance running, will send args and exit");
168
169 let args: Box<[_]> = std::env::args().collect();
170 let args = format!("\n{}\n", serde_json::to_string(&args).unwrap());
171
172 let try_write = move || -> std::io::Result<()> {
173 let mut file = std::fs::File::options()
174 .create(true)
175 .append(true)
176 .open(std::env::temp_dir().join(name.as_str()))?;
177 file.write_all(args.as_bytes())
178 };
179
180 for i in 0..5 {
181 if i > 0 {
182 std::thread::sleep(std::time::Duration::from_millis(300));
183 }
184 match try_write() {
185 Ok(_) => zng_env::exit(0),
186 Err(e) => {
187 eprintln!("error writing args (retries: {i}), {e}");
188 }
189 }
190 }
191 zng_env::exit(1);
192 }
193});
194
195struct SingleInstanceData {
196 _lock: single_instance::SingleInstance,
197 name: Txt,
198}
199
200static SINGLE_INSTANCE: parking_lot::Mutex<Option<SingleInstanceData>> = parking_lot::Mutex::new(None);