From 44558d0ce8e392f03e19d4f4125446cc8c40f61d Mon Sep 17 00:00:00 2001 From: Hocuri Date: Fri, 2 Oct 2020 15:34:22 +0200 Subject: [PATCH] Revert "Export to the new backup format (#1952)" (#1958) This reverts commit 1fdb697c09f4646566cfe4cf0dfb4fa46102afdf. because Desktop never had a release with tar-import --- src/dc_tools.rs | 22 ++++++++- src/imex.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/src/dc_tools.rs b/src/dc_tools.rs index afbb3e86b..0551418dc 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -537,9 +537,29 @@ pub fn dc_open_file_std>( } } +pub(crate) async fn get_next_backup_path_old( + folder: impl AsRef, + backup_time: i64, +) -> Result { + let folder = PathBuf::from(folder.as_ref()); + let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0) + .format("delta-chat-%Y-%m-%d") + .to_string(); + + // 64 backup files per day should be enough for everyone + for i in 0..64 { + let mut path = folder.clone(); + path.push(format!("{}-{}.bak", stem, i)); + if !path.exists().await { + return Ok(path); + } + } + bail!("could not create backup file, disk full?"); +} + /// Returns Ok((temp_path, dest_path)) on success. The backup can then be written to temp_path. If the backup succeeded, /// it can be renamed to dest_path. This guarantees that the backup is complete. -pub(crate) async fn get_next_backup_path( +pub(crate) async fn get_next_backup_path_new( folder: impl AsRef, backup_time: i64, ) -> Result<(PathBuf, PathBuf), Error> { diff --git a/src/imex.rs b/src/imex.rs index d72b5591b..fc01b81b5 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -1,7 +1,10 @@ //! # Import/export module use std::any::Any; -use std::ffi::OsStr; +use std::{ + cmp::{max, min}, + ffi::OsStr, +}; use anyhow::Context as _; use async_std::path::{Path, PathBuf}; @@ -462,7 +465,9 @@ async fn imex_inner( ImexMode::ExportSelfKeys => export_self_keys(context, path).await, ImexMode::ImportSelfKeys => import_self_keys(context, path).await, - ImexMode::ExportBackup => export_backup(context, path).await, + // TODO In some months we can change the export_backup_old() call to export_backup() and delete export_backup_old(). + // (now is 07/2020) + ImexMode::ExportBackup => export_backup_old(context, path).await, // import_backup() will call import_backup_old() if this is an old backup. ImexMode::ImportBackup => import_backup(context, path).await, } @@ -646,7 +651,7 @@ async fn import_backup_old(context: &Context, backup_to_import: impl AsRef async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { // get a fine backup file name (the name includes the date so that multiple backup instances are possible) let now = time(); - let (temp_path, dest_path) = get_next_backup_path(dir, now).await?; + let (temp_path, dest_path) = get_next_backup_path_new(dir, now).await?; let _d = DeleteOnDrop(temp_path.clone()); context @@ -720,6 +725,121 @@ async fn export_backup_inner(context: &Context, temp_path: &PathBuf) -> Result<( Ok(()) } +async fn export_backup_old(context: &Context, dir: impl AsRef) -> Result<()> { + // get a fine backup file name (the name includes the date so that multiple backup instances are possible) + // FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. + // let dest_path_filename = dc_get_next_backup_file(context, dir, res); + let now = time(); + let dest_path_filename = get_next_backup_path_old(dir, now).await?; + let dest_path_string = dest_path_filename.to_string_lossy().to_string(); + + sql::housekeeping(context).await; + + context.sql.execute("VACUUM;", paramsv![]).await.ok(); + + // we close the database during the copy of the dbfile + context.sql.close().await; + info!( + context, + "Backup '{}' to '{}'.", + context.get_dbfile().display(), + dest_path_filename.display(), + ); + let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename).await; + context + .sql + .open(&context, &context.get_dbfile(), false) + .await?; + + if !copied { + bail!( + "could not copy file from '{}' to '{}'", + context.get_dbfile().display(), + dest_path_string + ); + } + let dest_sql = Sql::new(); + dest_sql + .open(context, &dest_path_filename, false) + .await + .with_context(|| format!("could not open exported database {}", dest_path_string))?; + + let res = match add_files_to_export(context, &dest_sql).await { + Err(err) => { + dc_delete_file(context, &dest_path_filename).await; + error!(context, "backup failed: {}", err); + Err(err) + } + Ok(()) => { + dest_sql + .set_raw_config_int(context, "backup_time", now as i32) + .await?; + context.emit_event(EventType::ImexFileWritten(dest_path_filename)); + Ok(()) + } + }; + dest_sql.close().await; + + Ok(res?) +} + +async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { + // add all files as blobs to the database copy (this does not require + // the source to be locked, neigher the destination as it is used only here) + if !sql.table_exists("backup_blobs").await? { + sql.execute( + "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", + paramsv![], + ) + .await?; + } + // copy all files from BLOBDIR into backup-db + let mut total_files_cnt = 0; + let dir = context.get_blobdir(); + let dir_handle = async_std::fs::read_dir(&dir).await?; + total_files_cnt += dir_handle.filter(|r| r.is_ok()).count().await; + + info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); + + sql.with_conn_async(|conn| async move { + // scan directory, pass 2: copy files + let mut dir_handle = async_std::fs::read_dir(&dir).await?; + + let mut processed_files_cnt = 0; + while let Some(entry) = dir_handle.next().await { + let entry = entry?; + if context.shall_stop_ongoing().await { + return Ok(()); + } + processed_files_cnt += 1; + let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); + context.emit_event(EventType::ImexProgress(permille)); + + let name_f = entry.file_name(); + let name = name_f.to_string_lossy(); + if name.starts_with("delta-chat") && name.ends_with(".bak") { + continue; + } + info!(context, "EXPORT: copying filename={}", name); + let curr_path_filename = context.get_blobdir().join(entry.file_name()); + if let Ok(buf) = dc_read_file(context, &curr_path_filename).await { + if buf.is_empty() { + continue; + } + // bail out if we can't insert + let mut stmt = conn.prepare_cached( + "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", + )?; + stmt.execute(paramsv![name, buf])?; + } + } + Ok(()) + }) + .await?; + + Ok(()) +} + /******************************************************************************* * Classic key import ******************************************************************************/