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:
parent
e6f158e445
commit
8478492190
@ -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
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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(_)
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user