use std::path::PathBuf;
use anyhow::{Result, anyhow};
use io_pimdir::client::{
PimdirError, PimdirStore, blobs::PimdirBlobs, producer::PimdirProducer, reader::PimdirReader,
};
use crate::{
account::context::Account,
config::{AccountConfig, Config, PimdirConfig},
};
const PRODUCER: &str = "himalaya";
pub struct PimdirClient {
pub(crate) store: PimdirReader,
pub(crate) blobs: PimdirBlobs,
pub(crate) root: PathBuf,
pub(crate) account: Option<String>,
}
impl PimdirClient {
pub fn new(config: PimdirConfig) -> Result<Self> {
let root = config.root.clone();
if !root.join("pimdir.db").exists() {
return Err(anyhow!(
"No pimdir store at `{}`; check `pimdir.root`, and run a sync to create one",
root.display(),
));
}
let store = PimdirReader::open(&root)
.map_err(|err| anyhow!("Open pimdir store `{}`: {err}", root.display()))?
.with_pending();
let account = resolve_account(&store, config.account.clone())?;
let blobs = PimdirBlobs::open(&root, store.hash_algo());
Ok(Self {
store,
blobs,
root,
account,
})
}
pub(crate) fn producer(&self) -> Result<PimdirProducer> {
let producer = PimdirProducer::open(&self.root, PRODUCER)
.map_err(|err| anyhow!("Open pimdir producer `{}`: {err}", self.root.display()))?;
Ok(match &self.account {
Some(account) => producer.for_account(account),
None => producer,
})
}
}
impl PimdirClient {
pub fn cancel_queued(&self, id: i64) -> Result<bool> {
PimdirStore::cancel_action(&self.root, id).map_err(|err| match err {
PimdirError::Owned(_) => anyhow!(
"A sync is running on `{}`, so the queue cannot be edited; \
the action may have been applied already",
self.root.display(),
),
err => anyhow!("Cancel queued action {id}: {err}"),
})
}
}
pub fn build_pimdir_client(
config: Config,
name: String,
mut account_config: AccountConfig,
) -> Result<(Account, PimdirClient)> {
let pimdir_config = account_config
.pimdir
.take()
.ok_or_else(|| anyhow!("pimdir config is missing for account `{name}`"))?;
let account = Account::from(config).merge(Account::from(account_config));
Ok((account, PimdirClient::new(pimdir_config)?))
}
fn resolve_account(store: &PimdirReader, configured: Option<String>) -> Result<Option<String>> {
if configured.is_some() {
return Ok(configured);
}
let mut accounts: Vec<Option<String>> = store
.list_collections()
.map_err(|err| anyhow!("List pimdir collections: {err}"))?
.into_iter()
.map(|collection| collection.account)
.collect();
accounts.sort();
accounts.dedup();
match accounts.as_slice() {
[] | [_] => Ok(accounts.into_iter().next().flatten()),
_ => {
let names: Vec<&str> = accounts
.iter()
.map(|account| account.as_deref().unwrap_or("<ungrouped>"))
.collect();
Err(anyhow!(
"The pimdir store holds several accounts ({}); name one with `pimdir.account`",
names.join(", "),
))
}
}
}