mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Generalise IncrementableProgress
And use it in entire import_export code instead.
This commit is contained in:
parent
06cc44a3ed
commit
aba7b0830c
10 changed files with 140 additions and 96 deletions
|
@ -56,7 +56,7 @@ impl ImportExportService for Backend {
|
||||||
&self,
|
&self,
|
||||||
input: pb::ImportAnkiPackageRequest,
|
input: pb::ImportAnkiPackageRequest,
|
||||||
) -> Result<pb::ImportAnkiPackageResponse> {
|
) -> Result<pb::ImportAnkiPackageResponse> {
|
||||||
self.with_col(|col| col.import_apkg(&input.package_path, &mut self.import_progress_fn()))
|
self.with_col(|col| col.import_apkg(&input.package_path, self.import_progress_fn()))
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,30 +90,14 @@ impl SearchNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
fn import_progress_fn(&self) -> impl FnMut(ImportProgress) -> Result<()> {
|
fn import_progress_fn(&self) -> impl FnMut(ImportProgress, bool) -> bool {
|
||||||
let mut handler = self.new_progress_handler();
|
let mut handler = self.new_progress_handler();
|
||||||
move |progress| {
|
move |progress, throttle| handler.update(Progress::Import(progress), throttle)
|
||||||
let throttle = matches!(
|
|
||||||
progress,
|
|
||||||
ImportProgress::Media(_) | ImportProgress::Notes(_)
|
|
||||||
);
|
|
||||||
if handler.update(Progress::Import(progress), throttle) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(AnkiError::Interrupted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_progress_fn(&self) -> impl FnMut(usize) -> Result<()> {
|
fn export_progress_fn(&self) -> impl FnMut(usize, bool) -> bool {
|
||||||
let mut handler = self.new_progress_handler();
|
let mut handler = self.new_progress_handler();
|
||||||
move |media_files| {
|
move |progress, throttle| handler.update(Progress::Export(progress), throttle)
|
||||||
if handler.update(Progress::Export(media_files), true) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(AnkiError::Interrupted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,23 +15,84 @@ pub enum ImportProgress {
|
||||||
Notes(usize),
|
Notes(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct IncrementalProgress<F: FnMut(usize) -> Result<()>> {
|
/// Wrapper around a progress function, usually passed by the [crate::backend::Backend],
|
||||||
progress_fn: F,
|
/// to make repeated calls more ergonomic.
|
||||||
counter: usize,
|
pub(crate) struct IncrementableProgress<P> {
|
||||||
|
progress_fn: Box<dyn FnMut(P, bool) -> bool>,
|
||||||
|
count_map: Option<Box<dyn FnMut(usize) -> P>>,
|
||||||
|
count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FnMut(usize) -> Result<()>> IncrementalProgress<F> {
|
const UPDATE_INTERVAL: usize = 17;
|
||||||
pub(crate) fn new(progress_fn: F) -> Self {
|
|
||||||
|
impl<P> IncrementableProgress<P> {
|
||||||
|
/// `progress_fn: (progress, throttle) -> should_continue`
|
||||||
|
pub(crate) fn new(progress_fn: impl 'static + FnMut(P, bool) -> bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
progress_fn,
|
progress_fn: Box::new(progress_fn),
|
||||||
counter: 0,
|
count_map: None,
|
||||||
|
count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the count, and defines how it should be mapped to a progress value
|
||||||
|
/// in the future.
|
||||||
|
pub(crate) fn set_count_map(&mut self, count_map: impl 'static + FnMut(usize) -> P) {
|
||||||
|
self.count_map = Some(Box::new(count_map));
|
||||||
|
self.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increment the progress counter, periodically triggering an update.
|
||||||
|
/// Returns [AnkiError::Interrupted] if the operation should be cancelled.
|
||||||
|
/// Must have called `set_count_map()` before calling this.
|
||||||
pub(crate) fn increment(&mut self) -> Result<()> {
|
pub(crate) fn increment(&mut self) -> Result<()> {
|
||||||
self.counter += 1;
|
self.count += 1;
|
||||||
if self.counter % 17 == 0 {
|
if self.count % UPDATE_INTERVAL != 0 {
|
||||||
(self.progress_fn)(self.counter)
|
return Ok(());
|
||||||
|
}
|
||||||
|
let progress = self.mapped_progress()?;
|
||||||
|
self.update(progress, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manually trigger an update.
|
||||||
|
/// Returns [AnkiError::Interrupted] if the operation should be cancelled.
|
||||||
|
pub(crate) fn call(&mut self, progress: P) -> Result<()> {
|
||||||
|
self.update(progress, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mapped_progress(&mut self) -> Result<P> {
|
||||||
|
if let Some(count_map) = self.count_map.as_mut() {
|
||||||
|
Ok(count_map(self.count))
|
||||||
|
} else {
|
||||||
|
Err(AnkiError::invalid_input("count_map not set"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, progress: P, throttle: bool) -> Result<()> {
|
||||||
|
if (self.progress_fn)(progress, throttle) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(AnkiError::Interrupted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stopgap for returning a progress fn compliant with the media code.
|
||||||
|
pub(crate) fn get_inner(&mut self) -> Result<impl FnMut(usize) -> bool + '_> {
|
||||||
|
let count_map = self
|
||||||
|
.count_map
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| AnkiError::invalid_input("count_map not set"))?;
|
||||||
|
let progress_fn = &mut self.progress_fn;
|
||||||
|
Ok(move |count| progress_fn(count_map(count), true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncrementableProgress<usize> {
|
||||||
|
/// Allows incrementing without a map, if the progress is of type [usize].
|
||||||
|
pub(crate) fn increment_flat(&mut self) -> Result<()> {
|
||||||
|
self.count += 1;
|
||||||
|
if self.count % 17 == 0 {
|
||||||
|
self.update(self.count, true)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use crate::{
|
||||||
colpkg::export::{export_collection, MediaIter},
|
colpkg::export::{export_collection, MediaIter},
|
||||||
Meta,
|
Meta,
|
||||||
},
|
},
|
||||||
|
IncrementableProgress,
|
||||||
},
|
},
|
||||||
io::{atomic_rename, tempfile_in_parent_of},
|
io::{atomic_rename, tempfile_in_parent_of},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
@ -32,8 +33,9 @@ impl Collection {
|
||||||
with_media: bool,
|
with_media: bool,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
media_fn: Option<Box<dyn FnOnce(HashSet<PathBuf>) -> MediaIter>>,
|
media_fn: Option<Box<dyn FnOnce(HashSet<PathBuf>) -> MediaIter>>,
|
||||||
progress_fn: impl FnMut(usize) -> Result<()>,
|
progress_fn: impl 'static + FnMut(usize, bool) -> bool,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
|
let mut progress = IncrementableProgress::new(progress_fn);
|
||||||
let temp_apkg = tempfile_in_parent_of(out_path.as_ref())?;
|
let temp_apkg = tempfile_in_parent_of(out_path.as_ref())?;
|
||||||
let mut temp_col = NamedTempFile::new()?;
|
let mut temp_col = NamedTempFile::new()?;
|
||||||
let temp_col_path = temp_col
|
let temp_col_path = temp_col
|
||||||
|
@ -67,7 +69,7 @@ impl Collection {
|
||||||
col_size,
|
col_size,
|
||||||
media,
|
media,
|
||||||
&self.tr,
|
&self.tr,
|
||||||
progress_fn,
|
&mut progress,
|
||||||
)?;
|
)?;
|
||||||
atomic_rename(temp_apkg, out_path.as_ref(), true)?;
|
atomic_rename(temp_apkg, out_path.as_ref(), true)?;
|
||||||
Ok(data.notes.len())
|
Ok(data.notes.len())
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
media::{extract_media_entries, SafeMediaEntry},
|
media::{extract_media_entries, SafeMediaEntry},
|
||||||
Meta,
|
Meta,
|
||||||
},
|
},
|
||||||
ImportProgress, IncrementalProgress,
|
ImportProgress, IncrementableProgress,
|
||||||
},
|
},
|
||||||
media::{
|
media::{
|
||||||
files::{add_hash_suffix_to_file_stem, sha1_of_reader},
|
files::{add_hash_suffix_to_file_stem, sha1_of_reader},
|
||||||
|
@ -34,21 +34,22 @@ pub(super) struct MediaUseMap {
|
||||||
|
|
||||||
impl Context<'_> {
|
impl Context<'_> {
|
||||||
pub(super) fn prepare_media(&mut self) -> Result<MediaUseMap> {
|
pub(super) fn prepare_media(&mut self) -> Result<MediaUseMap> {
|
||||||
let progress_fn = |u| (&mut self.progress_fn)(ImportProgress::MediaCheck(u)).is_ok();
|
self.progress.set_count_map(ImportProgress::MediaCheck);
|
||||||
let existing_sha1s = self.target_col.all_existing_sha1s(progress_fn)?;
|
let existing_sha1s = self
|
||||||
|
.target_col
|
||||||
|
.all_existing_sha1s(self.progress.get_inner()?)?;
|
||||||
prepare_media(
|
prepare_media(
|
||||||
&self.meta,
|
&self.meta,
|
||||||
&mut self.archive,
|
&mut self.archive,
|
||||||
&existing_sha1s,
|
&existing_sha1s,
|
||||||
&mut self.progress_fn,
|
&mut self.progress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn copy_media(&mut self, media_map: &mut MediaUseMap) -> Result<()> {
|
pub(super) fn copy_media(&mut self, media_map: &mut MediaUseMap) -> Result<()> {
|
||||||
let mut progress =
|
self.progress.set_count_map(ImportProgress::Media);
|
||||||
IncrementalProgress::new(|u| (&mut self.progress_fn)(ImportProgress::Media(u)));
|
|
||||||
for entry in media_map.used_entries() {
|
for entry in media_map.used_entries() {
|
||||||
progress.increment()?;
|
self.progress.increment()?;
|
||||||
entry.copy_from_archive(&mut self.archive, &self.target_col.media_folder)?;
|
entry.copy_from_archive(&mut self.archive, &self.target_col.media_folder)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -69,10 +70,10 @@ fn prepare_media(
|
||||||
meta: &Meta,
|
meta: &Meta,
|
||||||
archive: &mut ZipArchive<File>,
|
archive: &mut ZipArchive<File>,
|
||||||
existing_sha1s: &HashMap<String, Sha1Hash>,
|
existing_sha1s: &HashMap<String, Sha1Hash>,
|
||||||
progress_fn: &mut impl FnMut(ImportProgress) -> Result<()>,
|
progress: &mut IncrementableProgress<ImportProgress>,
|
||||||
) -> Result<MediaUseMap> {
|
) -> Result<MediaUseMap> {
|
||||||
let mut media_map = MediaUseMap::default();
|
let mut media_map = MediaUseMap::default();
|
||||||
let mut progress = IncrementalProgress::new(|u| progress_fn(ImportProgress::MediaCheck(u)));
|
progress.set_count_map(ImportProgress::MediaCheck);
|
||||||
|
|
||||||
for mut entry in extract_media_entries(meta, archive)? {
|
for mut entry in extract_media_entries(meta, archive)? {
|
||||||
progress.increment()?;
|
progress.increment()?;
|
||||||
|
|
|
@ -19,30 +19,27 @@ use crate::{
|
||||||
import_export::{
|
import_export::{
|
||||||
gather::ExchangeData,
|
gather::ExchangeData,
|
||||||
package::{Meta, NoteLog},
|
package::{Meta, NoteLog},
|
||||||
ImportProgress,
|
ImportProgress, IncrementableProgress,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
search::SearchNode,
|
search::SearchNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProgressFn = dyn FnMut(ImportProgress) -> Result<()>;
|
|
||||||
|
|
||||||
struct Context<'a> {
|
struct Context<'a> {
|
||||||
target_col: &'a mut Collection,
|
target_col: &'a mut Collection,
|
||||||
archive: ZipArchive<File>,
|
archive: ZipArchive<File>,
|
||||||
meta: Meta,
|
meta: Meta,
|
||||||
data: ExchangeData,
|
data: ExchangeData,
|
||||||
usn: Usn,
|
usn: Usn,
|
||||||
progress_fn: &'a mut ProgressFn,
|
progress: IncrementableProgress<ImportProgress>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn import_apkg(
|
pub fn import_apkg(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
progress_fn: &mut ProgressFn,
|
progress_fn: impl 'static + FnMut(ImportProgress, bool) -> bool,
|
||||||
) -> Result<OpOutput<NoteLog>> {
|
) -> Result<OpOutput<NoteLog>> {
|
||||||
progress_fn(ImportProgress::File)?;
|
|
||||||
let file = File::open(path)?;
|
let file = File::open(path)?;
|
||||||
let archive = ZipArchive::new(file)?;
|
let archive = ZipArchive::new(file)?;
|
||||||
|
|
||||||
|
@ -57,8 +54,9 @@ impl<'a> Context<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
mut archive: ZipArchive<File>,
|
mut archive: ZipArchive<File>,
|
||||||
target_col: &'a mut Collection,
|
target_col: &'a mut Collection,
|
||||||
progress_fn: &'a mut ProgressFn,
|
progress_fn: impl 'static + FnMut(ImportProgress, bool) -> bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
let progress = IncrementableProgress::new(progress_fn);
|
||||||
let meta = Meta::from_archive(&mut archive)?;
|
let meta = Meta::from_archive(&mut archive)?;
|
||||||
let data = ExchangeData::gather_from_archive(
|
let data = ExchangeData::gather_from_archive(
|
||||||
&mut archive,
|
&mut archive,
|
||||||
|
@ -73,13 +71,13 @@ impl<'a> Context<'a> {
|
||||||
meta,
|
meta,
|
||||||
data,
|
data,
|
||||||
usn,
|
usn,
|
||||||
progress_fn,
|
progress,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import(&mut self) -> Result<NoteLog> {
|
fn import(&mut self) -> Result<NoteLog> {
|
||||||
let mut media_map = self.prepare_media()?;
|
let mut media_map = self.prepare_media()?;
|
||||||
(self.progress_fn)(ImportProgress::File)?;
|
self.progress.call(ImportProgress::File)?;
|
||||||
let note_imports = self.import_notes_and_notetypes(&mut media_map)?;
|
let note_imports = self.import_notes_and_notetypes(&mut media_map)?;
|
||||||
let imported_decks = self.import_decks_and_configs()?;
|
let imported_decks = self.import_decks_and_configs()?;
|
||||||
self.import_cards_and_revlog(¬e_imports.id_map, &imported_decks)?;
|
self.import_cards_and_revlog(¬e_imports.id_map, &imported_decks)?;
|
||||||
|
|
|
@ -10,11 +10,11 @@ use std::{
|
||||||
|
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
|
|
||||||
use super::{media::MediaUseMap, Context, ProgressFn};
|
use super::{media::MediaUseMap, Context};
|
||||||
use crate::{
|
use crate::{
|
||||||
import_export::{
|
import_export::{
|
||||||
package::{media::safe_normalized_file_name, LogNote, NoteLog},
|
package::{media::safe_normalized_file_name, LogNote, NoteLog},
|
||||||
ImportProgress, IncrementalProgress,
|
ImportProgress, IncrementableProgress,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
text::{
|
text::{
|
||||||
|
@ -108,7 +108,7 @@ impl Context<'_> {
|
||||||
) -> Result<NoteImports> {
|
) -> Result<NoteImports> {
|
||||||
let mut ctx = NoteContext::new(self.usn, self.target_col, media_map)?;
|
let mut ctx = NoteContext::new(self.usn, self.target_col, media_map)?;
|
||||||
ctx.import_notetypes(mem::take(&mut self.data.notetypes))?;
|
ctx.import_notetypes(mem::take(&mut self.data.notetypes))?;
|
||||||
ctx.import_notes(mem::take(&mut self.data.notes), self.progress_fn)?;
|
ctx.import_notes(mem::take(&mut self.data.notes), &mut self.progress)?;
|
||||||
Ok(ctx.imports)
|
Ok(ctx.imports)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,8 +184,12 @@ impl<'n> NoteContext<'n> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_notes(&mut self, notes: Vec<Note>, progress_fn: &mut ProgressFn) -> Result<()> {
|
fn import_notes(
|
||||||
let mut progress = IncrementalProgress::new(|u| progress_fn(ImportProgress::Notes(u)));
|
&mut self,
|
||||||
|
notes: Vec<Note>,
|
||||||
|
progress: &mut IncrementableProgress<ImportProgress>,
|
||||||
|
) -> Result<()> {
|
||||||
|
progress.set_count_map(ImportProgress::Notes);
|
||||||
|
|
||||||
for mut note in notes {
|
for mut note in notes {
|
||||||
progress.increment()?;
|
progress.increment()?;
|
||||||
|
@ -325,14 +329,14 @@ mod test {
|
||||||
let mut media_map = MediaUseMap::default();
|
let mut media_map = MediaUseMap::default();
|
||||||
let mut ctx = NoteContext::new(Usn(1), &mut $col, &mut media_map).unwrap();
|
let mut ctx = NoteContext::new(Usn(1), &mut $col, &mut media_map).unwrap();
|
||||||
ctx.remapped_notetypes.insert($old_notetype, $new_notetype);
|
ctx.remapped_notetypes.insert($old_notetype, $new_notetype);
|
||||||
ctx.import_notes(vec![$note], &mut |_progress| Ok(()))
|
let mut progress = IncrementableProgress::new(|_, _| true);
|
||||||
.unwrap();
|
ctx.import_notes(vec![$note], &mut progress).unwrap();
|
||||||
ctx.imports.log
|
ctx.imports.log
|
||||||
}};
|
}};
|
||||||
($col:expr, $note:expr, $media_map:expr) => {{
|
($col:expr, $note:expr, $media_map:expr) => {{
|
||||||
let mut ctx = NoteContext::new(Usn(1), &mut $col, &mut $media_map).unwrap();
|
let mut ctx = NoteContext::new(Usn(1), &mut $col, &mut $media_map).unwrap();
|
||||||
ctx.import_notes(vec![$note], &mut |_progress| Ok(()))
|
let mut progress = IncrementableProgress::new(|_, _| true);
|
||||||
.unwrap();
|
ctx.import_notes(vec![$note], &mut progress).unwrap();
|
||||||
ctx.imports.log
|
ctx.imports.log
|
||||||
}};
|
}};
|
||||||
($col:expr, $note:expr) => {{
|
($col:expr, $note:expr) => {{
|
||||||
|
|
|
@ -37,10 +37,10 @@ fn roundtrip() {
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
|_| Ok(()),
|
|_, _| true,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
target_col.import_apkg(&apkg_path, &mut |_| Ok(())).unwrap();
|
target_col.import_apkg(&apkg_path, |_, _| true).unwrap();
|
||||||
|
|
||||||
target_col.assert_decks();
|
target_col.assert_decks();
|
||||||
target_col.assert_notetype(¬etype);
|
target_col.assert_notetype(¬etype);
|
||||||
|
|
|
@ -22,6 +22,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,
|
||||||
|
import_export::IncrementableProgress,
|
||||||
io::{atomic_rename, read_dir_files, tempfile_in_parent_of},
|
io::{atomic_rename, read_dir_files, tempfile_in_parent_of},
|
||||||
media::files::filename_if_normalized,
|
media::files::filename_if_normalized,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
@ -39,8 +40,9 @@ impl Collection {
|
||||||
out_path: impl AsRef<Path>,
|
out_path: impl AsRef<Path>,
|
||||||
include_media: bool,
|
include_media: bool,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
progress_fn: impl FnMut(usize) -> Result<()>,
|
progress_fn: impl 'static + FnMut(usize, bool) -> bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let mut progress = IncrementableProgress::new(progress_fn);
|
||||||
let colpkg_name = out_path.as_ref();
|
let colpkg_name = out_path.as_ref();
|
||||||
let temp_colpkg = tempfile_in_parent_of(colpkg_name)?;
|
let temp_colpkg = tempfile_in_parent_of(colpkg_name)?;
|
||||||
let src_path = self.col_path.clone();
|
let src_path = self.col_path.clone();
|
||||||
|
@ -62,7 +64,7 @@ impl Collection {
|
||||||
src_media_folder,
|
src_media_folder,
|
||||||
legacy,
|
legacy,
|
||||||
&tr,
|
&tr,
|
||||||
progress_fn,
|
&mut progress,
|
||||||
)?;
|
)?;
|
||||||
atomic_rename(temp_colpkg, colpkg_name, true)
|
atomic_rename(temp_colpkg, colpkg_name, true)
|
||||||
}
|
}
|
||||||
|
@ -103,7 +105,7 @@ fn export_collection_file(
|
||||||
media_dir: Option<PathBuf>,
|
media_dir: Option<PathBuf>,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
tr: &I18n,
|
tr: &I18n,
|
||||||
progress_fn: impl FnMut(usize) -> Result<()>,
|
progress: &mut IncrementableProgress<usize>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let meta = if legacy {
|
let meta = if legacy {
|
||||||
Meta::new_legacy()
|
Meta::new_legacy()
|
||||||
|
@ -118,15 +120,7 @@ fn export_collection_file(
|
||||||
MediaIter::empty()
|
MediaIter::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
export_collection(
|
export_collection(meta, out_path, &mut col_file, col_size, media, tr, progress)
|
||||||
meta,
|
|
||||||
out_path,
|
|
||||||
&mut col_file,
|
|
||||||
col_size,
|
|
||||||
media,
|
|
||||||
tr,
|
|
||||||
progress_fn,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write copied collection data without any media.
|
/// Write copied collection data without any media.
|
||||||
|
@ -143,7 +137,7 @@ pub(crate) fn export_colpkg_from_data(
|
||||||
col_size,
|
col_size,
|
||||||
MediaIter::empty(),
|
MediaIter::empty(),
|
||||||
tr,
|
tr,
|
||||||
|_| Ok(()),
|
&mut IncrementableProgress::new(|_, _| true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +148,7 @@ pub(crate) fn export_collection(
|
||||||
col_size: usize,
|
col_size: usize,
|
||||||
media: MediaIter,
|
media: MediaIter,
|
||||||
tr: &I18n,
|
tr: &I18n,
|
||||||
progress_fn: impl FnMut(usize) -> Result<()>,
|
progress: &mut IncrementableProgress<usize>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let out_file = File::create(&out_path)?;
|
let out_file = File::create(&out_path)?;
|
||||||
let mut zip = ZipWriter::new(out_file);
|
let mut zip = ZipWriter::new(out_file);
|
||||||
|
@ -165,7 +159,7 @@ pub(crate) fn export_collection(
|
||||||
zip.write_all(&meta_bytes)?;
|
zip.write_all(&meta_bytes)?;
|
||||||
write_collection(&meta, &mut zip, col, col_size)?;
|
write_collection(&meta, &mut zip, col, col_size)?;
|
||||||
write_dummy_collection(&mut zip, tr)?;
|
write_dummy_collection(&mut zip, tr)?;
|
||||||
write_media(&meta, &mut zip, media, progress_fn)?;
|
write_media(&meta, &mut zip, media, progress)?;
|
||||||
zip.finish()?;
|
zip.finish()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -240,10 +234,10 @@ fn write_media(
|
||||||
meta: &Meta,
|
meta: &Meta,
|
||||||
zip: &mut ZipWriter<File>,
|
zip: &mut ZipWriter<File>,
|
||||||
media: MediaIter,
|
media: MediaIter,
|
||||||
progress_fn: impl FnMut(usize) -> Result<()>,
|
progress: &mut IncrementableProgress<usize>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut media_entries = vec![];
|
let mut media_entries = vec![];
|
||||||
write_media_files(meta, zip, media, &mut media_entries, progress_fn)?;
|
write_media_files(meta, zip, media, &mut media_entries, progress)?;
|
||||||
write_media_map(meta, media_entries, zip)?;
|
write_media_map(meta, media_entries, zip)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -284,12 +278,12 @@ fn write_media_files(
|
||||||
zip: &mut ZipWriter<File>,
|
zip: &mut ZipWriter<File>,
|
||||||
media: MediaIter,
|
media: MediaIter,
|
||||||
media_entries: &mut Vec<MediaEntry>,
|
media_entries: &mut Vec<MediaEntry>,
|
||||||
mut progress_fn: impl FnMut(usize) -> Result<()>,
|
progress: &mut IncrementableProgress<usize>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut copier = MediaCopier::new(meta);
|
let mut copier = MediaCopier::new(meta);
|
||||||
for (index, res) in media.0.enumerate() {
|
for (index, res) in media.0.enumerate() {
|
||||||
|
progress.increment_flat()?;
|
||||||
let path = res?;
|
let path = res?;
|
||||||
progress_fn(index)?;
|
|
||||||
|
|
||||||
zip.start_file(index.to_string(), file_options_stored())?;
|
zip.start_file(index.to_string(), file_options_stored())?;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
media::{extract_media_entries, SafeMediaEntry},
|
media::{extract_media_entries, SafeMediaEntry},
|
||||||
Meta,
|
Meta,
|
||||||
},
|
},
|
||||||
ImportProgress, IncrementalProgress,
|
ImportProgress, IncrementableProgress,
|
||||||
},
|
},
|
||||||
io::{atomic_rename, tempfile_in_parent_of},
|
io::{atomic_rename, tempfile_in_parent_of},
|
||||||
media::MediaManager,
|
media::MediaManager,
|
||||||
|
@ -30,10 +30,11 @@ pub fn import_colpkg(
|
||||||
target_col_path: &str,
|
target_col_path: &str,
|
||||||
target_media_folder: &Path,
|
target_media_folder: &Path,
|
||||||
media_db: &Path,
|
media_db: &Path,
|
||||||
mut progress_fn: impl FnMut(ImportProgress) -> Result<()>,
|
progress_fn: impl 'static + FnMut(ImportProgress, bool) -> bool,
|
||||||
log: &Logger,
|
log: &Logger,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
progress_fn(ImportProgress::File)?;
|
let mut progress = IncrementableProgress::new(progress_fn);
|
||||||
|
progress.call(ImportProgress::File)?;
|
||||||
let col_path = PathBuf::from(target_col_path);
|
let col_path = PathBuf::from(target_col_path);
|
||||||
let mut tempfile = tempfile_in_parent_of(&col_path)?;
|
let mut tempfile = tempfile_in_parent_of(&col_path)?;
|
||||||
|
|
||||||
|
@ -42,13 +43,13 @@ pub fn import_colpkg(
|
||||||
let meta = Meta::from_archive(&mut archive)?;
|
let meta = Meta::from_archive(&mut archive)?;
|
||||||
|
|
||||||
copy_collection(&mut archive, &mut tempfile, &meta)?;
|
copy_collection(&mut archive, &mut tempfile, &meta)?;
|
||||||
progress_fn(ImportProgress::File)?;
|
progress.call(ImportProgress::File)?;
|
||||||
check_collection_and_mod_schema(tempfile.path())?;
|
check_collection_and_mod_schema(tempfile.path())?;
|
||||||
progress_fn(ImportProgress::File)?;
|
progress.call(ImportProgress::File)?;
|
||||||
|
|
||||||
restore_media(
|
restore_media(
|
||||||
&meta,
|
&meta,
|
||||||
&mut progress_fn,
|
&mut progress,
|
||||||
&mut archive,
|
&mut archive,
|
||||||
target_media_folder,
|
target_media_folder,
|
||||||
media_db,
|
media_db,
|
||||||
|
@ -76,7 +77,7 @@ fn check_collection_and_mod_schema(col_path: &Path) -> Result<()> {
|
||||||
|
|
||||||
fn restore_media(
|
fn restore_media(
|
||||||
meta: &Meta,
|
meta: &Meta,
|
||||||
mut progress_fn: impl FnMut(ImportProgress) -> Result<()>,
|
progress: &mut IncrementableProgress<ImportProgress>,
|
||||||
archive: &mut ZipArchive<File>,
|
archive: &mut ZipArchive<File>,
|
||||||
media_folder: &Path,
|
media_folder: &Path,
|
||||||
media_db: &Path,
|
media_db: &Path,
|
||||||
|
@ -89,9 +90,9 @@ fn restore_media(
|
||||||
|
|
||||||
std::fs::create_dir_all(media_folder)?;
|
std::fs::create_dir_all(media_folder)?;
|
||||||
let media_manager = MediaManager::new(media_folder, media_db)?;
|
let media_manager = MediaManager::new(media_folder, media_db)?;
|
||||||
let mut media_comparer = MediaComparer::new(meta, &mut progress_fn, &media_manager, log)?;
|
let mut media_comparer = MediaComparer::new(meta, progress, &media_manager, log)?;
|
||||||
|
|
||||||
let mut progress = IncrementalProgress::new(|u| progress_fn(ImportProgress::Media(u)));
|
progress.set_count_map(ImportProgress::Media);
|
||||||
for mut entry in media_entries {
|
for mut entry in media_entries {
|
||||||
progress.increment()?;
|
progress.increment()?;
|
||||||
maybe_restore_media_file(meta, media_folder, archive, &mut entry, &mut media_comparer)?;
|
maybe_restore_media_file(meta, media_folder, archive, &mut entry, &mut media_comparer)?;
|
||||||
|
@ -157,15 +158,14 @@ struct MediaComparer<'a>(Option<Box<dyn FnMut(&str) -> Result<Option<Sha1Hash>>
|
||||||
impl<'a> MediaComparer<'a> {
|
impl<'a> MediaComparer<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
meta: &Meta,
|
meta: &Meta,
|
||||||
mut progress_fn: impl FnMut(ImportProgress) -> Result<()>,
|
progress: &mut IncrementableProgress<ImportProgress>,
|
||||||
media_manager: &'a MediaManager,
|
media_manager: &'a MediaManager,
|
||||||
log: &Logger,
|
log: &Logger,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(Self(if meta.media_list_is_hashmap() {
|
Ok(Self(if meta.media_list_is_hashmap() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let mut db_progress_fn = |u| progress_fn(ImportProgress::MediaCheck(u)).is_ok();
|
media_manager.register_changes(&mut progress.get_inner()?, log)?;
|
||||||
media_manager.register_changes(&mut db_progress_fn, log)?;
|
|
||||||
Some(Box::new(media_manager.checksum_getter()))
|
Some(Box::new(media_manager.checksum_getter()))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ fn roundtrip() -> Result<()> {
|
||||||
// export to a file
|
// export to a file
|
||||||
let col = collection_with_media(dir, name)?;
|
let col = collection_with_media(dir, name)?;
|
||||||
let colpkg_name = dir.join(format!("{name}.colpkg"));
|
let colpkg_name = dir.join(format!("{name}.colpkg"));
|
||||||
col.export_colpkg(&colpkg_name, true, legacy, |_| Ok(()))?;
|
col.export_colpkg(&colpkg_name, true, legacy, |_, _| true)?;
|
||||||
|
|
||||||
// import into a new collection
|
// import into a new collection
|
||||||
let anki2_name = dir
|
let anki2_name = dir
|
||||||
|
@ -57,7 +57,7 @@ fn roundtrip() -> Result<()> {
|
||||||
&anki2_name,
|
&anki2_name,
|
||||||
&import_media_dir,
|
&import_media_dir,
|
||||||
&import_media_db,
|
&import_media_db,
|
||||||
|_| Ok(()),
|
|_, _| true,
|
||||||
&terminal(),
|
&terminal(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ fn normalization_check_on_export() -> Result<()> {
|
||||||
// manually write a file in the wrong encoding.
|
// manually write a file in the wrong encoding.
|
||||||
std::fs::write(col.media_folder.join("ぱぱ.jpg"), "nfd encoding")?;
|
std::fs::write(col.media_folder.join("ぱぱ.jpg"), "nfd encoding")?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
col.export_colpkg(&colpkg_name, true, false, |_| Ok(()))
|
col.export_colpkg(&colpkg_name, true, false, |_, _| true,)
|
||||||
.unwrap_err(),
|
.unwrap_err(),
|
||||||
AnkiError::MediaCheckRequired
|
AnkiError::MediaCheckRequired
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue