use std::{collections::HashMap, sync::Arc, time::Duration};
#[cfg(feature = "async-std")]
use async_std::sync::RwLock;
#[cfg(feature = "tokio")]
use tokio::sync::RwLock;
use super::{api, Algorithm, Error, Item};
use crate::Key;
#[derive(Debug)]
pub struct Collection<'a> {
inner: Arc<api::Collection<'a>>,
service: Arc<api::Service<'a>>,
session: Arc<api::Session<'a>>,
algorithm: Algorithm,
available: RwLock<bool>,
aes_key: Option<Arc<Key>>,
}
impl<'a> Collection<'a> {
pub(crate) fn new(
service: Arc<api::Service<'a>>,
session: Arc<api::Session<'a>>,
algorithm: Algorithm,
collection: api::Collection<'a>,
aes_key: Option<Arc<Key>>,
) -> Collection<'a> {
Self {
inner: Arc::new(collection),
session,
service,
algorithm,
available: RwLock::new(true),
aes_key,
}
}
pub(crate) async fn is_available(&self) -> bool {
*self.available.read().await
}
pub async fn items(&self) -> Result<Vec<Item<'a>>, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
Ok(self
.inner
.items()
.await?
.into_iter()
.map(|item| {
Item::new(
Arc::clone(&self.service),
Arc::clone(&self.session),
self.algorithm,
item,
self.aes_key.as_ref().map(Arc::clone),
)
})
.collect::<Vec<_>>())
}
}
pub async fn label(&self) -> Result<String, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.label().await
}
}
pub async fn set_label(&self, label: &str) -> Result<(), Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.set_label(label).await
}
}
#[doc(alias = "Locked")]
pub async fn is_locked(&self) -> Result<bool, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.is_locked().await
}
}
pub async fn created(&self) -> Result<Duration, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.created().await
}
}
pub async fn modified(&self) -> Result<Duration, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.modified().await
}
}
pub async fn search_items(
&self,
attributes: HashMap<&str, &str>,
) -> Result<Vec<Item<'a>>, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
let items = self.inner.search_items(attributes).await?;
Ok(items
.into_iter()
.map(|item| {
Item::new(
Arc::clone(&self.service),
Arc::clone(&self.session),
self.algorithm,
item,
self.aes_key.as_ref().map(Arc::clone),
)
})
.collect::<Vec<_>>())
}
}
pub async fn create_item(
&self,
label: &str,
attributes: HashMap<&str, &str>,
secret: impl AsRef<[u8]>,
replace: bool,
content_type: &str,
) -> Result<Item<'a>, Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
let secret = match self.algorithm {
Algorithm::Plain => {
api::Secret::new(Arc::clone(&self.session), secret, content_type)
}
Algorithm::Encrypted => api::Secret::new_encrypted(
Arc::clone(&self.session),
secret,
content_type,
self.aes_key.as_ref().unwrap(),
),
};
let item = self
.inner
.create_item(label, attributes, &secret, replace)
.await?;
Ok(Item::new(
Arc::clone(&self.service),
Arc::clone(&self.session),
self.algorithm,
item,
self.aes_key.as_ref().map(Arc::clone),
))
}
}
pub async fn unlock(&self) -> Result<(), Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.service.unlock(&[self.inner.inner().path()]).await?;
Ok(())
}
}
pub async fn lock(&self) -> Result<(), Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.service.lock(&[self.inner.inner().path()]).await?;
Ok(())
}
}
pub async fn delete(&self) -> Result<(), Error> {
if !self.is_available().await {
Err(Error::Deleted)
} else {
self.inner.delete().await?;
*self.available.write().await = false;
Ok(())
}
}
}
#[cfg(test)]
#[cfg(feature = "async-std")]
mod tests {
#[cfg(feature = "local_tests")]
use super::*;
#[cfg(feature = "local_tests")]
use crate::dbus::Service;
#[cfg(feature = "local_tests")]
async fn create_item(service: Service<'_>, encrypted: bool) {
let mut attributes = HashMap::new();
let value = if encrypted {
"encrypted-type-test"
} else {
"plain-type-test"
};
attributes.insert("type", value);
let secret = "a password".as_bytes();
let collection = service.default_collection().await.unwrap();
let n_items = collection.items().await.unwrap().len();
let n_search_items = collection
.search_items(attributes.clone())
.await
.unwrap()
.len();
let item = collection
.create_item("A secret", attributes.clone(), secret, true, "text/plain")
.await
.unwrap();
assert_eq!(*item.secret().await.unwrap(), secret);
assert_eq!(item.attributes().await.unwrap()["type"], value);
assert_eq!(collection.items().await.unwrap().len(), n_items + 1);
assert_eq!(
collection
.search_items(attributes.clone())
.await
.unwrap()
.len(),
n_search_items + 1
);
item.delete().await.unwrap();
assert_eq!(collection.items().await.unwrap().len(), n_items);
assert_eq!(
collection
.search_items(attributes.clone())
.await
.unwrap()
.len(),
n_search_items
);
}
#[async_std::test]
#[cfg(feature = "local_tests")]
async fn create_plain_item() {
let service = Service::new(Algorithm::Plain).await.unwrap();
create_item(service, false).await;
}
#[async_std::test]
#[cfg(feature = "local_tests")]
async fn create_encrypted_item() {
let service = Service::new(Algorithm::Encrypted).await.unwrap();
create_item(service, true).await;
}
}