diff --git a/proto/anki/decks.proto b/proto/anki/decks.proto index 21973cce3..a1be6627c 100644 --- a/proto/anki/decks.proto +++ b/proto/anki/decks.proto @@ -21,6 +21,7 @@ service DecksService { rpc SetDeckCollapsed(SetDeckCollapsedRequest) returns (collection.OpChanges); rpc GetDeckLegacy(DeckId) returns (generic.Json); rpc GetDeckNames(GetDeckNamesRequest) returns (DeckNames); + rpc GetDeckAndChildNames(DeckId) returns (DeckNames); rpc NewDeckLegacy(generic.Bool) returns (generic.Json); rpc RemoveDecks(DeckIds) returns (collection.OpChangesWithCount); rpc ReparentDecks(ReparentDecksRequest) diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 5f2d563ab..cf7e08c8a 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -443,26 +443,31 @@ class DeckManager(DeprecatedNamesMixin): def key(cls, deck: DeckDict) -> list[str]: return cls.path(deck["name"]) - def children(self, did: DeckId) -> list[tuple[str, DeckId]]: - "All children of did, as (name, id)." - name = self.get(did)["name"] - actv = [] - for entry in self.all_names_and_ids(): - if entry.name.startswith(f"{name}::"): - actv.append((entry.name, DeckId(entry.id))) - return actv - - def child_ids(self, parent_name: str) -> Iterable[DeckId]: - prefix = f"{parent_name}::" + def deck_and_child_name_ids(self, deck_id: DeckId) -> Iterable[tuple[str, DeckId]]: + """The deck of did and all its children, as (name, id).""" return ( - DeckId(d.id) for d in self.all_names_and_ids() if d.name.startswith(prefix) + (entry.name, DeckId(entry.id)) + for entry in self.col._backend.get_deck_and_child_names(deck_id) ) + def children(self, did: DeckId) -> list[tuple[str, DeckId]]: + "All children of did, as (name, id)." + return [ + name_id + for name_id in self.deck_and_child_name_ids(did) + if name_id[1] != did + ] + + def child_ids(self, parent_name: str) -> Iterable[DeckId]: + if not (parent_id := self.id_for_name(parent_name)): + return [] + return (name_id[1] for name_id in self.children(parent_id)) + def deck_and_child_ids(self, deck_id: DeckId) -> list[DeckId]: - parent_name = self.name(deck_id) - out = [deck_id] - out.extend(self.child_ids(parent_name)) - return out + return [ + DeckId(entry.id) + for entry in self.col._backend.get_deck_and_child_names(deck_id) + ] def parents( self, did: DeckId, name_map: dict[str, DeckDict] | None = None diff --git a/rslib/src/backend/decks.rs b/rslib/src/backend/decks.rs index 026d588c9..08a7935ef 100644 --- a/rslib/src/backend/decks.rs +++ b/rslib/src/backend/decks.rs @@ -128,12 +128,14 @@ impl DecksService for Backend { } else { col.get_all_normal_deck_names()? }; - Ok(pb::DeckNames { - entries: names - .into_iter() - .map(|(id, name)| pb::DeckNameId { id: id.0, name }) - .collect(), - }) + Ok(names.into()) + }) + } + + fn get_deck_and_child_names(&self, input: pb::DeckId) -> Result { + self.with_col(|col| { + col.get_deck_and_child_names(input.did.into()) + .map(Into::into) }) } @@ -293,6 +295,23 @@ impl From for DeckKind { } } +impl From<(DeckId, String)> for pb::DeckNameId { + fn from(id_name: (DeckId, String)) -> Self { + pb::DeckNameId { + id: id_name.0 .0, + name: id_name.1, + } + } +} + +impl From> for pb::DeckNames { + fn from(id_names: Vec<(DeckId, String)>) -> Self { + pb::DeckNames { + entries: id_names.into_iter().map(Into::into).collect(), + } + } +} + // fn new_deck(&self, input: pb::Bool) -> Result { // let deck = if input.val { // Deck::new_filtered() diff --git a/rslib/src/decks/name.rs b/rslib/src/decks/name.rs index 6c036317f..d694c4a74 100644 --- a/rslib/src/decks/name.rs +++ b/rslib/src/decks/name.rs @@ -161,6 +161,15 @@ impl Collection { self.storage.get_all_deck_names() } } + + pub fn get_deck_and_child_names(&self, did: DeckId) -> Result> { + Ok(self + .storage + .deck_with_children(did)? + .iter() + .map(|deck| (deck.id, deck.name.human_name())) + .collect()) + } } fn invalid_char_for_deck_component(c: char) -> bool {