Exports the database to *dest*, encrypted using *passphrase*. The directory of *dest* must already exist, if *dest* itself exists it will be overwritten. This also verifies that IO is not running during the export.
(
context: &Context,
dest: &Path,
passphrase: String,
timestamp: i64,
)
| 739 | /// |
| 740 | /// This also verifies that IO is not running during the export. |
| 741 | async fn export_database( |
| 742 | context: &Context, |
| 743 | dest: &Path, |
| 744 | passphrase: String, |
| 745 | timestamp: i64, |
| 746 | ) -> Result<()> { |
| 747 | ensure!( |
| 748 | !context.scheduler.is_running().await, |
| 749 | "cannot export backup, IO is running" |
| 750 | ); |
| 751 | let timestamp = timestamp.try_into().context("32-bit UNIX time overflow")?; |
| 752 | |
| 753 | // TODO: Maybe introduce camino crate for UTF-8 paths where we need them. |
| 754 | let dest = dest |
| 755 | .to_str() |
| 756 | .with_context(|| format!("path {} is not valid unicode", dest.display()))?; |
| 757 | |
| 758 | context.set_config(Config::BccSelf, Some("1")).await?; |
| 759 | context |
| 760 | .sql |
| 761 | .set_raw_config_int("backup_time", timestamp) |
| 762 | .await?; |
| 763 | context |
| 764 | .sql |
| 765 | .set_raw_config_int("backup_version", DCBACKUP_VERSION) |
| 766 | .await?; |
| 767 | sql::housekeeping(context).await.log_err(context).ok(); |
| 768 | context |
| 769 | .sql |
| 770 | .call_write(|conn| { |
| 771 | conn.execute("VACUUM;", ()) |
| 772 | .map_err(|err| warn!(context, "Vacuum failed, exporting anyway {err}")) |
| 773 | .ok(); |
| 774 | conn.execute("ATTACH DATABASE ? AS backup KEY ?", (dest, passphrase)) |
| 775 | .context("failed to attach backup database")?; |
| 776 | let res = conn |
| 777 | .query_row("SELECT sqlcipher_export('backup')", [], |_row| Ok(())) |
| 778 | .context("failed to export to attached backup database"); |
| 779 | conn.execute( |
| 780 | "UPDATE backup.config SET value='0' WHERE keyname='verified_one_on_one_chats';", |
| 781 | [], |
| 782 | ) |
| 783 | .ok(); // Deprecated 2025-07. If verified_one_on_one_chats was not set, this errors, which we ignore |
| 784 | conn.execute("DETACH DATABASE backup", []) |
| 785 | .context("failed to detach backup database")?; |
| 786 | res?; |
| 787 | Ok(()) |
| 788 | }) |
| 789 | .await |
| 790 | } |
| 791 | |
| 792 | async fn check_backup_version(context: &Context) -> Result<()> { |
| 793 | let version = (context.sql.get_raw_config_int("backup_version").await?).unwrap_or(2); |
no test coverage detected