imessage_database/util/
platform.rs

1/*!
2 Contains data structures used to describe database platforms.
3*/
4
5use std::{fmt::Display, path::Path};
6
7use crate::{
8    error::table::{TableConnectError, TableError},
9    tables::table::DEFAULT_PATH_IOS,
10};
11
12/// Represents the platform that created the database this library connects to
13#[derive(PartialEq, Eq, Debug)]
14pub enum Platform {
15    /// macOS-sourced data
16    #[allow(non_camel_case_types)]
17    macOS,
18    /// iOS-sourced data
19    #[allow(non_camel_case_types)]
20    iOS,
21}
22
23impl Platform {
24    /// Try to determine the current platform, defaulting to macOS.
25    pub fn determine(db_path: &Path) -> Result<Self, TableError> {
26        // If the path ends with the default iOS path,
27        // the user accidentally passed the full iOS path,
28        // not the root of the backup
29        if db_path.ends_with(DEFAULT_PATH_IOS) {
30            return Err(TableError::CannotConnect(TableConnectError::NotBackupRoot));
31        }
32
33        if db_path.join(DEFAULT_PATH_IOS).exists() {
34            return Ok(Self::iOS);
35        } else if db_path.is_file() {
36            return Ok(Self::macOS);
37        }
38        // If we get here, the database is missing; that error is handled in the connection lifecycle
39        Ok(Self::default())
40    }
41
42    /// Given user's input, return a variant if the input matches one
43    #[must_use]
44    pub fn from_cli(platform: &str) -> Option<Self> {
45        match platform.to_lowercase().as_str() {
46            "macos" => Some(Self::macOS),
47            "ios" => Some(Self::iOS),
48            _ => None,
49        }
50    }
51}
52
53impl Default for Platform {
54    /// The default Platform is [`Platform::macOS`].
55    fn default() -> Self {
56        Self::macOS
57    }
58}
59
60impl Display for Platform {
61    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Platform::macOS => write!(fmt, "macOS"),
64            Platform::iOS => write!(fmt, "iOS"),
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use crate::{tables::table::DEFAULT_PATH_IOS, util::platform::Platform};
72
73    #[test]
74    fn can_parse_macos_any_case() {
75        assert!(matches!(Platform::from_cli("macos"), Some(Platform::macOS)));
76        assert!(matches!(Platform::from_cli("MACOS"), Some(Platform::macOS)));
77        assert!(matches!(Platform::from_cli("MacOS"), Some(Platform::macOS)));
78    }
79
80    #[test]
81    fn can_parse_ios_any_case() {
82        assert!(matches!(Platform::from_cli("ios"), Some(Platform::iOS)));
83        assert!(matches!(Platform::from_cli("IOS"), Some(Platform::iOS)));
84        assert!(matches!(Platform::from_cli("iOS"), Some(Platform::iOS)));
85    }
86
87    #[test]
88    fn cant_parse_invalid() {
89        assert!(Platform::from_cli("mac").is_none());
90        assert!(Platform::from_cli("iphone").is_none());
91        assert!(Platform::from_cli("").is_none());
92    }
93
94    #[test]
95    fn cant_build_ends_with_ios_backup() {
96        let path = std::path::PathBuf::from(DEFAULT_PATH_IOS);
97        assert!(Platform::determine(&path).is_err());
98    }
99}