Anki/rslib/src/backend/mod.rs
Damien Elmes 0eddb25287
Integrate AnkiDroid's backend patches into the repo (#2290)
* Relax chrono specification for AnkiDroid

https://github.com/ankidroid/Anki-Android-Backend/pull/251

* Add AnkiDroid service and AnkiDroid customizations

Most of the work here was done by David in the Backend repo; integrating
it into this repo for ease of future maintenance.

Based on 5d9f262f4c
with some tweaks:

- Protobuf imports have been fixed to match the recent refactor
- FatalError has been renamed to AnkidroidPanicError
- Tweaks to the desktop code to deal with the extra arg to open_collection,
and exclude AnkiDroid service methods from our Python code.

* Refactor AnkiDroid's DB code to avoid uses of unsafe
2023-01-03 13:11:23 +10:00

195 lines
6 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
// infallible backend methods still return a result
#![allow(clippy::unnecessary_wraps)]
mod adding;
mod ankidroid;
mod card;
mod cardrendering;
mod collection;
mod config;
mod dbproxy;
mod deckconfig;
mod decks;
mod error;
mod generic;
mod i18n;
mod import_export;
mod links;
mod media;
mod notes;
mod notetypes;
mod ops;
mod progress;
mod scheduler;
mod search;
mod stats;
mod sync;
mod tags;
use std::{
result,
sync::{Arc, Mutex},
thread::JoinHandle,
};
use once_cell::sync::OnceCell;
use progress::AbortHandleSlot;
use prost::Message;
use tokio::runtime::{
Runtime, {self},
};
use self::{
ankidroid::AnkidroidService,
card::CardsService,
cardrendering::CardRenderingService,
collection::CollectionService,
config::ConfigService,
deckconfig::DeckConfigService,
decks::DecksService,
i18n::I18nService,
import_export::ImportExportService,
links::LinksService,
media::MediaService,
notes::NotesService,
notetypes::NotetypesService,
progress::ProgressState,
scheduler::SchedulerService,
search::SearchService,
stats::StatsService,
sync::{SyncService, SyncState},
tags::TagsService,
};
use crate::{backend::dbproxy::db_command_bytes, pb, pb::backend::ServiceIndex, prelude::*};
pub struct Backend {
col: Arc<Mutex<Option<Collection>>>,
tr: I18n,
server: bool,
sync_abort: AbortHandleSlot,
progress_state: Arc<Mutex<ProgressState>>,
runtime: OnceCell<Runtime>,
state: Arc<Mutex<BackendState>>,
backup_task: Arc<Mutex<Option<JoinHandle<Result<()>>>>>,
}
#[derive(Default)]
struct BackendState {
sync: SyncState,
}
pub fn init_backend(init_msg: &[u8]) -> result::Result<Backend, String> {
let input: pb::backend::BackendInit = match pb::backend::BackendInit::decode(init_msg) {
Ok(req) => req,
Err(_) => return Err("couldn't decode init request".into()),
};
let tr = I18n::new(&input.preferred_langs);
Ok(Backend::new(tr, input.server))
}
impl Backend {
pub fn new(tr: I18n, server: bool) -> Backend {
Backend {
col: Arc::new(Mutex::new(None)),
tr,
server,
sync_abort: Arc::new(Mutex::new(None)),
progress_state: Arc::new(Mutex::new(ProgressState {
want_abort: false,
last_progress: None,
})),
runtime: OnceCell::new(),
state: Arc::new(Mutex::new(BackendState::default())),
backup_task: Arc::new(Mutex::new(None)),
}
}
pub fn i18n(&self) -> &I18n {
&self.tr
}
pub fn run_method(
&self,
service: u32,
method: u32,
input: &[u8],
) -> result::Result<Vec<u8>, Vec<u8>> {
ServiceIndex::from_i32(service as i32)
.or_invalid("invalid service")
.and_then(|service| match service {
ServiceIndex::Ankidroid => AnkidroidService::run_method(self, method, input),
ServiceIndex::Scheduler => SchedulerService::run_method(self, method, input),
ServiceIndex::Decks => DecksService::run_method(self, method, input),
ServiceIndex::Notes => NotesService::run_method(self, method, input),
ServiceIndex::Notetypes => NotetypesService::run_method(self, method, input),
ServiceIndex::Config => ConfigService::run_method(self, method, input),
ServiceIndex::Sync => SyncService::run_method(self, method, input),
ServiceIndex::Tags => TagsService::run_method(self, method, input),
ServiceIndex::DeckConfig => DeckConfigService::run_method(self, method, input),
ServiceIndex::CardRendering => {
CardRenderingService::run_method(self, method, input)
}
ServiceIndex::Media => MediaService::run_method(self, method, input),
ServiceIndex::Stats => StatsService::run_method(self, method, input),
ServiceIndex::Search => SearchService::run_method(self, method, input),
ServiceIndex::I18n => I18nService::run_method(self, method, input),
ServiceIndex::Links => LinksService::run_method(self, method, input),
ServiceIndex::Collection => CollectionService::run_method(self, method, input),
ServiceIndex::Cards => CardsService::run_method(self, method, input),
ServiceIndex::ImportExport => ImportExportService::run_method(self, method, input),
})
.map_err(|err| {
let backend_err = err.into_protobuf(&self.tr);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();
bytes
})
}
pub fn run_db_command_bytes(&self, input: &[u8]) -> result::Result<Vec<u8>, Vec<u8>> {
self.db_command(input).map_err(|err| {
let backend_err = err.into_protobuf(&self.tr);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();
bytes
})
}
/// If collection is open, run the provided closure while holding
/// the mutex.
/// If collection is not open, return an error.
fn with_col<F, T>(&self, func: F) -> Result<T>
where
F: FnOnce(&mut Collection) -> Result<T>,
{
func(
self.col
.lock()
.unwrap()
.as_mut()
.ok_or(AnkiError::CollectionNotOpen)?,
)
}
fn runtime_handle(&self) -> runtime::Handle {
self.runtime
.get_or_init(|| {
runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap()
})
.handle()
.clone()
}
fn db_command(&self, input: &[u8]) -> Result<Vec<u8>> {
self.with_col(|col| db_command_bytes(col, input))
}
}