2025-01-17 01:30:41 +01:00
|
|
|
// Copyright (c) 2022-2025 Sophie Herold
|
2024-09-27 02:20:32 +02:00
|
|
|
// Copyright (c) 2022 Christopher Davis
|
2022-01-16 19:03:13 -08:00
|
|
|
//
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
2023-12-06 23:45:02 +01:00
|
|
|
use std::cell::{OnceCell, RefCell};
|
2025-12-03 23:55:30 +01:00
|
|
|
use std::collections::{HashMap, VecDeque};
|
2024-09-27 02:20:32 +02:00
|
|
|
use std::sync::LazyLock;
|
2025-12-03 23:55:30 +01:00
|
|
|
use std::time::Instant;
|
2022-01-16 19:03:13 -08:00
|
|
|
|
2022-11-04 19:28:48 +01:00
|
|
|
use anyhow::Context;
|
2023-01-23 01:10:15 +01:00
|
|
|
use gio::prelude::*;
|
|
|
|
|
use gio::subclass::prelude::*;
|
2023-12-04 12:52:40 +01:00
|
|
|
use glib::subclass::Signal;
|
2023-04-12 01:11:59 +02:00
|
|
|
use glib::GString;
|
2023-04-12 04:13:30 +02:00
|
|
|
use indexmap::IndexMap;
|
2023-12-06 23:45:02 +01:00
|
|
|
|
|
|
|
|
use crate::deps::*;
|
|
|
|
|
use crate::util;
|
|
|
|
|
use crate::util::gettext::*;
|
2022-01-16 19:03:13 -08:00
|
|
|
|
2024-11-12 19:59:50 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct Entry {
|
2024-11-13 01:07:08 +01:00
|
|
|
/// Determines the sort order
|
2024-11-12 19:59:50 +01:00
|
|
|
sort: VecDeque<glib::FilenameCollationKey>,
|
|
|
|
|
file: gio::File,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Entry {
|
2025-12-03 23:55:30 +01:00
|
|
|
async fn new(
|
|
|
|
|
file: gio::File,
|
|
|
|
|
info: Option<gio::FileInfo>,
|
|
|
|
|
collation_key_cache: &mut HashMap<glib::GString, glib::FilenameCollationKey>,
|
|
|
|
|
) -> Self {
|
2024-11-12 19:59:50 +01:00
|
|
|
let mut sort = VecDeque::new();
|
|
|
|
|
let mut frac_file = file.clone();
|
|
|
|
|
|
2025-12-03 23:55:30 +01:00
|
|
|
// Use already available info for the filename as performance optimization
|
|
|
|
|
let mut available_file_info = info.map(Ok);
|
|
|
|
|
|
2024-11-13 01:07:08 +01:00
|
|
|
// Determine sort order for all path/uri segments one by one
|
2024-11-12 19:59:50 +01:00
|
|
|
loop {
|
2025-12-03 23:55:30 +01:00
|
|
|
let uri = frac_file.uri();
|
2024-11-12 19:59:50 +01:00
|
|
|
|
2025-12-03 23:55:30 +01:00
|
|
|
let key = if let Some(cached_key) = collation_key_cache.get(&uri) {
|
|
|
|
|
cached_key.clone()
|
|
|
|
|
} else {
|
|
|
|
|
let file_info = if let Some(file_info) = available_file_info {
|
|
|
|
|
file_info
|
|
|
|
|
} else {
|
|
|
|
|
frac_file
|
|
|
|
|
.query_info_future(
|
|
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
|
|
|
|
gio::FileQueryInfoFlags::NONE,
|
|
|
|
|
glib::Priority::LOW,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match file_info {
|
|
|
|
|
Ok(parent) => {
|
|
|
|
|
let key = glib::FilenameCollationKey::from(parent.display_name());
|
|
|
|
|
// Using this cache since each file info query can take noticable time (1 ms
|
|
|
|
|
// on local SMB). Only cache keys resulting from queries in this function
|
|
|
|
|
// since these will be path segments that can be (and are likely to) be
|
|
|
|
|
// repeated.
|
|
|
|
|
collation_key_cache.insert(uri, key.clone());
|
|
|
|
|
key
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::error!("Failed to obtain information for sorting '{}': {err}", uri);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-11-12 19:59:50 +01:00
|
|
|
}
|
2025-12-03 23:55:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sort.push_front(key);
|
2024-11-12 19:59:50 +01:00
|
|
|
|
|
|
|
|
if let Some(parent) = frac_file.parent() {
|
|
|
|
|
frac_file = parent;
|
2025-12-03 23:55:30 +01:00
|
|
|
available_file_info = None;
|
2024-11-12 19:59:50 +01:00
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Self { sort, file }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 19:03:13 -08:00
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
pub struct LpFileModel {
|
2023-11-29 21:15:36 +01:00
|
|
|
/// Use and IndexMap such that we can put the elements into an arbitrary
|
|
|
|
|
/// order while still having fast hash access via file URI.
|
2024-11-12 19:59:50 +01:00
|
|
|
pub(super) files: RefCell<IndexMap<GString, Entry>>,
|
2023-04-12 01:11:59 +02:00
|
|
|
pub(super) directory: OnceCell<gio::File>,
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Track file changes.
|
|
|
|
|
pub(super) monitor: OnceCell<gio::FileMonitor>,
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
|
impl ObjectSubclass for LpFileModel {
|
|
|
|
|
const NAME: &'static str = "LpFileModel";
|
|
|
|
|
type Type = super::LpFileModel;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
impl ObjectImpl for LpFileModel {
|
|
|
|
|
fn signals() -> &'static [Signal] {
|
2025-01-17 01:30:41 +01:00
|
|
|
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
|
|
|
|
|
vec![Signal::builder("changed")
|
|
|
|
|
.param_types([glib::Type::VARIANT])
|
|
|
|
|
.build()]
|
|
|
|
|
});
|
2023-01-23 01:10:15 +01:00
|
|
|
SIGNALS.as_ref()
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
2023-01-23 01:10:15 +01:00
|
|
|
pub struct LpFileModel(ObjectSubclass<imp::LpFileModel>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for LpFileModel {
|
|
|
|
|
fn default() -> Self {
|
2023-02-12 02:07:42 +01:00
|
|
|
glib::Object::new()
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpFileModel {
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Create with a single element
|
2024-11-13 01:07:08 +01:00
|
|
|
pub async fn from_file(file: gio::File) -> Self {
|
|
|
|
|
Self::from_files(vec![file]).await
|
2023-04-18 01:52:06 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-13 01:07:08 +01:00
|
|
|
pub async fn from_files(files: Vec<gio::File>) -> Self {
|
2023-01-23 01:10:15 +01:00
|
|
|
let model = Self::default();
|
2025-12-03 23:55:30 +01:00
|
|
|
let mut collation_key_cache = Default::default();
|
2022-01-16 19:03:13 -08:00
|
|
|
|
2024-11-13 01:07:08 +01:00
|
|
|
let mut stored_files: IndexMap<GString, Entry> = IndexMap::new();
|
|
|
|
|
for file in files {
|
2025-12-03 23:55:30 +01:00
|
|
|
stored_files.insert(
|
|
|
|
|
file.uri(),
|
|
|
|
|
Entry::new(file, None, &mut collation_key_cache).await,
|
|
|
|
|
);
|
2022-11-26 00:02:56 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-13 01:07:08 +01:00
|
|
|
*model.imp().files.borrow_mut() = stored_files;
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
model
|
2022-11-26 00:02:56 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Load all file from the given directory
|
|
|
|
|
///
|
|
|
|
|
/// This can be used if there are already files present
|
2023-04-12 01:11:59 +02:00
|
|
|
pub async fn load_directory(&self, directory: gio::File) -> anyhow::Result<()> {
|
2025-12-03 23:55:30 +01:00
|
|
|
let instant = Instant::now();
|
2023-01-23 01:10:15 +01:00
|
|
|
self.imp().directory.set(directory.clone()).unwrap();
|
2022-11-26 00:02:56 +01:00
|
|
|
|
2023-08-03 21:29:41 +02:00
|
|
|
let monitor =
|
|
|
|
|
directory.monitor_directory(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
|
|
|
|
|
match monitor {
|
|
|
|
|
Ok(monitor) => {
|
2024-06-18 00:37:38 +02:00
|
|
|
monitor.connect_changed(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |_monitor, file_a, file_b, event| {
|
2023-08-03 21:29:41 +02:00
|
|
|
obj.file_monitor_cb(event, file_a, file_b);
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2023-08-03 21:29:41 +02:00
|
|
|
self.imp().monitor.set(monitor).unwrap();
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::info!("Cannot monitor directory: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-16 20:14:39 +01:00
|
|
|
|
2024-11-13 01:07:08 +01:00
|
|
|
let mut new_files = IndexMap::new();
|
|
|
|
|
|
|
|
|
|
let enumerator = directory
|
|
|
|
|
.enumerate_children_future(
|
|
|
|
|
&format!(
|
|
|
|
|
"{},{},{},{}",
|
|
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_NAME,
|
|
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
|
|
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
|
2025-12-03 23:55:30 +01:00
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
2024-11-13 01:07:08 +01:00
|
|
|
),
|
|
|
|
|
gio::FileQueryInfoFlags::NONE,
|
|
|
|
|
glib::Priority::LOW,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.context(gettext("Could not list other files in directory."))?;
|
|
|
|
|
|
2025-12-03 23:55:30 +01:00
|
|
|
let mut collation_key_cache = Default::default();
|
|
|
|
|
|
2024-11-20 17:06:54 +01:00
|
|
|
loop {
|
|
|
|
|
let info = enumerator.next_files_future(1, glib::Priority::LOW).await;
|
2024-11-13 01:07:08 +01:00
|
|
|
match info {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::warn!("Unreadable entry in directory: {err}");
|
2024-11-20 17:06:54 +01:00
|
|
|
break;
|
2024-11-13 01:07:08 +01:00
|
|
|
}
|
2025-12-03 23:55:30 +01:00
|
|
|
Ok(mut info) => {
|
|
|
|
|
if let Some(info) = info.pop() {
|
2024-11-20 17:06:54 +01:00
|
|
|
// GVfs smb does not provide a CONTENT_TYPE if the content type is
|
|
|
|
|
// ambiguous. This happens for png/apng. Since we
|
|
|
|
|
// only need to know if something is probably an
|
|
|
|
|
// image, we can use the FAST_CONTENT_TYPE in these cases.
|
2025-12-03 23:55:30 +01:00
|
|
|
let content_type =
|
|
|
|
|
info.attribute_string(gio::FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
|
2024-03-28 23:05:32 +01:00
|
|
|
|
2024-11-20 17:06:54 +01:00
|
|
|
if let Some(content_type) =
|
|
|
|
|
content_type.and_then(|x| gio::content_type_get_mime_type(&x))
|
|
|
|
|
{
|
2025-12-03 21:43:48 +01:00
|
|
|
let hidden =
|
|
|
|
|
if info.has_attribute(gio::FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) {
|
|
|
|
|
info.is_hidden()
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-20 17:06:54 +01:00
|
|
|
// Filter out non-images types. The final decision if images are
|
|
|
|
|
// supported/kept will later be done by glycin when starting to load the
|
|
|
|
|
// images. Usually by inspecting the magic bytes.
|
2025-12-03 21:43:48 +01:00
|
|
|
if content_type.starts_with("image/") && !hidden {
|
2024-11-20 17:06:54 +01:00
|
|
|
let file = directory.child(info.name());
|
2025-12-03 23:55:30 +01:00
|
|
|
let entry =
|
|
|
|
|
Entry::new(file, Some(info), &mut collation_key_cache).await;
|
|
|
|
|
new_files.insert(entry.file.uri(), entry);
|
2024-11-20 17:06:54 +01:00
|
|
|
}
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
2024-11-20 17:06:54 +01:00
|
|
|
} else {
|
|
|
|
|
break;
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-13 01:07:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
{
|
2023-11-29 21:15:36 +01:00
|
|
|
// Here we use a nested scope so that the mutable borrow only lasts as long as
|
|
|
|
|
// we need it
|
2022-11-16 20:14:39 +01:00
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
let mut files = self.imp().files.borrow_mut();
|
2023-04-12 01:11:59 +02:00
|
|
|
for (uri, file) in files.iter() {
|
|
|
|
|
new_files.insert(uri.clone(), file.clone());
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
*files = new_files;
|
2022-01-16 19:03:13 -08:00
|
|
|
// Then sort by name.
|
2023-01-23 01:10:15 +01:00
|
|
|
Self::sort(&mut files);
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 23:55:30 +01:00
|
|
|
log::debug!(
|
|
|
|
|
"Completed loading list of files of '{}' after {:#?}",
|
|
|
|
|
directory.uri(),
|
|
|
|
|
instant.elapsed(),
|
|
|
|
|
);
|
|
|
|
|
|
2022-11-26 00:02:56 +01:00
|
|
|
Ok(())
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn directory(&self) -> Option<gio::File> {
|
2022-01-16 19:03:13 -08:00
|
|
|
self.imp().directory.get().cloned()
|
|
|
|
|
}
|
2022-01-19 23:11:38 -08:00
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn index_of(&self, file: &gio::File) -> Option<usize> {
|
|
|
|
|
self.imp().files.borrow().get_index_of(&file.uri())
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn n_files(&self) -> usize {
|
|
|
|
|
self.imp().files.borrow().len()
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 01:30:41 +01:00
|
|
|
pub fn contains_file(&self, file: &gio::File) -> bool {
|
2023-04-12 01:11:59 +02:00
|
|
|
self.imp().files.borrow().contains_key(&file.uri())
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn before(&self, file: &gio::File) -> Option<gio::File> {
|
2023-04-12 04:13:30 +02:00
|
|
|
let index = self.index_of(file)?;
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow()
|
|
|
|
|
.get_index(index.checked_sub(1)?)
|
2024-11-12 19:59:50 +01:00
|
|
|
.map(|x| &x.1.file)
|
2023-01-23 01:10:15 +01:00
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn after(&self, file: &gio::File) -> Option<gio::File> {
|
|
|
|
|
let index = self.index_of(file)?;
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow()
|
|
|
|
|
.get_index(index.checked_add(1)?)
|
2024-11-12 19:59:50 +01:00
|
|
|
.map(|x| &x.1.file)
|
2023-01-23 01:10:15 +01:00
|
|
|
.cloned()
|
2022-06-29 16:51:16 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-12 04:13:30 +02:00
|
|
|
/// Returns `n` elements before and after given file
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn files_around(&self, file: &gio::File, n: usize) -> IndexMap<GString, gio::File> {
|
|
|
|
|
let Some(index) = self.index_of(file) else {
|
|
|
|
|
log::error!("URI not in model: {}", file.uri());
|
|
|
|
|
return IndexMap::new();
|
2023-01-23 01:10:15 +01:00
|
|
|
};
|
|
|
|
|
|
2024-12-20 23:02:18 +01:00
|
|
|
let reduce = n.saturating_sub(index);
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
self.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow()
|
|
|
|
|
.iter()
|
|
|
|
|
.skip(index.saturating_sub(n))
|
|
|
|
|
.take(2 * n + 1 - reduce)
|
2024-11-12 19:59:50 +01:00
|
|
|
.map(|(k, v)| (k.clone(), v.file.clone()))
|
2023-01-23 01:10:15 +01:00
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 04:13:30 +02:00
|
|
|
/// Return first file
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn first(&self) -> Option<gio::File> {
|
2024-11-12 19:59:50 +01:00
|
|
|
self.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow()
|
|
|
|
|
.first()
|
|
|
|
|
.map(|x| &x.1.file)
|
|
|
|
|
.cloned()
|
2023-02-06 19:14:01 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-12 04:13:30 +02:00
|
|
|
/// Returns last file
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn last(&self) -> Option<gio::File> {
|
2024-11-12 19:59:50 +01:00
|
|
|
self.imp().files.borrow().last().map(|x| &x.1.file).cloned()
|
2023-02-06 19:14:01 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-28 00:44:04 +02:00
|
|
|
pub fn remove(&self, file: &gio::File) -> Option<gio::File> {
|
2024-11-12 19:59:50 +01:00
|
|
|
self.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.shift_remove(&file.uri())
|
|
|
|
|
.map(|x| x.file)
|
2023-04-28 00:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Currently sorts by name
|
2024-11-12 19:59:50 +01:00
|
|
|
fn sort(files: &mut IndexMap<GString, Entry>) {
|
|
|
|
|
files.sort_by(|_, x, _, y| x.sort.cmp(&y.sort));
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determines if a file is added
|
|
|
|
|
fn is_image(info: &gio::FileInfo) -> bool {
|
|
|
|
|
info.content_type()
|
|
|
|
|
.map(|t| t.to_string())
|
|
|
|
|
.filter(|t| t.starts_with("image/"))
|
|
|
|
|
.is_some()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_image_file(file: &gio::File) -> bool {
|
2023-07-06 23:48:56 +02:00
|
|
|
let Ok(info) =
|
|
|
|
|
util::query_attributes(file, vec![gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE])
|
|
|
|
|
else {
|
2023-04-12 04:13:30 +02:00
|
|
|
log::warn!("Could not query file info: {}", file.uri());
|
2023-01-23 01:10:15 +01:00
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Self::is_image(&info)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Signal that notifies about added/removed files
|
2025-01-17 01:30:41 +01:00
|
|
|
pub fn connect_changed(&self, f: impl Fn(&FileEvent) + 'static) {
|
|
|
|
|
self.connect_local("changed", false, move |args| {
|
|
|
|
|
if let Some(file_event) = args
|
|
|
|
|
.get(1)
|
|
|
|
|
.and_then(|x| x.get().ok())
|
|
|
|
|
.and_then(|x| FileEvent::from_variant(&x))
|
|
|
|
|
{
|
|
|
|
|
f(&file_event);
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("Failed to read arguments from 'changed' signal.");
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
None
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 01:30:41 +01:00
|
|
|
pub fn emmit_changed(&self, file_event: &FileEvent) {
|
|
|
|
|
self.emit_by_name::<()>("changed", &[&file_event.to_variant()]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Insert a file
|
|
|
|
|
async fn insert(&self, file: gio::File) -> bool {
|
2024-11-13 01:07:08 +01:00
|
|
|
let obj = self.clone();
|
2025-12-03 23:55:30 +01:00
|
|
|
let entry = Entry::new(file.clone(), None, &mut Default::default()).await;
|
2025-01-17 01:30:41 +01:00
|
|
|
let mut files = obj.imp().files.borrow_mut();
|
|
|
|
|
let changed = files.insert(file.uri(), entry).is_none();
|
|
|
|
|
if changed {
|
|
|
|
|
Self::sort(&mut files);
|
|
|
|
|
drop(files);
|
|
|
|
|
}
|
|
|
|
|
changed
|
2024-11-13 01:07:08 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
fn file_monitor_cb(
|
|
|
|
|
&self,
|
|
|
|
|
event: gio::FileMonitorEvent,
|
|
|
|
|
file_a: &gio::File,
|
|
|
|
|
file_b: Option<&gio::File>,
|
|
|
|
|
) {
|
2025-01-17 01:30:41 +01:00
|
|
|
let uri_a = file_a.uri();
|
2024-11-13 01:07:08 +01:00
|
|
|
match event {
|
2023-01-23 01:10:15 +01:00
|
|
|
gio::FileMonitorEvent::Created
|
|
|
|
|
| gio::FileMonitorEvent::MovedIn
|
|
|
|
|
| gio::FileMonitorEvent::ChangesDoneHint
|
|
|
|
|
if Self::is_image_file(file_a) =>
|
|
|
|
|
{
|
2025-01-17 01:30:41 +01:00
|
|
|
// ^^^^
|
|
|
|
|
// Changing file content could theoretically make it an image
|
|
|
|
|
// by adding a magic byte
|
|
|
|
|
|
|
|
|
|
log::debug!("File added: {uri_a}");
|
|
|
|
|
|
|
|
|
|
glib::spawn_future_local(glib::clone!(
|
|
|
|
|
#[strong(rename_to=obj)]
|
|
|
|
|
self,
|
|
|
|
|
#[strong]
|
|
|
|
|
file_a,
|
|
|
|
|
async move {
|
|
|
|
|
let changed = obj.insert(file_a.clone()).await;
|
|
|
|
|
if changed {
|
|
|
|
|
obj.emmit_changed(&FileEvent::New(uri_a.to_string()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2025-01-17 01:30:41 +01:00
|
|
|
gio::FileMonitorEvent::Deleted
|
|
|
|
|
| gio::FileMonitorEvent::MovedOut
|
|
|
|
|
| gio::FileMonitorEvent::Unmounted => {
|
|
|
|
|
log::debug!("File removed: {}", file_a.uri());
|
|
|
|
|
let removed = self
|
|
|
|
|
.imp()
|
|
|
|
|
.files
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.shift_remove(&file_a.uri())
|
|
|
|
|
.is_some();
|
2024-11-13 01:07:08 +01:00
|
|
|
if removed {
|
2025-01-17 01:30:41 +01:00
|
|
|
self.emmit_changed(&FileEvent::Removed(file_a.uri().to_string()));
|
2024-11-13 01:07:08 +01:00
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
gio::FileMonitorEvent::Renamed => {
|
2023-04-12 01:11:59 +02:00
|
|
|
if let Some(file_b) = file_b {
|
2023-01-23 01:10:15 +01:00
|
|
|
{
|
2025-01-17 01:30:41 +01:00
|
|
|
let uri_b = file_b.uri();
|
|
|
|
|
log::debug!("File moved from '{uri_a}' to '{uri_b}'");
|
|
|
|
|
let mut changed =
|
|
|
|
|
self.imp().files.borrow_mut().shift_remove(&uri_a).is_some();
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
if Self::is_image_file(file_b) {
|
2025-01-17 01:30:41 +01:00
|
|
|
glib::spawn_future_local(glib::clone!(
|
|
|
|
|
#[strong(rename_to=obj)]
|
|
|
|
|
self,
|
|
|
|
|
#[strong]
|
|
|
|
|
file_b,
|
|
|
|
|
async move {
|
|
|
|
|
changed |= obj.insert(file_b.clone()).await;
|
|
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
|
obj.emmit_changed(&FileEvent::Moved(
|
|
|
|
|
uri_a.to_string(),
|
|
|
|
|
uri_b.to_string(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-17 01:30:41 +01:00
|
|
|
_ => {}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
2022-01-16 19:03:13 -08:00
|
|
|
}
|
2025-01-17 01:30:41 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, glib::Variant)]
|
|
|
|
|
pub enum FileEvent {
|
|
|
|
|
New(String),
|
|
|
|
|
Removed(String),
|
|
|
|
|
Moved(String, String),
|
|
|
|
|
}
|