use crate::{
api::ApiProvider,
config::Config,
error::FavCoreError,
res::{Res, ResSet, ResSets},
FavCoreResult,
};
use core::future::Future;
use protobuf::MessageFull;
use reqwest::{header, Client, Response};
#[allow(missing_docs)]
#[trait_variant::make(Operations: Send)]
pub trait LocalOperations<SS, S, R, K>: ApiProvider<K> + Config
where
SS: ResSets<S, R> + MessageFull,
S: ResSet<R>,
R: Res,
K: Send,
{
async fn login(&mut self) -> FavCoreResult<()>;
async fn logout(&mut self) -> FavCoreResult<()>;
async fn fetch_sets(&self, sets: &mut SS) -> FavCoreResult<()>;
async fn fetch_set(&self, set: &mut S) -> FavCoreResult<()>;
async fn fetch(&self, resource: &mut R) -> FavCoreResult<()>;
async fn pull(&self, resource: &mut R) -> FavCoreResult<()>;
fn client(&self) -> &'static Client {
use std::sync::OnceLock;
let headers = self.headers();
static CLIENT: OnceLock<Client> = OnceLock::new();
CLIENT.get_or_init(|| Client::builder().default_headers(headers).build().unwrap())
}
fn request(
&self,
api_kind: K,
params: &[&str], ) -> impl Future<Output = FavCoreResult<Response>> {
async {
let client = self.client();
let api = self.api(api_kind);
let cookie = self.cookie_value(api.cookie_keys());
let resp = client
.request(api.method(), api.url(params))
.header(header::COOKIE, cookie)
.send()
.await?;
Ok(resp)
}
}
}
pub trait LocalOperationsExt<SS, S, R, K>: LocalOperations<SS, S, R, K>
where
SS: ResSets<S, R> + MessageFull,
S: ResSet<R>,
R: Res,
K: Send,
{
fn fetch_all(&self, resources: &mut S) -> impl Future<Output = FavCoreResult<()>> {
async {
for r in resources.iter_mut() {
if let Err(e) = self.fetch(r).await {
match e {
FavCoreError::Cancel => break,
_ => println!("{e}"),
}
}
}
Ok(())
}
}
fn pull_all(&self, resources: &mut S) -> impl Future<Output = FavCoreResult<()>> {
async {
for r in resources.iter_mut() {
if let Err(e) = self.pull(r).await {
match e {
FavCoreError::Cancel => break,
_ => println!("{e}"),
}
}
}
Ok(())
}
}
}
impl<T, SS, S, R, K> LocalOperationsExt<SS, S, R, K> for T
where
T: LocalOperations<SS, S, R, K>,
SS: ResSets<S, R> + MessageFull,
S: ResSet<R>,
R: Res,
K: Send,
{
}
pub trait OperationsExt<SS, S, R, K>: Operations<SS, S, R, K>
where
SS: ResSets<S, R> + MessageFull + 'static,
S: ResSet<R> + 'static,
R: Res + 'static,
K: Send + 'static,
{
fn fetch_all(
&'static self,
resources: &'static mut S,
) -> impl Future<Output = FavCoreResult<()>> {
async {
let mut rs = resources.iter_mut();
loop {
let batch: Vec<_> = rs.by_ref().take(10).collect();
if batch.is_empty() {
break;
}
let jhs: Vec<_> = batch
.into_iter()
.map(|r| tokio::spawn(self.fetch(r)))
.collect();
for jh in jhs {
if let Err(e) = jh.await.unwrap() {
println!("{e}");
}
}
}
Ok(())
}
}
fn pull_all(
&'static self,
resources: &'static mut S,
) -> impl Future<Output = FavCoreResult<()>> {
async {
let mut rs = resources.iter_mut();
loop {
let batch: Vec<_> = rs.by_ref().take(10).collect();
if batch.is_empty() {
break;
}
let jhs: Vec<_> = batch
.into_iter()
.map(|r| tokio::spawn(self.pull(r)))
.collect();
for jh in jhs {
if let Err(e) = jh.await.unwrap() {
match e {
FavCoreError::Cancel => {
println!("{e}");
break;
}
_ => println!("{e}"),
}
}
}
}
Ok(())
}
}
}
impl<T, SS, S, R, K> OperationsExt<SS, S, R, K> for T
where
T: Operations<SS, S, R, K>,
SS: ResSets<S, R> + MessageFull + 'static,
S: ResSet<R> + 'static,
R: Res + 'static,
K: Send + 'static,
{
}