lib/applebooks/macos/
utils.rs

1//! Utilities for working with Apple Books.
2
3use std::collections::HashSet;
4use std::path::PathBuf;
5
6use once_cell::sync::Lazy;
7use plist::Value;
8use sysinfo::System;
9
10/// Returns the version Apple Books for macOS as `v[short]-[long]` e.g. `v3.2-2217`.
11///
12/// * Returns `v?` if the Apple Books application cannot be found.
13/// * Returns `v[short]-?`, `v?-[long]` or `v?-?` depending on what version numbers can be located.
14pub static APPLEBOOKS_VERSION: Lazy<String> = Lazy::new(|| {
15    let path: PathBuf = [
16        "/",
17        "System",
18        "Applications",
19        "Books.app",
20        "Contents",
21        "Info.plist",
22    ]
23    .iter()
24    .collect();
25
26    let Ok(value) = Value::from_file(path) else {
27        // This can happen if the user is on a non-macOS device.
28        log::warn!("could not determine Apple Books version");
29        return "v?".to_owned();
30    };
31
32    // -> 3.2
33    let version_short = value
34        .as_dictionary()
35        .and_then(|d| d.get("CFBundleShortVersionString"))
36        .and_then(plist::Value::as_string)
37        .unwrap_or_else(|| {
38            log::warn!("could not determine 'CFBundleShortVersionString'");
39            "?"
40        });
41
42    // -> 2217
43    let version_long = value
44        .as_dictionary()
45        .and_then(|d| d.get("CFBundleVersion"))
46        .and_then(plist::Value::as_string)
47        .unwrap_or_else(|| {
48            log::warn!("could not determine 'CFBundleVersion'");
49            "?"
50        });
51
52    // v3.2-2217
53    format!("v{version_short}-{version_long}")
54});
55
56/// Returns a boolean based on if Apple Books is running or not.
57#[must_use]
58pub fn applebooks_is_running() -> bool {
59    let process_names: HashSet<String> = System::new_all()
60        .processes()
61        .values()
62        .map(|process| process.name().to_string_lossy())
63        .map(String::from)
64        .collect();
65
66    // "Returns true if self has no elements in common with other. This is equivalent to checking
67    // for an empty intersection."
68    //
69    // https://doc.rust-lang.org/std/collections/hash_set/struct.HashSet.html#method.is_disjoint
70    !super::defaults::APPLEBOOKS_NAMES.is_disjoint(&process_names)
71}