Check ids when gathering data (#1928)

This will throw an error if a card, note or revlog id from the future
is found during apkg import or export.
This commit is contained in:
RumovZ 2022-06-24 05:56:52 +02:00 committed by GitHub
parent e6f158e445
commit 8478492190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 0 deletions

View File

@ -11,6 +11,7 @@ errors-multiple-notetypes-selected = Please select notes from only one notetype.
errors-please-check-database = Please use the Check Database action, then try again. errors-please-check-database = Please use the Check Database action, then try again.
errors-please-check-media = Please use the Check Media action, then try again. errors-please-check-media = Please use the Check Media action, then try again.
errors-collection-too-new = This collection requires a newer version of Anki to open. errors-collection-too-new = This collection requires a newer version of Anki to open.
errors-invalid-ids = This deck contains timestamps in the future. Please contact the deck author and ask them to fix the issue.
## Card Rendering ## Card Rendering

View File

@ -39,6 +39,7 @@ impl AnkiError {
AnkiError::ImportError(_) => Kind::ImportError, AnkiError::ImportError(_) => Kind::ImportError,
AnkiError::FileIoError(_) => Kind::IoError, AnkiError::FileIoError(_) => Kind::IoError,
AnkiError::MediaCheckRequired => Kind::InvalidInput, AnkiError::MediaCheckRequired => Kind::InvalidInput,
AnkiError::InvalidId => Kind::InvalidInput,
}; };
pb::BackendError { pb::BackendError {

View File

@ -49,6 +49,7 @@ pub enum AnkiError {
MediaCheckRequired, MediaCheckRequired,
CustomStudyError(CustomStudyError), CustomStudyError(CustomStudyError),
ImportError(ImportError), ImportError(ImportError),
InvalidId,
} }
impl std::error::Error for AnkiError {} impl std::error::Error for AnkiError {}
@ -105,6 +106,7 @@ impl AnkiError {
AnkiError::CustomStudyError(err) => err.localized_description(tr), AnkiError::CustomStudyError(err) => err.localized_description(tr),
AnkiError::ImportError(err) => err.localized_description(tr), AnkiError::ImportError(err) => err.localized_description(tr),
AnkiError::Deleted => tr.browsing_row_deleted().into(), AnkiError::Deleted => tr.browsing_row_deleted().into(),
AnkiError::InvalidId => tr.errors_invalid_ids().into(),
AnkiError::IoError(_) AnkiError::IoError(_)
| AnkiError::JsonError(_) | AnkiError::JsonError(_)
| AnkiError::ProtoError(_) | AnkiError::ProtoError(_)

View File

@ -41,6 +41,7 @@ impl ExchangeData {
self.cards = col.gather_cards()?; self.cards = col.gather_cards()?;
self.decks = col.gather_decks()?; self.decks = col.gather_decks()?;
self.notetypes = col.gather_notetypes()?; self.notetypes = col.gather_notetypes()?;
self.check_ids()?;
if with_scheduling { if with_scheduling {
self.revlog = col.gather_revlog()?; self.revlog = col.gather_revlog()?;
@ -114,6 +115,18 @@ impl ExchangeData {
card.deck_id = deck_id; card.deck_id = deck_id;
} }
} }
fn check_ids(&self) -> Result<()> {
let now = TimestampMillis::now().0;
self.cards
.iter()
.map(|card| card.id.0)
.chain(self.notes.iter().map(|note| note.id.0))
.chain(self.revlog.iter().map(|entry| entry.id.0))
.any(|timestamp| timestamp > now)
.then(|| Err(AnkiError::InvalidId))
.unwrap_or(Ok(()))
}
} }
fn gather_media_names_from_note( fn gather_media_names_from_note(
@ -225,3 +238,36 @@ impl Collection {
.collect() .collect()
} }
} }
#[cfg(test)]
mod test {
use super::*;
use crate::{collection::open_test_collection, search::SearchNode};
#[test]
fn should_gather_valid_notes() {
let mut data = ExchangeData::default();
let mut col = open_test_collection();
let note = col.add_new_note("Basic");
data.gather_data(&mut col, SearchNode::WholeCollection, true)
.unwrap();
assert_eq!(data.notes, [note]);
}
#[test]
fn should_err_if_note_has_invalid_id() {
let mut data = ExchangeData::default();
let mut col = open_test_collection();
let now_micros = TimestampMillis::now().0 * 1000;
let mut note = col.add_new_note("Basic");
note.id = NoteId(now_micros);
col.add_note_only_with_id_undoable(&mut note).unwrap();
assert!(data
.gather_data(&mut col, SearchNode::WholeCollection, true)
.is_err());
}
}