CH-J Toolsδιαχείριση διακομιστή πάνω από SSH
Μενού
Δημοσίευση πηγής

CH-J Tools

Περιηγηθείτε σε καταλόγους και αρχεία για μια συγκεκριμένη έκδοση εφαρμογών.

Κατεβάστε την πηγή ZIP
CH-J / Server Manager 1704 αρχεία
0.1.2-beta Προστέθηκε: · Χτισμένο:
integration.rs Προστέθηκε: Ημερομηνία που δεν έχει καταγραφεί 23,2 KB · 591 γραμμές
Κωδικός Λήψη αρχείου
1//! System entries of the helper, all per user and removable (no admin rights, no system folders):
2//! * macOS: Finder Quick Actions in `~/Library/Services/*.workflow`, login item `~/Library/LaunchAgents/*.plist`.
3//! * Windows: Explorer context menu `HKCU\Software\Classes\*\shell\CHJ.*` (in Windows 11 under "Show more
4//! options"), login item `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\CH-J-Standby-Helper`.
5//! * Linux: Nautilus and Nemo scripts, Dolphin service menu, XDG autostart entry.
6//! Menu entries only run `ch-j-standby-helper verify|sign <file>`; login items run the helper with `run`.
7use crate::settings::{Context, Settings};
8use serde_json::{json, Value};
9use std::path::{Path, PathBuf};
11pub const ACTIONS: [&str; 2] = ["verify", "sign"];
12#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
13/// LaunchAgent label, also the CFBundleIdentifier of the macOS helper bundle (`tools/mac-signing.mjs` `HELPER_BUNDLE_ID`).
14const AGENT_ID: &str = "de.ch-j.standby-helper";
16#[cfg_attr(not(any(target_os = "macos", target_os = "linux", test)), allow(dead_code))]
17fn write(path: &Path, content: &str, executable: bool) -> Result<(), String> {
18 if let Some(parent) = path.parent() {
19 std::fs::create_dir_all(parent).map_err(|e| format!("{}: {e}", parent.display()))?;
20 }
21 if std::fs::read_to_string(path).ok().as_deref() != Some(content) {
22 let temp = path.with_extension("chj-tmp");
23 std::fs::write(&temp, content).map_err(|e| format!("{}: {e}", path.display()))?;
24 std::fs::rename(&temp, path).map_err(|e| format!("{}: {e}", path.display()))?;
25 }
26 #[cfg(unix)]
27 {
28 use std::os::unix::fs::PermissionsExt;
29 let mode = if executable { 0o755 } else { 0o644 };
30 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).map_err(|e| format!("{}: {e}", path.display()))?;
31 }
32 #[cfg(not(unix))]
33 let _ = executable;
34 Ok(())
37fn remove(path: &Path) -> Result<(), String> {
38 let result = if path.is_dir() { std::fs::remove_dir_all(path) } else { std::fs::remove_file(path) };
39 match result {
40 Ok(()) => Ok(()),
41 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
42 Err(e) => Err(format!("{}: {e}", path.display())),
43 }
46pub fn xml(text: &str) -> String {
47 text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;")
50/// POSIX shell single-quoted literal.
51pub fn sh_quote(text: &str) -> String {
52 format!("'{}'", text.replace('\'', "'\\''"))
55// ---------- macOS ----------
57#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
58fn workflow_path(ctx: &Context, action: &str) -> PathBuf {
59 // Stable file names; the visible title comes from Info.plist (NSMenuItem), so a language change needs no rename.
60 ctx.home.join("Library").join("Services").join(format!("CH-J {}.workflow", if action == "verify" { "Verify" } else { "Sign" }))
63#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
64fn workflow_files(ctx: &Context, action: &str) -> (String, String) {
65 let info = format!(
66 r#"<?xml version="1.0" encoding="UTF-8"?>
67<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
68<plist version="1.0">
69<dict>
70 <key>NSServices</key>
71 <array>
72 <dict>
73 <key>NSMenuItem</key>
74 <dict>
75 <key>default</key>
76 <string>{label}</string>
77 </dict>
78 <key>NSMessage</key>
79 <string>runWorkflowAsService</string>
80 <key>NSRequiredContext</key>
81 <dict>
82 <key>NSApplicationIdentifier</key>
83 <string>com.apple.finder</string>
84 </dict>
85 <key>NSSendFileTypes</key>
86 <array>
87 <string>public.item</string>
88 </array>
89 </dict>
90 </array>
91</dict>
92</plist>
93"#,
94 label = xml(ctx.label(action))
95 );
96 // Only the first selected file: the quick window handles one file at a time.
97 let command = format!("[ -n \"$1\" ] && exec {} {} \"$1\"", sh_quote(&ctx.helper.to_string_lossy()), action);
98 let document = format!(
99 r#"<?xml version="1.0" encoding="UTF-8"?>
100<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
101<plist version="1.0">
102<dict>
103 <key>AMApplicationBuild</key>
104 <string>523</string>
105 <key>AMApplicationVersion</key>
106 <string>2.10</string>
107 <key>AMDocumentVersion</key>
108 <string>2</string>
109 <key>actions</key>
110 <array>
111 <dict>
112 <key>action</key>
113 <dict>
114 <key>AMAccepts</key>
115 <dict>
116 <key>Container</key>
117 <string>List</string>
118 <key>Optional</key>
119 <true/>
120 <key>Types</key>
121 <array>
122 <string>com.apple.cocoa.path</string>
123 </array>
124 </dict>
125 <key>AMActionVersion</key>
126 <string>2.0.3</string>
127 <key>AMApplication</key>
128 <array>
129 <string>Automator</string>
130 </array>
131 <key>AMParameterProperties</key>
132 <dict>
133 <key>COMMAND_STRING</key>
134 <dict/>
135 <key>CheckedForUserDefaultShell</key>
136 <dict/>
137 <key>inputMethod</key>
138 <dict/>
139 <key>shell</key>
140 <dict/>
141 <key>source</key>
142 <dict/>
143 </dict>
144 <key>AMProvides</key>
145 <dict>
146 <key>Container</key>
147 <string>List</string>
148 <key>Types</key>
149 <array>
150 <string>com.apple.cocoa.string</string>
151 </array>
152 </dict>
153 <key>ActionBundlePath</key>
154 <string>/System/Library/Automator/Run Shell Script.action</string>
155 <key>ActionName</key>
156 <string>Run Shell Script</string>
157 <key>ActionParameters</key>
158 <dict>
159 <key>COMMAND_STRING</key>
160 <string>{command}</string>
161 <key>CheckedForUserDefaultShell</key>
162 <true/>
163 <key>inputMethod</key>
164 <integer>1</integer>
165 <key>shell</key>
166 <string>/bin/sh</string>
167 <key>source</key>
168 <string></string>
169 </dict>
170 <key>BundleIdentifier</key>
171 <string>com.apple.RunShellScript</string>
172 <key>CFBundleVersion</key>
173 <string>2.0.3</string>
174 <key>CanShowSelectedItemsWhenRun</key>
175 <false/>
176 <key>CanShowWhenRun</key>
177 <true/>
178 <key>Category</key>
179 <array>
180 <string>AMCategoryUtilities</string>
181 </array>
182 <key>Class Name</key>
183 <string>RunShellScriptAction</string>
184 <key>InputUUID</key>
185 <string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000001</string>
186 <key>Keywords</key>
187 <array>
188 <string>Shell</string>
189 <string>Script</string>
190 </array>
191 <key>OutputUUID</key>
192 <string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000002</string>
193 <key>UUID</key>
194 <string>6E1E1D0A-3C5B-4C1E-9C40-{n}0000000003</string>
195 <key>UnlocalizedApplications</key>
196 <array>
197 <string>Automator</string>
198 </array>
199 <key>arguments</key>
200 <dict/>
201 <key>isViewVisible</key>
202 <integer>1</integer>
203 <key>location</key>
204 <string>309.000000:253.000000</string>
205 <key>nibPath</key>
206 <string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
207 </dict>
208 <key>isViewVisible</key>
209 <integer>1</integer>
210 </dict>
211 </array>
212 <key>connectors</key>
213 <dict/>
214 <key>workflowMetaData</key>
215 <dict>
216 <key>applicationBundleIDsByPath</key>
217 <dict/>
218 <key>applicationPaths</key>
219 <array/>
220 <key>inputTypeIdentifier</key>
221 <string>com.apple.Automator.fileSystemObject</string>
222 <key>outputTypeIdentifier</key>
223 <string>com.apple.Automator.nothing</string>
224 <key>presentationMode</key>
225 <integer>15</integer>
226 <key>processesInput</key>
227 <false/>
228 <key>serviceInputTypeIdentifier</key>
229 <string>com.apple.Automator.fileSystemObject</string>
230 <key>serviceOutputTypeIdentifier</key>
231 <string>com.apple.Automator.nothing</string>
232 <key>serviceProcessesInput</key>
233 <false/>
234 <key>systemImageName</key>
235 <string>NSActionTemplate</string>
236 <key>useAutomaticInputType</key>
237 <false/>
238 <key>workflowTypeIdentifier</key>
239 <string>com.apple.Automator.servicesMenu</string>
240 </dict>
241</dict>
242</plist>
243"#,
244 command = xml(&command),
245 n = if action == "verify" { "1" } else { "2" }
246 );
247 (info, document)
250#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
251fn agent_path(ctx: &Context) -> PathBuf {
252 ctx.home.join("Library").join("LaunchAgents").join(format!("{AGENT_ID}.plist"))
255#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
256fn agent_plist(ctx: &Context) -> String {
257 format!(
258 r#"<?xml version="1.0" encoding="UTF-8"?>
259<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
260<plist version="1.0">
261<dict>
262 <key>Label</key>
263 <string>{AGENT_ID}</string>
264 <key>AssociatedBundleIdentifiers</key>
265 <string>{AGENT_ID}</string>
266 <key>ProgramArguments</key>
267 <array>
268 <string>{helper}</string>
269 <string>run</string>
270 </array>
271 <key>RunAtLoad</key>
272 <true/>
273 <key>KeepAlive</key>
274 <false/>
275 <key>ProcessType</key>
276 <string>Background</string>
277 <key>LimitLoadToSessionType</key>
278 <string>Aqua</string>
279</dict>
280</plist>
281"#,
282 helper = xml(&ctx.helper.to_string_lossy())
283 )
286/// The LaunchAgent as it is on disk now (to see whether `apply` changed it).
287#[cfg(target_os = "macos")]
288pub fn agent_text(ctx: &Context) -> Option<String> {
289 std::fs::read_to_string(agent_path(ctx)).ok()
292/// Lets launchd own the background process, so System Settings → Login Items lists it as running (a process the
293/// app starts itself shows as "not running"). Loads the agent when it is wanted and not loaded, reloads it when
294/// `apply` changed it (e.g. new helper path after an update), unloads it otherwise. Returns true when launchd now
295/// runs the helper. `before_bootstrap` runs right before loading (stops a helper the app started directly).
296#[cfg(target_os = "macos")]
297pub fn launchd_sync(ctx: &Context, settings: &Settings, changed: bool, running: bool, before_bootstrap: impl FnOnce()) -> bool {
298 use std::os::unix::fs::MetadataExt;
299 let Ok(uid) = std::fs::metadata(&ctx.home).map(|m| m.uid()) else { return false };
300 let launchctl = |args: &[&str]| {
301 std::process::Command::new("/bin/launchctl").args(args).stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).status().is_ok_and(|s| s.success())
302 };
303 let (domain, service) = (format!("gui/{uid}"), format!("gui/{uid}/{AGENT_ID}"));
304 let mut loaded = launchctl(&["print", &service]);
305 if settings.wants_login() {
306 if loaded && changed {
307 launchctl(&["bootout", &service]);
308 loaded = false;
309 }
310 if !loaded {
311 before_bootstrap();
312 loaded = launchctl(&["bootstrap", &domain, &agent_path(ctx).to_string_lossy()]);
313 } else if !running {
314 // Registered but not running (crashed or stopped; KeepAlive is off): start it again.
315 return launchctl(&["kickstart", &service]);
316 }
317 loaded
318 } else {
319 if loaded {
320 launchctl(&["bootout", &service]);
321 }
322 false
323 }
326// ---------- Linux ----------
328#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
329fn data_home(ctx: &Context) -> PathBuf {
330 std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| ctx.home.join(".local").join("share"))
333#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
334fn linux_script_paths(ctx: &Context, action: &str) -> Vec<PathBuf> {
335 let name = ctx.label(action);
336 vec![data_home(ctx).join("nautilus").join("scripts").join(name), data_home(ctx).join("nemo").join("scripts").join(name)]
339#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
340fn linux_script(ctx: &Context, action: &str) -> String {
341 // Nautilus/Nemo pass the selected names relative to the current folder; the helper resolves them.
342 format!("#!/bin/sh\n# CH-J Standby Helper\n[ -n \"$1\" ] && exec {} {} \"$1\"\n", sh_quote(&ctx.helper.to_string_lossy()), action)
345#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
346fn dolphin_path(ctx: &Context) -> PathBuf {
347 data_home(ctx).join("kio").join("servicemenus").join("ch-j-standby-helper.desktop")
350/// Desktop Entry `Exec` argument quoting.
351fn exec_quote(text: &str) -> String {
352 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\"").replace('`', "\\`").replace('$', "\\$"))
355#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
356fn dolphin_menu(ctx: &Context, settings: &Settings) -> String {
357 let actions: Vec<&str> = ACTIONS.iter().copied().filter(|a| if *a == "verify" { settings.wants_verify() } else { settings.wants_sign() }).collect();
358 let mut out = format!(
359 "[Desktop Entry]\nType=Service\nMimeType=application/octet-stream;\nX-KDE-ServiceTypes=KonqPopupMenu/Plugin\nActions={};\n",
360 actions.iter().map(|a| format!("chj_{a}")).collect::<Vec<_>>().join(";")
361 );
362 for action in actions {
363 out.push_str(&format!("\n[Desktop Action chj_{action}]\nName={}\nIcon=ch-j-tools\nExec={} {action} %f\n", ctx.label(action), exec_quote(&ctx.helper.to_string_lossy())));
364 }
365 out
368#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
369fn autostart_path(ctx: &Context) -> PathBuf {
370 let config = std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| ctx.home.join(".config"));
371 config.join("autostart").join("ch-j-standby-helper.desktop")
374#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
375fn autostart_entry(ctx: &Context) -> String {
376 format!(
377 "[Desktop Entry]\nType=Application\nName=CH-J-Standby-Helper\nExec={} run\nIcon=ch-j-tools\nNoDisplay=true\nTerminal=false\nX-GNOME-Autostart-enabled=true\n",
378 exec_quote(&ctx.helper.to_string_lossy())
379 )
382// ---------- Windows ----------
384#[cfg(windows)]
385const WINDOWS_RUN_KEY: &str = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run";
386#[cfg(windows)]
387const WINDOWS_RUN_VALUE: &str = "CH-J-Standby-Helper";
389#[cfg_attr(not(any(windows, test)), allow(dead_code))]
390fn windows_login_command(ctx: &Context) -> String {
391 format!("\"{}\" run", ctx.helper.display())
394#[cfg(windows)]
395fn reg(args: &[&str]) -> Result<bool, String> {
396 use std::os::windows::process::CommandExt;
397 let status = std::process::Command::new("reg.exe")
398 .args(args)
399 .creation_flags(0x0800_0000 /* CREATE_NO_WINDOW */)
400 .stdout(std::process::Stdio::null())
401 .stderr(std::process::Stdio::null())
402 .status()
403 .map_err(|e| format!("reg.exe: {e}"))?;
404 Ok(status.success())
407#[cfg(windows)]
408fn windows_key(action: &str) -> String {
409 format!(r"HKCU\Software\Classes\*\shell\CHJ.{}", if action == "verify" { "Verify" } else { "Sign" })
412// ---------- shared ----------
414/// Brings every entry in line with `settings` (writes wanted ones, removes the others).
415pub fn apply(ctx: &Context, settings: &Settings) -> Result<(), String> {
416 let mut errors = Vec::new();
417 let mut note = |r: Result<(), String>| {
418 if let Err(e) = r {
419 errors.push(e)
420 }
421 };
422 for action in ACTIONS {
423 let wanted = if action == "verify" { settings.wants_verify() } else { settings.wants_sign() };
424 #[cfg(target_os = "macos")]
425 {
426 let dir = workflow_path(ctx, action);
427 if wanted {
428 let (info, document) = workflow_files(ctx, action);
429 note(write(&dir.join("Contents").join("Info.plist"), &info, false));
430 note(write(&dir.join("Contents").join("document.wflow"), &document, false));
431 } else {
432 note(remove(&dir));
433 }
434 }
435 #[cfg(target_os = "linux")]
436 for path in linux_script_paths(ctx, action) {
437 // Remove the other languages' names too, so a language change leaves no stale entry.
438 if let Some(dir) = path.parent() {
439 for other in ["cs", "de", "en"] {
440 let label = Context { language: other, home: ctx.home.clone(), helper: ctx.helper.clone(), app: ctx.app.clone() }.label(action);
441 if label != ctx.label(action) {
442 note(remove(&dir.join(label)));
443 }
444 }
445 }
446 note(if wanted { write(&path, &linux_script(ctx, action), true) } else { remove(&path) });
447 }
448 #[cfg(windows)]
449 {
450 let key = windows_key(action);
451 if wanted {
452 let command = format!("\"{}\" {action} \"%1\"", ctx.helper.display());
453 let icon = format!("\"{}\",0", ctx.app.display());
454 let ok = reg(&["add", &key, "/v", "MUIVerb", "/d", ctx.label(action), "/f"])?
455 && reg(&["add", &key, "/v", "Icon", "/d", &icon, "/f"])?
456 && reg(&["add", &format!(r"{key}\command"), "/ve", "/d", &command, "/f"])?;
457 if !ok {
458 errors.push(format!("{key}: reg add failed"));
459 }
460 } else {
461 let _ = reg(&["delete", &key, "/f"]);
462 }
463 }
464 #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
465 let _ = wanted;
466 }
467 #[cfg(target_os = "macos")]
468 note(if settings.wants_login() { write(&agent_path(ctx), &agent_plist(ctx), false) } else { remove(&agent_path(ctx)) });
469 #[cfg(target_os = "linux")]
470 {
471 let wanted = settings.wants_verify() || settings.wants_sign();
472 note(if wanted { write(&dolphin_path(ctx), &dolphin_menu(ctx, settings), true) } else { remove(&dolphin_path(ctx)) });
473 note(if settings.wants_login() { write(&autostart_path(ctx), &autostart_entry(ctx), false) } else { remove(&autostart_path(ctx)) });
474 }
475 #[cfg(target_os = "macos")]
476 refresh_services();
477 #[cfg(windows)]
478 if settings.wants_login() {
479 if !reg(&["add", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE, "/t", "REG_SZ", "/d", &windows_login_command(ctx), "/f"])? {
480 errors.push("standby helper login: reg add failed".into());
481 }
482 } else {
483 // Missing values are normal, including on first install and repeated removal.
484 let _ = reg(&["delete", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE, "/f"]);
485 }
486 if errors.is_empty() {
487 Ok(())
488 } else {
489 Err(errors.join("; "))
490 }
493/// Finder reads new Quick Actions only after the services cache is rebuilt.
494#[cfg(target_os = "macos")]
495fn refresh_services() {
496 let _ = std::process::Command::new("/System/Library/CoreServices/pbs").arg("-update").stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).status();
499pub fn remove_all(ctx: &Context) -> Result<(), String> {
500 apply(ctx, &Settings { enabled: false, ..Settings::default() })
503/// Which entries exist now (read back from the system, not from the settings).
504pub fn installed(ctx: &Context) -> Value {
505 let _ = ctx;
506 #[cfg(target_os = "macos")]
507 return json!({
508 "kind": "finder-quick-actions",
509 "verify": workflow_path(ctx, "verify").join("Contents/document.wflow").is_file(),
510 "sign": workflow_path(ctx, "sign").join("Contents/document.wflow").is_file(),
511 "login": agent_path(ctx).is_file(),
512 });
513 #[cfg(target_os = "linux")]
514 return json!({
515 "kind": "nautilus-nemo-dolphin",
516 "verify": linux_script_paths(ctx, "verify").iter().any(|p| p.is_file()),
517 "sign": linux_script_paths(ctx, "sign").iter().any(|p| p.is_file()),
518 "dolphin": dolphin_path(ctx).is_file(),
519 "login": autostart_path(ctx).is_file(),
520 });
521 #[cfg(windows)]
522 return json!({
523 "kind": "explorer-context-menu",
524 "verify": reg(&["query", &windows_key("verify")]).unwrap_or(false),
525 "sign": reg(&["query", &windows_key("sign")]).unwrap_or(false),
526 "login": reg(&["query", WINDOWS_RUN_KEY, "/v", WINDOWS_RUN_VALUE]).unwrap_or(false),
527 });
528 #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
529 return json!({"kind": "unsupported"});
532#[cfg(test)]
533mod tests {
534 use super::*;
536 fn ctx(home: &Path, helper: &str) -> Context {
537 Context { home: home.to_path_buf(), helper: PathBuf::from(helper), app: PathBuf::from("/Applications/CH-J Tools.app/Contents/MacOS/ch-j-tools"), language: "cs" }
538 }
540 #[test]
541 fn quoting_keeps_spaces_and_quotes_literal() {
542 assert_eq!(sh_quote("/A B/it's"), r"'/A B/it'\''s'");
543 assert_eq!(exec_quote("/A B/\"x\"$y"), "\"/A B/\\\"x\\\"\\$y\"");
544 assert_eq!(xml("a<b>&\""), "a&lt;b&gt;&amp;&quot;");
545 }
547 #[test]
548 fn windows_login_quotes_the_helper_path_before_the_run_argument() {
549 let c = ctx(Path::new("unused"), r"C:\Program Files\CH-J Tools\ch-j-standby-helper.exe");
550 assert_eq!(windows_login_command(&c), r#""C:\Program Files\CH-J Tools\ch-j-standby-helper.exe" run"#);
551 }
553 #[test]
554 fn generated_entries_only_call_the_helper_with_the_selected_file() {
555 let home = std::env::temp_dir().join(format!("chj-standby-gen-{}", std::process::id()));
556 let c = ctx(&home, "/Applications/CH-J Tools.app/Contents/MacOS/ch-j-standby-helper");
557 let (info, document) = workflow_files(&c, "sign");
558 assert!(info.contains("<string>CH-J – Podepsat (OpenPGP)</string>") && info.contains("public.item"));
559 assert!(document.contains("exec '/Applications/CH-J Tools.app/Contents/MacOS/ch-j-standby-helper' sign"));
560 assert!(document.contains("com.apple.Automator.servicesMenu"));
561 assert!(agent_plist(&c).contains("<string>run</string>"));
562 // Login items show the helper bundle's name ("CH-J-Standby-Helper"), not the certificate's developer name.
563 assert!(agent_plist(&c).contains("<key>AssociatedBundleIdentifiers</key>\n\t<string>de.ch-j.standby-helper</string>"));
564 assert!(autostart_entry(&ctx(&home, "/usr/bin/ch-j-standby-helper")).contains("\nName=CH-J-Standby-Helper\n"));
565 let script = linux_script(&ctx(&home, "/usr/bin/ch-j-standby-helper"), "verify");
566 assert!(script.starts_with("#!/bin/sh\n") && script.contains("exec '/usr/bin/ch-j-standby-helper' verify \"$1\""));
567 let only_verify = Settings { enabled: true, start_at_login: false, menu_verify: true, menu_sign: false };
568 let menu = dolphin_menu(&ctx(&home, "/usr/bin/ch-j-standby-helper"), &only_verify);
569 assert!(menu.contains("Actions=chj_verify;") && !menu.contains("chj_sign") && menu.contains("Exec=\"/usr/bin/ch-j-standby-helper\" verify %f"));
570 assert!(autostart_entry(&ctx(&home, "/usr/bin/ch-j-standby-helper")).contains("Exec=\"/usr/bin/ch-j-standby-helper\" run"));
571 }
573 #[cfg(any(target_os = "macos", target_os = "linux"))]
574 #[test]
575 fn apply_writes_wanted_entries_and_removes_the_rest() {
576 let home = std::env::temp_dir().join(format!("chj-standby-apply-{}", std::process::id()));
577 let _ = std::fs::remove_dir_all(&home);
578 let c = ctx(&home, "/opt/chj/ch-j-standby-helper");
579 let all = Settings { enabled: true, start_at_login: true, menu_verify: true, menu_sign: true };
580 apply(&c, &all).unwrap();
581 let state = installed(&c);
582 assert_eq!((state["verify"].as_bool(), state["sign"].as_bool(), state["login"].as_bool()), (Some(true), Some(true), Some(true)), "{state}");
583 apply(&c, &Settings { menu_sign: false, start_at_login: false, ..all.clone() }).unwrap();
584 let state = installed(&c);
585 assert_eq!((state["verify"].as_bool(), state["sign"].as_bool(), state["login"].as_bool()), (Some(true), Some(false), Some(false)), "{state}");
586 remove_all(&c).unwrap();
587 let state = installed(&c);
588 assert_eq!((state["verify"].as_bool(), state["sign"].as_bool()), (Some(false), Some(false)));
589 std::fs::remove_dir_all(&home).unwrap();
590 }
SHA-256be6c9267103f0fabf29195a92aa47bb9436e8827e1cb0f49f8e2099e28a29d57

CH-J Proprietary Software License 1.14 · Η πηγή παρέχεται στο CH-J Proprietary Software License 1.14. Η διαθεσιμότητά του δεν αλλάζει τους όρους της άδειας χρήσης ούτε χορηγεί πρόσθετα δικαιώματα.

Archive SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Αρχεία