oo7 0.1.2

James Bond went on a new mission and this time as a Secret Service provider
Documentation
use std::{collections::HashMap, fmt};

use futures_util::StreamExt;
use zbus::zvariant::{self, ObjectPath, OwnedObjectPath, OwnedValue, Type, Value};

use super::{
    secret::SecretInner, Collection, Item, Prompt, Properties, Secret, Session, Unlockable,
    DESTINATION, PATH,
};
use crate::{
    dbus::{Algorithm, Error},
    Key,
};

#[derive(Type)]
#[zvariant(signature = "o")]
#[doc(alias = "org.freedesktop.secrets")]
pub struct Service<'a>(zbus::Proxy<'a>);

impl<'a> Service<'a> {
    pub async fn new(connection: &zbus::Connection) -> Result<Service<'a>, Error> {
        let inner = zbus::ProxyBuilder::new_bare(connection)
            .path(PATH)?
            .destination(DESTINATION)?
            .interface("org.freedesktop.Secret.Service")?
            .cache_properties(zbus::CacheProperties::No)
            .build()
            .await?;
        Ok(Self(inner))
    }

    pub fn inner(&self) -> &zbus::Proxy {
        &self.0
    }

    #[doc(alias = "CollectionCreated")]
    pub async fn receive_collection_created(&self) -> Result<Collection<'a>, Error> {
        let mut stream = self.inner().receive_signal("CollectionCreated").await?;
        let message = stream.next().await.unwrap();
        let object_path = message.body::<OwnedObjectPath>()?;
        Collection::new(self.inner().connection(), object_path).await
    }

    #[doc(alias = "CollectionDeleted")]
    pub async fn receive_collection_deleted(&self) -> Result<Collection<'a>, Error> {
        let mut stream = self.inner().receive_signal("CollectionDeleted").await?;
        let message = stream.next().await.unwrap();
        let object_path = message.body::<OwnedObjectPath>()?;
        Collection::new(self.inner().connection(), object_path).await
    }

    #[doc(alias = "CollectionChanged")]
    pub async fn receive_collection_changed(&self) -> Result<Collection<'a>, Error> {
        let mut stream = self.inner().receive_signal("CollectionChanged").await?;
        let message = stream.next().await.unwrap();
        let object_path = message.body::<OwnedObjectPath>()?;
        Collection::new(self.inner().connection(), object_path).await
    }

    pub async fn collections(&self) -> Result<Vec<Collection<'a>>, Error> {
        let collections_paths = self
            .inner()
            .get_property::<Vec<ObjectPath>>("Collections")
            .await?;
        Collection::from_paths(self.inner().connection(), collections_paths).await
    }

    #[doc(alias = "OpenSession")]
    pub async fn open_session(
        &self,
        client_public_key: Option<&Key>,
    ) -> Result<(Option<Key>, Session<'a>), Error> {
        let (algorithm, key): (_, Value<'_>) = match client_public_key {
            None => (Algorithm::Plain, zvariant::Str::default().into()),
            Some(key) => (Algorithm::Encrypted, key.into()),
        };
        let (service_key, session_path) = self
            .inner()
            .call_method("OpenSession", &(&algorithm, key))
            .await?
            .body::<(OwnedValue, OwnedObjectPath)>()?;
        let session = Session::new(self.inner().connection(), session_path).await?;

        let key = match algorithm {
            Algorithm::Plain => None,
            Algorithm::Encrypted => Some(Key::from(service_key)),
        };

        Ok((key, session))
    }

    #[doc(alias = "CreateCollection")]
    pub async fn create_collection(
        &self,
        label: &str,
        alias: Option<&str>,
    ) -> Result<Collection<'a>, Error> {
        let properties = Properties::for_collection(label);
        let (collection_path, prompt_path) = self
            .inner()
            .call_method("CreateCollection", &(properties, alias.unwrap_or_default()))
            .await?
            .body::<(OwnedObjectPath, OwnedObjectPath)>()?;

        let collection_path = if let Some(prompt) =
            Prompt::new(self.inner().connection(), prompt_path).await?
        {
            let response = prompt.receive_completed().await?;
            OwnedObjectPath::try_from(response).map_err::<zbus::zvariant::Error, _>(From::from)?
        } else {
            collection_path
        };
        Collection::new(self.inner().connection(), collection_path).await
    }

    #[doc(alias = "SearchItems")]
    pub async fn search_items(
        &self,
        attributes: HashMap<&str, &str>,
    ) -> Result<(Vec<Item<'a>>, Vec<Item<'a>>), Error> {
        let (unlocked_item_paths, locked_item_paths) = self
            .inner()
            .call_method("SearchItems", &(attributes))
            .await?
            .body::<(Vec<OwnedObjectPath>, Vec<OwnedObjectPath>)>()?;
        let cnx = self.inner().connection();

        let unlocked_items = Item::from_paths(cnx, unlocked_item_paths).await?;
        let locked_items = Item::from_paths(cnx, locked_item_paths).await?;

        Ok((unlocked_items, locked_items))
    }

    pub async fn unlock(&self, items: &[impl Unlockable]) -> Result<Vec<OwnedObjectPath>, Error> {
        let (mut unlocked_item_paths, prompt_path) = self
            .inner()
            .call_method("Unlock", &(items))
            .await?
            .body::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
        let cnx = self.inner().connection();

        if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
            let response = prompt.receive_completed().await?;
            let locked_paths = Vec::<OwnedObjectPath>::try_from(response)
                .map_err::<zbus::zvariant::Error, _>(From::from)?;
            unlocked_item_paths.extend(locked_paths);
        };
        Ok(unlocked_item_paths)
    }

    pub async fn lock(&self, items: &[impl Unlockable]) -> Result<Vec<OwnedObjectPath>, Error> {
        let (mut locked_item_paths, prompt_path) = self
            .inner()
            .call_method("Lock", &(items))
            .await?
            .body::<(Vec<OwnedObjectPath>, OwnedObjectPath)>()?;
        let cnx = self.inner().connection();

        if let Some(prompt) = Prompt::new(cnx, prompt_path).await? {
            let response = prompt.receive_completed().await?;
            let locked_paths = Vec::<OwnedObjectPath>::try_from(response)
                .map_err::<zbus::zvariant::Error, _>(From::from)?;
            locked_item_paths.extend(locked_paths);
        };

        Ok(locked_item_paths)
    }

    #[doc(alias = "GetSecrets")]
    pub async fn secrets(
        &self,
        items: &[Item<'_>],
        session: &Session<'_>,
    ) -> Result<HashMap<Item<'_>, Secret<'_>>, Error> {
        let secrets = self
            .inner()
            .call_method("GetSecrets", &(items, session))
            .await?
            .body::<HashMap<OwnedObjectPath, SecretInner>>()?;

        let cnx = self.inner().connection();
        let mut output = HashMap::with_capacity(secrets.capacity());
        for (path, secret_inner) in secrets {
            output.insert(
                Item::new(cnx, path).await?,
                Secret::from_inner(cnx, secret_inner).await?,
            );
        }

        Ok(output)
    }

    #[doc(alias = "ReadAlias")]
    pub async fn read_alias(&self, name: &str) -> Result<Option<Collection<'a>>, Error> {
        let collection_path = self
            .inner()
            .call_method("ReadAlias", &(name))
            .await?
            .body::<zbus::zvariant::OwnedObjectPath>()?;

        if collection_path.as_str() != "/" {
            let collection = Collection::new(self.inner().connection(), collection_path).await?;
            Ok(Some(collection))
        } else {
            Ok(None)
        }
    }

    #[doc(alias = "SetAlias")]
    pub async fn set_alias(&self, name: &str, collection: &Collection<'_>) -> Result<(), Error> {
        self.inner()
            .call_method("SetAlias", &(name, collection))
            .await?;
        Ok(())
    }
}

impl<'a> fmt::Debug for Service<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("Service")
            .field(&self.inner().path().as_str())
            .finish()
    }
}