Ensure partial colpkg file removed if export fails

This commit is contained in:
Damien Elmes 2022-03-17 20:38:07 +10:00
parent f8ed4d89ba
commit bf8e70c70f
6 changed files with 32 additions and 15 deletions

View File

@ -20,6 +20,7 @@ use zstd::{
use super::super::{MediaEntries, MediaEntry, Meta, Version}; use super::super::{MediaEntries, MediaEntry, Meta, Version};
use crate::{ use crate::{
collection::CollectionBuilder, collection::CollectionBuilder,
io::atomic_rename,
media::files::{filename_if_normalized, sha1_of_data}, media::files::{filename_if_normalized, sha1_of_data},
prelude::*, prelude::*,
}; };
@ -38,6 +39,7 @@ impl Collection {
progress_fn: impl FnMut(usize), progress_fn: impl FnMut(usize),
) -> Result<()> { ) -> Result<()> {
let colpkg_name = out_path.as_ref(); let colpkg_name = out_path.as_ref();
let temp_colpkg = NamedTempFile::new_in(colpkg_name.parent().ok_or(AnkiError::NotFound)?)?;
let src_path = self.col_path.clone(); let src_path = self.col_path.clone();
let src_media_folder = if include_media { let src_media_folder = if include_media {
Some(self.media_folder.clone()) Some(self.media_folder.clone())
@ -50,14 +52,16 @@ impl Collection {
// exporting code should be downgrading to 18 instead of 11 (which will probably require // exporting code should be downgrading to 18 instead of 11 (which will probably require
// changing the boolean to an enum). // changing the boolean to an enum).
self.close(true)?; self.close(true)?;
export_collection_file( export_collection_file(
&colpkg_name, temp_colpkg.path(),
&src_path, &src_path,
src_media_folder, src_media_folder,
legacy, legacy,
&tr, &tr,
progress_fn, progress_fn,
) )?;
atomic_rename(temp_colpkg, colpkg_name)
} }
} }

View File

@ -22,6 +22,7 @@ use crate::{
package::{MediaEntries, MediaEntry, Meta}, package::{MediaEntries, MediaEntry, Meta},
ImportProgress, ImportProgress,
}, },
io::atomic_rename,
media::files::normalize_filename, media::files::normalize_filename,
prelude::*, prelude::*,
}; };
@ -85,11 +86,7 @@ pub fn import_colpkg(
}); });
// Proceed with replacing collection, regardless of media import result // Proceed with replacing collection, regardless of media import result
tempfile.as_file().sync_all()?; atomic_rename(tempfile, &col_path)?;
tempfile.persist(&col_path).map_err(|err| err.error)?;
if !cfg!(windows) {
File::open(col_dir)?.sync_all()?;
}
media_import_result media_import_result
} }

View File

@ -88,6 +88,8 @@ fn normalization_check_on_export() -> Result<()> {
.unwrap_err(), .unwrap_err(),
AnkiError::MediaCheckRequired AnkiError::MediaCheckRequired
); );
// file should have been cleaned up
assert!(!colpkg_name.exists());
Ok(()) Ok(())
} }

19
rslib/src/io.rs Normal file
View File

@ -0,0 +1,19 @@
use std::path::Path;
use tempfile::NamedTempFile;
use crate::prelude::*;
pub(crate) fn atomic_rename(file: NamedTempFile, target: &Path) -> Result<()> {
file.as_file().sync_all()?;
file.persist(&target)
.map_err(|err| AnkiError::IoError(format!("write {target:?} failed: {err}")))?;
if !cfg!(windows) {
if let Some(parent) = target.parent() {
std::fs::File::open(parent)
.and_then(|file| file.sync_all())
.map_err(|err| AnkiError::IoError(format!("sync {parent:?} failed: {err}")))?;
}
}
Ok(())
}

View File

@ -19,6 +19,7 @@ pub mod error;
pub mod findreplace; pub mod findreplace;
pub mod i18n; pub mod i18n;
pub mod import_export; pub mod import_export;
mod io;
pub mod latex; pub mod latex;
pub mod links; pub mod links;
pub mod log; pub mod log;

View File

@ -21,6 +21,7 @@ use crate::{
deckconfig::DeckConfSchema11, deckconfig::DeckConfSchema11,
decks::DeckSchema11, decks::DeckSchema11,
error::{SyncError, SyncErrorKind}, error::{SyncError, SyncErrorKind},
io::atomic_rename,
notes::Note, notes::Note,
notetype::{Notetype, NotetypeSchema11}, notetype::{Notetype, NotetypeSchema11},
prelude::*, prelude::*,
@ -679,14 +680,7 @@ impl Collection {
let db = open_and_check_sqlite_file(out_file.path())?; let db = open_and_check_sqlite_file(out_file.path())?;
db.execute_batch("update col set ls=mod")?; db.execute_batch("update col set ls=mod")?;
drop(db); drop(db);
// overwrite existing collection atomically atomic_rename(out_file, &col_path)?;
out_file.as_file().sync_all()?;
out_file
.persist(&col_path)
.map_err(|e| AnkiError::IoError(format!("download save failed: {}", e)))?;
if !cfg!(windows) {
std::fs::File::open(col_folder)?.sync_all()?;
}
Ok(()) Ok(())
} }