more methods

This commit is contained in:
Damien Elmes 2020-05-23 12:58:13 +10:00
parent 175afa9fee
commit 081a61a438
11 changed files with 196 additions and 169 deletions

View file

@ -29,6 +29,10 @@ message String {
string val = 1;
}
message Bytes {
bytes val = 1;
}
// New style RPC definitions
///////////////////////////////////////////////////////////
@ -41,9 +45,16 @@ service BackendService {
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
rpc CheckMedia (Empty) returns (CheckMediaOut);
rpc LocalMinutesWest (Int64) returns (Int32);
rpc StripAvTags (String) returns (String);
rpc StripAVTags (String) returns (String);
rpc ExtractAVTags (ExtractAVTagsIn) returns (ExtractAVTagsOut);
rpc ExtractLatex (ExtractLatexIn) returns (ExtractLatexOut);
rpc DeckTreeLegacy (Empty) returns (Bytes);
rpc CheckDatabase (Empty) returns (CheckDatabaseOut);
rpc GetEmptyCards (Empty) returns (EmptyCardsReport);
rpc SyncMedia (SyncMediaIn) returns (Empty);
rpc TrashMediaFiles (TrashMediaFilesIn) returns (Empty);
rpc GetDeckLegacy (Int64) returns (Bytes);
rpc GetDeckIDByName (String) returns (Int64);
}
// Protobuf stored in .anki2 files
@ -290,8 +301,6 @@ message I18nBackendInit {
message BackendInput {
oneof value {
AddMediaFileIn add_media_file = 26;
SyncMediaIn sync_media = 27;
TrashMediaFilesIn trash_media_files = 29;
TranslateStringIn translate_string = 30;
FormatTimeSpanIn format_time_span = 31;
StudiedTodayIn studied_today = 32;
@ -320,7 +329,6 @@ message BackendInput {
int32 get_changed_notetypes = 56;
AddOrUpdateNotetypeIn add_or_update_notetype = 57;
Empty get_all_decks = 58;
Empty check_database = 59;
StockNoteType get_stock_notetype_legacy = 60;
int64 get_notetype_legacy = 61;
Empty get_notetype_names = 62;
@ -331,14 +339,10 @@ message BackendInput {
AddNoteIn add_note = 67;
Note update_note = 68;
int64 get_note = 69;
Empty get_empty_cards = 70;
int64 get_deck_legacy = 71;
string get_deck_id_by_name = 72;
GetDeckNamesIn get_deck_names = 73;
AddOrUpdateDeckLegacyIn add_or_update_deck_legacy = 74;
bool new_deck_legacy = 75;
int64 remove_deck = 76;
Empty deck_tree_legacy = 77;
FieldNamesForNotesIn field_names_for_notes = 78;
FindAndReplaceIn find_and_replace = 79;
AfterNoteUpdatesIn after_note_updates = 80;
@ -363,8 +367,6 @@ message BackendOutput {
// fallible commands
string add_media_file = 26;
Empty sync_media = 27;
Empty trash_media_files = 29;
Empty empty_trash = 34;
Empty restore_trash = 35;
Empty open_collection = 36;
@ -388,7 +390,6 @@ message BackendOutput {
bytes get_changed_notetypes = 56;
int64 add_or_update_notetype = 57;
bytes get_all_decks = 58;
CheckDatabaseOut check_database = 59;
bytes get_notetype_legacy = 61;
NoteTypeNames get_notetype_names = 62;
NoteTypeUseCounts get_notetype_names_and_counts = 63;
@ -398,14 +399,10 @@ message BackendOutput {
int64 add_note = 67;
Empty update_note = 68;
Note get_note = 69;
EmptyCardsReport get_empty_cards = 70;
bytes get_deck_legacy = 71;
int64 get_deck_id_by_name = 72;
DeckNames get_deck_names = 73;
int64 add_or_update_deck_legacy = 74;
bytes new_deck_legacy = 75;
Empty remove_deck = 76;
bytes deck_tree_legacy = 77;
FieldNamesForNotesOut field_names_for_notes = 78;
uint32 find_and_replace = 79;
Empty after_note_updates = 80;

View file

@ -593,7 +593,7 @@ select id from notes where mid = ?) limit 1"""
"""
self.save(trx=False)
try:
problems = self.backend.check_database()
problems = list(self.backend.check_database())
ok = not problems
problems.append(self.tr(TR.DATABASE_CHECK_REBUILT))
except DBError as e:

View file

@ -11,7 +11,7 @@ import anki.backend_pb2 as pb
from anki.consts import *
from anki.errors import DeckRenameError
from anki.lang import _
from anki.rsbackend import DeckTreeNode
from anki.rsbackend import DeckTreeNode, NotFoundError, from_json_bytes
from anki.utils import ids2str, intTime
# legacy code may pass this in as the type argument to .id()
@ -113,10 +113,13 @@ class DeckManager:
)
def id_for_name(self, name: str) -> Optional[int]:
return self.col.backend.get_deck_id_by_name(name)
return self.col.backend.get_deck_id_by_name(name) or None
def get_legacy(self, did: int) -> Optional[Dict]:
return self.col.backend.get_deck_legacy(did)
try:
return from_json_bytes(self.col.backend.get_deck_legacy(did))
except NotFoundError:
return None
def have(self, id: int) -> bool:
return not self.get_legacy(int(id))

View file

@ -246,17 +246,6 @@ class RustBackend:
)
).add_media_file
def sync_media(self, hkey: str, endpoint: str) -> None:
self._run_command(
pb.BackendInput(sync_media=pb.SyncMediaIn(hkey=hkey, endpoint=endpoint,)),
release_gil=True,
)
def trash_media_files(self, fnames: List[str]) -> None:
self._run_command(
pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames))
)
def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str:
return self._run_command(
pb.BackendInput(translate_string=translate_string_in(key, **kwargs))
@ -506,20 +495,6 @@ class RustBackend:
except NotFoundError:
return None
def empty_cards_report(self) -> pb.EmptyCardsReport:
return self._run_command(
pb.BackendInput(get_empty_cards=pb.Empty()), release_gil=True
).get_empty_cards
def get_deck_legacy(self, did: int) -> Optional[Dict]:
try:
bytes = self._run_command(
pb.BackendInput(get_deck_legacy=did)
).get_deck_legacy
return orjson.loads(bytes)
except NotFoundError:
return None
def get_deck_names_and_ids(
self, skip_empty_default: bool, include_filtered: bool = True
) -> Sequence[pb.DeckNameID]:
@ -551,30 +526,9 @@ class RustBackend:
).new_deck_legacy
return orjson.loads(jstr)
def get_deck_id_by_name(self, name: str) -> Optional[int]:
return (
self._run_command(
pb.BackendInput(get_deck_id_by_name=name)
).get_deck_id_by_name
or None
)
def remove_deck(self, did: int) -> None:
self._run_command(pb.BackendInput(remove_deck=did))
def check_database(self) -> List[str]:
return list(
self._run_command(
pb.BackendInput(check_database=pb.Empty()), release_gil=True
).check_database.problems
)
def legacy_deck_tree(self) -> Sequence:
bytes = self._run_command(
pb.BackendInput(deck_tree_legacy=pb.Empty())
).deck_tree_legacy
return orjson.loads(bytes)[5]
def field_names_for_note_ids(self, nids: List[int]) -> Sequence[str]:
return self._run_command(
pb.BackendInput(field_names_for_notes=pb.FieldNamesForNotesIn(nids=nids)),
@ -725,7 +679,7 @@ class RustBackend:
output.ParseFromString(self._run_command2(9, input))
return output.val
def extract_a_v_tags(self, text: str, question_side: bool) -> pb.ExtractAVTagsOut:
def extract_av_tags(self, text: str, question_side: bool) -> pb.ExtractAVTagsOut:
input = pb.ExtractAVTagsIn(text=text, question_side=question_side)
output = pb.ExtractAVTagsOut()
output.ParseFromString(self._run_command2(10, input))
@ -739,6 +693,48 @@ class RustBackend:
output.ParseFromString(self._run_command2(11, input))
return output
def deck_tree_legacy(self) -> bytes:
input = pb.Empty()
output = pb.Bytes()
output.ParseFromString(self._run_command2(12, input))
return output.val
def check_database(self) -> Sequence[str]:
input = pb.Empty()
output = pb.CheckDatabaseOut()
output.ParseFromString(self._run_command2(13, input))
return output.problems
def get_empty_cards(self) -> pb.EmptyCardsReport:
input = pb.Empty()
output = pb.EmptyCardsReport()
output.ParseFromString(self._run_command2(14, input))
return output
def sync_media(self, hkey: str, endpoint: str) -> pb.Empty:
input = pb.SyncMediaIn(hkey=hkey, endpoint=endpoint)
output = pb.Empty()
output.ParseFromString(self._run_command2(15, input))
return output
def trash_media_files(self, fnames: Sequence[str]) -> pb.Empty:
input = pb.TrashMediaFilesIn(fnames=fnames)
output = pb.Empty()
output.ParseFromString(self._run_command2(16, input))
return output
def get_deck_legacy(self, val: int) -> bytes:
input = pb.Int64(val=val)
output = pb.Bytes()
output.ParseFromString(self._run_command2(17, input))
return output.val
def get_deck_id_by_name(self, val: str) -> int:
input = pb.String(val=val)
output = pb.Int64()
output.ParseFromString(self._run_command2(18, input))
return output.val
# @@AUTOGEN@@

View file

@ -213,7 +213,7 @@ order by due"""
print(
"deckDueTree() is deprecated; use decks.deck_tree() for a tree without counts, or sched.deck_due_tree()"
)
return self.col.backend.legacy_deck_tree()
return self.col.backend.deck_tree_legacy()
def deck_due_tree(self, top_deck_id: int = 0) -> DeckTreeNode:
"""Returns a tree of decks with counts.

View file

@ -215,10 +215,10 @@ class TemplateRenderContext:
)
qtext = apply_custom_filters(partial.qnodes, self, front_side=None)
qout = self.col().backend.extract_a_v_tags(qtext, True)
qout = self.col().backend.extract_av_tags(qtext, True)
atext = apply_custom_filters(partial.anodes, self, front_side=qtext)
aout = self.col().backend.extract_a_v_tags(atext, False)
aout = self.col().backend.extract_av_tags(atext, False)
output = TemplateRenderOutput(
question_text=qout.text,

View file

@ -51,7 +51,7 @@ def test_genrem():
t = m["tmpls"][1]
t["qfmt"] = "{{Back}}"
mm.save(m, templates=True)
rep = d.backend.empty_cards_report()
rep = d.backend.get_empty_cards()
for note in rep.notes:
d.remCards(note.card_ids)
assert len(f.cards()) == 1

View file

@ -55,6 +55,17 @@ def python_type_inner(field):
raise Exception(f"unknown type: {type}")
# get_deck_i_d -> get_deck_id etc
def fix_snakecase(name):
for fix in "a_v", "i_d":
name = re.sub(
f"(\w)({fix})(\w)",
lambda m: m.group(1) + m.group(2).replace("_", "") + m.group(3),
name,
)
return name
def get_input_args(msg):
fields = sorted(msg.fields, key=lambda x: x.number)
return ", ".join(["self"] + [f"{f.name}: {python_type(f)}" for f in fields])
@ -68,7 +79,7 @@ def get_input_assign(msg):
def render_method(method, idx):
input_args = get_input_args(method.input_type)
input_assign = get_input_assign(method.input_type)
name = stringcase.snakecase(method.name)
name = fix_snakecase(stringcase.snakecase(method.name))
if len(method.output_type.fields) == 1:
# unwrap single return arg
f = method.output_type.fields[0]

View file

@ -24,7 +24,7 @@ def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
diag = EmptyCardsDialog(mw, report)
diag.show()
mw.taskman.run_in_background(mw.col.backend.empty_cards_report, on_done)
mw.taskman.run_in_background(mw.col.backend.get_empty_cards, on_done)
class EmptyCardsDialog(QDialog):

View file

@ -11,6 +11,7 @@ from typing import List
import anki
import aqt
from anki.sound import SoundOrVideoTag
from anki.template import av_tags_to_native
from aqt.theme import theme_manager
# Routines removed from pylib/
@ -24,8 +25,12 @@ def bodyClass(col, card) -> str:
def allSounds(text) -> List:
print("allSounds() deprecated")
text, tags = aqt.mw.col.backend.extract_av_tags(text, True)
return [x.filename for x in tags if isinstance(x, SoundOrVideoTag)]
out = aqt.mw.col.backend.extract_av_tags(text, True)
return [
x.filename
for x in av_tags_to_native(out.av_tags)
if isinstance(x, SoundOrVideoTag)
]
def stripSounds(text) -> str:

View file

@ -147,6 +147,30 @@ pub fn init_backend(init_msg: &[u8]) -> std::result::Result<Backend, String> {
Ok(Backend::new(i18n, input.server))
}
impl From<Vec<u8>> for pb::Bytes {
fn from(val: Vec<u8>) -> Self {
pb::Bytes { val }
}
}
impl From<String> for pb::String {
fn from(val: String) -> Self {
pb::String { val }
}
}
impl From<i64> for pb::Int64 {
fn from(val: i64) -> Self {
pb::Int64 { val }
}
}
impl From<()> for pb::Empty {
fn from(_val: ()) -> Self {
pb::Empty {}
}
}
impl BackendService for Backend {
fn render_existing_card(
&mut self,
@ -310,6 +334,92 @@ impl BackendService for Backend {
.collect(),
})
}
fn check_database(&mut self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
self.with_col(|col| {
col.check_database().map(|problems| pb::CheckDatabaseOut {
problems: problems.to_i18n_strings(&col.i18n),
})
})
}
fn deck_tree_legacy(&mut self, _input: pb::Empty) -> BackendResult<pb::Bytes> {
self.with_col(|col| {
let tree = col.legacy_deck_tree()?;
serde_json::to_vec(&tree)
.map_err(Into::into)
.map(Into::into)
})
}
fn get_empty_cards(&mut self, _input: pb::Empty) -> Result<pb::EmptyCardsReport> {
self.with_col(|col| {
let mut empty = col.empty_cards()?;
let report = col.empty_cards_report(&mut empty)?;
let mut outnotes = vec![];
for (_ntid, notes) in empty {
outnotes.extend(notes.into_iter().map(|e| pb::NoteWithEmptyCards {
note_id: e.nid.0,
will_delete_note: e.empty.len() == e.current_count,
card_ids: e.empty.into_iter().map(|(_ord, id)| id.0).collect(),
}))
}
Ok(pb::EmptyCardsReport {
report,
notes: outnotes,
})
})
}
fn get_deck_legacy(&mut self, input: pb::Int64) -> Result<pb::Bytes> {
self.with_col(|col| {
let deck: DeckSchema11 = col
.storage
.get_deck(DeckID(input.val))?
.ok_or(AnkiError::NotFound)?
.into();
serde_json::to_vec(&deck)
.map_err(Into::into)
.map(Into::into)
})
}
fn get_deck_id_by_name(&mut self, input: pb::String) -> Result<pb::Int64> {
self.with_col(|col| {
col.get_deck_id(&input.val)
.map(|d| d.map(|d| d.0).unwrap_or_default())
.map(Into::into)
})
}
fn sync_media(&mut self, input: SyncMediaIn) -> BackendResult<Empty> {
let mut guard = self.col.lock().unwrap();
let col = guard.as_mut().unwrap();
col.set_media_sync_running()?;
let folder = col.media_folder.clone();
let db = col.media_db.clone();
let log = col.log.clone();
drop(guard);
let res = self.sync_media_inner(input, folder, db, log);
self.with_col(|col| col.set_media_sync_finished())?;
res.map(Into::into)
}
fn trash_media_files(&mut self, input: pb::TrashMediaFilesIn) -> BackendResult<Empty> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
mgr.remove_files(&mut ctx, &input.fnames)
})
.map(Into::into)
}
}
impl Backend {
@ -403,14 +513,6 @@ impl Backend {
use pb::backend_output::Value as OValue;
Ok(match ival {
Value::AddMediaFile(input) => OValue::AddMediaFile(self.add_media_file(input)?),
Value::SyncMedia(input) => {
self.sync_media(input)?;
OValue::SyncMedia(Empty {})
}
Value::TrashMediaFiles(input) => {
self.remove_media_files(&input.fnames)?;
OValue::TrashMediaFiles(Empty {})
}
Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)),
Value::FormatTimeSpan(input) => OValue::FormatTimeSpan(self.format_time_span(input)),
Value::StudiedToday(input) => OValue::StudiedToday(studied_today(
@ -509,11 +611,6 @@ impl Backend {
OValue::UpdateNote(pb::Empty {})
}
Value::GetNote(nid) => OValue::GetNote(self.get_note(nid)?),
Value::GetEmptyCards(_) => OValue::GetEmptyCards(self.get_empty_cards()?),
Value::GetDeckLegacy(did) => OValue::GetDeckLegacy(self.get_deck_legacy(did)?),
Value::GetDeckIdByName(name) => {
OValue::GetDeckIdByName(self.get_deck_id_by_name(&name)?)
}
Value::GetDeckNames(input) => OValue::GetDeckNames(self.get_deck_names(input)?),
Value::AddOrUpdateDeckLegacy(input) => {
OValue::AddOrUpdateDeckLegacy(self.add_or_update_deck_legacy(input)?)
@ -526,8 +623,6 @@ impl Backend {
self.remove_deck(did)?;
pb::Empty {}
}),
Value::CheckDatabase(_) => OValue::CheckDatabase(self.check_database()?),
Value::DeckTreeLegacy(_) => OValue::DeckTreeLegacy(self.deck_tree_legacy()?),
Value::FieldNamesForNotes(input) => {
OValue::FieldNamesForNotes(self.field_names_for_notes(input)?)
}
@ -625,25 +720,6 @@ impl Backend {
})
}
fn sync_media(&mut self, input: SyncMediaIn) -> Result<()> {
let mut guard = self.col.lock().unwrap();
let col = guard.as_mut().unwrap();
col.set_media_sync_running()?;
let folder = col.media_folder.clone();
let db = col.media_db.clone();
let log = col.log.clone();
drop(guard);
let res = self.sync_media_inner(input, folder, db, log);
self.with_col(|col| col.set_media_sync_finished())?;
res
}
fn sync_media_inner(
&mut self,
input: pb::SyncMediaIn,
@ -679,14 +755,6 @@ impl Backend {
}
}
fn remove_media_files(&self, fnames: &[String]) -> Result<()> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
mgr.remove_files(&mut ctx, fnames)
})
}
fn translate_string(&self, input: pb::TranslateStringIn) -> String {
let key = match pb::FluentString::from_i32(input.key) {
Some(key) => key,
@ -1010,44 +1078,6 @@ impl Backend {
})
}
fn get_empty_cards(&self) -> Result<pb::EmptyCardsReport> {
self.with_col(|col| {
let mut empty = col.empty_cards()?;
let report = col.empty_cards_report(&mut empty)?;
let mut outnotes = vec![];
for (_ntid, notes) in empty {
outnotes.extend(notes.into_iter().map(|e| pb::NoteWithEmptyCards {
note_id: e.nid.0,
will_delete_note: e.empty.len() == e.current_count,
card_ids: e.empty.into_iter().map(|(_ord, id)| id.0).collect(),
}))
}
Ok(pb::EmptyCardsReport {
report,
notes: outnotes,
})
})
}
fn get_deck_legacy(&self, did: i64) -> Result<Vec<u8>> {
self.with_col(|col| {
let deck: DeckSchema11 = col
.storage
.get_deck(DeckID(did))?
.ok_or(AnkiError::NotFound)?
.into();
serde_json::to_vec(&deck).map_err(Into::into)
})
}
fn get_deck_id_by_name(&self, human_name: &str) -> Result<i64> {
self.with_col(|col| {
col.get_deck_id(human_name)
.map(|d| d.map(|d| d.0).unwrap_or_default())
})
}
fn get_deck_names(&self, input: pb::GetDeckNamesIn) -> Result<pb::DeckNames> {
self.with_col(|col| {
let names = if input.include_filtered {
@ -1094,21 +1124,6 @@ impl Backend {
self.with_col(|col| col.remove_deck_and_child_decks(DeckID(did)))
}
fn check_database(&self) -> Result<pb::CheckDatabaseOut> {
self.with_col(|col| {
col.check_database().map(|problems| pb::CheckDatabaseOut {
problems: problems.to_i18n_strings(&col.i18n),
})
})
}
fn deck_tree_legacy(&self) -> Result<Vec<u8>> {
self.with_col(|col| {
let tree = col.legacy_deck_tree()?;
serde_json::to_vec(&tree).map_err(Into::into)
})
}
fn field_names_for_notes(
&self,
input: pb::FieldNamesForNotesIn,