#![allow(clippy::needless_doctest_main)]
pub mod anki;
#[cfg(feature = "cache")]
pub mod cache;
pub mod decks;
pub mod error;
pub mod generic;
pub mod model;
pub mod notes;
pub mod result;
mod str_utils;
mod test_utils;
use std::{ops::Deref, sync::Arc};
use error::{AnkiError, CustomSerdeError};
use getset::{Getters, MutGetters};
use num_traits::PrimInt;
use reqwest::blocking::Client as BlockingClient;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg(feature = "cache")]
use crate::cache::Cache;
use crate::error::AnkiResult;
pub use reqwest::Client as ReqwestClient;
#[derive(Clone, Debug, Getters, MutGetters)]
pub struct AnkiClient {
backend: Arc<Backend>,
modules: Arc<AnkiModules>,
#[cfg(feature = "cache")]
#[getset(get = "pub", get_mut = "pub")]
cache: Cache,
}
impl AnkiClient {
pub fn new_port(port: &str) -> AnkiResult<Self> {
let backend = Arc::new(Backend::new_port(port)?);
let modules = Arc::new(AnkiModules::new(backend.clone()));
Ok(Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules),
})
}
pub fn default_latest() -> AnkiResult<Self> {
let backend = Arc::new(Backend::default_latest()?);
let modules = Arc::new(AnkiModules::new(backend.clone()));
Ok(Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules),
})
}
pub fn new_port_version(port: &str, version: u8) -> Self {
let backend = Arc::new(Backend::new_port_version(port, version));
let modules = Arc::new(AnkiModules::new(backend.clone()));
Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules),
}
}
pub fn notes(&self) -> &NotesProxy {
&self.modules.notes
}
pub fn models(&self) -> &ModelsProxy {
&self.modules.models
}
pub fn decks(&self) -> &DecksProxy {
&self.modules.decks
}
pub fn reqwest_client(&self) -> &BlockingClient {
&self.backend.client
}
}
#[derive(Clone, Debug, Getters)]
pub struct AnkiModules {
backend: Arc<Backend>,
notes: NotesProxy,
models: ModelsProxy,
#[getset(get = "pub")]
decks: DecksProxy,
}
impl PartialEq for AnkiModules {
fn eq(&self, other: &Self) -> bool {
let Self { backend, .. } = other;
self.backend == *backend
}
}
impl AnkiModules {
fn new(backend: Arc<Backend>) -> Self {
Self {
backend: backend.clone(),
notes: NotesProxy(backend.clone()),
models: ModelsProxy(backend.clone()),
decks: DecksProxy(backend.clone()),
}
}
}
#[derive(Clone, Debug)]
pub struct NotesProxy(Arc<Backend>);
impl Deref for NotesProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct ModelsProxy(Arc<Backend>);
impl Deref for ModelsProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct DecksProxy(Arc<Backend>);
impl Deref for DecksProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for AnkiClient {
fn default() -> Self {
let backend = Arc::new(Backend::default());
let modules = Arc::new(AnkiModules::new(backend.clone()));
Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules.clone()),
}
}
}
impl Deref for AnkiClient {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.backend
}
}
#[derive(Clone, Debug)]
pub struct Backend {
pub endpoint: String,
pub client: BlockingClient,
pub version: u8,
}
impl PartialEq for Backend {
fn eq(&self, other: &Self) -> bool {
let Self {
endpoint, version, ..
} = self;
other.endpoint == *endpoint && other.version == *version
}
}
impl Eq for Backend {}
impl Default for Backend {
fn default() -> Self {
Self::new_port_version("8765", 6)
}
}
impl Backend {
pub fn new_port(port: &str) -> Result<Self, AnkiError> {
let client = BlockingClient::new();
let endpoint = Self::format_url(port);
let version = Backend::get_version_internal(&client, &endpoint)?;
let ac = Self {
endpoint,
client,
version,
};
Ok(ac)
}
pub fn default_latest() -> Result<Self, AnkiError> {
Self::new_port("8765")
}
pub fn new_port_version(port: &str, version: u8) -> Self {
Self {
endpoint: Self::format_url(port),
client: BlockingClient::new(),
version,
}
}
pub fn format_url(port: &str) -> String {
format!("http://localhost:{port}")
}
pub fn get_version_internal(c: &BlockingClient, url: &str) -> Result<u8, AnkiError> {
let res = match c.get(url).send() {
Ok(response) => response,
Err(_) => return Err(AnkiError::ConnectionNotFound(url.to_string())),
};
let val: Value = res.json().unwrap();
let Some(res) = val.as_object() else {
let cse = CustomSerdeError::expected(None, val, None);
return Err(AnkiError::CustomSerde(cse));
};
let version: String = res.get("apiVersion").unwrap().to_string();
let mut version_str = version
.split_once(".")
.expect("no delimiter `.` found")
.1
.to_string();
version_str.remove(1);
let version = version_str
.parse::<u8>()
.map_err(|_| AnkiError::ParseIntError(version_str.to_string()))?;
Ok(version)
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Number(isize);
impl Number {
pub fn new(int: impl PrimInt) -> Self {
Self(
int.to_isize()
.unwrap_or_else(|| panic!("num cannot be converted to isize")),
)
}
pub fn from_slice_to_vec(slice: &[impl PrimInt]) -> Vec<Number> {
slice.iter().map(|int| Number::new(*int)).collect()
}
}
impl Deref for Number {
type Target = isize;
fn deref(&self) -> &Self::Target {
&self.0
}
}