diff --git a/proto/backend.proto b/proto/backend.proto index 16eadd120..18b6c174c 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -7,13 +7,9 @@ package backend_proto; message Empty {} message BackendInit { - string collection_path = 1; - string media_folder_path = 2; - string media_db_path = 3; - repeated string preferred_langs = 4; - string locale_folder_path = 5; - string log_path = 6; - bool server = 7; + repeated string preferred_langs = 1; + string locale_folder_path = 2; + bool server = 3; } message I18nBackendInit { @@ -45,6 +41,8 @@ message BackendInput { CongratsLearnMsgIn congrats_learn_msg = 33; Empty empty_trash = 34; Empty restore_trash = 35; + OpenCollectionIn open_collection = 36; + Empty close_collection = 37; } } @@ -73,6 +71,8 @@ message BackendOutput { Empty trash_media_files = 29; Empty empty_trash = 34; Empty restore_trash = 35; + Empty open_collection = 36; + Empty close_collection = 37; BackendError error = 2047; } @@ -91,7 +91,6 @@ message BackendError { SyncError sync_error = 7; // user interrupted operation Empty interrupted = 8; - Empty collection_not_open = 9; } } @@ -326,3 +325,10 @@ message CongratsLearnMsgIn { float next_due = 1; uint32 remaining = 2; } + +message OpenCollectionIn { + string collection_path = 1; + string media_folder_path = 2; + string media_db_path = 3; + string log_path = 4; +} diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 680b98434..3408f2e2a 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -256,8 +256,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", self.save(trx=False) if not self.server: self.db.execute("pragma journal_mode = delete") - self.db = None - self.backend = None + self.backend.close_collection() self.media.close() self._closeLog() diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index a15444a53..5b57224cc 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -200,22 +200,11 @@ def _on_progress(progress_bytes: bytes) -> bool: class RustBackend: - def __init__( - self, - col_path: str, - media_folder_path: str, - media_db_path: str, - log_path: str, - server: bool, - ) -> None: + def __init__(self, server: bool = False) -> None: ftl_folder = os.path.join(anki.lang.locale_folder, "fluent") init_msg = pb.BackendInit( - collection_path=col_path, - media_folder_path=media_folder_path, - media_db_path=media_db_path, locale_folder_path=ftl_folder, preferred_langs=[anki.lang.currentLang], - log_path=log_path, server=server, ) self._backend = ankirspy.open_backend(init_msg.SerializeToString()) @@ -234,6 +223,23 @@ 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, + ) + ) + ) + + def close_collection(self): + self._run_command(pb.BackendInput(close_collection=pb.Empty())) + def template_requirements( self, template_fronts: List[str], field_map: Dict[str, int] ) -> AllTemplateReqs: diff --git a/pylib/anki/storage.py b/pylib/anki/storage.py index 10dca3cd1..f19c4d327 100644 --- a/pylib/anki/storage.py +++ b/pylib/anki/storage.py @@ -27,18 +27,24 @@ class ServerData: minutes_west: Optional[int] = None -def Collection(path: str, server: Optional[ServerData] = None) -> _Collection: +def Collection( + path: str, + backend: Optional[RustBackend] = None, + server: Optional[ServerData] = None, +) -> _Collection: "Open a new or existing collection. Path must be unicode." assert path.endswith(".anki2") + if backend is None: + backend = RustBackend(server=server is not None) + (media_dir, media_db) = media_paths_from_col_path(path) log_path = "" if not server: log_path = path.replace(".anki2", "2.log") path = os.path.abspath(path) + # connect - backend = RustBackend( - path, media_dir, media_db, log_path, server=server is not None - ) + backend.open_collection(path, media_dir, media_db, log_path) db = DBProxy(weakref.proxy(backend), path) # initial setup required? diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 2b0957865..0025e6f97 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -35,6 +35,7 @@ pub struct Backend { progress_callback: Option, i18n: I18n, log: Logger, + server: bool, } enum Progress<'a> { @@ -56,7 +57,8 @@ fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError { } AnkiError::SyncError { kind, .. } => V::SyncError(pb::SyncError { kind: kind.into() }), AnkiError::Interrupted => V::Interrupted(Empty {}), - AnkiError::CollectionNotOpen => V::CollectionNotOpen(pb::Empty {}), + AnkiError::CollectionNotOpen => V::InvalidInput(pb::Empty {}), + AnkiError::CollectionAlreadyOpen => V::InvalidInput(pb::Empty {}), }; pb::BackendError { @@ -105,46 +107,24 @@ pub fn init_backend(init_msg: &[u8]) -> std::result::Result { Err(_) => return Err("couldn't decode init request".into()), }; - 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).map_err(|e| format!("Unable to open log file: {:?}", e))?; - let i18n = I18n::new( &input.preferred_langs, input.locale_folder_path, log::terminal(), ); - let col = open_collection( - input.collection_path, - input.media_folder_path, - input.media_db_path, - input.server, - i18n.clone(), - logger.clone(), - ) - .map_err(|e| format!("Unable to open collection: {:?}", e))?; - - match Backend::new(col, i18n, logger) { - Ok(backend) => Ok(backend), - Err(e) => Err(format!("{:?}", e)), - } + Ok(Backend::new(i18n, log::terminal(), input.server)) } impl Backend { - pub fn new(col: Collection, i18n: I18n, log: Logger) -> Result { - Ok(Backend { - col: Arc::new(Mutex::new(Some(col))), + pub fn new(i18n: I18n, log: Logger, server: bool) -> Backend { + Backend { + col: Arc::new(Mutex::new(None)), progress_callback: None, i18n, log, - }) + server, + } } pub fn i18n(&self) -> &I18n { @@ -259,9 +239,57 @@ impl Backend { self.restore_trash()?; OValue::RestoreTrash(Empty {}) } + Value::OpenCollection(input) => { + self.open_collection(input)?; + OValue::OpenCollection(Empty {}) + } + Value::CloseCollection(_) => { + self.close_collection()?; + OValue::CloseCollection(Empty {}) + } }) } + fn open_collection(&self, input: pb::OpenCollectionIn) -> Result<()> { + let mut mutex = self.col.lock().unwrap(); + if mutex.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 col = open_collection( + input.collection_path, + input.media_folder_path, + input.media_db_path, + self.server, + self.i18n.clone(), + logger, + )?; + + *mutex = Some(col); + + Ok(()) + } + + fn close_collection(&self) -> Result<()> { + let mut mutex = self.col.lock().unwrap(); + if mutex.is_none() { + return Err(AnkiError::CollectionNotOpen); + } + + *mutex = None; + + 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); diff --git a/rslib/src/err.rs b/rslib/src/err.rs index 1ec149bc4..8208370d5 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -36,6 +36,9 @@ pub enum AnkiError { #[fail(display = "Operation requires an open collection.")] CollectionNotOpen, + + #[fail(display = "Close the existing collection first.")] + CollectionAlreadyOpen, } // error helpers