// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html //! Adding and updating. use super::name::immediate_parent_name; use crate::error::FilteredDeckError; use crate::prelude::*; impl Collection { /// Add a new deck. The id must be 0, as it will be automatically assigned. pub fn add_deck(&mut self, deck: &mut Deck) -> Result> { self.transact(Op::AddDeck, |col| col.add_deck_inner(deck, col.usn()?)) } pub fn update_deck(&mut self, deck: &mut Deck) -> Result> { self.transact(Op::UpdateDeck, |col| { let existing_deck = col.storage.get_deck(deck.id)?.or_not_found(deck.id)?; col.update_deck_inner(deck, existing_deck, col.usn()?) }) } /// Add or update an existing deck modified by the user. May add parents, /// or rename children as required. Prefer add_deck() or update_deck() to /// be explicit about your intentions; this function mainly exists so we /// can integrate with older Python code that behaved this way. pub fn add_or_update_deck(&mut self, deck: &mut Deck) -> Result> { if deck.id.0 == 0 { self.add_deck(deck) } else { self.update_deck(deck) } } } impl Collection { /// Rename deck if not unique. Bumps mtime and usn if /// name was changed, but otherwise leaves it the same. pub(super) fn prepare_deck_for_update(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> { if deck.name.maybe_normalize() { deck.set_modified(usn); } self.ensure_deck_name_unique(deck, usn) } pub(crate) fn add_deck_inner(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> { require!(deck.id.0 == 0, "deck to add must have id 0"); self.prepare_deck_for_update(deck, usn)?; deck.set_modified(usn); self.match_or_create_parents(deck, usn)?; self.add_deck_undoable(deck) } pub(crate) fn update_deck_inner( &mut self, deck: &mut Deck, original: Deck, usn: Usn, ) -> Result<()> { self.prepare_deck_for_update(deck, usn)?; if deck == &original { return Ok(()); } 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. pub(crate) fn add_or_update_single_deck_with_existing_id( &mut self, deck: &mut Deck, usn: Usn, ) -> Result<()> { self.prepare_deck_for_update(deck, usn)?; self.add_or_update_deck_with_existing_id_undoable(deck) } pub(crate) fn recover_missing_deck(&mut self, did: DeckId, usn: Usn) -> Result<()> { let mut deck = Deck::new_normal(); deck.id = did; deck.name = NativeDeckName::from_native_str(format!("recovered{}", did)); deck.set_modified(usn); self.add_or_update_single_deck_with_existing_id(&mut deck, usn) } /// Add a single, normal deck with the provided name for a child deck. /// Caller must have done necessarily validation on name. fn add_parent_deck(&mut self, machine_name: &str, usn: Usn) -> Result<()> { let mut deck = Deck::new_normal(); deck.name = NativeDeckName::from_native_str(machine_name); deck.set_modified(usn); self.add_deck_undoable(&mut deck) } /// If parent deck(s) exist, rewrite name to match their case. /// If they don't exist, create them. /// Returns an error if a DB operation fails, or if the first existing /// parent is a filtered deck. fn match_or_create_parents(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> { let child_split: Vec<_> = deck.name.components().collect(); if let Some(parent_deck) = self.first_existing_parent(deck.name.as_native_str(), 0)? { if parent_deck.is_filtered() { return Err(FilteredDeckError::MustBeLeafNode.into()); } let parent_count = parent_deck.name.components().count(); let need_create = parent_count != child_split.len() - 1; deck.name = NativeDeckName::from_native_str(format!( "{}\x1f{}", parent_deck.name, &child_split[parent_count..].join("\x1f") )); if need_create { self.create_missing_parents(&deck.name, usn)?; } Ok(()) } else if child_split.len() == 1 { // no parents required Ok(()) } else { // no existing parents self.create_missing_parents(&deck.name, usn) } } fn create_missing_parents(&mut self, name: &NativeDeckName, usn: Usn) -> Result<()> { let mut machine_name = name.as_native_str(); while let Some(parent_name) = immediate_parent_name(machine_name) { if self.storage.get_deck_id(parent_name)?.is_none() { self.add_parent_deck(parent_name, usn)?; } machine_name = parent_name; } Ok(()) } pub(crate) fn first_existing_parent( &self, machine_name: &str, recursion_level: usize, ) -> Result> { require!(recursion_level < 11, "deck nesting level too deep"); if let Some(parent_name) = immediate_parent_name(machine_name) { if let Some(parent_did) = self.storage.get_deck_id(parent_name)? { self.storage.get_deck(parent_did) } else { self.first_existing_parent(parent_name, recursion_level + 1) } } else { Ok(None) } } }