mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Give deck.name the newtype NativeDeckName
The deck name must be constructed by calling associated functions of NativeDeckName, unless the name is guaranteed to be valid machine name (like "Default"). NativeDeckName exposes methods to mutate the deck name and return the human name. The storage routines take &strs, but those should be slices of NativeDeckNames to ensure machine form and normalization.
This commit is contained in:
parent
ac46d40db3
commit
20bd207f00
14 changed files with 158 additions and 171 deletions
|
@ -6,10 +6,7 @@ use std::convert::TryFrom;
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::{self as pb},
|
backend_proto::{self as pb},
|
||||||
decks::{
|
decks::{DeckSchema11, FilteredSearchOrder},
|
||||||
human_deck_name_to_native, native_deck_name_to_human, Deck, DeckId, DeckSchema11,
|
|
||||||
FilteredSearchOrder,
|
|
||||||
},
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
scheduler::filtered::FilteredDeckForUpdate,
|
scheduler::filtered::FilteredDeckForUpdate,
|
||||||
};
|
};
|
||||||
|
@ -89,7 +86,6 @@ impl DecksService for Backend {
|
||||||
.storage
|
.storage
|
||||||
.get_deck(input.into())?
|
.get_deck(input.into())?
|
||||||
.ok_or(AnkiError::NotFound)?
|
.ok_or(AnkiError::NotFound)?
|
||||||
.with_human_name()
|
|
||||||
.into())
|
.into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -241,7 +237,7 @@ impl From<Deck> for pb::Deck {
|
||||||
fn from(d: Deck) -> Self {
|
fn from(d: Deck) -> Self {
|
||||||
pb::Deck {
|
pb::Deck {
|
||||||
id: d.id.0,
|
id: d.id.0,
|
||||||
name: native_deck_name_to_human(&d.name),
|
name: d.name.human_name(),
|
||||||
mtime_secs: d.mtime_secs.0,
|
mtime_secs: d.mtime_secs.0,
|
||||||
usn: d.usn.0,
|
usn: d.usn.0,
|
||||||
common: Some(d.common),
|
common: Some(d.common),
|
||||||
|
@ -256,7 +252,7 @@ impl TryFrom<pb::Deck> for Deck {
|
||||||
fn try_from(d: pb::Deck) -> Result<Self, Self::Error> {
|
fn try_from(d: pb::Deck) -> Result<Self, Self::Error> {
|
||||||
Ok(Deck {
|
Ok(Deck {
|
||||||
id: DeckId(d.id),
|
id: DeckId(d.id),
|
||||||
name: human_deck_name_to_native(&d.name),
|
name: NativeDeckName::from_human_name(&d.name),
|
||||||
mtime_secs: TimestampSecs(d.mtime_secs),
|
mtime_secs: TimestampSecs(d.mtime_secs),
|
||||||
usn: Usn(d.usn),
|
usn: Usn(d.usn),
|
||||||
common: d.common.unwrap_or_default(),
|
common: d.common.unwrap_or_default(),
|
||||||
|
|
|
@ -430,7 +430,7 @@ mod test {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
col.storage.get_deck(DeckId(123))?.unwrap().name,
|
col.storage.get_deck(DeckId(123))?.unwrap().name.as_str(),
|
||||||
"recovered123"
|
"recovered123"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
use super::name::{immediate_parent_name, normalize_native_name};
|
use super::name::immediate_parent_name;
|
||||||
use crate::{error::FilteredDeckError, prelude::*};
|
use crate::{error::FilteredDeckError, prelude::*};
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
/// Normalize deck name and rename if not unique. Bumps mtime and usn if
|
/// Rename deck if not unique. Bumps mtime and usn if
|
||||||
/// name was changed, but otherwise leaves it the same.
|
/// name was changed, but otherwise leaves it the same.
|
||||||
pub(super) fn prepare_deck_for_update(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
pub(super) fn prepare_deck_for_update(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
if let Cow::Owned(name) = normalize_native_name(&deck.name) {
|
|
||||||
deck.name = name;
|
|
||||||
deck.set_modified(usn);
|
|
||||||
}
|
|
||||||
self.ensure_deck_name_unique(deck, usn)
|
self.ensure_deck_name_unique(deck, usn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +64,7 @@ impl Collection {
|
||||||
if name_changed {
|
if name_changed {
|
||||||
// after updating, we need to ensure all grandparents exist, which may not be the case
|
// after updating, we need to ensure all grandparents exist, which may not be the case
|
||||||
// in the parent->child case
|
// in the parent->child case
|
||||||
self.create_missing_parents(&deck.name, usn)?;
|
self.create_missing_parents(deck.name.as_str(), usn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -89,7 +84,7 @@ impl Collection {
|
||||||
pub(crate) fn recover_missing_deck(&mut self, did: DeckId, usn: Usn) -> Result<()> {
|
pub(crate) fn recover_missing_deck(&mut self, did: DeckId, usn: Usn) -> Result<()> {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.id = did;
|
deck.id = did;
|
||||||
deck.name = format!("recovered{}", did);
|
deck.name = NativeDeckName(format!("recovered{}", did));
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
self.add_or_update_single_deck_with_existing_id(&mut deck, usn)
|
self.add_or_update_single_deck_with_existing_id(&mut deck, usn)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +95,7 @@ impl Collection {
|
||||||
/// Caller must have done necessarily validation on name.
|
/// Caller must have done necessarily validation on name.
|
||||||
fn add_parent_deck(&mut self, machine_name: &str, usn: Usn) -> Result<()> {
|
fn add_parent_deck(&mut self, machine_name: &str, usn: Usn) -> Result<()> {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.name = machine_name.into();
|
deck.name = NativeDeckName(machine_name.into());
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
self.add_deck_undoable(&mut deck)
|
self.add_deck_undoable(&mut deck)
|
||||||
}
|
}
|
||||||
|
@ -109,20 +104,20 @@ impl Collection {
|
||||||
/// If they don't exist, create them.
|
/// 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.
|
/// 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<()> {
|
fn match_or_create_parents(&mut self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
let child_split: Vec<_> = deck.name.split('\x1f').collect();
|
let child_split: Vec<_> = deck.name.components().collect();
|
||||||
if let Some(parent_deck) = self.first_existing_parent(&deck.name, 0)? {
|
if let Some(parent_deck) = self.first_existing_parent(deck.name.as_str(), 0)? {
|
||||||
if parent_deck.is_filtered() {
|
if parent_deck.is_filtered() {
|
||||||
return Err(FilteredDeckError::MustBeLeafNode.into());
|
return Err(FilteredDeckError::MustBeLeafNode.into());
|
||||||
}
|
}
|
||||||
let parent_count = parent_deck.name.matches('\x1f').count() + 1;
|
let parent_count = parent_deck.name.components().count();
|
||||||
let need_create = parent_count != child_split.len() - 1;
|
let need_create = parent_count != child_split.len() - 1;
|
||||||
deck.name = format!(
|
deck.name = NativeDeckName(format!(
|
||||||
"{}\x1f{}",
|
"{}\x1f{}",
|
||||||
parent_deck.name,
|
parent_deck.name,
|
||||||
&child_split[parent_count..].join("\x1f")
|
&child_split[parent_count..].join("\x1f")
|
||||||
);
|
));
|
||||||
if need_create {
|
if need_create {
|
||||||
self.create_missing_parents(&deck.name, usn)?;
|
self.create_missing_parents(deck.name.as_str(), usn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if child_split.len() == 1 {
|
} else if child_split.len() == 1 {
|
||||||
|
@ -130,7 +125,7 @@ impl Collection {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// no existing parents
|
// no existing parents
|
||||||
self.create_missing_parents(&deck.name, usn)
|
self.create_missing_parents(deck.name.as_str(), usn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ impl Deck {
|
||||||
filt.reschedule = true;
|
filt.reschedule = true;
|
||||||
Deck {
|
Deck {
|
||||||
id: DeckId(0),
|
id: DeckId(0),
|
||||||
name: "".into(),
|
name: NativeDeckName("".into()),
|
||||||
mtime_secs: TimestampSecs(0),
|
mtime_secs: TimestampSecs(0),
|
||||||
usn: Usn(0),
|
usn: Usn(0),
|
||||||
common: DeckCommon {
|
common: DeckCommon {
|
||||||
|
|
|
@ -25,9 +25,8 @@ use crate::{
|
||||||
text::sanitize_html_no_images,
|
text::sanitize_html_no_images,
|
||||||
};
|
};
|
||||||
pub(crate) use counts::DueCounts;
|
pub(crate) use counts::DueCounts;
|
||||||
pub(crate) use name::{
|
pub(crate) use name::immediate_parent_name;
|
||||||
human_deck_name_to_native, immediate_parent_name, native_deck_name_to_human,
|
pub use name::NativeDeckName;
|
||||||
};
|
|
||||||
pub use schema11::DeckSchema11;
|
pub use schema11::DeckSchema11;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ define_newtype!(DeckId, i64);
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Deck {
|
pub struct Deck {
|
||||||
pub id: DeckId,
|
pub id: DeckId,
|
||||||
pub name: String,
|
pub name: NativeDeckName,
|
||||||
pub mtime_secs: TimestampSecs,
|
pub mtime_secs: TimestampSecs,
|
||||||
pub usn: Usn,
|
pub usn: Usn,
|
||||||
pub common: DeckCommon,
|
pub common: DeckCommon,
|
||||||
|
@ -47,7 +46,7 @@ impl Deck {
|
||||||
pub fn new_normal() -> Deck {
|
pub fn new_normal() -> Deck {
|
||||||
Deck {
|
Deck {
|
||||||
id: DeckId(0),
|
id: DeckId(0),
|
||||||
name: "".into(),
|
name: NativeDeckName("".into()),
|
||||||
mtime_secs: TimestampSecs(0),
|
mtime_secs: TimestampSecs(0),
|
||||||
usn: Usn(0),
|
usn: Usn(0),
|
||||||
common: DeckCommon {
|
common: DeckCommon {
|
||||||
|
@ -150,12 +149,12 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result<Deck> {
|
pub fn get_or_create_normal_deck(&mut self, human_name: &str) -> Result<Deck> {
|
||||||
let native_name = human_deck_name_to_native(human_name);
|
let name = NativeDeckName::from_human_name(human_name);
|
||||||
if let Some(did) = self.storage.get_deck_id(&native_name)? {
|
if let Some(did) = self.storage.get_deck_id(name.as_str())? {
|
||||||
self.storage.get_deck(did).map(|opt| opt.unwrap())
|
self.storage.get_deck(did).map(|opt| opt.unwrap())
|
||||||
} else {
|
} else {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.name = native_name;
|
deck.name = name;
|
||||||
self.add_or_update_deck(&mut deck)?;
|
self.add_or_update_deck(&mut deck)?;
|
||||||
Ok(deck)
|
Ok(deck)
|
||||||
}
|
}
|
||||||
|
@ -164,18 +163,14 @@ impl Collection {
|
||||||
/// Get a deck based on its human name. If you have a machine name,
|
/// Get a deck based on its human name. If you have a machine name,
|
||||||
/// use the method in storage instead.
|
/// use the method in storage instead.
|
||||||
pub(crate) fn get_deck_id(&self, human_name: &str) -> Result<Option<DeckId>> {
|
pub(crate) fn get_deck_id(&self, human_name: &str) -> Result<Option<DeckId>> {
|
||||||
let machine_name = human_deck_name_to_native(&human_name);
|
self.storage
|
||||||
self.storage.get_deck_id(&machine_name)
|
.get_deck_id(NativeDeckName::from_human_name(human_name).as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{collection::open_test_collection, prelude::*, search::SortMode};
|
||||||
collection::{open_test_collection, Collection},
|
|
||||||
error::Result,
|
|
||||||
search::SortMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn sorted_names(col: &Collection) -> Vec<String> {
|
fn sorted_names(col: &Collection) -> Vec<String> {
|
||||||
col.storage
|
col.storage
|
||||||
|
@ -212,7 +207,7 @@ mod test {
|
||||||
|
|
||||||
let _ = col.get_or_create_normal_deck("foo::bar::baz")?;
|
let _ = col.get_or_create_normal_deck("foo::bar::baz")?;
|
||||||
let mut top_deck = col.get_or_create_normal_deck("foo")?;
|
let mut top_deck = col.get_or_create_normal_deck("foo")?;
|
||||||
top_deck.name = "other".into();
|
top_deck.name = NativeDeckName("other".into());
|
||||||
col.add_or_update_deck(&mut top_deck)?;
|
col.add_or_update_deck(&mut top_deck)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sorted_names(&col),
|
sorted_names(&col),
|
||||||
|
@ -221,7 +216,7 @@ mod test {
|
||||||
|
|
||||||
// should do the right thing in the middle of the tree as well
|
// should do the right thing in the middle of the tree as well
|
||||||
let mut middle = col.get_or_create_normal_deck("other::bar")?;
|
let mut middle = col.get_or_create_normal_deck("other::bar")?;
|
||||||
middle.name = "quux\x1ffoo".into();
|
middle.name = NativeDeckName("quux\x1ffoo".into());
|
||||||
col.add_or_update_deck(&mut middle)?;
|
col.add_or_update_deck(&mut middle)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sorted_names(&col),
|
sorted_names(&col),
|
||||||
|
@ -234,7 +229,7 @@ mod test {
|
||||||
// quux::foo -> quux::foo::baz::four
|
// quux::foo -> quux::foo::baz::four
|
||||||
// means quux::foo::baz2 should be quux::foo::baz::four::baz2
|
// means quux::foo::baz2 should be quux::foo::baz::four::baz2
|
||||||
// and a new quux::foo should have been created
|
// and a new quux::foo should have been created
|
||||||
middle.name = "quux\x1ffoo\x1fbaz\x1ffour".into();
|
middle.name = NativeDeckName("quux\x1ffoo\x1fbaz\x1ffour".into());
|
||||||
col.add_or_update_deck(&mut middle)?;
|
col.add_or_update_deck(&mut middle)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sorted_names(&col),
|
sorted_names(&col),
|
||||||
|
@ -251,9 +246,9 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
// should handle name conflicts
|
// should handle name conflicts
|
||||||
middle.name = "other".into();
|
middle.name = NativeDeckName("other".into());
|
||||||
col.add_or_update_deck(&mut middle)?;
|
col.add_or_update_deck(&mut middle)?;
|
||||||
assert_eq!(middle.name, "other+");
|
assert_eq!(middle.name.as_str(), "other+");
|
||||||
|
|
||||||
// public function takes human name
|
// public function takes human name
|
||||||
col.rename_deck(middle.id, "one::two")?;
|
col.rename_deck(middle.id, "one::two")?;
|
||||||
|
@ -282,7 +277,7 @@ mod test {
|
||||||
let mut col = open_test_collection();
|
let mut col = open_test_collection();
|
||||||
|
|
||||||
let mut default = col.get_or_create_normal_deck("default")?;
|
let mut default = col.get_or_create_normal_deck("default")?;
|
||||||
default.name = "one\x1ftwo".into();
|
default.name = NativeDeckName("one\x1ftwo".into());
|
||||||
col.add_or_update_deck(&mut default)?;
|
col.add_or_update_deck(&mut default)?;
|
||||||
|
|
||||||
// create a non-default deck confusingly named "default"
|
// create a non-default deck confusingly named "default"
|
||||||
|
|
|
@ -1,17 +1,74 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
use crate::{prelude::*, text::normalize_to_nfc};
|
use crate::{prelude::*, text::normalize_to_nfc};
|
||||||
|
use itertools::Itertools;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct NativeDeckName(pub String);
|
||||||
|
|
||||||
|
impl NativeDeckName {
|
||||||
|
pub fn from_human_name(name: &str) -> Self {
|
||||||
|
NativeDeckName(
|
||||||
|
name.split("::")
|
||||||
|
.map(normalized_deck_name_component)
|
||||||
|
.join("\x1f"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn human_name(&self) -> String {
|
||||||
|
self.0.replace('\x1f', "::")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_suffix(&mut self, suffix: &str) {
|
||||||
|
self.0 += suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn components(&self) -> std::str::Split<'_, char> {
|
||||||
|
self.0.split('\x1f')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine name to rename a deck to, when `self` is dropped on `target`.
|
||||||
|
/// `target` being unset represents a drop at the top or bottom of the deck list.
|
||||||
|
/// The returned name should be used to replace `self`.
|
||||||
|
pub(crate) fn reparented_name(&self, target: Option<&NativeDeckName>) -> Option<Self> {
|
||||||
|
let dragged_base = self.0.rsplit('\x1f').next().unwrap();
|
||||||
|
if let Some(target) = target {
|
||||||
|
if target.0.starts_with(&self.0) {
|
||||||
|
// foo onto foo::bar, or foo onto itself -> no-op
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// foo::bar onto baz -> baz::bar
|
||||||
|
Some(NativeDeckName(format!("{}\x1f{}", target.0, dragged_base)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// foo::bar onto top level -> bar
|
||||||
|
Some(NativeDeckName(dragged_base.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace the old parent's name with the new parent's name in self's name, where the old
|
||||||
|
/// parent's name is expected to be a prefix.
|
||||||
|
fn reparent(&mut self, old_parent: &NativeDeckName, new_parent: &NativeDeckName) {
|
||||||
|
self.0 = std::iter::once(new_parent.as_str())
|
||||||
|
.chain(self.components().skip(old_parent.components().count()))
|
||||||
|
.join("\x1f")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for NativeDeckName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deck {
|
impl Deck {
|
||||||
pub fn human_name(&self) -> String {
|
pub fn human_name(&self) -> String {
|
||||||
self.name.replace("\x1f", "::")
|
self.name.human_name()
|
||||||
}
|
|
||||||
|
|
||||||
// Mutate name to human representation for sharing.
|
|
||||||
pub fn with_human_name(mut self) -> Self {
|
|
||||||
self.name = self.human_name();
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +89,7 @@ impl Collection {
|
||||||
self.transact(Op::RenameDeck, |col| {
|
self.transact(Op::RenameDeck, |col| {
|
||||||
let existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?;
|
let existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?;
|
||||||
let mut deck = existing_deck.clone();
|
let mut deck = existing_deck.clone();
|
||||||
deck.name = human_deck_name_to_native(new_human_name);
|
deck.name = NativeDeckName::from_human_name(new_human_name);
|
||||||
col.update_deck_inner(&mut deck, existing_deck, col.usn()?)
|
col.update_deck_inner(&mut deck, existing_deck, col.usn()?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -40,18 +97,13 @@ impl Collection {
|
||||||
pub(super) fn rename_child_decks(
|
pub(super) fn rename_child_decks(
|
||||||
&mut self,
|
&mut self,
|
||||||
old: &Deck,
|
old: &Deck,
|
||||||
new_name: &str,
|
new_name: &NativeDeckName,
|
||||||
usn: Usn,
|
usn: Usn,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let children = self.storage.child_decks(old)?;
|
let children = self.storage.child_decks(old)?;
|
||||||
let old_component_count = old.name.matches('\x1f').count() + 1;
|
|
||||||
|
|
||||||
for mut child in children {
|
for mut child in children {
|
||||||
let original = child.clone();
|
let original = child.clone();
|
||||||
let child_components: Vec<_> = child.name.split('\x1f').collect();
|
child.name.reparent(&old.name, new_name);
|
||||||
let child_only = &child_components[old_component_count..];
|
|
||||||
let new_name = format!("{}\x1f{}", new_name, child_only.join("\x1f"));
|
|
||||||
child.name = new_name;
|
|
||||||
child.set_modified(usn);
|
child.set_modified(usn);
|
||||||
self.update_single_deck_undoable(&mut child, original)?;
|
self.update_single_deck_undoable(&mut child, original)?;
|
||||||
}
|
}
|
||||||
|
@ -61,14 +113,12 @@ impl Collection {
|
||||||
|
|
||||||
pub(crate) fn ensure_deck_name_unique(&self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
pub(crate) fn ensure_deck_name_unique(&self, deck: &mut Deck, usn: Usn) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match self.storage.get_deck_id(&deck.name)? {
|
match self.storage.get_deck_id(deck.name.as_str())? {
|
||||||
Some(did) if did == deck.id => {
|
Some(did) if did == deck.id => break,
|
||||||
break;
|
|
||||||
}
|
|
||||||
None => break,
|
None => break,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
deck.name += "+";
|
deck.name.add_suffix("+");
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,59 +158,10 @@ fn normalized_deck_name_component(comp: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn normalize_native_name(name: &str) -> Cow<str> {
|
|
||||||
if name
|
|
||||||
.split('\x1f')
|
|
||||||
.any(|comp| matches!(normalized_deck_name_component(comp), Cow::Owned(_)))
|
|
||||||
{
|
|
||||||
let comps: Vec<_> = name
|
|
||||||
.split('\x1f')
|
|
||||||
.map(normalized_deck_name_component)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
comps.join("\x1f").into()
|
|
||||||
} else {
|
|
||||||
// no changes required
|
|
||||||
name.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn human_deck_name_to_native(name: &str) -> String {
|
|
||||||
let mut out = String::with_capacity(name.len());
|
|
||||||
for comp in name.split("::") {
|
|
||||||
out.push_str(&normalized_deck_name_component(comp));
|
|
||||||
out.push('\x1f');
|
|
||||||
}
|
|
||||||
out.trim_end_matches('\x1f').into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn native_deck_name_to_human(name: &str) -> String {
|
|
||||||
name.replace('\x1f', "::")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn immediate_parent_name(machine_name: &str) -> Option<&str> {
|
pub(crate) fn immediate_parent_name(machine_name: &str) -> Option<&str> {
|
||||||
machine_name.rsplitn(2, '\x1f').nth(1)
|
machine_name.rsplitn(2, '\x1f').nth(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine name to rename a deck to, when `dragged` is dropped on `dropped`.
|
|
||||||
/// `dropped` being unset represents a drop at the top or bottom of the deck list.
|
|
||||||
/// The returned name should be used to rename `dragged`.
|
|
||||||
/// Arguments are expected in 'machine' form with an \x1f separator.
|
|
||||||
pub(crate) fn reparented_name(dragged: &str, dropped: Option<&str>) -> Option<String> {
|
|
||||||
let dragged_base = dragged.rsplit('\x1f').next().unwrap();
|
|
||||||
if let Some(dropped) = dropped {
|
|
||||||
if dropped.starts_with(dragged) {
|
|
||||||
// foo onto foo::bar, or foo onto itself -> no-op
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// foo::bar onto baz -> baz::bar
|
|
||||||
Some(format!("{}\x1f{}", dropped, dragged_base))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// foo::bar onto top level -> bar
|
|
||||||
Some(dragged_base.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -177,54 +178,52 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_human() {
|
fn from_human() {
|
||||||
assert_eq!(&human_deck_name_to_native("foo"), "foo");
|
fn native_name(name: &str) -> String {
|
||||||
assert_eq!(&human_deck_name_to_native("foo::bar"), "foo\x1fbar");
|
NativeDeckName::from_human_name(name).0
|
||||||
assert_eq!(&human_deck_name_to_native("fo\x1fo::ba\nr"), "foo\x1fbar");
|
}
|
||||||
assert_eq!(
|
|
||||||
&human_deck_name_to_native("foo::::baz"),
|
|
||||||
"foo\x1fblank\x1fbaz"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
assert_eq!(native_name("foo"), "foo");
|
||||||
fn normalize() {
|
assert_eq!(native_name("foo::bar"), "foo\x1fbar");
|
||||||
assert_eq!(&normalize_native_name("foo\x1fbar"), "foo\x1fbar");
|
assert_eq!(native_name("foo::::baz"), "foo\x1fblank\x1fbaz");
|
||||||
assert_eq!(&normalize_native_name("fo\u{a}o\x1fbar"), "foo\x1fbar");
|
// normalize
|
||||||
|
assert_eq!(native_name("fo\x1fo::ba\nr"), "foo\x1fbar");
|
||||||
|
assert_eq!(native_name("fo\u{a}o\x1fbar"), "foobar");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn drag_drop() {
|
fn drag_drop() {
|
||||||
// use custom separator to make the tests easier to read
|
// use custom separator to make the tests easier to read
|
||||||
fn n(s: &str) -> String {
|
fn n(s: &str) -> NativeDeckName {
|
||||||
s.replace(":", "\x1f")
|
NativeDeckName(s.replace(":", "\x1f"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn n_opt(s: &str) -> Option<String> {
|
fn n_opt(s: &str) -> Option<NativeDeckName> {
|
||||||
Some(n(s))
|
Some(n(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reparented_name(drag: &str, drop: Option<&str>) -> Option<NativeDeckName> {
|
||||||
|
n(drag).reparented_name(drop.map(n).as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(reparented_name("drag", Some("drop")), n_opt("drop:drag"));
|
assert_eq!(reparented_name("drag", Some("drop")), n_opt("drop:drag"));
|
||||||
assert_eq!(reparented_name("drag", None), n_opt("drag"));
|
assert_eq!(reparented_name("drag", None), n_opt("drag"));
|
||||||
assert_eq!(reparented_name(&n("drag:child"), None), n_opt("child"));
|
assert_eq!(reparented_name("drag:child", None), n_opt("child"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reparented_name(&n("drag:child"), Some(&n("drop:deck"))),
|
reparented_name("drag:child", Some("drop:deck")),
|
||||||
n_opt("drop:deck:child")
|
n_opt("drop:deck:child")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reparented_name(&n("drag:child"), Some("drag")),
|
reparented_name("drag:child", Some("drag")),
|
||||||
n_opt("drag:child")
|
n_opt("drag:child")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reparented_name(&n("drag:child:grandchild"), Some("drag")),
|
reparented_name("drag:child:grandchild", Some("drag")),
|
||||||
n_opt("drag:grandchild")
|
n_opt("drag:grandchild")
|
||||||
);
|
);
|
||||||
// drops to child not supported
|
// drops to child not supported
|
||||||
assert_eq!(
|
assert_eq!(reparented_name("drag", Some("drag:child:grandchild")), None);
|
||||||
reparented_name(&n("drag"), Some(&n("drag:child:grandchild"))),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
// name doesn't change when deck dropped on itself
|
// name doesn't change when deck dropped on itself
|
||||||
assert_eq!(reparented_name(&n("foo:bar"), Some(&n("foo:bar"))), None);
|
assert_eq!(reparented_name("foo:bar", Some("foo:bar")), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl Collection {
|
||||||
// if the default deck is included, just ensure it's reset to the default
|
// if the default deck is included, just ensure it's reset to the default
|
||||||
// name, as we've already removed its cards
|
// name, as we've already removed its cards
|
||||||
let mut modified_default = deck.clone();
|
let mut modified_default = deck.clone();
|
||||||
modified_default.name = self.tr.deck_config_default_name().into();
|
modified_default.name = NativeDeckName(self.tr.deck_config_default_name().into());
|
||||||
self.prepare_deck_for_update(&mut modified_default, usn)?;
|
self.prepare_deck_for_update(&mut modified_default, usn)?;
|
||||||
modified_default.set_modified(usn);
|
modified_default.set_modified(usn);
|
||||||
self.update_single_deck_undoable(&mut modified_default, deck.clone())?;
|
self.update_single_deck_undoable(&mut modified_default, deck.clone())?;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
use super::name::reparented_name;
|
|
||||||
use crate::{error::FilteredDeckError, prelude::*};
|
use crate::{error::FilteredDeckError, prelude::*};
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
|
@ -28,14 +27,14 @@ impl Collection {
|
||||||
return Err(FilteredDeckError::MustBeLeafNode.into());
|
return Err(FilteredDeckError::MustBeLeafNode.into());
|
||||||
}
|
}
|
||||||
target_deck = target;
|
target_deck = target;
|
||||||
target_name = Some(target_deck.name.as_str());
|
target_name = Some(&target_deck.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for deck in deck_ids {
|
for deck in deck_ids {
|
||||||
if let Some(mut deck) = self.storage.get_deck(*deck)? {
|
if let Some(mut deck) = self.storage.get_deck(*deck)? {
|
||||||
if let Some(new_name) = reparented_name(&deck.name, target_name) {
|
if let Some(new_name) = deck.name.reparented_name(target_name) {
|
||||||
count += 1;
|
count += 1;
|
||||||
let orig = deck.clone();
|
let orig = deck.clone();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use super::DeckId;
|
use super::{DeckCommon, FilteredDeck, FilteredSearchTerm, NormalDeck};
|
||||||
use super::{
|
|
||||||
human_deck_name_to_native, native_deck_name_to_human, DeckCommon, FilteredDeck,
|
|
||||||
FilteredSearchTerm, NormalDeck,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
serde::{default_on_invalid, deserialize_bool_from_anything, deserialize_number_from_string},
|
serde::{default_on_invalid, deserialize_bool_from_anything, deserialize_number_from_string},
|
||||||
|
@ -239,7 +235,7 @@ impl From<DeckSchema11> for Deck {
|
||||||
match deck {
|
match deck {
|
||||||
DeckSchema11::Normal(d) => Deck {
|
DeckSchema11::Normal(d) => Deck {
|
||||||
id: d.common.id,
|
id: d.common.id,
|
||||||
name: human_deck_name_to_native(&d.common.name),
|
name: NativeDeckName::from_human_name(&d.common.name),
|
||||||
mtime_secs: d.common.mtime,
|
mtime_secs: d.common.mtime,
|
||||||
usn: d.common.usn,
|
usn: d.common.usn,
|
||||||
common: (&d.common).into(),
|
common: (&d.common).into(),
|
||||||
|
@ -247,7 +243,7 @@ impl From<DeckSchema11> for Deck {
|
||||||
},
|
},
|
||||||
DeckSchema11::Filtered(d) => Deck {
|
DeckSchema11::Filtered(d) => Deck {
|
||||||
id: d.common.id,
|
id: d.common.id,
|
||||||
name: human_deck_name_to_native(&d.common.name),
|
name: NativeDeckName::from_human_name(&d.common.name),
|
||||||
mtime_secs: d.common.mtime,
|
mtime_secs: d.common.mtime,
|
||||||
usn: d.common.usn,
|
usn: d.common.usn,
|
||||||
common: (&d.common).into(),
|
common: (&d.common).into(),
|
||||||
|
@ -362,7 +358,7 @@ impl From<Deck> for DeckCommonSchema11 {
|
||||||
DeckCommonSchema11 {
|
DeckCommonSchema11 {
|
||||||
id: deck.id,
|
id: deck.id,
|
||||||
mtime: deck.mtime_secs,
|
mtime: deck.mtime_secs,
|
||||||
name: native_deck_name_to_human(&deck.name),
|
name: deck.human_name(),
|
||||||
usn: deck.usn,
|
usn: deck.usn,
|
||||||
today: (&deck).into(),
|
today: (&deck).into(),
|
||||||
study_collapsed: deck.common.study_collapsed,
|
study_collapsed: deck.common.study_collapsed,
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub use crate::{
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
config::BoolKey,
|
config::BoolKey,
|
||||||
deckconf::{DeckConf, DeckConfId},
|
deckconf::{DeckConf, DeckConfId},
|
||||||
decks::{Deck, DeckId, DeckKind},
|
decks::{Deck, DeckId, DeckKind, NativeDeckName},
|
||||||
error::{AnkiError, Result},
|
error::{AnkiError, Result},
|
||||||
i18n::I18n,
|
i18n::I18n,
|
||||||
notes::{Note, NoteId},
|
notes::{Note, NoteId},
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::ConfigKey,
|
config::ConfigKey,
|
||||||
decks::{human_deck_name_to_native, FilteredDeck, FilteredSearchTerm},
|
decks::{FilteredDeck, FilteredSearchTerm},
|
||||||
error::FilteredDeckError,
|
error::FilteredDeckError,
|
||||||
search::writer::{deck_search, normalize_search},
|
search::writer::{deck_search, normalize_search},
|
||||||
};
|
};
|
||||||
|
@ -146,8 +146,11 @@ impl Collection {
|
||||||
Ok(position)
|
Ok(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_next_filtered_deck_name(&self) -> String {
|
fn get_next_filtered_deck_name(&self) -> NativeDeckName {
|
||||||
format!("Filtered Deck {}", TimestampSecs::now().time_string())
|
NativeDeckName(format!(
|
||||||
|
"Filtered Deck {}",
|
||||||
|
TimestampSecs::now().time_string()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_or_update_filtered_deck_inner(
|
fn add_or_update_filtered_deck_inner(
|
||||||
|
@ -253,6 +256,6 @@ impl TryFrom<Deck> for FilteredDeckForUpdate {
|
||||||
|
|
||||||
fn apply_update_to_filtered_deck(deck: &mut Deck, update: FilteredDeckForUpdate) {
|
fn apply_update_to_filtered_deck(deck: &mut Deck, update: FilteredDeckForUpdate) {
|
||||||
deck.id = update.id;
|
deck.id = update.id;
|
||||||
deck.name = human_deck_name_to_native(&update.human_name);
|
deck.name = NativeDeckName::from_human_name(&update.human_name);
|
||||||
deck.kind = DeckKind::Filtered(update.config);
|
deck.kind = DeckKind::Filtered(update.config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ use super::{
|
||||||
use crate::{
|
use crate::{
|
||||||
card::{CardQueue, CardType},
|
card::{CardQueue, CardType},
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
decks::human_deck_name_to_native,
|
|
||||||
error::Result,
|
error::Result,
|
||||||
notes::field_checksum,
|
notes::field_checksum,
|
||||||
notetype::NotetypeId,
|
notetype::NotetypeId,
|
||||||
|
@ -347,11 +346,11 @@ impl SqlWriter<'_> {
|
||||||
.storage
|
.storage
|
||||||
.get_deck(current_did)?
|
.get_deck(current_did)?
|
||||||
.map(|d| d.name)
|
.map(|d| d.name)
|
||||||
.unwrap_or_else(|| "Default".into())
|
.unwrap_or_else(|| NativeDeckName("Default".into()))
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
human_deck_name_to_native(&to_re(deck))
|
NativeDeckName::from_human_name(&to_re(deck)).0
|
||||||
};
|
};
|
||||||
|
|
||||||
// convert to a regex that includes child decks
|
// convert to a regex that includes child decks
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
decks::{Deck, DeckCommon, DeckId, DeckKindContainer, DeckSchema11, DueCounts},
|
decks::{Deck, DeckCommon, DeckId, DeckKindContainer, DeckSchema11, DueCounts},
|
||||||
error::{AnkiError, DbErrorKind, Result},
|
error::{AnkiError, DbErrorKind, Result},
|
||||||
i18n::I18n,
|
i18n::I18n,
|
||||||
|
prelude::*,
|
||||||
timestamp::TimestampMillis,
|
timestamp::TimestampMillis,
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
@ -23,7 +24,7 @@ fn row_to_deck(row: &Row) -> Result<Deck> {
|
||||||
let id = row.get(0)?;
|
let id = row.get(0)?;
|
||||||
Ok(Deck {
|
Ok(Deck {
|
||||||
id,
|
id,
|
||||||
name: row.get(1)?,
|
name: NativeDeckName(row.get(1)?),
|
||||||
mtime_secs: row.get(2)?,
|
mtime_secs: row.get(2)?,
|
||||||
usn: row.get(3)?,
|
usn: row.get(3)?,
|
||||||
common,
|
common,
|
||||||
|
@ -123,7 +124,7 @@ impl SqliteStorage {
|
||||||
let mut kind = vec![];
|
let mut kind = vec![];
|
||||||
kind_enum.encode(&mut kind)?;
|
kind_enum.encode(&mut kind)?;
|
||||||
let count = stmt.execute(params![
|
let count = stmt.execute(params![
|
||||||
deck.name,
|
deck.name.as_str(),
|
||||||
deck.mtime_secs,
|
deck.mtime_secs,
|
||||||
deck.usn,
|
deck.usn,
|
||||||
common,
|
common,
|
||||||
|
@ -158,7 +159,7 @@ impl SqliteStorage {
|
||||||
kind_enum.encode(&mut kind)?;
|
kind_enum.encode(&mut kind)?;
|
||||||
stmt.execute(params![
|
stmt.execute(params![
|
||||||
deck.id,
|
deck.id,
|
||||||
deck.name,
|
deck.name.as_str(),
|
||||||
deck.mtime_secs,
|
deck.mtime_secs,
|
||||||
deck.usn,
|
deck.usn,
|
||||||
common,
|
common,
|
||||||
|
@ -228,9 +229,13 @@ impl SqliteStorage {
|
||||||
/// Return the parents of `child`, with the most immediate parent coming first.
|
/// Return the parents of `child`, with the most immediate parent coming first.
|
||||||
pub(crate) fn parent_decks(&self, child: &Deck) -> Result<Vec<Deck>> {
|
pub(crate) fn parent_decks(&self, child: &Deck) -> Result<Vec<Deck>> {
|
||||||
let mut decks: Vec<Deck> = vec![];
|
let mut decks: Vec<Deck> = vec![];
|
||||||
while let Some(parent_name) =
|
while let Some(parent_name) = immediate_parent_name(
|
||||||
immediate_parent_name(decks.last().map(|d| &d.name).unwrap_or_else(|| &child.name))
|
decks
|
||||||
{
|
.last()
|
||||||
|
.map(|d| &d.name)
|
||||||
|
.unwrap_or_else(|| &child.name)
|
||||||
|
.as_str(),
|
||||||
|
) {
|
||||||
if let Some(parent_did) = self.get_deck_id(parent_name)? {
|
if let Some(parent_did) = self.get_deck_id(parent_name)? {
|
||||||
let parent = self.get_deck(parent_did)?.unwrap();
|
let parent = self.get_deck(parent_did)?.unwrap();
|
||||||
decks.push(parent);
|
decks.push(parent);
|
||||||
|
@ -324,7 +329,7 @@ impl SqliteStorage {
|
||||||
"create temporary table active_decks (id integer primary key not null);"
|
"create temporary table active_decks (id integer primary key not null);"
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let top = ¤t.name;
|
let top = current.name.as_str();
|
||||||
let prefix_start = &format!("{}\x1f", top);
|
let prefix_start = &format!("{}\x1f", top);
|
||||||
let prefix_end = &format!("{}\x20", top);
|
let prefix_end = &format!("{}\x20", top);
|
||||||
|
|
||||||
|
@ -341,7 +346,7 @@ impl SqliteStorage {
|
||||||
let mut deck = Deck::new_normal();
|
let mut deck = Deck::new_normal();
|
||||||
deck.id.0 = 1;
|
deck.id.0 = 1;
|
||||||
// fixme: separate key
|
// fixme: separate key
|
||||||
deck.name = tr.deck_config_default_name().into();
|
deck.name = NativeDeckName(tr.deck_config_default_name().into());
|
||||||
self.add_or_update_deck_with_existing_id(&deck)
|
self.add_or_update_deck_with_existing_id(&deck)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,12 +361,12 @@ impl SqliteStorage {
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
let name = UniCase::new(deck.name.clone());
|
let name = UniCase::new(deck.name.0.clone());
|
||||||
if !names.contains(&name) {
|
if !names.contains(&name) {
|
||||||
names.insert(name);
|
names.insert(name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
deck.name.push('_');
|
deck.name.add_suffix("_");
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
}
|
}
|
||||||
self.add_or_update_deck_with_existing_id(&deck)?;
|
self.add_or_update_deck_with_existing_id(&deck)?;
|
||||||
|
|
|
@ -1500,7 +1500,7 @@ mod test {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut deck = col2.storage.get_deck(deck.id)?.unwrap();
|
let mut deck = col2.storage.get_deck(deck.id)?.unwrap();
|
||||||
deck.name = "newer".into();
|
deck.name = NativeDeckName("newer".into());
|
||||||
col2.add_or_update_deck(&mut deck)?;
|
col2.add_or_update_deck(&mut deck)?;
|
||||||
|
|
||||||
let mut nt = col2.storage.get_notetype(nt.id)?.unwrap();
|
let mut nt = col2.storage.get_notetype(nt.id)?.unwrap();
|
||||||
|
|
Loading…
Reference in a new issue