clean up invalid media DB entries on the fly, instead of requiring DB check

This commit is contained in:
Damien Elmes 2020-02-08 17:58:27 +10:00
parent 7ae6244f6a
commit eddf9fdc44

View file

@ -309,8 +309,16 @@ where
break; break;
} }
let zip_data = zip_files(&self.mgr.media_folder, &pending)?; let zip_data = zip_files(&mut self.ctx, &self.mgr.media_folder, &pending)?;
let reply = self.send_zip_data(zip_data).await?; if zip_data.is_none() {
self.progress.checked += pending.len();
self.maybe_fire_progress_cb()?;
// discard zip info and retry batch - not particularly efficient,
// but this is a corner case
continue;
}
let reply = self.send_zip_data(zip_data.unwrap()).await?;
let (processed_files, processed_deletions): (Vec<_>, Vec<_>) = pending let (processed_files, processed_deletions): (Vec<_>, Vec<_>) = pending
.iter() .iter()
@ -646,8 +654,13 @@ fn record_clean(ctx: &mut MediaDatabaseContext, clean: &[&String]) -> Result<()>
Ok(()) Ok(())
} }
fn zip_files(media_folder: &Path, files: &[MediaEntry]) -> Result<Vec<u8>> { fn zip_files<'a>(
ctx: &mut MediaDatabaseContext,
media_folder: &Path,
files: &'a [MediaEntry],
) -> Result<Option<Vec<u8>>> {
let buf = vec![]; let buf = vec![];
let mut invalid_entries = vec![];
let w = std::io::Cursor::new(buf); let w = std::io::Cursor::new(buf);
let mut zip = zip::ZipWriter::new(w); let mut zip = zip::ZipWriter::new(w);
@ -666,17 +679,20 @@ fn zip_files(media_folder: &Path, files: &[MediaEntry]) -> Result<Vec<u8>> {
let normalized = normalize_filename(&file.fname); let normalized = normalize_filename(&file.fname);
if let Cow::Owned(o) = normalized { if let Cow::Owned(o) = normalized {
debug!("media check required: {} should be {}", &file.fname, o); debug!("media check required: {} should be {}", &file.fname, o);
return Err(media_check_required()); invalid_entries.push(&file.fname);
continue;
} }
let file_data = data_for_file(media_folder, &file.fname)?; let file_data = data_for_file(media_folder, &file.fname)?;
if let Some(data) = &file_data { if let Some(data) = &file_data {
if data.is_empty() { if data.is_empty() {
return Err(media_check_required()); invalid_entries.push(&file.fname);
continue;
} }
if data.len() > SYNC_SINGLE_FILE_MAX_BYTES { if data.len() > SYNC_SINGLE_FILE_MAX_BYTES {
return Err(media_check_required()); invalid_entries.push(&file.fname);
continue;
} }
accumulated_size += data.len(); accumulated_size += data.len();
zip.start_file(format!("{}", idx), options)?; zip.start_file(format!("{}", idx), options)?;
@ -703,26 +719,30 @@ fn zip_files(media_folder: &Path, files: &[MediaEntry]) -> Result<Vec<u8>> {
}); });
} }
if !invalid_entries.is_empty() {
// clean up invalid entries; we'll build a new zip
ctx.transact(|ctx| {
for fname in invalid_entries {
ctx.remove_entry(fname)?;
}
Ok(())
})?;
return Ok(None);
}
let meta = serde_json::to_string(&entries)?; let meta = serde_json::to_string(&entries)?;
zip.start_file("_meta", options)?; zip.start_file("_meta", options)?;
zip.write_all(meta.as_bytes())?; zip.write_all(meta.as_bytes())?;
let w = zip.finish()?; let w = zip.finish()?;
Ok(w.into_inner()) Ok(Some(w.into_inner()))
} }
fn version_string() -> String { fn version_string() -> String {
format!("anki,{},{}", version(), std::env::consts::OS) format!("anki,{},{}", version(), std::env::consts::OS)
} }
fn media_check_required() -> AnkiError {
AnkiError::SyncError {
info: "".into(),
kind: SyncErrorKind::MediaCheckRequired,
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::err::Result; use crate::err::Result;