migrate more scheduling/media/etc

almost there
This commit is contained in:
Damien Elmes 2020-05-23 21:34:19 +10:00
parent a105037ec9
commit 4bf8175bcb
8 changed files with 328 additions and 341 deletions

View file

@ -81,17 +81,23 @@ service BackendService {
rpc SearchCards (SearchCardsIn) returns (SearchCardsOut);
rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut);
rpc FindAndReplace (FindAndReplaceIn) returns (UInt32);
// scheduling
rpc LocalMinutesWest (Int64) returns (Int32);
rpc SetLocalMinutesWest (Int32) returns (Empty);
rpc SchedTimingToday (Empty) returns (SchedTimingTodayOut);
rpc StudiedToday (StudiedTodayIn) returns (String);
rpc CongratsLearnMessage (CongratsLearnMessageIn) returns (String);
// media
rpc CheckMedia (Empty) returns (CheckMediaOut);
rpc SyncMedia (SyncMediaIn) returns (Empty);
rpc TrashMediaFiles (TrashMediaFilesIn) returns (Empty);
rpc AddMediaFile (AddMediaFileIn) returns (String);
rpc EmptyTrash (Empty) returns (Empty);
rpc RestoreTrash (Empty) returns (Empty);
// decks
@ -141,10 +147,17 @@ service BackendService {
rpc GetNotetypeIDByName (String) returns (NoteTypeID);
rpc RemoveNotetype (NoteTypeID) returns (Empty);
// misc
// collection
rpc OpenCollection (OpenCollectionIn) returns (Empty);
rpc CloseCollection (CloseCollectionIn) returns (Empty);
rpc CheckDatabase (Empty) returns (CheckDatabaseOut);
// sync
rpc SyncMedia (SyncMediaIn) returns (Empty);
rpc AbortMediaSync (Empty) returns (Empty);
rpc BeforeUpload (Empty) returns (Empty);
}
// Protobuf stored in .anki2 files
@ -390,20 +403,8 @@ message I18nBackendInit {
message BackendInput {
oneof value {
AddMediaFileIn add_media_file = 26;
Empty empty_trash = 34;
Empty restore_trash = 35;
Empty abort_media_sync = 46;
TranslateStringIn translate_string = 30;
FormatTimeSpanIn format_time_span = 31;
StudiedTodayIn studied_today = 32;
CongratsLearnMsgIn congrats_learn_msg = 33;
OpenCollectionIn open_collection = 36;
CloseCollectionIn close_collection = 37;
Empty before_upload = 47;
RegisterTagsIn register_tags = 48;
Empty all_tags = 50;
@ -414,9 +415,6 @@ message BackendInput {
bytes set_all_config = 54;
Empty get_all_config = 55;
FindAndReplaceIn find_and_replace = 79;
int32 set_local_minutes_west = 83;
Empty get_preferences = 84;
Preferences set_preferences = 85;
}
@ -424,31 +422,18 @@ message BackendInput {
message BackendOutput {
oneof value {
// infallible commands
string translate_string = 30;
string format_time_span = 31;
string studied_today = 32;
string congrats_learn_msg = 33;
Empty abort_media_sync = 46;
// fallible commands
string add_media_file = 26;
Empty empty_trash = 34;
Empty restore_trash = 35;
Empty open_collection = 36;
Empty close_collection = 37;
Empty before_upload = 47;
bool register_tags = 48;
AllTagsOut all_tags = 50;
GetChangedTagsOut get_changed_tags = 51;
bytes get_config_json = 52;
Empty set_config_json = 53;
Empty set_all_config = 54;
bytes get_all_config = 55;
uint32 find_and_replace = 79;
Empty set_local_minutes_west = 83;
Preferences get_preferences = 84;
Empty set_preferences = 85;
@ -663,7 +648,7 @@ message StudiedTodayIn {
double seconds = 2;
}
message CongratsLearnMsgIn {
message CongratsLearnMessageIn {
float next_due = 1;
uint32 remaining = 2;
}

View file

@ -209,7 +209,7 @@ class Collection:
else:
self.db.rollback()
self.models._clear_cache()
self.backend.close_collection(downgrade=downgrade)
self.backend.close_collection(downgrade_to_schema11=downgrade)
self.db = None
self.media.close()
self._closeLog()

View file

@ -38,7 +38,7 @@ def findReplace(
fold: bool = True,
) -> int:
"Find and replace fields in a note. Returns changed note count."
return col.backend.find_and_replace(nids, src, dst, regex, fold, field)
return col.backend.find_and_replace(nids, src, dst, regex, not fold, field)
def fieldNamesForNotes(col: Collection, nids: List[int]) -> List[str]:

View file

@ -96,7 +96,7 @@ class MediaManager:
"""Write the file to the media folder, renaming if not unique.
Returns possibly-renamed filename."""
return self.col.backend.add_file_to_media_folder(desired_fname, data)
return self.col.backend.add_media_file(desired_fname, data)
def add_extension_based_on_mime(self, fname: str, content_type: str) -> str:
"If jpg or png mime, add .png/.jpg if missing extension."

View file

@ -216,36 +216,6 @@ class RustBackend:
else:
return output
def open_collection(
self, col_path: str, media_folder_path: str, media_db_path: str, log_path: str
):
self._run_command(
pb.BackendInput(
open_collection=pb.OpenCollectionIn(
collection_path=col_path,
media_folder_path=media_folder_path,
media_db_path=media_db_path,
log_path=log_path,
)
),
release_gil=True,
)
def close_collection(self, downgrade=True):
self._run_command(
pb.BackendInput(
close_collection=pb.CloseCollectionIn(downgrade_to_schema11=downgrade)
),
release_gil=True,
)
def add_file_to_media_folder(self, desired_name: str, data: bytes) -> str:
return self._run_command(
pb.BackendInput(
add_media_file=pb.AddMediaFileIn(desired_name=desired_name, data=data)
)
).add_media_file
def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str:
return self._run_command(
pb.BackendInput(translate_string=translate_string_in(key, **kwargs))
@ -262,28 +232,6 @@ class RustBackend:
)
).format_time_span
def studied_today(self, cards: int, seconds: float) -> str:
return self._run_command(
pb.BackendInput(
studied_today=pb.StudiedTodayIn(cards=cards, seconds=seconds)
)
).studied_today
def learning_congrats_msg(self, next_due: float, remaining: int) -> str:
return self._run_command(
pb.BackendInput(
congrats_learn_msg=pb.CongratsLearnMsgIn(
next_due=next_due, remaining=remaining
)
)
).congrats_learn_msg
def empty_trash(self):
self._run_command(pb.BackendInput(empty_trash=pb.Empty()))
def restore_trash(self):
self._run_command(pb.BackendInput(restore_trash=pb.Empty()))
def db_query(
self, sql: str, args: Sequence[ValueForDB], first_row_only: bool
) -> List[DBRow]:
@ -306,9 +254,6 @@ class RustBackend:
def _db_command(self, input: Dict[str, Any]) -> Any:
return orjson.loads(self._backend.db_command(orjson.dumps(input)))
def abort_media_sync(self):
self._run_command(pb.BackendInput(abort_media_sync=pb.Empty()))
def all_tags(self) -> Iterable[TagUsnTuple]:
return self._run_command(pb.BackendInput(all_tags=pb.Empty())).all_tags.tags
@ -331,9 +276,6 @@ class RustBackend:
)
).register_tags
def before_upload(self):
self._run_command(pb.BackendInput(before_upload=pb.Empty()))
def get_changed_tags(self, usn: int) -> List[str]:
return list(
self._run_command(
@ -370,32 +312,6 @@ class RustBackend:
def set_all_config(self, conf: Dict[str, Any]):
self._run_command(pb.BackendInput(set_all_config=orjson.dumps(conf)))
def find_and_replace(
self,
nids: List[int],
search: str,
repl: str,
re: bool,
nocase: bool,
field_name: Optional[str],
) -> int:
return self._run_command(
pb.BackendInput(
find_and_replace=pb.FindAndReplaceIn(
nids=nids,
search=search,
replacement=repl,
regex=re,
match_case=not nocase,
field_name=field_name,
)
),
release_gil=True,
).find_and_replace
def set_local_minutes_west(self, mins: int) -> None:
self._run_command(pb.BackendInput(set_local_minutes_west=mins))
def get_preferences(self) -> pb.Preferences:
return self._run_command(
pb.BackendInput(get_preferences=pb.Empty())
@ -473,34 +389,85 @@ class RustBackend:
output.ParseFromString(self._run_command2(8, input))
return output.note_ids
def find_and_replace(
self,
nids: Sequence[int],
search: str,
replacement: str,
regex: bool,
match_case: bool,
field_name: str,
) -> int:
input = pb.FindAndReplaceIn(
nids=nids,
search=search,
replacement=replacement,
regex=regex,
match_case=match_case,
field_name=field_name,
)
output = pb.UInt32()
output.ParseFromString(self._run_command2(9, input))
return output.val
def local_minutes_west(self, val: int) -> int:
input = pb.Int64(val=val)
output = pb.Int32()
output.ParseFromString(self._run_command2(9, input))
output.ParseFromString(self._run_command2(10, input))
return output.val
def set_local_minutes_west(self, val: int) -> pb.Empty:
input = pb.Int32(val=val)
output = pb.Empty()
output.ParseFromString(self._run_command2(11, input))
return output
def sched_timing_today(self) -> pb.SchedTimingTodayOut:
input = pb.Empty()
output = pb.SchedTimingTodayOut()
output.ParseFromString(self._run_command2(10, input))
output.ParseFromString(self._run_command2(12, input))
return output
def studied_today(self, cards: int, seconds: float) -> str:
input = pb.StudiedTodayIn(cards=cards, seconds=seconds)
output = pb.String()
output.ParseFromString(self._run_command2(13, input))
return output.val
def congrats_learn_message(self, next_due: float, remaining: int) -> str:
input = pb.CongratsLearnMessageIn(next_due=next_due, remaining=remaining)
output = pb.String()
output.ParseFromString(self._run_command2(14, input))
return output.val
def check_media(self) -> pb.CheckMediaOut:
input = pb.Empty()
output = pb.CheckMediaOut()
output.ParseFromString(self._run_command2(11, 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(12, input))
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(13, input))
output.ParseFromString(self._run_command2(16, input))
return output
def add_media_file(self, desired_name: str, data: bytes) -> str:
input = pb.AddMediaFileIn(desired_name=desired_name, data=data)
output = pb.String()
output.ParseFromString(self._run_command2(17, input))
return output.val
def empty_trash(self) -> pb.Empty:
input = pb.Empty()
output = pb.Empty()
output.ParseFromString(self._run_command2(18, input))
return output
def restore_trash(self) -> pb.Empty:
input = pb.Empty()
output = pb.Empty()
output.ParseFromString(self._run_command2(19, input))
return output
def add_or_update_deck_legacy(
@ -510,37 +477,37 @@ class RustBackend:
deck=deck, preserve_usn_and_mtime=preserve_usn_and_mtime
)
output = pb.DeckID()
output.ParseFromString(self._run_command2(14, input))
output.ParseFromString(self._run_command2(20, input))
return output.did
def deck_tree(self, include_counts: bool, top_deck_id: int) -> pb.DeckTreeNode:
input = pb.DeckTreeIn(include_counts=include_counts, top_deck_id=top_deck_id)
output = pb.DeckTreeNode()
output.ParseFromString(self._run_command2(15, input))
output.ParseFromString(self._run_command2(21, input))
return output
def deck_tree_legacy(self) -> bytes:
input = pb.Empty()
output = pb.Json()
output.ParseFromString(self._run_command2(16, input))
output.ParseFromString(self._run_command2(22, input))
return output.json
def get_all_decks_legacy(self) -> bytes:
input = pb.Empty()
output = pb.Json()
output.ParseFromString(self._run_command2(17, input))
output.ParseFromString(self._run_command2(23, input))
return output.json
def get_deck_id_by_name(self, val: str) -> int:
input = pb.String(val=val)
output = pb.DeckID()
output.ParseFromString(self._run_command2(18, input))
output.ParseFromString(self._run_command2(24, input))
return output.did
def get_deck_legacy(self, did: int) -> bytes:
input = pb.DeckID(did=did)
output = pb.Json()
output.ParseFromString(self._run_command2(19, input))
output.ParseFromString(self._run_command2(25, input))
return output.json
def get_deck_names(
@ -550,19 +517,19 @@ class RustBackend:
skip_empty_default=skip_empty_default, include_filtered=include_filtered
)
output = pb.DeckNames()
output.ParseFromString(self._run_command2(20, input))
output.ParseFromString(self._run_command2(26, input))
return output.entries
def new_deck_legacy(self, val: bool) -> bytes:
input = pb.Bool(val=val)
output = pb.Json()
output.ParseFromString(self._run_command2(21, input))
output.ParseFromString(self._run_command2(27, input))
return output.json
def remove_deck(self, did: int) -> pb.Empty:
input = pb.DeckID(did=did)
output = pb.Empty()
output.ParseFromString(self._run_command2(22, input))
output.ParseFromString(self._run_command2(28, input))
return output
def add_or_update_deck_config_legacy(
@ -572,76 +539,76 @@ class RustBackend:
config=config, preserve_usn_and_mtime=preserve_usn_and_mtime
)
output = pb.DeckConfigID()
output.ParseFromString(self._run_command2(23, input))
output.ParseFromString(self._run_command2(29, input))
return output.dcid
def all_deck_config_legacy(self) -> bytes:
input = pb.Empty()
output = pb.Json()
output.ParseFromString(self._run_command2(24, input))
output.ParseFromString(self._run_command2(30, input))
return output.json
def get_deck_config_legacy(self, dcid: int) -> bytes:
input = pb.DeckConfigID(dcid=dcid)
output = pb.Json()
output.ParseFromString(self._run_command2(25, input))
output.ParseFromString(self._run_command2(31, input))
return output.json
def new_deck_config_legacy(self) -> bytes:
input = pb.Empty()
output = pb.Json()
output.ParseFromString(self._run_command2(26, input))
output.ParseFromString(self._run_command2(32, input))
return output.json
def remove_deck_config(self, dcid: int) -> pb.Empty:
input = pb.DeckConfigID(dcid=dcid)
output = pb.Empty()
output.ParseFromString(self._run_command2(27, input))
output.ParseFromString(self._run_command2(33, input))
return output
def get_card(self, cid: int) -> pb.Card:
input = pb.CardID(cid=cid)
output = pb.Card()
output.ParseFromString(self._run_command2(28, input))
output.ParseFromString(self._run_command2(34, input))
return output
def update_card(self, input: pb.Card) -> pb.Empty:
output = pb.Empty()
output.ParseFromString(self._run_command2(29, input))
output.ParseFromString(self._run_command2(35, input))
return output
def add_card(self, input: pb.Card) -> int:
output = pb.CardID()
output.ParseFromString(self._run_command2(30, input))
output.ParseFromString(self._run_command2(36, input))
return output.cid
def new_note(self, ntid: int) -> pb.Note:
input = pb.NoteTypeID(ntid=ntid)
output = pb.Note()
output.ParseFromString(self._run_command2(31, input))
output.ParseFromString(self._run_command2(37, input))
return output
def add_note(self, note: pb.Note, deck_id: int) -> int:
input = pb.AddNoteIn(note=note, deck_id=deck_id)
output = pb.NoteID()
output.ParseFromString(self._run_command2(32, input))
output.ParseFromString(self._run_command2(38, input))
return output.nid
def update_note(self, input: pb.Note) -> pb.Empty:
output = pb.Empty()
output.ParseFromString(self._run_command2(33, input))
output.ParseFromString(self._run_command2(39, input))
return output
def get_note(self, nid: int) -> pb.Note:
input = pb.NoteID(nid=nid)
output = pb.Note()
output.ParseFromString(self._run_command2(34, input))
output.ParseFromString(self._run_command2(40, input))
return output
def add_note_tags(self, nids: Sequence[int], tags: str) -> int:
input = pb.AddNoteTagsIn(nids=nids, tags=tags)
output = pb.UInt32()
output.ParseFromString(self._run_command2(35, input))
output.ParseFromString(self._run_command2(41, input))
return output.val
def update_note_tags(
@ -651,12 +618,12 @@ class RustBackend:
nids=nids, tags=tags, replacement=replacement, regex=regex
)
output = pb.UInt32()
output.ParseFromString(self._run_command2(36, input))
output.ParseFromString(self._run_command2(42, input))
return output.val
def cloze_numbers_in_note(self, input: pb.Note) -> Sequence[int]:
output = pb.ClozeNumbersInNoteOut()
output.ParseFromString(self._run_command2(37, input))
output.ParseFromString(self._run_command2(43, input))
return output.numbers
def after_note_updates(
@ -668,13 +635,13 @@ class RustBackend:
generate_cards=generate_cards,
)
output = pb.Empty()
output.ParseFromString(self._run_command2(38, input))
output.ParseFromString(self._run_command2(44, input))
return output
def field_names_for_notes(self, nids: Sequence[int]) -> Sequence[str]:
input = pb.FieldNamesForNotesIn(nids=nids)
output = pb.FieldNamesForNotesOut()
output.ParseFromString(self._run_command2(39, input))
output.ParseFromString(self._run_command2(45, input))
return output.fields
def add_or_update_notetype(self, json: bytes, preserve_usn_and_mtime: bool) -> int:
@ -682,51 +649,92 @@ class RustBackend:
json=json, preserve_usn_and_mtime=preserve_usn_and_mtime
)
output = pb.NoteTypeID()
output.ParseFromString(self._run_command2(40, input))
output.ParseFromString(self._run_command2(46, input))
return output.ntid
def get_stock_notetype_legacy(self, kind: pb.StockNoteType) -> bytes:
input = pb.GetStockNotetypeIn(kind=kind)
output = pb.Json()
output.ParseFromString(self._run_command2(41, input))
output.ParseFromString(self._run_command2(47, input))
return output.json
def get_notetype_legacy(self, ntid: int) -> bytes:
input = pb.NoteTypeID(ntid=ntid)
output = pb.Json()
output.ParseFromString(self._run_command2(42, input))
output.ParseFromString(self._run_command2(48, input))
return output.json
def get_notetype_names(self) -> Sequence[pb.NoteTypeNameID]:
input = pb.Empty()
output = pb.NoteTypeNames()
output.ParseFromString(self._run_command2(43, input))
output.ParseFromString(self._run_command2(49, input))
return output.entries
def get_notetype_names_and_counts(self) -> Sequence[pb.NoteTypeNameIDUseCount]:
input = pb.Empty()
output = pb.NoteTypeUseCounts()
output.ParseFromString(self._run_command2(44, input))
output.ParseFromString(self._run_command2(50, input))
return output.entries
def get_notetype_id_by_name(self, val: str) -> int:
input = pb.String(val=val)
output = pb.NoteTypeID()
output.ParseFromString(self._run_command2(45, input))
output.ParseFromString(self._run_command2(51, input))
return output.ntid
def remove_notetype(self, ntid: int) -> pb.Empty:
input = pb.NoteTypeID(ntid=ntid)
output = pb.Empty()
output.ParseFromString(self._run_command2(46, input))
output.ParseFromString(self._run_command2(52, input))
return output
def open_collection(
self,
collection_path: str,
media_folder_path: str,
media_db_path: str,
log_path: str,
) -> pb.Empty:
input = pb.OpenCollectionIn(
collection_path=collection_path,
media_folder_path=media_folder_path,
media_db_path=media_db_path,
log_path=log_path,
)
output = pb.Empty()
output.ParseFromString(self._run_command2(53, input))
return output
def close_collection(self, downgrade_to_schema11: bool) -> pb.Empty:
input = pb.CloseCollectionIn(downgrade_to_schema11=downgrade_to_schema11)
output = pb.Empty()
output.ParseFromString(self._run_command2(54, input))
return output
def check_database(self) -> Sequence[str]:
input = pb.Empty()
output = pb.CheckDatabaseOut()
output.ParseFromString(self._run_command2(47, input))
output.ParseFromString(self._run_command2(55, input))
return output.problems
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(56, input))
return output
def abort_media_sync(self) -> pb.Empty:
input = pb.Empty()
output = pb.Empty()
output.ParseFromString(self._run_command2(57, input))
return output
def before_upload(self) -> pb.Empty:
input = pb.Empty()
output = pb.Empty()
output.ParseFromString(self._run_command2(58, input))
return output
# @@AUTOGEN@@

View file

@ -1270,7 +1270,7 @@ from cards where did in {dids} and queue = {QUEUE_TYPE_LRN}
remaining = remaining or 0
if next and next < self.dayCutoff:
next -= intTime() - self.col.conf["collapseTime"]
return self.col.backend.learning_congrats_msg(abs(next), remaining)
return self.col.backend.congrats_learn_message(abs(next), remaining)
else:
return ""

View file

@ -356,6 +356,28 @@ impl BackendService for Backend {
})
}
fn find_and_replace(&mut self, input: pb::FindAndReplaceIn) -> BackendResult<pb::UInt32> {
let mut search = if input.regex {
input.search
} else {
regex::escape(&input.search)
};
if !input.match_case {
search = format!("(?i){}", search);
}
let nids = input.nids.into_iter().map(NoteID).collect();
let field_name = if input.field_name.is_empty() {
None
} else {
Some(input.field_name)
};
let repl = input.replacement;
self.with_col(|col| {
col.find_and_replace(nids, &search, &repl, field_name)
.map(|cnt| pb::UInt32 { val: cnt as u32 })
})
}
// scheduling
//-----------------------------------------------
@ -369,6 +391,25 @@ impl BackendService for Backend {
})
}
fn set_local_minutes_west(&mut self, input: pb::Int32) -> BackendResult<Empty> {
self.with_col(|col| {
col.transact(None, |col| {
col.set_local_mins_west(input.val).map(Into::into)
})
})
}
fn studied_today(&mut self, input: pb::StudiedTodayIn) -> BackendResult<pb::String> {
Ok(studied_today(input.cards as usize, input.seconds as f32, &self.i18n).into())
}
fn congrats_learn_message(
&mut self,
input: pb::CongratsLearnMessageIn,
) -> BackendResult<pb::String> {
Ok(learning_congrats(input.remaining as usize, input.next_due, &self.i18n).into())
}
// decks
//-----------------------------------------------
@ -740,23 +781,46 @@ impl BackendService for Backend {
// media
//-------------------------------------------------------------------
fn sync_media(&mut self, input: SyncMediaIn) -> BackendResult<Empty> {
let mut guard = self.col.lock().unwrap();
fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> BackendResult<pb::String> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
Ok(mgr
.add_file(&mut ctx, &input.desired_name, &input.data)?
.to_string()
.into())
})
}
let col = guard.as_mut().unwrap();
col.set_media_sync_running()?;
fn empty_trash(&mut self, _input: Empty) -> BackendResult<Empty> {
let callback =
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
let folder = col.media_folder.clone();
let db = col.media_db.clone();
let log = col.log.clone();
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
col.transact(None, |ctx| {
let mut checker = MediaChecker::new(ctx, &mgr, callback);
drop(guard);
checker.empty_trash()
})
})
.map(Into::into)
}
let res = self.sync_media_inner(input, folder, db, log);
fn restore_trash(&mut self, _input: Empty) -> BackendResult<Empty> {
let callback =
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
self.with_col(|col| col.set_media_sync_finished())?;
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
res.map(Into::into)
col.transact(None, |ctx| {
let mut checker = MediaChecker::new(ctx, &mgr, callback);
checker.restore_trash()
})
})
.map(Into::into)
}
fn trash_media_files(&mut self, input: pb::TrashMediaFilesIn) -> BackendResult<Empty> {
@ -790,7 +854,8 @@ impl BackendService for Backend {
})
}
// misc
// collection
//-------------------------------------------------------------------
fn check_database(&mut self, _input: pb::Empty) -> BackendResult<pb::CheckDatabaseOut> {
self.with_col(|col| {
@ -799,6 +864,89 @@ impl BackendService for Backend {
})
})
}
fn open_collection(&mut self, input: pb::OpenCollectionIn) -> BackendResult<Empty> {
let mut col = self.col.lock().unwrap();
if col.is_some() {
return Err(AnkiError::CollectionAlreadyOpen);
}
let mut path = input.collection_path.clone();
path.push_str(".log");
let log_path = match input.log_path.as_str() {
"" => None,
path => Some(path),
};
let logger = default_logger(log_path)?;
let new_col = open_collection(
input.collection_path,
input.media_folder_path,
input.media_db_path,
self.server,
self.i18n.clone(),
logger,
)?;
*col = Some(new_col);
Ok(().into())
}
fn close_collection(&mut self, input: pb::CloseCollectionIn) -> BackendResult<Empty> {
let mut col = self.col.lock().unwrap();
if col.is_none() {
return Err(AnkiError::CollectionNotOpen);
}
if !col.as_ref().unwrap().can_close() {
return Err(AnkiError::invalid_input("can't close yet"));
}
let col_inner = col.take().unwrap();
if input.downgrade_to_schema11 {
let log = log::terminal();
if let Err(e) = col_inner.close(input.downgrade_to_schema11) {
error!(log, " failed: {:?}", e);
}
}
Ok(().into())
}
// sync
//-------------------------------------------------------------------
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 abort_media_sync(&mut self, _input: Empty) -> BackendResult<Empty> {
if let Some(handle) = self.media_sync_abort.take() {
handle.abort();
}
Ok(().into())
}
fn before_upload(&mut self, _input: Empty) -> BackendResult<Empty> {
self.with_col(|col| col.before_upload().map(Into::into))
}
}
impl Backend {
@ -891,43 +1039,8 @@ impl Backend {
) -> Result<pb::backend_output::Value> {
use pb::backend_output::Value as OValue;
Ok(match ival {
Value::AddMediaFile(input) => OValue::AddMediaFile(self.add_media_file(input)?),
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(
input.cards as usize,
input.seconds as f32,
&self.i18n,
)),
Value::CongratsLearnMsg(input) => OValue::CongratsLearnMsg(learning_congrats(
input.remaining as usize,
input.next_due,
&self.i18n,
)),
Value::EmptyTrash(_) => {
self.empty_trash()?;
OValue::EmptyTrash(Empty {})
}
Value::RestoreTrash(_) => {
self.restore_trash()?;
OValue::RestoreTrash(Empty {})
}
Value::OpenCollection(input) => {
self.open_collection(input)?;
OValue::OpenCollection(Empty {})
}
Value::CloseCollection(input) => {
self.close_collection(input.downgrade_to_schema11)?;
OValue::CloseCollection(Empty {})
}
Value::AbortMediaSync(_) => {
self.abort_media_sync();
OValue::AbortMediaSync(pb::Empty {})
}
Value::BeforeUpload(_) => {
self.before_upload()?;
OValue::BeforeUpload(pb::Empty {})
}
Value::AllTags(_) => OValue::AllTags(self.all_tags()?),
Value::RegisterTags(input) => OValue::RegisterTags(self.register_tags(input)?),
Value::GetChangedTags(usn) => OValue::GetChangedTags(self.get_changed_tags(usn)?),
@ -942,11 +1055,6 @@ impl Backend {
pb::Empty {}
}),
Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?),
Value::FindAndReplace(input) => OValue::FindAndReplace(self.find_and_replace(input)?),
Value::SetLocalMinutesWest(mins) => OValue::SetLocalMinutesWest({
self.set_local_mins_west(mins)?;
pb::Empty {}
}),
Value::GetPreferences(_) => OValue::GetPreferences(self.get_preferences()?),
Value::SetPreferences(prefs) => OValue::SetPreferences({
self.set_preferences(prefs)?;
@ -955,56 +1063,6 @@ impl Backend {
})
}
fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<()> {
let mut col = self.col.lock().unwrap();
if col.is_some() {
return Err(AnkiError::CollectionAlreadyOpen);
}
let mut path = input.collection_path.clone();
path.push_str(".log");
let log_path = match input.log_path.as_str() {
"" => None,
path => Some(path),
};
let logger = default_logger(log_path)?;
let new_col = open_collection(
input.collection_path,
input.media_folder_path,
input.media_db_path,
self.server,
self.i18n.clone(),
logger,
)?;
*col = Some(new_col);
Ok(())
}
fn close_collection(&self, downgrade: bool) -> Result<()> {
let mut col = self.col.lock().unwrap();
if col.is_none() {
return Err(AnkiError::CollectionNotOpen);
}
if !col.as_ref().unwrap().can_close() {
return Err(AnkiError::invalid_input("can't close yet"));
}
let col_inner = col.take().unwrap();
if downgrade {
let log = log::terminal();
if let Err(e) = col_inner.close(downgrade) {
error!(log, " failed: {:?}", e);
}
}
Ok(())
}
fn fire_progress_callback(&self, progress: Progress) -> bool {
if let Some(cb) = &self.progress_callback {
let bytes = progress_to_proto_bytes(progress, &self.i18n);
@ -1018,16 +1076,6 @@ impl Backend {
self.progress_callback = progress_cb;
}
fn add_media_file(&mut self, input: pb::AddMediaFileIn) -> Result<String> {
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
let mut ctx = mgr.dbctx();
Ok(mgr
.add_file(&mut ctx, &input.desired_name, &input.data)?
.into())
})
}
fn sync_media_inner(
&mut self,
input: pb::SyncMediaIn,
@ -1057,12 +1105,6 @@ impl Backend {
ret
}
fn abort_media_sync(&mut self) {
if let Some(handle) = self.media_sync_abort.take() {
handle.abort();
}
}
fn translate_string(&self, input: pb::TranslateStringIn) -> String {
let key = match pb::FluentString::from_i32(input.key) {
Some(key) => key,
@ -1094,43 +1136,10 @@ impl Backend {
}
}
fn empty_trash(&self) -> Result<()> {
let callback =
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
col.transact(None, |ctx| {
let mut checker = MediaChecker::new(ctx, &mgr, callback);
checker.empty_trash()
})
})
}
fn restore_trash(&self) -> Result<()> {
let callback =
|progress: usize| self.fire_progress_callback(Progress::MediaCheck(progress as u32));
self.with_col(|col| {
let mgr = MediaManager::new(&col.media_folder, &col.media_db)?;
col.transact(None, |ctx| {
let mut checker = MediaChecker::new(ctx, &mgr, callback);
checker.restore_trash()
})
})
}
pub fn db_command(&self, input: &[u8]) -> Result<String> {
self.with_col(|col| db_command_bytes(&col.storage, input))
}
fn before_upload(&self) -> Result<()> {
self.with_col(|col| col.before_upload())
}
fn all_tags(&self) -> Result<pb::AllTagsOut> {
let tags = self.with_col(|col| col.storage.all_tags())?;
let tags: Vec<_> = tags
@ -1209,32 +1218,6 @@ impl Backend {
})
}
fn find_and_replace(&self, input: pb::FindAndReplaceIn) -> Result<u32> {
let mut search = if input.regex {
input.search
} else {
regex::escape(&input.search)
};
if !input.match_case {
search = format!("(?i){}", search);
}
let nids = input.nids.into_iter().map(NoteID).collect();
let field_name = if input.field_name.is_empty() {
None
} else {
Some(input.field_name)
};
let repl = input.replacement;
self.with_col(|col| {
col.find_and_replace(nids, &search, &repl, field_name)
.map(|cnt| cnt as u32)
})
}
fn set_local_mins_west(&self, mins: i32) -> Result<()> {
self.with_col(|col| col.transact(None, |col| col.set_local_mins_west(mins)))
}
fn get_preferences(&self) -> Result<pb::Preferences> {
self.with_col(|col| col.get_preferences())
}

View file

@ -82,6 +82,17 @@ fn want_release_gil(method: u32) -> bool {
BackendMethod::GetNotetypeIDByName => true,
BackendMethod::RemoveNotetype => true,
BackendMethod::CheckDatabase => true,
BackendMethod::FindAndReplace => true,
BackendMethod::SetLocalMinutesWest => false,
BackendMethod::StudiedToday => false,
BackendMethod::CongratsLearnMessage => false,
BackendMethod::AddMediaFile => true,
BackendMethod::EmptyTrash => true,
BackendMethod::RestoreTrash => true,
BackendMethod::OpenCollection => true,
BackendMethod::CloseCollection => true,
BackendMethod::AbortMediaSync => true,
BackendMethod::BeforeUpload => true,
}
} else {
false