Skip to main content

imessage_database/util/
dirs.rs

1/*!
2 Default local paths for Messages data.
3*/
4
5use std::{
6    borrow::Cow,
7    env::{home_dir, var_os},
8    path::PathBuf,
9};
10
11use crate::tables::table::DEFAULT_PATH_MACOS;
12
13/// Return the default macOS Messages database path.
14///
15/// Resolves the home directory with [`std::env::home_dir`]: `HOME` on Unix and `USERPROFILE` on
16/// Windows. Returns `None` when neither the environment nor the OS reports a home directory.
17///
18/// # Example:
19///
20/// ```
21/// use imessage_database::util::dirs::default_db_path;
22///
23/// let path = default_db_path().unwrap();
24/// println!("{path:?}");
25/// ```
26#[must_use]
27pub fn default_db_path() -> Option<PathBuf> {
28    home_dir().map(|home| home.join(DEFAULT_PATH_MACOS))
29}
30
31/// Return the host's default parent directories for per-device backups from Finder, iTunes,
32/// and Apple Devices.
33///
34/// macOS: `~/Library/Application Support/MobileSync/Backup`. Windows:
35/// `%APPDATA%\Apple Computer\MobileSync\Backup` for iTunes from Apple and
36/// `%USERPROFILE%\Apple\MobileSync\Backup` for iTunes and Apple Devices from the Microsoft Store.
37/// On Linux, return an empty list; no standard backup location is defined.
38///
39/// # Example:
40///
41/// ```
42/// use imessage_database::util::dirs::default_backup_roots;
43///
44/// for root in default_backup_roots() {
45///     println!("{}", root.display());
46/// }
47/// ```
48#[must_use]
49pub fn default_backup_roots() -> Vec<PathBuf> {
50    let mut roots = Vec::new();
51    if cfg!(target_os = "macos")
52        && let Some(home) = home_dir()
53    {
54        roots.push(
55            home.join("Library")
56                .join("Application Support")
57                .join("MobileSync")
58                .join("Backup"),
59        );
60    }
61    if cfg!(target_os = "windows") {
62        if let Some(appdata) = var_os("APPDATA") {
63            roots.push(
64                PathBuf::from(appdata)
65                    .join("Apple Computer")
66                    .join("MobileSync")
67                    .join("Backup"),
68            );
69        }
70        if let Some(home) = home_dir() {
71            roots.push(home.join("Apple").join("MobileSync").join("Backup"));
72        }
73    }
74    roots
75}
76
77/// Replace a leading `~` with the current user's home directory.
78///
79/// macOS stores attachment paths with a literal `~` prefix. The path is returned unchanged when it
80/// has no `~` prefix or when the home directory is unknown.
81///
82/// # Example:
83///
84/// ```
85/// use imessage_database::util::dirs::expand_home;
86///
87/// let path = expand_home("~/Library/Messages");
88/// println!("{path}");
89/// ```
90#[must_use]
91pub fn expand_home(path: &str) -> Cow<'_, str> {
92    match (path.strip_prefix('~'), home_dir()) {
93        (Some(rest), Some(home)) => Cow::Owned(format!("{}{rest}", home.display())),
94        _ => Cow::Borrowed(path),
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use std::{borrow::Cow, env::home_dir};
101
102    use crate::{
103        tables::table::DEFAULT_PATH_MACOS, util::dirs::default_db_path, util::dirs::expand_home,
104    };
105
106    #[test]
107    fn default_db_path_is_under_home() {
108        let home = home_dir().unwrap();
109        assert_eq!(default_db_path().unwrap(), home.join(DEFAULT_PATH_MACOS));
110    }
111
112    #[test]
113    fn expand_home_replaces_leading_tilde() {
114        let home = home_dir().unwrap();
115        assert_eq!(
116            expand_home("~/Library/Messages"),
117            format!("{}/Library/Messages", home.display())
118        );
119    }
120
121    #[test]
122    fn expand_home_leaves_other_paths_borrowed() {
123        assert!(matches!(
124            expand_home("/Library/Messages"),
125            Cow::Borrowed("/Library/Messages")
126        ));
127        assert!(matches!(expand_home("a/~/b"), Cow::Borrowed("a/~/b")));
128    }
129}