mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
add progress to db check
This commit is contained in:
parent
a0c1b68b86
commit
7c444b4d35
9 changed files with 183 additions and 46 deletions
|
@ -496,6 +496,7 @@ message Progress {
|
||||||
string media_check = 3;
|
string media_check = 3;
|
||||||
FullSyncProgress full_sync = 4;
|
FullSyncProgress full_sync = 4;
|
||||||
NormalSyncProgress normal_sync = 5;
|
NormalSyncProgress normal_sync = 5;
|
||||||
|
DatabaseCheckProgress database_check = 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,6 +522,12 @@ message NormalSyncProgress {
|
||||||
string removed = 3;
|
string removed = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DatabaseCheckProgress {
|
||||||
|
string stage = 1;
|
||||||
|
uint32 stage_total = 2;
|
||||||
|
uint32 stage_current = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -153,6 +153,7 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
||||||
MediaSyncProgress = pb.MediaSyncProgress
|
MediaSyncProgress = pb.MediaSyncProgress
|
||||||
FullSyncProgress = pb.FullSyncProgress
|
FullSyncProgress = pb.FullSyncProgress
|
||||||
NormalSyncProgress = pb.NormalSyncProgress
|
NormalSyncProgress = pb.NormalSyncProgress
|
||||||
|
DatabaseCheckProgress = pb.DatabaseCheckProgress
|
||||||
|
|
||||||
FormatTimeSpanContext = pb.FormatTimespanIn.Context
|
FormatTimeSpanContext = pb.FormatTimespanIn.Context
|
||||||
|
|
||||||
|
@ -163,12 +164,19 @@ class ProgressKind(enum.Enum):
|
||||||
MediaCheck = 2
|
MediaCheck = 2
|
||||||
FullSync = 3
|
FullSync = 3
|
||||||
NormalSync = 4
|
NormalSync = 4
|
||||||
|
DatabaseCheck = 5
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Progress:
|
class Progress:
|
||||||
kind: ProgressKind
|
kind: ProgressKind
|
||||||
val: Union[MediaSyncProgress, pb.FullSyncProgress, NormalSyncProgress, str]
|
val: Union[
|
||||||
|
MediaSyncProgress,
|
||||||
|
pb.FullSyncProgress,
|
||||||
|
NormalSyncProgress,
|
||||||
|
DatabaseCheckProgress,
|
||||||
|
str,
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_proto(proto: pb.Progress) -> Progress:
|
def from_proto(proto: pb.Progress) -> Progress:
|
||||||
|
@ -181,6 +189,8 @@ class Progress:
|
||||||
return Progress(kind=ProgressKind.FullSync, val=proto.full_sync)
|
return Progress(kind=ProgressKind.FullSync, val=proto.full_sync)
|
||||||
elif kind == "normal_sync":
|
elif kind == "normal_sync":
|
||||||
return Progress(kind=ProgressKind.NormalSync, val=proto.normal_sync)
|
return Progress(kind=ProgressKind.NormalSync, val=proto.normal_sync)
|
||||||
|
elif kind == "database_check":
|
||||||
|
return Progress(kind=ProgressKind.DatabaseCheck, val=proto.database_check)
|
||||||
else:
|
else:
|
||||||
return Progress(kind=ProgressKind.NoProgress, val="")
|
return Progress(kind=ProgressKind.NoProgress, val="")
|
||||||
|
|
||||||
|
|
55
qt/aqt/dbcheck.py
Normal file
55
qt/aqt/dbcheck.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import aqt
|
||||||
|
from anki.rsbackend import DatabaseCheckProgress, ProgressKind
|
||||||
|
from aqt.qt import *
|
||||||
|
from aqt.utils import showText, tooltip
|
||||||
|
|
||||||
|
|
||||||
|
def on_progress(mw: aqt.main.AnkiQt):
|
||||||
|
progress = mw.col.latest_progress()
|
||||||
|
if progress.kind != ProgressKind.DatabaseCheck:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert isinstance(progress.val, DatabaseCheckProgress)
|
||||||
|
mw.progress.update(
|
||||||
|
process=False, label=progress.val.stage,
|
||||||
|
value=progress.val.stage_current,
|
||||||
|
max=progress.val.stage_total,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_db(mw: aqt.AnkiQt) -> None:
|
||||||
|
def on_timer():
|
||||||
|
on_progress(mw)
|
||||||
|
|
||||||
|
timer = QTimer(mw)
|
||||||
|
qconnect(timer.timeout, on_timer)
|
||||||
|
timer.start(100)
|
||||||
|
|
||||||
|
def on_future_done(fut):
|
||||||
|
timer.stop()
|
||||||
|
ret, ok = fut.result()
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
showText(ret)
|
||||||
|
else:
|
||||||
|
tooltip(ret)
|
||||||
|
|
||||||
|
# if an error has directed the user to check the database,
|
||||||
|
# silently clean up any broken reset hooks which distract from
|
||||||
|
# the underlying issue
|
||||||
|
n = 0
|
||||||
|
while n < 10:
|
||||||
|
try:
|
||||||
|
mw.reset()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print("swallowed exception in reset hook:", e)
|
||||||
|
n += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
mw.taskman.with_progress(mw.col.fixIntegrity, on_future_done)
|
|
@ -13,7 +13,6 @@ import time
|
||||||
import weakref
|
import weakref
|
||||||
import zipfile
|
import zipfile
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from concurrent.futures import Future
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
|
@ -35,6 +34,7 @@ from anki.sound import AVTag, SoundOrVideoTag
|
||||||
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user
|
from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user
|
||||||
|
from aqt.dbcheck import check_db
|
||||||
from aqt.emptycards import show_empty_cards
|
from aqt.emptycards import show_empty_cards
|
||||||
from aqt.legacy import install_pylib_legacy
|
from aqt.legacy import install_pylib_legacy
|
||||||
from aqt.mediacheck import check_media_db
|
from aqt.mediacheck import check_media_db
|
||||||
|
@ -59,7 +59,6 @@ from aqt.utils import (
|
||||||
saveGeom,
|
saveGeom,
|
||||||
saveSplitter,
|
saveSplitter,
|
||||||
showInfo,
|
showInfo,
|
||||||
showText,
|
|
||||||
showWarning,
|
showWarning,
|
||||||
tooltip,
|
tooltip,
|
||||||
tr,
|
tr,
|
||||||
|
@ -1334,28 +1333,7 @@ will be lost. Continue?"""
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def onCheckDB(self):
|
def onCheckDB(self):
|
||||||
def on_done(future: Future):
|
check_db(self)
|
||||||
ret, ok = future.result()
|
|
||||||
|
|
||||||
if not ok:
|
|
||||||
showText(ret)
|
|
||||||
else:
|
|
||||||
tooltip(ret)
|
|
||||||
|
|
||||||
# if an error has directed the user to check the database,
|
|
||||||
# silently clean up any broken reset hooks which distract from
|
|
||||||
# the underlying issue
|
|
||||||
n = 0
|
|
||||||
while n < 10:
|
|
||||||
try:
|
|
||||||
self.reset()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("swallowed exception in reset hook:", e)
|
|
||||||
n += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.taskman.with_progress(self.col.fixIntegrity, on_done)
|
|
||||||
|
|
||||||
def on_check_media_db(self) -> None:
|
def on_check_media_db(self) -> None:
|
||||||
check_media_db(self)
|
check_media_db(self)
|
||||||
|
|
|
@ -107,11 +107,13 @@ class ProgressManager:
|
||||||
elapsed = time.time() - self._lastUpdate
|
elapsed = time.time() - self._lastUpdate
|
||||||
if label:
|
if label:
|
||||||
self._win.form.label.setText(label)
|
self._win.form.label.setText(label)
|
||||||
self._max = max
|
|
||||||
|
self._max = max or 0
|
||||||
|
self._win.form.progressBar.setMaximum(self._max)
|
||||||
if self._max:
|
if self._max:
|
||||||
self._win.form.progressBar.setMaximum(max)
|
|
||||||
self._counter = value or (self._counter + 1)
|
self._counter = value or (self._counter + 1)
|
||||||
self._win.form.progressBar.setValue(self._counter)
|
self._win.form.progressBar.setValue(self._counter)
|
||||||
|
|
||||||
if process and elapsed >= 0.2:
|
if process and elapsed >= 0.2:
|
||||||
self._updating = True
|
self._updating = True
|
||||||
self.app.processEvents()
|
self.app.processEvents()
|
||||||
|
@ -139,7 +141,6 @@ class ProgressManager:
|
||||||
if not self._levels:
|
if not self._levels:
|
||||||
return
|
return
|
||||||
if self._shown:
|
if self._shown:
|
||||||
self.update(maybeShow=False)
|
|
||||||
return
|
return
|
||||||
delta = time.time() - self._firstTime
|
delta = time.time() - self._firstTime
|
||||||
if delta > 0.5:
|
if delta > 0.5:
|
||||||
|
|
|
@ -41,3 +41,12 @@ database-check-revlog-properties = { $count ->
|
||||||
[one] Fixed { $count } review entry with invalid properties.
|
[one] Fixed { $count } review entry with invalid properties.
|
||||||
*[other] Fixed { $count } review entries with invalid properties.
|
*[other] Fixed { $count } review entries with invalid properties.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Progress info
|
||||||
|
|
||||||
|
database-check-checking-integrity = Checking collection...
|
||||||
|
database-check-rebuilding = Rebuilding...
|
||||||
|
database-check-checking-cards = Checking cards...
|
||||||
|
database-check-checking-notes = Checking notes...
|
||||||
|
database-check-checking-history = Checking history...
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
cloze::add_cloze_numbers_in_string,
|
cloze::add_cloze_numbers_in_string,
|
||||||
collection::{open_collection, Collection},
|
collection::{open_collection, Collection},
|
||||||
config::SortKind,
|
config::SortKind,
|
||||||
|
dbcheck::DatabaseCheckProgress,
|
||||||
deckconf::{DeckConf, DeckConfID, DeckConfSchema11},
|
deckconf::{DeckConf, DeckConfID, DeckConfSchema11},
|
||||||
decks::{Deck, DeckID, DeckSchema11},
|
decks::{Deck, DeckID, DeckSchema11},
|
||||||
err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind},
|
err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind},
|
||||||
|
@ -120,6 +121,7 @@ enum Progress {
|
||||||
MediaCheck(u32),
|
MediaCheck(u32),
|
||||||
FullSync(FullSyncProgress),
|
FullSync(FullSyncProgress),
|
||||||
NormalSync(NormalSyncProgress),
|
NormalSync(NormalSyncProgress),
|
||||||
|
DatabaseCheck(DatabaseCheckProgress),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an Anki error to a protobuf error.
|
/// Convert an Anki error to a protobuf error.
|
||||||
|
@ -1008,10 +1010,15 @@ impl BackendService for Backend {
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
fn check_database(&mut self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
|
fn check_database(&mut self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
|
||||||
|
let mut handler = self.new_progress_handler();
|
||||||
|
let progress_fn = move |progress, throttle| {
|
||||||
|
handler.update(Progress::DatabaseCheck(progress), throttle);
|
||||||
|
};
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
col.check_database().map(|problems| pb::CheckDatabaseOut {
|
col.check_database(progress_fn)
|
||||||
problems: problems.to_i18n_strings(&col.i18n),
|
.map(|problems| pb::CheckDatabaseOut {
|
||||||
})
|
problems: problems.to_i18n_strings(&col.i18n),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1605,6 +1612,27 @@ fn progress_to_proto(progress: Option<Progress>, i18n: &I18n) -> pb::Progress {
|
||||||
removed,
|
removed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Progress::DatabaseCheck(p) => {
|
||||||
|
let mut stage_total = 0;
|
||||||
|
let mut stage_current = 0;
|
||||||
|
let stage = match p {
|
||||||
|
DatabaseCheckProgress::Integrity => i18n.tr(TR::DatabaseCheckCheckingIntegrity),
|
||||||
|
DatabaseCheckProgress::Optimize => i18n.tr(TR::DatabaseCheckRebuilding),
|
||||||
|
DatabaseCheckProgress::Cards => i18n.tr(TR::DatabaseCheckCheckingCards),
|
||||||
|
DatabaseCheckProgress::Notes { current, total } => {
|
||||||
|
stage_total = total;
|
||||||
|
stage_current = current;
|
||||||
|
i18n.tr(TR::DatabaseCheckCheckingNotes)
|
||||||
|
}
|
||||||
|
DatabaseCheckProgress::History => i18n.tr(TR::DatabaseCheckCheckingHistory),
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
pb::progress::Value::DatabaseCheck(pb::DatabaseCheckProgress {
|
||||||
|
stage,
|
||||||
|
stage_current,
|
||||||
|
stage_total,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pb::progress::Value::None(pb::Empty {})
|
pb::progress::Value::None(pb::Empty {})
|
||||||
|
|
|
@ -29,6 +29,15 @@ pub struct CheckDatabaseOutput {
|
||||||
field_count_mismatch: usize,
|
field_count_mismatch: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) enum DatabaseCheckProgress {
|
||||||
|
Integrity,
|
||||||
|
Optimize,
|
||||||
|
Cards,
|
||||||
|
Notes { current: u32, total: u32 },
|
||||||
|
History,
|
||||||
|
}
|
||||||
|
|
||||||
impl CheckDatabaseOutput {
|
impl CheckDatabaseOutput {
|
||||||
pub fn to_i18n_strings(&self, i18n: &I18n) -> Vec<String> {
|
pub fn to_i18n_strings(&self, i18n: &I18n) -> Vec<String> {
|
||||||
let mut probs = Vec::new();
|
let mut probs = Vec::new();
|
||||||
|
@ -88,7 +97,11 @@ impl CheckDatabaseOutput {
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
/// Check the database, returning a list of problems that were fixed.
|
/// Check the database, returning a list of problems that were fixed.
|
||||||
pub(crate) fn check_database(&mut self) -> Result<CheckDatabaseOutput> {
|
pub(crate) fn check_database<F>(&mut self, mut progress_fn: F) -> Result<CheckDatabaseOutput>
|
||||||
|
where
|
||||||
|
F: FnMut(DatabaseCheckProgress, bool),
|
||||||
|
{
|
||||||
|
progress_fn(DatabaseCheckProgress::Integrity, false);
|
||||||
debug!(self.log, "quick check");
|
debug!(self.log, "quick check");
|
||||||
if self.storage.quick_check_corrupt() {
|
if self.storage.quick_check_corrupt() {
|
||||||
debug!(self.log, "quick check failed");
|
debug!(self.log, "quick check failed");
|
||||||
|
@ -98,16 +111,21 @@ impl Collection {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress_fn(DatabaseCheckProgress::Optimize, false);
|
||||||
debug!(self.log, "optimize");
|
debug!(self.log, "optimize");
|
||||||
self.storage.optimize()?;
|
self.storage.optimize()?;
|
||||||
|
|
||||||
self.transact(None, |col| col.check_database_inner())
|
self.transact(None, |col| col.check_database_inner(progress_fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_database_inner(&mut self) -> Result<CheckDatabaseOutput> {
|
fn check_database_inner<F>(&mut self, mut progress_fn: F) -> Result<CheckDatabaseOutput>
|
||||||
|
where
|
||||||
|
F: FnMut(DatabaseCheckProgress, bool),
|
||||||
|
{
|
||||||
let mut out = CheckDatabaseOutput::default();
|
let mut out = CheckDatabaseOutput::default();
|
||||||
|
|
||||||
// cards first, as we need to be able to read them to process notes
|
// cards first, as we need to be able to read them to process notes
|
||||||
|
progress_fn(DatabaseCheckProgress::Cards, false);
|
||||||
debug!(self.log, "check cards");
|
debug!(self.log, "check cards");
|
||||||
self.check_card_properties(&mut out)?;
|
self.check_card_properties(&mut out)?;
|
||||||
self.check_orphaned_cards(&mut out)?;
|
self.check_orphaned_cards(&mut out)?;
|
||||||
|
@ -117,7 +135,9 @@ impl Collection {
|
||||||
self.check_filtered_cards(&mut out)?;
|
self.check_filtered_cards(&mut out)?;
|
||||||
|
|
||||||
debug!(self.log, "check notetypes");
|
debug!(self.log, "check notetypes");
|
||||||
self.check_notetypes(&mut out)?;
|
self.check_notetypes(&mut out, &mut progress_fn)?;
|
||||||
|
|
||||||
|
progress_fn(DatabaseCheckProgress::History, false);
|
||||||
|
|
||||||
debug!(self.log, "check review log");
|
debug!(self.log, "check review log");
|
||||||
self.check_revlog(&mut out)?;
|
self.check_revlog(&mut out)?;
|
||||||
|
@ -192,7 +212,14 @@ impl Collection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_notetypes(&mut self, out: &mut CheckDatabaseOutput) -> Result<()> {
|
fn check_notetypes<F>(
|
||||||
|
&mut self,
|
||||||
|
out: &mut CheckDatabaseOutput,
|
||||||
|
mut progress_fn: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(DatabaseCheckProgress, bool),
|
||||||
|
{
|
||||||
let nids_by_notetype = self.storage.all_note_ids_by_notetype()?;
|
let nids_by_notetype = self.storage.all_note_ids_by_notetype()?;
|
||||||
let norm = self.normalize_note_text();
|
let norm = self.normalize_note_text();
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
|
@ -201,6 +228,9 @@ impl Collection {
|
||||||
// will rebuild tag list below
|
// will rebuild tag list below
|
||||||
self.storage.clear_tags()?;
|
self.storage.clear_tags()?;
|
||||||
|
|
||||||
|
let total_notes = self.storage.total_notes()?;
|
||||||
|
let mut checked_notes = 0;
|
||||||
|
|
||||||
for (ntid, group) in &nids_by_notetype.into_iter().group_by(|tup| tup.0) {
|
for (ntid, group) in &nids_by_notetype.into_iter().group_by(|tup| tup.0) {
|
||||||
debug!(self.log, "check notetype: {}", ntid);
|
debug!(self.log, "check notetype: {}", ntid);
|
||||||
let mut group = group.peekable();
|
let mut group = group.peekable();
|
||||||
|
@ -214,6 +244,15 @@ impl Collection {
|
||||||
|
|
||||||
let mut genctx = None;
|
let mut genctx = None;
|
||||||
for (_, nid) in group {
|
for (_, nid) in group {
|
||||||
|
progress_fn(
|
||||||
|
DatabaseCheckProgress::Notes {
|
||||||
|
current: checked_notes,
|
||||||
|
total: total_notes,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
checked_notes += 1;
|
||||||
|
|
||||||
let mut note = self.storage.get_note(nid)?.unwrap();
|
let mut note = self.storage.get_note(nid)?.unwrap();
|
||||||
|
|
||||||
let cards = self.storage.existing_cards_for_note(nid)?;
|
let cards = self.storage.existing_cards_for_note(nid)?;
|
||||||
|
@ -328,6 +367,8 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{collection::open_test_collection, decks::DeckID, search::SortMode};
|
use crate::{collection::open_test_collection, decks::DeckID, search::SortMode};
|
||||||
|
|
||||||
|
fn progress_fn(_progress: DatabaseCheckProgress, _throttle: bool) {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cards() -> Result<()> {
|
fn cards() -> Result<()> {
|
||||||
let mut col = open_test_collection();
|
let mut col = open_test_collection();
|
||||||
|
@ -340,7 +381,7 @@ mod test {
|
||||||
.db
|
.db
|
||||||
.execute_batch("update cards set ivl=1.5,due=2000000,odue=1.5")?;
|
.execute_batch("update cards set ivl=1.5,due=2000000,odue=1.5")?;
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -350,12 +391,12 @@ mod test {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// should be idempotent
|
// should be idempotent
|
||||||
assert_eq!(col.check_database()?, Default::default());
|
assert_eq!(col.check_database(progress_fn)?, Default::default());
|
||||||
|
|
||||||
// missing deck
|
// missing deck
|
||||||
col.storage.db.execute_batch("update cards set did=123")?;
|
col.storage.db.execute_batch("update cards set did=123")?;
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -370,7 +411,7 @@ mod test {
|
||||||
|
|
||||||
// missing note
|
// missing note
|
||||||
col.storage.remove_note(note.id)?;
|
col.storage.remove_note(note.id)?;
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -396,7 +437,7 @@ mod test {
|
||||||
values (0,0,0,0,1.5,1.5,0,0,0)",
|
values (0,0,0,0,1.5,1.5,0,0,0)",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -426,7 +467,7 @@ mod test {
|
||||||
card.id.0 += 1;
|
card.id.0 += 1;
|
||||||
col.storage.add_card(&mut card)?;
|
col.storage.add_card(&mut card)?;
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -446,7 +487,7 @@ mod test {
|
||||||
card.ord = 10;
|
card.ord = 10;
|
||||||
col.storage.add_card(&mut card)?;
|
col.storage.add_card(&mut card)?;
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -473,7 +514,7 @@ mod test {
|
||||||
col.storage
|
col.storage
|
||||||
.db
|
.db
|
||||||
.execute_batch("update notes set flds = 'a\x1fb\x1fc\x1fd'")?;
|
.execute_batch("update notes set flds = 'a\x1fb\x1fc\x1fd'")?;
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -488,7 +529,7 @@ mod test {
|
||||||
col.storage
|
col.storage
|
||||||
.db
|
.db
|
||||||
.execute_batch("update notes set flds = 'a'")?;
|
.execute_batch("update notes set flds = 'a'")?;
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
@ -516,7 +557,7 @@ mod test {
|
||||||
.execute(&[deck.id])?;
|
.execute(&[deck.id])?;
|
||||||
assert_eq!(col.storage.get_all_deck_names()?.len(), 2);
|
assert_eq!(col.storage.get_all_deck_names()?.len(), 2);
|
||||||
|
|
||||||
let out = col.check_database()?;
|
let out = col.check_database(progress_fn)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
CheckDatabaseOutput {
|
CheckDatabaseOutput {
|
||||||
|
|
|
@ -130,4 +130,12 @@ impl super::SqliteStorage {
|
||||||
.query_and_then(params![csum, ntid, nid], |r| r.get(0).map_err(Into::into))?
|
.query_and_then(params![csum, ntid, nid], |r| r.get(0).map_err(Into::into))?
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return total number of notes. Slow.
|
||||||
|
pub(crate) fn total_notes(&self) -> Result<u32> {
|
||||||
|
self.db
|
||||||
|
.prepare("select count() from notes")?
|
||||||
|
.query_row(NO_PARAMS, |r| r.get(0))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue