2021-12-21 16:09:48 -08:00
|
|
|
use crate::deps::*;
|
2023-01-31 21:56:44 +01:00
|
|
|
use crate::i18n::*;
|
2021-12-21 16:09:48 -08:00
|
|
|
|
2023-01-31 21:56:44 +01:00
|
|
|
use anyhow::{anyhow, Context};
|
2023-01-23 01:10:15 +01:00
|
|
|
use gio::prelude::*;
|
2023-01-31 21:56:44 +01:00
|
|
|
use once_cell::sync::Lazy;
|
2022-07-14 15:25:03 -04:00
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
use std::fmt::{Debug, Write};
|
2023-01-31 21:56:44 +01:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-07-14 15:25:03 -04:00
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
pub fn get_file_display_name(file: &gio::File) -> Option<String> {
|
2021-12-27 15:03:31 -08:00
|
|
|
let info = query_attributes(file, vec![&gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME]).ok()?;
|
2021-09-05 05:51:34 -07:00
|
|
|
|
|
|
|
|
Some(info.display_name().to_string())
|
|
|
|
|
}
|
2021-12-27 15:03:31 -08:00
|
|
|
|
2022-07-14 15:25:03 -04:00
|
|
|
pub fn query_attributes(file: &gio::File, attributes: Vec<&str>) -> anyhow::Result<gio::FileInfo> {
|
2021-12-27 15:03:31 -08:00
|
|
|
let mut attributes = attributes;
|
|
|
|
|
let mut attr_str = String::from(attributes.remove(0));
|
|
|
|
|
for attr in attributes {
|
2022-07-14 15:25:03 -04:00
|
|
|
write!(attr_str, ",{}", attr)?;
|
2021-12-27 15:03:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.query_info(
|
|
|
|
|
&attr_str,
|
|
|
|
|
gio::FileQueryInfoFlags::empty(),
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
)
|
2022-07-14 15:25:03 -04:00
|
|
|
.context("Failed to query string")
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-26 11:31:11 -04:00
|
|
|
pub async fn query_attributes_future(
|
|
|
|
|
file: &gio::File,
|
|
|
|
|
attributes: Vec<&str>,
|
|
|
|
|
) -> anyhow::Result<gio::FileInfo> {
|
2022-11-04 19:27:43 +01:00
|
|
|
let mut attr_str = String::from(*attributes.first().context("No attributes")?);
|
2022-07-26 11:31:11 -04:00
|
|
|
|
|
|
|
|
for attr in &attributes[1..] {
|
|
|
|
|
write!(attr_str, ",{}", attr)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.query_info_future(
|
|
|
|
|
&attr_str,
|
|
|
|
|
gio::FileQueryInfoFlags::empty(),
|
|
|
|
|
glib::Priority::default(),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.context("Failed to query attributes")
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
pub fn compare_by_name(file_a: &Path, file_b: &Path) -> std::cmp::Ordering {
|
|
|
|
|
let name_a = file_a.display().to_string();
|
|
|
|
|
let name_b = file_b.display().to_string();
|
2022-07-14 15:25:03 -04:00
|
|
|
|
2023-01-22 14:21:38 -05:00
|
|
|
let key_a = glib::FilenameCollationKey::from(name_a);
|
|
|
|
|
let key_b = glib::FilenameCollationKey::from(name_b);
|
|
|
|
|
|
|
|
|
|
key_a.cmp(&key_b)
|
2021-12-27 15:03:31 -08:00
|
|
|
}
|
2022-11-16 20:14:39 +01:00
|
|
|
|
2023-01-31 21:56:44 +01:00
|
|
|
static FILE_ATTRIBUTE_TRASH: Lazy<String> = Lazy::new(|| {
|
|
|
|
|
[
|
|
|
|
|
*gio::FILE_ATTRIBUTE_STANDARD_NAME,
|
|
|
|
|
*gio::FILE_ATTRIBUTE_TRASH_ORIG_PATH,
|
|
|
|
|
]
|
|
|
|
|
.join(",")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/// Recover file from trash
|
|
|
|
|
///
|
|
|
|
|
/// This is based on Nautilus' implementation
|
|
|
|
|
/// <https://gitlab.gnome.org/GNOME/glib/-/issues/845>
|
|
|
|
|
pub async fn untrash(path: &Path) -> anyhow::Result<()> {
|
|
|
|
|
let trash = gio::File::for_uri("trash:///");
|
|
|
|
|
|
|
|
|
|
let enumerator = trash
|
|
|
|
|
.enumerate_children_future(
|
|
|
|
|
&FILE_ATTRIBUTE_TRASH,
|
|
|
|
|
gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS,
|
|
|
|
|
glib::Priority::default(),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let mut error = Err(anyhow!("Image not found in trash"));
|
|
|
|
|
|
|
|
|
|
while let Ok(info) = enumerator
|
|
|
|
|
.next_files_future(1, glib::Priority::default())
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
let Some(file_info) = info.first()
|
|
|
|
|
else { break; };
|
|
|
|
|
|
|
|
|
|
let Some(original_path) = file_info.attribute_byte_string(&gio::FILE_ATTRIBUTE_TRASH_ORIG_PATH).as_ref().map(PathBuf::from)
|
|
|
|
|
else { break; };
|
|
|
|
|
|
|
|
|
|
if original_path == path {
|
|
|
|
|
let trash_file = trash.child(file_info.name());
|
|
|
|
|
let original_file = gio::File::for_path(original_path);
|
|
|
|
|
error = trash_file
|
|
|
|
|
.move_future(
|
|
|
|
|
&original_file,
|
|
|
|
|
gio::FileCopyFlags::NOFOLLOW_SYMLINKS,
|
|
|
|
|
glib::Priority::default(),
|
|
|
|
|
)
|
|
|
|
|
.0
|
|
|
|
|
.await
|
|
|
|
|
.context(i18n("Failed to restore image from trash"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
pub async fn spawn<T: Debug + Send + 'static>(
|
|
|
|
|
name: &str,
|
|
|
|
|
f: impl Fn() -> T + Send + 'static,
|
|
|
|
|
) -> async_std::io::Result<T> {
|
|
|
|
|
log::trace!("Starting thread '{name}'");
|
|
|
|
|
|
|
|
|
|
Ok(async_std::task::Builder::new()
|
|
|
|
|
.name(name.to_string())
|
|
|
|
|
.spawn(async_global_executor::spawn_blocking(f))?
|
|
|
|
|
.await)
|
|
|
|
|
}
|
2023-02-06 19:14:01 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum Position {
|
|
|
|
|
First,
|
|
|
|
|
Last,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum Direction {
|
|
|
|
|
Back,
|
|
|
|
|
Forward,
|
|
|
|
|
}
|