From 984e2c2666bf29a040707eed127e7def21c46764 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 11 Mar 2021 18:54:30 +1000 Subject: [PATCH] add a separate 'rename deck' method --- pylib/anki/decks.py | 10 +++-- rslib/backend.proto | 6 +++ rslib/src/backend/decks.rs | 17 +++++++++ rslib/src/backend/generic.rs | 12 ------ rslib/src/decks/mod.rs | 74 ++++++++++++++++++++++++------------ rslib/src/decks/undo.rs | 6 +-- rslib/src/undo/ops.rs | 2 + 7 files changed, 84 insertions(+), 43 deletions(-) diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 41be21647..4f9f336ff 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -250,11 +250,13 @@ class DeckManager: deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn ) - def rename(self, g: Deck, newName: str) -> None: + def rename(self, deck: Union[Deck, int], new_name: str) -> None: "Rename deck prefix to NAME if not exists. Updates children." - g["name"] = newName - self.update(g, preserve_usn=False) - return + if isinstance(deck, int): + deck_id = deck + else: + deck_id = deck["id"] + self.col._backend.rename_deck(deck_id=deck_id, new_name=new_name) # Drag/drop ############################################################# diff --git a/rslib/backend.proto b/rslib/backend.proto index 50a7db282..f9bec2995 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -132,6 +132,7 @@ service DecksService { rpc NewDeckLegacy(Bool) returns (Json); rpc RemoveDeck(DeckID) returns (Empty); rpc DragDropDecks(DragDropDecksIn) returns (Empty); + rpc RenameDeck(RenameDeckIn) returns (Empty); } service NotesService { @@ -1448,3 +1449,8 @@ message DeckAndNotetype { int64 deck_id = 1; int64 notetype_id = 2; } + +message RenameDeckIn { + int64 deck_id = 1; + string new_name = 2; +} \ No newline at end of file diff --git a/rslib/src/backend/decks.rs b/rslib/src/backend/decks.rs index e072f777f..7b2c8d613 100644 --- a/rslib/src/backend/decks.rs +++ b/rslib/src/backend/decks.rs @@ -124,4 +124,21 @@ impl DecksService for Backend { self.with_col(|col| col.drag_drop_decks(&source_dids, target_did)) .map(Into::into) } + + fn rename_deck(&self, input: pb::RenameDeckIn) -> Result { + self.with_col(|col| col.rename_deck(input.deck_id.into(), &input.new_name)) + .map(Into::into) + } +} + +impl From for DeckID { + fn from(did: pb::DeckId) -> Self { + DeckID(did.did) + } +} + +impl From for pb::DeckId { + fn from(did: DeckID) -> Self { + pb::DeckId { did: did.0 } + } } diff --git a/rslib/src/backend/generic.rs b/rslib/src/backend/generic.rs index b3ea2be60..1919901a9 100644 --- a/rslib/src/backend/generic.rs +++ b/rslib/src/backend/generic.rs @@ -63,18 +63,6 @@ impl From for NoteTypeID { } } -impl From for DeckID { - fn from(did: pb::DeckId) -> Self { - DeckID(did.did) - } -} - -impl From for pb::DeckId { - fn from(did: DeckID) -> Self { - pb::DeckId { did: did.0 } - } -} - impl From for DeckConfID { fn from(dcid: pb::DeckConfigId) -> Self { DeckConfID(dcid.dcid) diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index 16cd714b5..9d8997dec 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -292,32 +292,41 @@ impl Collection { }) } - pub(crate) fn update_deck(&mut self, deck: &mut Deck) -> Result<()> { + pub fn update_deck(&mut self, deck: &mut Deck) -> Result<()> { self.transact(Some(UndoableOpKind::UpdateDeck), |col| { - let usn = col.usn()?; - col.prepare_deck_for_update(deck, usn)?; - deck.set_modified(usn); - if let Some(existing_deck) = col.storage.get_deck(deck.id)? { - let name_changed = existing_deck.name != deck.name; - if name_changed { - // match closest parent name - col.match_or_create_parents(deck, usn)?; - // rename children - col.rename_child_decks(&existing_deck, &deck.name, usn)?; - } - col.update_single_deck_undoable(deck, &existing_deck)?; - if name_changed { - // after updating, we need to ensure all grandparents exist, which may not be the case - // in the parent->child case - col.create_missing_parents(&deck.name, usn)?; - } - Ok(()) - } else { - Err(AnkiError::invalid_input("updating non-existent deck")) - } + let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?; + col.update_deck_inner(deck, existing_deck, col.usn()?) }) } + pub fn rename_deck(&mut self, did: DeckID, new_human_name: &str) -> Result<()> { + self.transact(Some(UndoableOpKind::RenameDeck), |col| { + let existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?; + let mut deck = existing_deck.clone(); + deck.name = human_deck_name_to_native(new_human_name); + col.update_deck_inner(&mut deck, existing_deck, col.usn()?) + }) + } + + fn update_deck_inner(&mut self, deck: &mut Deck, original: Deck, usn: Usn) -> Result<()> { + self.prepare_deck_for_update(deck, usn)?; + deck.set_modified(usn); + let name_changed = original.name != deck.name; + if name_changed { + // match closest parent name + self.match_or_create_parents(deck, usn)?; + // rename children + self.rename_child_decks(&original, &deck.name, usn)?; + } + self.update_single_deck_undoable(deck, original)?; + if name_changed { + // after updating, we need to ensure all grandparents exist, which may not be the case + // in the parent->child case + self.create_missing_parents(&deck.name, usn)?; + } + Ok(()) + } + /// Add/update a single deck when syncing/importing. Ensures name is unique /// & normalized, but does not check parents/children or update mtime /// (unless the name was changed). Caller must set up transaction. @@ -377,7 +386,7 @@ impl Collection { let new_name = format!("{}\x1f{}", new_name, child_only.join("\x1f")); child.name = new_name; child.set_modified(usn); - self.update_single_deck_undoable(&mut child, &original)?; + self.update_single_deck_undoable(&mut child, original)?; } Ok(()) @@ -600,7 +609,7 @@ impl Collection { deck.reset_stats_if_day_changed(today); mutator(&mut deck.common); deck.set_modified(usn); - self.update_single_deck_undoable(deck, &original) + self.update_single_deck_undoable(deck, original) } pub fn drag_drop_decks( @@ -761,6 +770,23 @@ mod test { col.add_or_update_deck(&mut middle)?; assert_eq!(middle.name, "other+"); + // public function takes human name + col.rename_deck(middle.id, "one::two")?; + assert_eq!( + sorted_names(&col), + vec![ + "Default", + "one", + "one::two", + "one::two::baz", + "one::two::baz2", + "other", + "quux", + "quux::foo", + "quux::foo::baz", + ] + ); + Ok(()) } diff --git a/rslib/src/decks/undo.rs b/rslib/src/decks/undo.rs index bd826becf..8d019ffb2 100644 --- a/rslib/src/decks/undo.rs +++ b/rslib/src/decks/undo.rs @@ -22,7 +22,7 @@ impl Collection { .storage .get_deck(deck.id)? .ok_or_else(|| AnkiError::invalid_input("deck disappeared"))?; - self.update_single_deck_undoable(&mut *deck, ¤t) + self.update_single_deck_undoable(&mut *deck, current) } UndoableDeckChange::Removed(deck) => self.restore_deleted_deck(*deck), UndoableDeckChange::GraveAdded(e) => self.remove_deck_grave(e.0, e.1), @@ -52,10 +52,10 @@ impl Collection { pub(super) fn update_single_deck_undoable( &mut self, deck: &mut Deck, - original: &Deck, + original: Deck, ) -> Result<()> { self.state.deck_cache.clear(); - self.save_undo(UndoableDeckChange::Updated(Box::new(original.clone()))); + self.save_undo(UndoableDeckChange::Updated(Box::new(original))); self.storage.update_deck(deck) } diff --git a/rslib/src/undo/ops.rs b/rslib/src/undo/ops.rs index d708ef943..23d9019d3 100644 --- a/rslib/src/undo/ops.rs +++ b/rslib/src/undo/ops.rs @@ -11,6 +11,7 @@ pub enum UndoableOpKind { Bury, RemoveDeck, RemoveNote, + RenameDeck, Suspend, UnburyUnsuspend, UpdateCard, @@ -35,6 +36,7 @@ impl Collection { UndoableOpKind::Bury => TR::StudyingBury, UndoableOpKind::RemoveDeck => TR::DecksDeleteDeck, UndoableOpKind::RemoveNote => TR::StudyingDeleteNote, + UndoableOpKind::RenameDeck => TR::ActionsRenameDeck, UndoableOpKind::Suspend => TR::StudyingSuspend, UndoableOpKind::UnburyUnsuspend => TR::UndoUnburyUnsuspend, UndoableOpKind::UpdateCard => TR::UndoUpdateCard,