Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/cs/better fallback sql #166

Merged
merged 6 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[workspace]
edition = "2021"
resolver = "2"
members = [
"imessage-database",
"imessage-exporter",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Documentation for the library is located [here](imessage-database/README.md).

### Supported Features

This crate supports every iMessage feature as of MacOS 13.5 (22G74) and iOS 16.6 (20G75):
This crate supports every iMessage feature as of macOS 13.5.2 (22G91) and iOS 16.6.1 (20G81):

- Multi-part messages
- Replies/Threads
Expand Down
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This exporter is fully-featured and well-documented.

## Targeted Versions

This tool targets the current latest public release for MacOS and iMessage. It may work with older databases, but all features may not be available.
This tool targets the current latest public release for macOS and iMessage. It may work with older databases, but all features may not be available.

## Supported Message Features

Expand Down
2 changes: 1 addition & 1 deletion imessage-database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn iter_messages() -> Result<(), TableError> {
for message in messages {
let mut msg = Message::extract(message)?;

/// Parse message body if it was sent from MacOS 13.0 or newer
/// Parse message body if it was sent from macOS 13.0 or newer
msg.gen_text(&db);

/// Emit debug info for each message
Expand Down
18 changes: 9 additions & 9 deletions imessage-database/src/tables/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,13 @@ impl Attachment {

/// Given a platform and database source, resolve the path for the current attachment
///
/// For MacOS, `db_path` is unused. For iOS, `db_path` is the path to the root of the backup directory.
/// For macOS, `db_path` is unused. For iOS, `db_path` is the path to the root of the backup directory.
///
/// iOS Parsing logic source is from [here](https://github.com/nprezant/iMessageBackup/blob/940d001fb7be557d5d57504eb26b3489e88de26e/imessage_backup_tools.py#L83-L85).
pub fn resolved_attachment_path(&self, platform: &Platform, db_path: &Path) -> Option<String> {
if let Some(path_str) = &self.filename {
return match platform {
Platform::MacOS => Some(Attachment::gen_macos_attachment(path_str)),
Platform::macOS => Some(Attachment::gen_macos_attachment(path_str)),
Platform::iOS => Attachment::gen_ios_attachment(path_str, db_path),
};
}
Expand All @@ -218,7 +218,7 @@ impl Attachment {
///
/// let db_path = default_db_path();
/// let conn = get_connection(&db_path).unwrap();
/// Attachment::run_diagnostic(&conn, &db_path, &Platform::MacOS);
/// Attachment::run_diagnostic(&conn, &db_path, &Platform::macOS);
/// ```
pub fn run_diagnostic(
db: &Connection,
Expand All @@ -242,7 +242,7 @@ impl Attachment {
total_attachments += 1;
if let Ok(filepath) = path {
match platform {
Platform::MacOS => {
Platform::macOS => {
!Path::new(&Attachment::gen_macos_attachment(filepath)).exists()
}
Platform::iOS => {
Expand Down Expand Up @@ -293,7 +293,7 @@ impl Attachment {
Ok(())
}

/// Generate a MacOS path for an attachment
/// Generate a macOS path for an attachment
fn gen_macos_attachment(path: &str) -> String {
if path.starts_with('~') {
return path.replacen('~', &home(), 1);
Expand Down Expand Up @@ -416,7 +416,7 @@ mod tests {
let attachment = sample_attachment();

assert_eq!(
attachment.resolved_attachment_path(&Platform::MacOS, &db_path),
attachment.resolved_attachment_path(&Platform::macOS, &db_path),
Some("a/b/c.png".to_string())
);
}
Expand All @@ -429,7 +429,7 @@ mod tests {

assert!(
attachment
.resolved_attachment_path(&Platform::MacOS, &db_path)
.resolved_attachment_path(&Platform::macOS, &db_path)
.unwrap()
.len()
> attachment.filename.unwrap().len()
Expand All @@ -443,7 +443,7 @@ mod tests {
attachment.filename = Some("~/a/b/c~d.png".to_string());

assert!(attachment
.resolved_attachment_path(&Platform::MacOS, &db_path)
.resolved_attachment_path(&Platform::macOS, &db_path)
.unwrap()
.ends_with("c~d.png"));
}
Expand All @@ -466,7 +466,7 @@ mod tests {
attachment.filename = None;

assert_eq!(
attachment.resolved_attachment_path(&Platform::MacOS, &db_path),
attachment.resolved_attachment_path(&Platform::macOS, &db_path),
None
);
}
Expand Down
32 changes: 28 additions & 4 deletions imessage-database/src/tables/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ impl Table for Message {
}

fn get(db: &Connection) -> Result<Statement, TableError> {
// If the database has `chat_recoverable_message_join`, we can restore some deleted messages.
// If database has `thread_originator_guid`, we can parse replies, otherwise default to 0
// The first sql statement is the "current" schema, the second one is the "most compatible" schema, i.e. supports older DBs
Ok(db.prepare(&format!(
// macOS Ventura+ and i0S 16+ schema
"SELECT
*,
c.chat_id,
Expand All @@ -147,14 +148,29 @@ impl Table for Message {
ORDER BY
m.date;
"
))
)).or(db.prepare(&format!(
// macOS Big Sur to Monterey, iOS 14 to iOS 15 schema
"SELECT
*,
c.chat_id,
(SELECT COUNT(*) FROM {MESSAGE_ATTACHMENT_JOIN} a WHERE m.ROWID = a.message_id) as num_attachments,
NULL as deleted_from,
(SELECT COUNT(*) FROM {MESSAGE} m2 WHERE m2.thread_originator_guid = m.guid) as num_replies
FROM
message as m
LEFT JOIN {CHAT_MESSAGE_JOIN} as c ON m.ROWID = c.message_id
ORDER BY
m.date;
"
)))
.unwrap_or(db.prepare(&format!(
// macOS Catalina, iOS 13 and older
"SELECT
*,
c.chat_id,
(SELECT COUNT(*) FROM {MESSAGE_ATTACHMENT_JOIN} a WHERE m.ROWID = a.message_id) as num_attachments,
(SELECT NULL) as deleted_from,
(SELECT 0) as num_replies
NULL as deleted_from,
0 as num_replies
FROM
message as m
LEFT JOIN {CHAT_MESSAGE_JOIN} as c ON m.ROWID = c.message_id
Expand Down Expand Up @@ -505,6 +521,14 @@ impl Message {
}

/// `true` if the message was deleted and is recoverable, else `false`
///
/// Messages removed by deleting an entire conversation or by deleting a single message
/// from a conversation are moved to a separate collection for up to 30 days. Messages
/// present in this collection are restored to the conversations they belong to. Apple
/// details this process [here](https://support.apple.com/en-us/HT202549#delete).
///
/// Messages that have expired from this restoration process are permanently deleted and
/// cannot be recovered.
pub fn is_deleted(&self) -> bool {
self.deleted_from.is_some()
}
Expand Down
2 changes: 1 addition & 1 deletion imessage-database/src/tables/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Representations of iMessage database tables as structs.

Many of these tables do not include all available columns. Even on the same versions
of MacOS, the schema of the iMessage database can vary.
of macOS, the schema of the iMessage database can vary.
*/

pub mod attachment;
Expand Down
2 changes: 1 addition & 1 deletion imessage-database/src/tables/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub const ME: &str = "Me";
pub const YOU: &str = "You";
/// Name used for contacts or chats where the name cannot be discovered
pub const UNKNOWN: &str = "Unknown";
/// Default location for the Messages database on MacOS
/// Default location for the Messages database on macOS
pub const DEFAULT_PATH_MACOS: &str = "Library/Messages/chat.db";
/// Default location for the Messages database in an unencrypted iOS backup
pub const DEFAULT_PATH_IOS: &str = "3d/3d0d7e5fb2ce288813306e4d4636395e047a3d28";
Expand Down
4 changes: 2 additions & 2 deletions imessage-database/src/util/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{env::var, path::PathBuf};

use crate::tables::table::DEFAULT_PATH_MACOS;

/// Get the user's home directory (MacOS only)
/// Get the user's home directory (macOS only)
///
/// # Example:
///
Expand All @@ -23,7 +23,7 @@ pub fn home() -> String {
}
}

/// Get the default path the MacOS iMessage database is located at (MacOS only)
/// Get the default path the macOS iMessage database is located at (macOS only)
///
/// # Example:
///
Expand Down
23 changes: 12 additions & 11 deletions imessage-database/src/util/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ use crate::tables::table::DEFAULT_PATH_IOS;

/// Represents the platform that created the database this library connects to
pub enum Platform {
/// MacOS-sourced data
MacOS,
/// macOS-sourced data
#[allow(non_camel_case_types)]
macOS,
/// iOS-sourced data
#[allow(non_camel_case_types)]
iOS,
}

impl Platform {
/// Try to determine the current platform, defaulting to MacOS.
/// Try to determine the current platform, defaulting to macOS.
pub fn determine(db_path: &Path) -> Self {
if db_path.join(DEFAULT_PATH_IOS).exists() {
return Self::iOS;
} else if db_path.is_file() {
return Self::MacOS;
return Self::macOS;
}
// If we get here, the database is missing; that error is handled in the connection lifecycle
Self::default()
Expand All @@ -30,24 +31,24 @@ impl Platform {
/// Given user's input, return a variant if the input matches one
pub fn from_cli(platform: &str) -> Option<Self> {
match platform.to_lowercase().as_str() {
"macos" => Some(Self::MacOS),
"macos" => Some(Self::macOS),
"ios" => Some(Self::iOS),
_ => None,
}
}
}

impl Default for Platform {
/// The default Platform is [Platform::MacOS].
/// The default Platform is [Platform::macOS].
fn default() -> Self {
Self::MacOS
Self::macOS
}
}

impl Display for Platform {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Platform::MacOS => write!(fmt, "MacOS"),
Platform::macOS => write!(fmt, "macOS"),
Platform::iOS => write!(fmt, "iOS"),
}
}
Expand All @@ -59,9 +60,9 @@ mod tests {

#[test]
fn can_parse_macos_any_case() {
assert!(matches!(Platform::from_cli("macos"), Some(Platform::MacOS)));
assert!(matches!(Platform::from_cli("MACOS"), Some(Platform::MacOS)));
assert!(matches!(Platform::from_cli("MacOS"), Some(Platform::MacOS)));
assert!(matches!(Platform::from_cli("macos"), Some(Platform::macOS)));
assert!(matches!(Platform::from_cli("MACOS"), Some(Platform::macOS)));
assert!(matches!(Platform::from_cli("MacOS"), Some(Platform::macOS)));
}

#[test]
Expand Down
8 changes: 4 additions & 4 deletions imessage-exporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ The [releases page](https://github.com/ReagentX/imessage-exporter/releases) prov

-p, --db-path <path/to/source>
Specify a custom path for the iMessage database location
For MacOS, specify a path to a `chat.db` file
For macOS, specify a path to a `chat.db` file
For iOS, specify a path to the root of an unencrypted backup directory
If omitted, the default directory is ~/Library/Messages/chat.db

-a, --platform <MacOS, iOS>
-a, --platform <macOS, iOS>
Specify the platform the database was created on
If omitted, the platform type is determined automatically

Expand Down Expand Up @@ -109,10 +109,10 @@ Export as `html` from `/Volumes/external/chat.db` to `/Volumes/external/export`
% imessage-exporter -f html -c disabled -p /Volumes/external/chat.db -o /Volumes/external/export
```

Export messages from `2020-01-01` to `2020-12-31` as `txt` from the default MacOS iMessage Database location to `~/export-2020`:
Export messages from `2020-01-01` to `2020-12-31` as `txt` from the default macOS iMessage Database location to `~/export-2020`:

```zsh
% imessage-exporter -f txt -o ~/export-2020 -s 2020-01-01 -e 2021-01-01 -a MacOS
% imessage-exporter -f txt -o ~/export-2020 -s 2020-01-01 -e 2021-01-01 -a macOS
```

## Features
Expand Down
2 changes: 1 addition & 1 deletion imessage-exporter/src/app/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn exists(name: &str) -> bool {

/// Convert a HEIC image file to a JPEG
///
/// This uses the MacOS builtin `sips` program
/// This uses the macOS builtin `sips` program
/// Docs: <https://www.unix.com/man-page/osx/1/sips/> (or `man sips`)
///
/// If `to` contains a directory that does not exist, i.e. `/fake/out.jpg`, instead
Expand Down
6 changes: 3 additions & 3 deletions imessage-exporter/src/app/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub const OPTION_PLATFORM: &str = "platform";

// Other CLI Text
pub const SUPPORTED_FILE_TYPES: &str = "txt, html";
pub const SUPPORTED_PLATFORMS: &str = "MacOS, iOS";
pub const SUPPORTED_PLATFORMS: &str = "macOS, iOS";
pub const SUPPORTED_ATTACHMENT_MANAGER_MODES: &str = "compatible, efficient, disabled";
pub const ABOUT: &str = concat!(
"The `imessage-exporter` binary exports iMessage data to\n",
Expand Down Expand Up @@ -173,7 +173,7 @@ impl<'a> Options<'a> {
pub fn get_db_path(&self) -> PathBuf {
match self.platform {
Platform::iOS => self.db_path.join(DEFAULT_PATH_IOS),
Platform::MacOS => self.db_path.clone(),
Platform::macOS => self.db_path.clone(),
}
}
}
Expand Down Expand Up @@ -255,7 +255,7 @@ pub fn from_command_line() -> ArgMatches {
Arg::new(OPTION_DB_PATH)
.short('p')
.long(OPTION_DB_PATH)
.help(&*format!("Specify a custom path for the iMessage database location\nFor MacOS, specify a path to a `chat.db` file\nFor iOS, specify a path to the root of an unencrypted backup directory\nIf omitted, the default directory is {}", default_db_path().display()))
.help(&*format!("Specify a custom path for the iMessage database location\nFor macOS, specify a path to a `chat.db` file\nFor iOS, specify a path to the root of an unencrypted backup directory\nIf omitted, the default directory is {}", default_db_path().display()))
.takes_value(true)
.display_order(3)
.value_name("path/to/source"),
Expand Down
6 changes: 3 additions & 3 deletions imessage-exporter/src/app/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ mod filename_tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
platform: Platform::MacOS,
platform: Platform::macOS,
}
}

Expand Down Expand Up @@ -548,7 +548,7 @@ mod who_tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
platform: Platform::MacOS,
platform: Platform::macOS,
}
}

Expand Down Expand Up @@ -769,7 +769,7 @@ mod directory_tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
platform: Platform::MacOS,
platform: Platform::macOS,
}
}

Expand Down
2 changes: 1 addition & 1 deletion imessage-exporter/src/exporters/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@ mod tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
platform: Platform::MacOS,
platform: Platform::macOS,
}
}

Expand Down
2 changes: 1 addition & 1 deletion imessage-exporter/src/exporters/resources/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<div class="name"><b>GitHub</b></div>
</div>
<div class="app_footer">
<div class="subcaption"><xmp>ReagentX/imessage-exporter: Export MacOS iMessage data + run iMessage Diagnostics</xmp></div>
<div class="subcaption"><xmp>ReagentX/imessage-exporter: Export macOS iMessage data + run iMessage Diagnostics</xmp></div>
</div>
</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion imessage-exporter/src/exporters/txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ mod tests {
query_context: QueryContext::default(),
no_lazy: false,
custom_name: None,
platform: Platform::MacOS,
platform: Platform::macOS,
}
}

Expand Down
Loading