pub(crate) const DESTINATION: &str = "org.freedesktop.portal.Documents";
pub(crate) const PATH: &str = "/org/freedesktop/portal/documents";
use std::{
collections::HashMap, ffi::CString, os::unix::ffi::OsStrExt, os::unix::prelude::AsRawFd,
};
use std::{
fmt::Debug,
path::{Path, PathBuf},
str::FromStr,
};
use enumflags2::BitFlags;
use serde::{de::Deserializer, Deserialize, Serialize, Serializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
use strum_macros::{AsRefStr, EnumString, IntoStaticStr, ToString};
use zvariant::{Fd, Signature};
use zvariant_derive::Type;
use crate::{
helpers::{call_method, path_from_null_terminated},
Error,
};
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Copy, Clone, BitFlags, Debug, Type)]
#[repr(u32)]
pub enum Flags {
ReuseExisting = 1,
Persistent = 2,
AsNeededByApp = 4,
ExportDirectory = 8,
}
pub type Permissions = HashMap<String, Vec<Permission>>;
#[derive(Debug, Clone, AsRefStr, EnumString, IntoStaticStr, ToString, PartialEq, Eq)]
#[strum(serialize_all = "lowercase")]
pub enum Permission {
Read,
Write,
#[strum(serialize = "grant-permissions")]
GrantPermissions,
Delete,
}
impl zvariant::Type for Permission {
fn signature() -> Signature<'static> {
String::signature()
}
}
impl Serialize for Permission {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de> Deserialize<'de> for Permission {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Permission::from_str(&String::deserialize(deserializer)?).expect("invalid permission"))
}
}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Documents")]
pub struct DocumentsProxy<'a>(zbus::azync::Proxy<'a>);
impl<'a> DocumentsProxy<'a> {
pub async fn new(connection: &zbus::azync::Connection) -> Result<DocumentsProxy<'a>, Error> {
let proxy = zbus::azync::ProxyBuilder::new_bare(connection)
.interface("org.freedesktop.portal.Documents")?
.path(PATH)?
.destination(DESTINATION)?
.build()
.await?;
Ok(Self(proxy))
}
pub fn inner(&self) -> &zbus::azync::Proxy<'_> {
&self.0
}
#[doc(alias = "Add")]
pub async fn add<F>(
&self,
o_path_fd: &F,
reuse_existing: bool,
persistent: bool,
) -> Result<String, Error>
where
F: AsRawFd + Debug,
{
call_method(
&self.0,
"Add",
&(Fd::from(o_path_fd.as_raw_fd()), reuse_existing, persistent),
)
.await
}
#[doc(alias = "AddFull")]
pub async fn add_full<F: AsRawFd>(
&self,
o_path_fds: &[&F],
flags: BitFlags<Flags>,
app_id: &str,
permissions: &[Permission],
) -> Result<(Vec<String>, HashMap<String, zvariant::OwnedValue>), Error> {
let o_path: Vec<Fd> = o_path_fds.iter().map(|f| Fd::from(f.as_raw_fd())).collect();
call_method(&self.0, "AddFull", &(o_path, flags, app_id, permissions)).await
}
#[doc(alias = "AddNamed")]
pub async fn add_named<F, P>(
&self,
o_path_parent_fd: &F,
filename: P,
reuse_existing: bool,
persistent: bool,
) -> Result<String, Error>
where
F: AsRawFd + Debug,
P: AsRef<Path> + Serialize + zvariant::Type + Debug,
{
let cstr = CString::new(filename.as_ref().as_os_str().as_bytes())
.expect("`filename` should not be null terminated");
call_method(
&self.0,
"AddNamed",
&(
Fd::from(o_path_parent_fd.as_raw_fd()),
cstr.as_bytes_with_nul(),
reuse_existing,
persistent,
),
)
.await
}
#[doc(alias = "AddNamedFull")]
pub async fn add_named_full<F, P>(
&self,
o_path_fd: &F,
filename: P,
flags: BitFlags<Flags>,
app_id: &str,
permissions: &[Permission],
) -> Result<(String, HashMap<String, zvariant::OwnedValue>), Error>
where
F: AsRawFd + Debug,
P: AsRef<Path> + Serialize + zvariant::Type + Debug,
{
let cstr = CString::new(filename.as_ref().as_os_str().as_bytes())
.expect("`filename` should not be null terminated");
call_method(
&self.0,
"AddNamedFull",
&(
Fd::from(o_path_fd.as_raw_fd()),
cstr.as_bytes_with_nul(),
flags,
app_id,
permissions,
),
)
.await
}
#[doc(alias = "Delete")]
pub async fn delete(&self, doc_id: &str) -> Result<(), Error> {
call_method(&self.0, "Delete", &(doc_id)).await
}
#[doc(alias = "GetMountPoint")]
#[doc(alias = "get_mount_point")]
pub async fn mount_point(&self) -> Result<PathBuf, Error> {
let bytes: Vec<u8> = call_method(&self.0, "GetMountPoint", &()).await?;
Ok(path_from_null_terminated(bytes))
}
#[doc(alias = "GrantPermissions")]
pub async fn grant_permissions(
&self,
doc_id: &str,
app_id: &str,
permissions: &[Permission],
) -> Result<(), Error> {
call_method(&self.0, "GrantPermissions", &(doc_id, app_id, permissions)).await
}
#[doc(alias = "Info")]
pub async fn info(&self, doc_id: &str) -> Result<(PathBuf, Permissions), Error> {
let (bytes, permissions): (Vec<u8>, Permissions) =
call_method(&self.0, "Info", &(doc_id)).await?;
Ok((path_from_null_terminated(bytes), permissions))
}
#[doc(alias = "List")]
pub async fn list(&self, app_id: &str) -> Result<HashMap<String, PathBuf>, Error> {
let response: HashMap<String, Vec<u8>> = call_method(&self.0, "List", &(app_id)).await?;
let mut new_response: HashMap<String, PathBuf> = HashMap::new();
for (key, bytes) in response {
new_response.insert(key, path_from_null_terminated(bytes));
}
Ok(new_response)
}
#[doc(alias = "Lookup")]
pub async fn lookup<P: AsRef<Path> + Serialize + zvariant::Type + Debug>(
&self,
filename: P,
) -> Result<Option<String>, Error> {
let cstr = CString::new(filename.as_ref().as_os_str().as_bytes())
.expect("`filename` should not be null terminated");
let doc_id: String = call_method(&self.0, "Lookup", &(cstr.as_bytes_with_nul())).await?;
if doc_id.is_empty() {
Ok(None)
} else {
Ok(Some(doc_id))
}
}
#[doc(alias = "RevokePermissions")]
pub async fn revoke_permissions(
&self,
doc_id: &str,
app_id: &str,
permissions: &[Permission],
) -> Result<(), Error> {
call_method(&self.0, "RevokePermissions", &(doc_id, app_id, permissions)).await
}
}
mod file_transfer;
pub use file_transfer::FileTransferProxy;