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")))]
15
16use std::{
17 io::{Read, Write},
18 time::Duration,
19};
20
21use zng_app::{
22 event::{event, event_args},
23 handler::{async_hn, clmv},
24};
25use zng_ext_fs_watcher::WATCHER;
26use zng_txt::{ToTxt, Txt};
27
28event_args! {
29 pub struct AppInstanceArgs {
31 pub args: Box<[Txt]>,
35
36 pub count: usize,
39
40 ..
41
42 fn is_in_target(&self, _id: WidgetId) -> bool {
43 false
44 }
45 }
46}
47impl AppInstanceArgs {
48 pub fn is_current(&self) -> bool {
52 self.count == 0
53 }
54}
55
56event! {
57 pub static APP_INSTANCE_EVENT: AppInstanceArgs;
62}
63
64zng_env::on_process_start!(|args| {
65 if zng_env::about().is_test {
66 tracing::debug!("ignoring single_instance because is test process");
67 return;
68 }
69
70 if args.next_handlers_count > 0 && args.yield_count < zng_env::ProcessStartArgs::MAX_YIELD_COUNT {
71 return args.yield_once();
73 }
74
75 let mut lock = SINGLE_INSTANCE.lock();
76 assert!(lock.is_none(), "single_instance already called in this process");
77
78 let name = std::env::current_exe()
79 .and_then(dunce::canonicalize)
80 .expect("current exe is required")
81 .display()
82 .to_txt();
83 let name: String = name
84 .chars()
85 .map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '_' })
86 .collect();
87 let mut name = name.as_str();
88 if name.len() > 128 {
89 name = &name[name.len() - 128..];
90 }
91 let name = zng_txt::formatx!("zng-si-{name}");
92
93 let l = single_instance::SingleInstance::new(&name).expect("failed to create single instance lock");
94
95 if l.is_single() {
96 *lock = Some(SingleInstanceData::new(l, name));
97 } else {
98 tracing::info!("another instance running, will send args and exit");
99
100 let args: Box<[_]> = std::env::args().collect();
101 let args = format!("\n{}\n", serde_json::to_string(&args).unwrap());
102
103 let try_write = move || -> std::io::Result<()> {
104 let mut file = std::fs::File::options()
105 .create(true)
106 .append(true)
107 .open(std::env::temp_dir().join(name.as_str()))?;
108 file.write_all(args.as_bytes())
109 };
110
111 for i in 0..5 {
112 if i > 0 {
113 std::thread::sleep(std::time::Duration::from_millis(300));
114 }
115 match try_write() {
116 Ok(_) => zng_env::exit(0),
117 Err(e) => {
118 eprintln!("error writing args (retries: {i}), {e}");
119 }
120 }
121 }
122 zng_env::exit(1);
123 }
124});
125
126struct SingleInstanceData {
127 _lock: single_instance::SingleInstance,
128 name: Txt,
129}
130
131static SINGLE_INSTANCE: parking_lot::Mutex<Option<SingleInstanceData>> = parking_lot::Mutex::new(None);
132
133impl SingleInstanceData {
134 fn new(_lock: single_instance::SingleInstance, name: Txt) -> Self {
135 let s = Self { _lock, name };
136
137 let args: Box<[_]> = std::env::args().map(Txt::from).collect();
138 APP_INSTANCE_EVENT.notify(AppInstanceArgs::now(args, 0usize));
139
140 let args_file = std::env::temp_dir().join(&s.name);
141 let mut count = 1usize;
142 WATCHER
143 .on_file_changed(
144 &args_file,
145 true,
146 async_hn!(args_file, |_| {
147 let args = zng_task::wait(clmv!(args_file, || {
148 for i in 0..5 {
149 if i > 0 {
150 std::thread::sleep(Duration::from_millis(200));
151 }
152
153 match std::fs::File::options().read(true).write(true).open(&args_file) {
156 Ok(mut file) => {
157 let mut s = String::new();
158 if let Err(e) = file.read_to_string(&mut s) {
159 tracing::error!("error reading args (retry {i}), {e}");
160 continue;
161 }
162 file.set_len(0).unwrap();
163 return s;
164 }
165 Err(e) => {
166 if e.kind() == std::io::ErrorKind::NotFound {
167 return String::new();
168 }
169 tracing::error!("error reading args (retry {i}), {e}")
170 }
171 }
172 }
173 String::new()
174 }))
175 .await;
176
177 for line in args.lines() {
179 let line = line.trim();
180 if line.is_empty() {
181 continue;
182 }
183
184 let args = match serde_json::from_str::<Box<[Txt]>>(line) {
185 Ok(args) => args,
186 Err(e) => {
187 tracing::error!("invalid args, {e}");
188 Box::new([])
189 }
190 };
191
192 APP_INSTANCE_EVENT.notify(AppInstanceArgs::now(args, count));
193
194 count += 1;
195 }
196 }),
197 )
198 .perm();
199
200 s
201 }
202}