mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
add missing decks in backend
- need to compare parents with unicode case folding - duplicate check enforced by the DB
This commit is contained in:
parent
67421e02ec
commit
5fb5338d97
8 changed files with 49 additions and 33 deletions
|
@ -71,7 +71,7 @@ message BackendInput {
|
||||||
int32 get_changed_notetypes = 56;
|
int32 get_changed_notetypes = 56;
|
||||||
AddOrUpdateNotetypeIn add_or_update_notetype = 57;
|
AddOrUpdateNotetypeIn add_or_update_notetype = 57;
|
||||||
Empty get_all_decks = 58;
|
Empty get_all_decks = 58;
|
||||||
// bytes set_all_decks = 59;
|
Empty check_database = 59;
|
||||||
Empty all_stock_notetypes = 60;
|
Empty all_stock_notetypes = 60;
|
||||||
int64 get_notetype_legacy = 61;
|
int64 get_notetype_legacy = 61;
|
||||||
Empty get_notetype_names = 62;
|
Empty get_notetype_names = 62;
|
||||||
|
@ -140,7 +140,7 @@ message BackendOutput {
|
||||||
bytes get_changed_notetypes = 56;
|
bytes get_changed_notetypes = 56;
|
||||||
int64 add_or_update_notetype = 57;
|
int64 add_or_update_notetype = 57;
|
||||||
bytes get_all_decks = 58;
|
bytes get_all_decks = 58;
|
||||||
// Empty set_all_decks = 59;
|
Empty check_database = 59;
|
||||||
bytes get_notetype_legacy = 61;
|
bytes get_notetype_legacy = 61;
|
||||||
NoteTypeNames get_notetype_names = 62;
|
NoteTypeNames get_notetype_names = 62;
|
||||||
NoteTypeUseCounts get_notetype_names_and_counts = 63;
|
NoteTypeUseCounts get_notetype_names_and_counts = 63;
|
||||||
|
|
|
@ -958,6 +958,8 @@ and type=0 and queue!=4""",
|
||||||
# models
|
# models
|
||||||
if self.models.ensureNotEmpty():
|
if self.models.ensureNotEmpty():
|
||||||
problems.append("Added missing note type.")
|
problems.append("Added missing note type.")
|
||||||
|
# misc other
|
||||||
|
self.backend.check_database()
|
||||||
# and finally, optimize
|
# and finally, optimize
|
||||||
self.optimize()
|
self.optimize()
|
||||||
newSize = os.stat(self.path)[stat.ST_SIZE]
|
newSize = os.stat(self.path)[stat.ST_SIZE]
|
||||||
|
|
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki.backend_pb2 as pb
|
import anki.backend_pb2 as pb
|
||||||
|
@ -524,37 +524,8 @@ class DeckManager:
|
||||||
# )
|
# )
|
||||||
# self.col.db.mod = mod
|
# self.col.db.mod = mod
|
||||||
|
|
||||||
def _checkDeckTree(self) -> None:
|
|
||||||
decks = self.all()
|
|
||||||
decks.sort(key=self.key)
|
|
||||||
names: Set[str] = set()
|
|
||||||
|
|
||||||
for deck in decks:
|
|
||||||
# two decks with the same name?
|
|
||||||
if deck["name"] in names:
|
|
||||||
self.col.log("fix duplicate deck name", deck["name"])
|
|
||||||
deck["name"] += "%d" % intTime(1000)
|
|
||||||
self.save(deck)
|
|
||||||
|
|
||||||
# ensure no sections are blank
|
|
||||||
if not all(self.path(deck["name"])):
|
|
||||||
self.col.log("fix deck with missing sections", deck["name"])
|
|
||||||
deck["name"] = "recovered%d" % intTime(1000)
|
|
||||||
self.save(deck)
|
|
||||||
|
|
||||||
# immediate parent must exist
|
|
||||||
if "::" in deck["name"]:
|
|
||||||
immediateParent = self.immediate_parent(deck["name"])
|
|
||||||
if immediateParent not in names:
|
|
||||||
self.col.log("fix deck with missing parent", deck["name"])
|
|
||||||
self._ensureParents(deck["name"])
|
|
||||||
names.add(immediateParent)
|
|
||||||
|
|
||||||
names.add(deck["name"])
|
|
||||||
|
|
||||||
def checkIntegrity(self) -> None:
|
def checkIntegrity(self) -> None:
|
||||||
self._recoverOrphans()
|
self._recoverOrphans()
|
||||||
self._checkDeckTree()
|
|
||||||
|
|
||||||
def should_deck_be_displayed(
|
def should_deck_be_displayed(
|
||||||
self, deck, force_default: bool = True, assume_no_child: bool = False
|
self, deck, force_default: bool = True, assume_no_child: bool = False
|
||||||
|
|
|
@ -735,6 +735,9 @@ class RustBackend:
|
||||||
pb.BackendInput(deck_tree=pb.DeckTreeIn(include_counts=include_counts))
|
pb.BackendInput(deck_tree=pb.DeckTreeIn(include_counts=include_counts))
|
||||||
).deck_tree
|
).deck_tree
|
||||||
|
|
||||||
|
def check_database(self) -> None:
|
||||||
|
self._run_command(pb.BackendInput(check_database=pb.Empty()))
|
||||||
|
|
||||||
|
|
||||||
def translate_string_in(
|
def translate_string_in(
|
||||||
key: TR, **kwargs: Union[str, int, float]
|
key: TR, **kwargs: Union[str, int, float]
|
||||||
|
|
|
@ -356,6 +356,10 @@ impl Backend {
|
||||||
self.remove_deck(did)?;
|
self.remove_deck(did)?;
|
||||||
pb::Empty {}
|
pb::Empty {}
|
||||||
}),
|
}),
|
||||||
|
Value::CheckDatabase(_) => {
|
||||||
|
self.check_database()?;
|
||||||
|
OValue::CheckDatabase(pb::Empty {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1046,6 +1050,10 @@ impl Backend {
|
||||||
fn remove_deck(&self, did: i64) -> Result<()> {
|
fn remove_deck(&self, did: i64) -> Result<()> {
|
||||||
self.with_col(|col| col.remove_deck_and_child_decks(DeckID(did)))
|
self.with_col(|col| col.remove_deck_and_child_decks(DeckID(did)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_database(&self) -> Result<()> {
|
||||||
|
self.with_col(|col| col.transact(None, |col| col.check_database()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
||||||
|
|
12
rslib/src/dbcheck.rs
Normal file
12
rslib/src/dbcheck.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use crate::{collection::Collection, err::Result};
|
||||||
|
|
||||||
|
impl Collection {
|
||||||
|
pub(crate) fn check_database(&mut self) -> Result<()> {
|
||||||
|
let names = self.storage.get_all_deck_names()?;
|
||||||
|
self.add_missing_decks(&names)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,11 @@
|
||||||
|
|
||||||
use super::Deck;
|
use super::Deck;
|
||||||
use crate::{backend_proto::DeckTreeNode, collection::Collection, decks::DeckID, err::Result};
|
use crate::{backend_proto::DeckTreeNode, collection::Collection, decks::DeckID, err::Result};
|
||||||
use std::{collections::HashMap, iter::Peekable};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
iter::Peekable,
|
||||||
|
};
|
||||||
|
use unicase::UniCase;
|
||||||
|
|
||||||
// fixme: handle mixed case of parents
|
// fixme: handle mixed case of parents
|
||||||
|
|
||||||
|
@ -80,6 +84,21 @@ impl Collection {
|
||||||
|
|
||||||
Ok(tree)
|
Ok(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_missing_decks(&mut self, names: &[(DeckID, String)]) -> Result<()> {
|
||||||
|
let mut parents = HashSet::new();
|
||||||
|
for (_id, name) in names {
|
||||||
|
parents.insert(UniCase::new(name.as_str()));
|
||||||
|
if let Some(immediate_parent) = name.rsplitn(2, "::").nth(1) {
|
||||||
|
let immediate_parent_uni = UniCase::new(immediate_parent);
|
||||||
|
if !parents.contains(&immediate_parent_uni) {
|
||||||
|
self.get_or_create_normal_deck(immediate_parent)?;
|
||||||
|
parents.insert(immediate_parent_uni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod card;
|
||||||
pub mod cloze;
|
pub mod cloze;
|
||||||
pub mod collection;
|
pub mod collection;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod dbcheck;
|
||||||
pub mod deckconf;
|
pub mod deckconf;
|
||||||
pub mod decks;
|
pub mod decks;
|
||||||
pub mod err;
|
pub mod err;
|
||||||
|
|
Loading…
Reference in a new issue