use std::sync::Arc;
use ashpd::WindowIdentifier;
use futures_util::{Stream, StreamExt};
use zbus::zvariant::OwnedObjectPath;
use super::{Algorithm, Collection, Error, ServiceError, api};
use crate::Key;
#[derive(Debug)]
pub struct Service {
inner: Arc<api::Service>,
aes_key: Option<Arc<Key>>,
session: Arc<api::Session>,
algorithm: Algorithm,
}
impl Service {
pub const DEFAULT_COLLECTION: &'static str = "default";
pub const SESSION_COLLECTION: &'static str = "session";
pub async fn new() -> Result<Self, Error> {
let service = match Self::encrypted().await {
Ok(service) => Ok(service),
Err(Error::ZBus(zbus::Error::MethodError(..))) => Self::plain().await,
Err(Error::Service(ServiceError::ZBus(zbus::Error::MethodError(..)))) => {
Self::plain().await
}
Err(e) => Err(e),
}?;
Ok(service)
}
pub async fn plain() -> Result<Self, Error> {
Self::with_algorithm(Algorithm::Plain).await
}
pub async fn encrypted() -> Result<Self, Error> {
Self::with_algorithm(Algorithm::Encrypted).await
}
async fn with_algorithm(algorithm: Algorithm) -> Result<Self, Error> {
let cnx = zbus::connection::Builder::session()?
.method_timeout(std::time::Duration::from_secs(30))
.build()
.await?;
let service = Arc::new(api::Service::new(&cnx).await?);
let (aes_key, session) = match algorithm {
Algorithm::Plain => {
#[cfg(feature = "tracing")]
tracing::debug!("Starting an unencrypted Secret Service session");
let (_service_key, session) = service.open_session(None).await?;
(None, session)
}
Algorithm::Encrypted => {
#[cfg(feature = "tracing")]
tracing::debug!("Starting an encrypted Secret Service session");
let private_key = Key::generate_private_key()?;
let public_key = Key::generate_public_key(&private_key)?;
let (service_key, session) = service.open_session(Some(public_key)).await?;
let aes_key = service_key
.map(|service_key| Key::generate_aes_key(&private_key, &service_key))
.transpose()?
.map(Arc::new);
(aes_key, session)
}
};
Ok(Self {
aes_key,
inner: service,
session: Arc::new(session),
algorithm,
})
}
pub async fn default_collection(&self) -> Result<Collection, Error> {
self.with_alias_or_create(Self::DEFAULT_COLLECTION, "Default", None)
.await
}
pub async fn session_collection(&self) -> Result<Collection, Error> {
self.with_alias_or_create(Self::SESSION_COLLECTION, "Session", None)
.await
}
pub async fn with_alias_or_create(
&self,
alias: &str,
label: &str,
window_id: Option<WindowIdentifier>,
) -> Result<Collection, Error> {
match self.with_alias(alias).await {
Ok(Some(collection)) => Ok(collection),
Ok(None) => self.create_collection(label, Some(alias), window_id).await,
Err(err) => Err(err),
}
}
pub async fn with_alias(&self, alias: &str) -> Result<Option<Collection>, Error> {
Ok(self
.inner
.read_alias(alias)
.await?
.map(|collection| self.new_collection(collection)))
}
pub async fn collections(&self) -> Result<Vec<Collection>, Error> {
Ok(self
.inner
.collections()
.await?
.into_iter()
.map(|collection| self.new_collection(collection))
.collect::<Vec<_>>())
}
pub async fn create_collection(
&self,
label: &str,
alias: Option<&str>,
window_id: Option<WindowIdentifier>,
) -> Result<Collection, Error> {
self.inner
.create_collection(label, alias, window_id)
.await
.map(|collection| self.new_collection(collection))
}
pub async fn with_label(&self, label: &str) -> Result<Option<Collection>, Error> {
let collections = self.collections().await?;
for collection in collections {
if collection.label().await? == label {
return Ok(Some(collection));
}
}
Ok(None)
}
pub async fn receive_collection_created(
&self,
) -> Result<impl Stream<Item = Collection> + '_, Error> {
Ok(self
.inner
.receive_collection_created()
.await?
.map(|collection| self.new_collection(collection)))
}
pub async fn receive_collection_changed(
&self,
) -> Result<impl Stream<Item = Collection> + '_, Error> {
Ok(self
.inner
.receive_collection_changed()
.await?
.map(|collection| self.new_collection(collection)))
}
pub async fn receive_collection_deleted(
&self,
) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
self.inner.receive_collection_deleted().await
}
fn new_collection(&self, collection: api::Collection) -> Collection {
Collection::new(
Arc::clone(&self.inner),
Arc::clone(&self.session),
self.algorithm,
collection,
self.aes_key.clone(), )
}
}
impl Drop for Service {
fn drop(&mut self) {
if Arc::strong_count(&self.session) == 1 {
let session = Arc::clone(&self.session);
#[cfg(feature = "tokio")]
{
tokio::spawn(async move {
let _ = session.close().await;
});
}
#[cfg(feature = "async-std")]
{
blocking::unblock(move || {
futures_lite::future::block_on(async move {
let _ = session.close().await;
})
})
.detach();
}
}
}
}