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