mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
support opening and closing the DB while keeping backend alive
This is safer than just dropping the backend, as .close() will block if something else is holding the mutex. Also means we can drop the extra I18nBackend code. Media syncing still needs fixing.
This commit is contained in:
parent
94e4c40ebf
commit
874bc085fe
6 changed files with 103 additions and 55 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -35,6 +35,7 @@ pub struct Backend {
|
|||
progress_callback: Option<ProtoProgressCallback>,
|
||||
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<Backend, String> {
|
|||
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<Backend> {
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue