diff --git a/proto/backend.proto b/proto/backend.proto index d4dfd3553..fa73d4c7f 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -97,6 +97,8 @@ message SyncError { CLIENT_TOO_OLD = 3; AUTH_FAILED = 4; SERVER_MESSAGE = 5; + MEDIA_CHECK_REQUIRED = 6; + RESYNC_REQUIRED = 7; } SyncErrorKind kind = 2; } diff --git a/qt/aqt/mediasync.py b/qt/aqt/mediasync.py index e4a01d9bc..0dbef445a 100644 --- a/qt/aqt/mediasync.py +++ b/qt/aqt/mediasync.py @@ -44,20 +44,6 @@ class MediaSyncState: removed_files: int = 0 -# fixme: sync.rs fixmes -# fixme: maximum size when uploading -# fixme: concurrent modifications during upload step -# fixme: mediaSanity -# elif evt == "mediaSanity": -# showWarning( -# _( -# """\ -# A problem occurred while syncing media. Please use Tools>Check Media, then \ -# sync again to correct the issue.""" -# ) -# ) - - LogEntry = Union[MediaSyncState, str] @@ -170,6 +156,14 @@ class MediaSyncer: "AnkiWeb encountered a problem. Please try again in a few minutes." ) ) + elif kind == SyncErrorKind.MEDIA_CHECK_REQUIRED: + showWarning(_("Please use the Tools>Check Media menu option.")) + elif kind == SyncErrorKind.RESYNC_REQUIRED: + showWarning( + _( + "Please sync again, and post on the support forum if this message keeps appearing." + ) + ) else: showWarning(_("Unexpected error: {}").format(str(exc))) elif isinstance(exc, NetworkError): diff --git a/rslib/src/backend.rs b/rslib/src/backend.rs index 377d0c4d5..e0bf8f909 100644 --- a/rslib/src/backend.rs +++ b/rslib/src/backend.rs @@ -87,6 +87,8 @@ impl std::convert::From for i32 { SyncErrorKind::ClientTooOld => V::ClientTooOld, SyncErrorKind::AuthFailed => V::AuthFailed, SyncErrorKind::ServerMessage => V::ServerMessage, + SyncErrorKind::MediaCheckRequired => V::MediaCheckRequired, + SyncErrorKind::ResyncRequired => V::ResyncRequired, SyncErrorKind::Other => V::Other, }) as i32 } diff --git a/rslib/src/err.rs b/rslib/src/err.rs index 5df5ea266..303e24b98 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -129,6 +129,8 @@ pub enum SyncErrorKind { AuthFailed, ServerMessage, Other, + MediaCheckRequired, + ResyncRequired, } fn error_for_status_code(info: String, code: StatusCode) -> AnkiError { diff --git a/rslib/src/media/database.rs b/rslib/src/media/database.rs index bf160d50c..85d4dc384 100644 --- a/rslib/src/media/database.rs +++ b/rslib/src/media/database.rs @@ -244,6 +244,12 @@ delete from media where fname=?" .collect(); Ok(map?) } + + pub(super) fn force_resync(&mut self) -> Result<()> { + self.db + .execute_batch("delete from media; update meta set lastUsn=0, usn=0") + .map_err(Into::into) + } } #[cfg(test)] diff --git a/rslib/src/media/sync.rs b/rslib/src/media/sync.rs index dfbd467d2..2bbdb58a1 100644 --- a/rslib/src/media/sync.rs +++ b/rslib/src/media/sync.rs @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::err::{AnkiError, Result}; +use crate::err::{AnkiError, Result, SyncErrorKind}; use crate::media::database::{MediaDatabaseContext, MediaDatabaseMetadata, MediaEntry}; use crate::media::files::{ add_file_from_ankiweb, data_for_file, normalize_filename, remove_files, AddedFile, @@ -22,6 +22,10 @@ use std::{io, time}; static SYNC_MAX_FILES: usize = 25; static SYNC_MAX_BYTES: usize = (2.5 * 1024.0 * 1024.0) as usize; +static SYNC_SINGLE_FILE_MAX_BYTES: usize = 100 * 1024 * 1024; + +// fixme: non-normalized filenames on ankiweb +// fixme: concurrent modifications during upload step /// The counts are not cumulative - the progress hook should accumulate them. #[derive(Debug)] @@ -182,8 +186,11 @@ where if data == "OK" { Ok(()) } else { - // fixme: force resync, handle better - Err(AnkiError::server_message("resync required")) + self.ctx.transact(|ctx| ctx.force_resync())?; + Err(AnkiError::SyncError { + info: "".into(), + kind: SyncErrorKind::ResyncRequired, + }) } } else { Err(AnkiError::server_message(resp.err)) @@ -556,16 +563,17 @@ fn zip_files(media_folder: &Path, files: &[MediaEntry]) -> Result> { let normalized = normalize_filename(&file.fname); if let Cow::Owned(_) = normalized { - // fixme: non-string err, or should ignore instead - return Err(AnkiError::sync_misc("invalid file found")); + return Err(media_check_required()); } let file_data = data_for_file(media_folder, &file.fname)?; if let Some(data) = &file_data { if data.is_empty() { - // fixme: should ignore these, not error - return Err(AnkiError::sync_misc("0 byte file found")); + return Err(media_check_required()); + } + if data.len() > SYNC_SINGLE_FILE_MAX_BYTES { + return Err(media_check_required()); } accumulated_size += data.len(); zip.start_file(format!("{}", idx), options)?; @@ -605,6 +613,13 @@ fn version_string() -> String { format!("anki,{},{}", version(), std::env::consts::OS) } +fn media_check_required() -> AnkiError { + AnkiError::SyncError { + info: "".into(), + kind: SyncErrorKind::MediaCheckRequired, + } +} + #[derive(Serialize)] struct FinalizeRequest { local: u32,