use std::{collections::HashMap, fmt, os::fd::BorrowedFd, path::Path, str::FromStr};
use enumflags2::{bitflags, BitFlags};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zbus::zvariant::{Fd, OwnedValue, Type};
pub use crate::app_id::DocumentID;
use crate::{proxy::Proxy, AppID, Error, FilePath};
#[bitflags]
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
#[repr(u32)]
pub enum DocumentFlags {
ReuseExisting,
Persistent,
AsNeededByApp,
ExportDirectory,
}
pub type Permissions = HashMap<AppID, Vec<Permission>>;
#[cfg_attr(feature = "glib", derive(glib::Enum))]
#[cfg_attr(feature = "glib", enum_type(name = "AshpdPermission"))]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Eq, Type)]
#[zvariant(signature = "s")]
#[serde(rename_all = "kebab-case")]
pub enum Permission {
Read,
Write,
GrantPermissions,
Delete,
}
impl fmt::Display for Permission {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read => write!(f, "Read"),
Self::Write => write!(f, "Write"),
Self::GrantPermissions => write!(f, "Grant Permissions"),
Self::Delete => write!(f, "Delete"),
}
}
}
impl AsRef<str> for Permission {
fn as_ref(&self) -> &str {
match self {
Self::Read => "Read",
Self::Write => "Write",
Self::GrantPermissions => "Grant Permissions",
Self::Delete => "Delete",
}
}
}
impl From<Permission> for &'static str {
fn from(p: Permission) -> Self {
match p {
Permission::Read => "Read",
Permission::Write => "Write",
Permission::GrantPermissions => "Grant Permissions",
Permission::Delete => "Delete",
}
}
}
impl FromStr for Permission {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Read" | "read" => Ok(Permission::Read),
"Write" | "write" => Ok(Permission::Write),
"GrantPermissions" | "grant-permissions" => Ok(Permission::GrantPermissions),
"Delete" | "delete" => Ok(Permission::Delete),
_ => Err(Error::ParseError("Failed to parse priority, invalid value")),
}
}
}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Documents")]
pub struct Documents<'a>(Proxy<'a>);
impl<'a> Documents<'a> {
pub async fn new() -> Result<Documents<'a>, Error> {
let proxy = Proxy::new_documents("org.freedesktop.portal.Documents").await?;
Ok(Self(proxy))
}
#[doc(alias = "Add")]
pub async fn add(
&self,
o_path_fd: &BorrowedFd<'_>,
reuse_existing: bool,
persistent: bool,
) -> Result<DocumentID, Error> {
self.0
.call("Add", &(Fd::from(o_path_fd), reuse_existing, persistent))
.await
}
#[doc(alias = "AddFull")]
pub async fn add_full(
&self,
o_path_fds: &[&BorrowedFd<'_>],
flags: BitFlags<DocumentFlags>,
app_id: Option<AppID>,
permissions: &[Permission],
) -> Result<(Vec<DocumentID>, HashMap<String, OwnedValue>), Error> {
let o_path: Vec<Fd> = o_path_fds.iter().map(Fd::from).collect();
let app_id = app_id.as_deref().unwrap_or("");
self.0
.call_versioned("AddFull", &(o_path, flags, app_id, permissions), 2)
.await
}
#[doc(alias = "AddNamed")]
pub async fn add_named(
&self,
o_path_parent_fd: &BorrowedFd<'_>,
filename: impl AsRef<Path>,
reuse_existing: bool,
persistent: bool,
) -> Result<DocumentID, Error> {
let filename = FilePath::new(filename)?;
self.0
.call(
"AddNamed",
&(
Fd::from(o_path_parent_fd),
filename,
reuse_existing,
persistent,
),
)
.await
}
#[doc(alias = "AddNamedFull")]
pub async fn add_named_full(
&self,
o_path_fd: &BorrowedFd<'_>,
filename: impl AsRef<Path>,
flags: BitFlags<DocumentFlags>,
app_id: Option<AppID>,
permissions: &[Permission],
) -> Result<(DocumentID, HashMap<String, OwnedValue>), Error> {
let app_id = app_id.as_deref().unwrap_or("");
let filename = FilePath::new(filename)?;
self.0
.call_versioned(
"AddNamedFull",
&(Fd::from(o_path_fd), filename, flags, app_id, permissions),
3,
)
.await
}
#[doc(alias = "Delete")]
pub async fn delete(&self, doc_id: impl Into<DocumentID>) -> Result<(), Error> {
self.0.call("Delete", &(doc_id.into())).await
}
#[doc(alias = "GetMountPoint")]
#[doc(alias = "get_mount_point")]
pub async fn mount_point(&self) -> Result<FilePath, Error> {
self.0.call("GetMountPoint", &()).await
}
#[doc(alias = "GrantPermissions")]
pub async fn grant_permissions(
&self,
doc_id: impl Into<DocumentID>,
app_id: AppID,
permissions: &[Permission],
) -> Result<(), Error> {
self.0
.call("GrantPermissions", &(doc_id.into(), app_id, permissions))
.await
}
#[doc(alias = "Info")]
pub async fn info(
&self,
doc_id: impl Into<DocumentID>,
) -> Result<(FilePath, Permissions), Error> {
self.0.call("Info", &(doc_id.into())).await
}
#[doc(alias = "List")]
pub async fn list(
&self,
app_id: Option<AppID>,
) -> Result<HashMap<DocumentID, FilePath>, Error> {
let app_id = app_id.as_deref().unwrap_or("");
let response: HashMap<String, FilePath> = self.0.call("List", &(app_id)).await?;
let mut new_response: HashMap<DocumentID, FilePath> = HashMap::new();
for (key, file_name) in response {
new_response.insert(DocumentID::from(key), file_name);
}
Ok(new_response)
}
#[doc(alias = "Lookup")]
pub async fn lookup(&self, filename: impl AsRef<Path>) -> Result<Option<DocumentID>, Error> {
let filename = FilePath::new(filename)?;
let doc_id: String = self.0.call("Lookup", &(filename)).await?;
if doc_id.is_empty() {
Ok(None)
} else {
Ok(Some(doc_id.into()))
}
}
#[doc(alias = "RevokePermissions")]
pub async fn revoke_permissions(
&self,
doc_id: impl Into<DocumentID>,
app_id: AppID,
permissions: &[Permission],
) -> Result<(), Error> {
self.0
.call("RevokePermissions", &(doc_id.into(), app_id, permissions))
.await
}
}
impl<'a> std::ops::Deref for Documents<'a> {
type Target = zbus::Proxy<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
mod file_transfer;
pub use file_transfer::FileTransfer;
#[cfg(test)]
mod tests {
use crate::documents::Permission;
#[test]
fn serialize_deserialize() {
let permission = Permission::GrantPermissions;
let string = serde_json::to_string(&permission).unwrap();
assert_eq!(string, "\"grant-permissions\"");
let decoded = serde_json::from_str(&string).unwrap();
assert_eq!(permission, decoded);
}
}