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}