use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zbus::zvariant::{DeserializeDict, SerializeDict, Type};
use super::{HandleToken, Request};
use crate::{proxy::Proxy, Error, FilePath, WindowIdentifier};
#[derive(Clone, Serialize, Deserialize, Type, Debug, PartialEq)]
pub struct FileFilter(String, Vec<(FilterType, String)>);
#[derive(Clone, Serialize_repr, Deserialize_repr, Debug, Type, PartialEq)]
#[repr(u32)]
enum FilterType {
GlobPattern = 0,
MimeType = 1,
}
impl FilterType {
fn is_mimetype(&self) -> bool {
matches!(self, FilterType::MimeType)
}
fn is_pattern(&self) -> bool {
matches!(self, FilterType::GlobPattern)
}
}
impl FileFilter {
pub fn new(label: &str) -> Self {
Self(label.to_owned(), vec![])
}
#[must_use]
pub fn mimetype(mut self, mimetype: &str) -> Self {
self.1.push((FilterType::MimeType, mimetype.to_owned()));
self
}
#[must_use]
pub fn glob(mut self, pattern: &str) -> Self {
self.1.push((FilterType::GlobPattern, pattern.to_owned()));
self
}
}
impl FileFilter {
pub fn label(&self) -> &str {
&self.0
}
pub fn mimetype_filters(&self) -> Vec<&str> {
self.1
.iter()
.filter_map(|(type_, string)| type_.is_mimetype().then_some(string.as_str()))
.collect()
}
pub fn pattern_filters(&self) -> Vec<&str> {
self.1
.iter()
.filter_map(|(type_, string)| type_.is_pattern().then_some(string.as_str()))
.collect()
}
}
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
pub struct Choice(String, String, Vec<(String, String)>, String);
impl Choice {
pub fn boolean(id: &str, label: &str, state: bool) -> Self {
Self::new(id, label, &state.to_string())
}
pub fn new(id: &str, label: &str, initial_selection: &str) -> Self {
Self(
id.to_owned(),
label.to_owned(),
vec![],
initial_selection.to_owned(),
)
}
#[must_use]
pub fn insert(mut self, key: &str, value: &str) -> Self {
self.2.push((key.to_owned(), value.to_owned()));
self
}
pub fn id(&self) -> &str {
&self.0
}
pub fn label(&self) -> &str {
&self.1
}
pub fn pairs(&self) -> Vec<(&str, &str)> {
self.2
.iter()
.map(|(x, y)| (x.as_str(), y.as_str()))
.collect::<Vec<_>>()
}
pub fn initial_selection(&self) -> &str {
&self.3
}
}
#[derive(SerializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct OpenFileOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
multiple: Option<bool>,
directory: Option<bool>,
filters: Vec<FileFilter>,
current_filter: Option<FileFilter>,
choices: Option<Vec<Choice>>,
current_folder: Option<FilePath>,
}
#[derive(SerializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct SaveFileOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
current_name: Option<String>,
current_folder: Option<FilePath>,
current_file: Option<FilePath>,
filters: Vec<FileFilter>,
current_filter: Option<FileFilter>,
choices: Option<Vec<Choice>>,
}
#[derive(SerializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct SaveFilesOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
choices: Option<Vec<Choice>>,
current_folder: Option<FilePath>,
files: Option<Vec<FilePath>>,
}
#[derive(Debug, Type, DeserializeDict)]
#[zvariant(signature = "dict")]
pub struct SelectedFiles {
uris: Vec<url::Url>,
choices: Option<Vec<(String, String)>>,
}
impl SelectedFiles {
pub fn open_file() -> OpenFileRequest {
OpenFileRequest::default()
}
pub fn save_file() -> SaveFileRequest {
SaveFileRequest::default()
}
pub fn save_files() -> SaveFilesRequest {
SaveFilesRequest::default()
}
pub fn uris(&self) -> &[url::Url] {
self.uris.as_slice()
}
pub fn choices(&self) -> &[(String, String)] {
self.choices.as_deref().unwrap_or_default()
}
}
#[doc(alias = "org.freedesktop.portal.FileChooser")]
struct FileChooserProxy<'a>(Proxy<'a>);
impl<'a> FileChooserProxy<'a> {
pub async fn new() -> Result<FileChooserProxy<'a>, Error> {
let proxy = Proxy::new_desktop("org.freedesktop.portal.FileChooser").await?;
Ok(Self(proxy))
}
pub async fn open_file(
&self,
identifier: &WindowIdentifier,
title: &str,
options: OpenFileOptions,
) -> Result<Request<SelectedFiles>, Error> {
self.0
.request(
&options.handle_token,
"OpenFile",
&(&identifier, title, &options),
)
.await
}
pub async fn save_file(
&self,
identifier: &WindowIdentifier,
title: &str,
options: SaveFileOptions,
) -> Result<Request<SelectedFiles>, Error> {
self.0
.request(
&options.handle_token,
"SaveFile",
&(&identifier, title, &options),
)
.await
}
pub async fn save_files(
&self,
identifier: &WindowIdentifier,
title: &str,
options: SaveFilesOptions,
) -> Result<Request<SelectedFiles>, Error> {
self.0
.request(
&options.handle_token,
"SaveFiles",
&(&identifier, title, &options),
)
.await
}
}
impl<'a> std::ops::Deref for FileChooserProxy<'a> {
type Target = zbus::Proxy<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_open_file")]
pub struct OpenFileRequest {
identifier: WindowIdentifier,
title: String,
options: OpenFileOptions,
}
impl OpenFileRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into().unwrap_or_default();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn multiple(mut self, multiple: impl Into<Option<bool>>) -> Self {
self.options.multiple = multiple.into();
self
}
#[must_use]
pub fn directory(mut self, directory: impl Into<Option<bool>>) -> Self {
self.options.directory = directory.into();
self
}
#[must_use]
pub fn filter(mut self, filter: FileFilter) -> Self {
self.options.filters.push(filter);
self
}
#[must_use]
pub fn filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.options.filters = filters.into_iter().collect();
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.options.current_filter = current_filter.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options
.choices
.get_or_insert_with(Vec::new)
.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = Some(choices.into_iter().collect());
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = FileChooserProxy::new().await?;
proxy
.open_file(&self.identifier, &self.title, self.options)
.await
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_save_files")]
pub struct SaveFilesRequest {
identifier: WindowIdentifier,
title: String,
options: SaveFilesOptions,
}
impl SaveFilesRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into().unwrap_or_default();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options
.choices
.get_or_insert_with(Vec::new)
.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = Some(choices.into_iter().collect());
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
pub fn files<P: IntoIterator<Item = impl AsRef<Path>>>(
mut self,
files: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.files = files
.into()
.map(|files| files.into_iter().map(|s| FilePath::new(s)).collect())
.transpose()?;
Ok(self)
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = FileChooserProxy::new().await?;
proxy
.save_files(&self.identifier, &self.title, self.options)
.await
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_save_file")]
pub struct SaveFileRequest {
identifier: WindowIdentifier,
title: String,
options: SaveFileOptions,
}
impl SaveFileRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into().unwrap_or_default();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn current_name<'a>(mut self, current_name: impl Into<Option<&'a str>>) -> Self {
self.options.current_name = current_name.into().map(ToOwned::to_owned);
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
pub fn current_file<P: AsRef<Path>>(
mut self,
current_file: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_file = current_file.into().map(|c| FilePath::new(c)).transpose()?;
Ok(self)
}
#[must_use]
pub fn filter(mut self, filter: FileFilter) -> Self {
self.options.filters.push(filter);
self
}
#[must_use]
pub fn filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.options.filters = filters.into_iter().collect();
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.options.current_filter = current_filter.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options
.choices
.get_or_insert_with(Vec::new)
.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = Some(choices.into_iter().collect());
self
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = FileChooserProxy::new().await?;
proxy
.save_file(&self.identifier, &self.title, self.options)
.await
}
}