fileModel: Make file info queries async
We are not entirely sure if looking up the display name might do IO for google drives.
This commit is contained in:
parent
400c7ac2c4
commit
9156533fea
3 changed files with 93 additions and 92 deletions
1
news.d/48.alpha/fixed-sort-order-not-following-nautilus
Normal file
1
news.d/48.alpha/fixed-sort-order-not-following-nautilus
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
The sort order is now also following the one found in Files (Nautilus) if the file name contains non-ascii characters.
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2022-2024 Sophie Herold
|
// Copyright (c) 2022-2024 Sophie Herold
|
||||||
// Copyright (c) 2022 Christopher Davis
|
// Copyright (c) 2022 Christopher Davis
|
||||||
// Copyright (c) 2023 Julian Hofer
|
|
||||||
// Copyright (c) 2023 Gage Berz
|
// Copyright (c) 2023 Gage Berz
|
||||||
// Copyright (c) 2024 Not Committed Yet
|
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -36,27 +34,25 @@ use crate::util::gettext::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Entry {
|
struct Entry {
|
||||||
|
/// Determines the sort order
|
||||||
sort: VecDeque<glib::FilenameCollationKey>,
|
sort: VecDeque<glib::FilenameCollationKey>,
|
||||||
file: gio::File,
|
file: gio::File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
//async
|
async fn new(file: gio::File) -> Self {
|
||||||
fn new(file: gio::File) -> Self {
|
|
||||||
let mut sort = VecDeque::new();
|
let mut sort = VecDeque::new();
|
||||||
let mut frac_file = file.clone();
|
let mut frac_file = file.clone();
|
||||||
|
|
||||||
|
// Determine sort order for all path/uri segments one by one
|
||||||
loop {
|
loop {
|
||||||
match frac_file
|
match frac_file
|
||||||
.query_info
|
.query_info_future(
|
||||||
//_future
|
|
||||||
(
|
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
||||||
gio::FileQueryInfoFlags::NONE,
|
gio::FileQueryInfoFlags::NONE,
|
||||||
//glib::Priority::LOW,
|
glib::Priority::LOW,
|
||||||
gio::Cancellable::NONE
|
|
||||||
)
|
)
|
||||||
//.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(parent) => {
|
Ok(parent) => {
|
||||||
let key = glib::FilenameCollationKey::from(parent.display_name());
|
let key = glib::FilenameCollationKey::from(parent.display_name());
|
||||||
|
|
@ -123,20 +119,20 @@ impl Default for LpFileModel {
|
||||||
|
|
||||||
impl LpFileModel {
|
impl LpFileModel {
|
||||||
/// Create with a single element
|
/// Create with a single element
|
||||||
pub fn from_file(file: gio::File) -> Self {
|
pub async fn from_file(file: gio::File) -> Self {
|
||||||
Self::from_files(vec![file])
|
Self::from_files(vec![file]).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_files(files: Vec<gio::File>) -> Self {
|
pub async fn from_files(files: Vec<gio::File>) -> Self {
|
||||||
let model = Self::default();
|
let model = Self::default();
|
||||||
|
|
||||||
{
|
let mut stored_files: IndexMap<GString, Entry> = IndexMap::new();
|
||||||
let mut vec = model.imp().files.borrow_mut();
|
for file in files {
|
||||||
for file in files {
|
stored_files.insert(file.uri(), Entry::new(file).await);
|
||||||
vec.insert(file.uri(), Entry::new(file));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*model.imp().files.borrow_mut() = stored_files;
|
||||||
|
|
||||||
model
|
model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,25 +160,29 @@ impl LpFileModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_files_result = gio::spawn_blocking(move || {
|
let mut new_files = IndexMap::new();
|
||||||
let mut files = IndexMap::new();
|
|
||||||
|
|
||||||
let enumerator = directory
|
let enumerator = directory
|
||||||
.enumerate_children(
|
.enumerate_children_future(
|
||||||
&format!(
|
&format!(
|
||||||
"{},{},{},{}",
|
"{},{},{},{}",
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_NAME,
|
gio::FILE_ATTRIBUTE_STANDARD_NAME,
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
|
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
|
gio::FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
|
gio::FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
|
||||||
),
|
),
|
||||||
gio::FileQueryInfoFlags::NONE,
|
gio::FileQueryInfoFlags::NONE,
|
||||||
gio::Cancellable::NONE,
|
glib::Priority::LOW,
|
||||||
)
|
)
|
||||||
.context(gettext("Could not list other files in directory."))?;
|
.await
|
||||||
|
.context(gettext("Could not list other files in directory."))?;
|
||||||
|
|
||||||
enumerator.for_each(|info| {
|
for info in enumerator {
|
||||||
if let Ok(info) = info {
|
match info {
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Unreadable entry in directory: {err}");
|
||||||
|
}
|
||||||
|
Ok(info) => {
|
||||||
// GVfs smb does not provide a CONTENT_TYPE if the content type is ambiguous.
|
// 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
|
// 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.
|
// probably an image, we can use the FAST_CONTENT_TYPE in these cases.
|
||||||
|
|
@ -201,22 +201,12 @@ impl LpFileModel {
|
||||||
// images. Usually by inspecting the magic bytes.
|
// images. Usually by inspecting the magic bytes.
|
||||||
if content_type.starts_with("image/") && !info.is_hidden() {
|
if content_type.starts_with("image/") && !info.is_hidden() {
|
||||||
let file = directory.child(info.name());
|
let file = directory.child(info.name());
|
||||||
files.insert(file.uri(), Entry::new(file));
|
new_files.insert(file.uri(), Entry::new(file).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
Ok::<_, anyhow::Error>(files)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let Ok(new_files) = new_files_result else {
|
|
||||||
log::debug!("Thread listing directory canceled.");
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_files = new_files?;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Here we use a nested scope so that the mutable borrow only lasts as long as
|
// Here we use a nested scope so that the mutable borrow only lasts as long as
|
||||||
|
|
@ -346,13 +336,32 @@ impl LpFileModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a file and signal changes
|
||||||
|
///
|
||||||
|
/// Setting `changed` to true will always trigger a `notify::changed``
|
||||||
|
/// notification. Otherwise, a change signal is only sent if no file with
|
||||||
|
/// the same URI existed before.
|
||||||
|
pub fn insert(&self, file: gio::File, mut changed: bool) {
|
||||||
|
let obj = self.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let entry = Entry::new(file.clone()).await;
|
||||||
|
let mut files = obj.imp().files.borrow_mut();
|
||||||
|
changed |= files.insert(file.uri(), entry).is_none();
|
||||||
|
if changed {
|
||||||
|
Self::sort(&mut files);
|
||||||
|
drop(files);
|
||||||
|
obj.emit_by_name::<()>("changed", &[]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn file_monitor_cb(
|
fn file_monitor_cb(
|
||||||
&self,
|
&self,
|
||||||
event: gio::FileMonitorEvent,
|
event: gio::FileMonitorEvent,
|
||||||
file_a: &gio::File,
|
file_a: &gio::File,
|
||||||
file_b: Option<&gio::File>,
|
file_b: Option<&gio::File>,
|
||||||
) {
|
) {
|
||||||
let changed = match event {
|
match event {
|
||||||
gio::FileMonitorEvent::Created
|
gio::FileMonitorEvent::Created
|
||||||
| gio::FileMonitorEvent::MovedIn
|
| gio::FileMonitorEvent::MovedIn
|
||||||
// Changing file content could theoretically make it an image
|
// Changing file content could theoretically make it an image
|
||||||
|
|
@ -360,40 +369,25 @@ impl LpFileModel {
|
||||||
| gio::FileMonitorEvent::ChangesDoneHint
|
| gio::FileMonitorEvent::ChangesDoneHint
|
||||||
if Self::is_image_file(file_a) =>
|
if Self::is_image_file(file_a) =>
|
||||||
{
|
{
|
||||||
let mut files = self.imp().files.borrow_mut();
|
self.insert(file_a.clone(), false);
|
||||||
let changed = files.insert(file_a.uri(),Entry::new(file_a.clone())).is_none();
|
|
||||||
if changed {
|
|
||||||
Self::sort(&mut files);
|
|
||||||
}
|
|
||||||
changed
|
|
||||||
}
|
}
|
||||||
gio::FileMonitorEvent::Deleted | gio::FileMonitorEvent::MovedOut | gio::FileMonitorEvent::Unmounted => {
|
gio::FileMonitorEvent::Deleted | gio::FileMonitorEvent::MovedOut | gio::FileMonitorEvent::Unmounted => {
|
||||||
let mut files = self.imp().files.borrow_mut();
|
let removed = self.imp().files.borrow_mut().shift_remove(&file_a.uri()).is_some();
|
||||||
files.shift_remove(&file_a.uri()).is_some()
|
if removed {
|
||||||
|
self.emit_by_name::<()>("changed", &[]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gio::FileMonitorEvent::Renamed => {
|
gio::FileMonitorEvent::Renamed => {
|
||||||
if let Some(file_b) = file_b {
|
if let Some(file_b) = file_b {
|
||||||
let mut changed = false;
|
|
||||||
{
|
{
|
||||||
let mut files = self.imp().files.borrow_mut();
|
let changed = self.imp().files.borrow_mut().shift_remove(&file_a.uri()).is_some();
|
||||||
changed |= files.shift_remove(&file_a.uri()).is_some();
|
|
||||||
if Self::is_image_file(file_b) {
|
if Self::is_image_file(file_b) {
|
||||||
changed |= files.insert(file_b.uri(), Entry::new(file_b.clone())).is_none();
|
self.insert(file_b.clone(), changed);
|
||||||
if changed {
|
|
||||||
Self::sort(&mut files);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changed
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => {},
|
||||||
};
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
self.emit_by_name::<()>("changed", &[]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -439,16 +439,22 @@ impl LpImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_files(&self, files: Vec<gio::File>) {
|
fn load_files(&self, files: Vec<gio::File>) {
|
||||||
let sliding_view = self.sliding_view().editor();
|
|
||||||
|
|
||||||
if let Some(first) = files.first().cloned() {
|
if let Some(first) = files.first().cloned() {
|
||||||
let model = LpFileModel::from_files(files);
|
glib::spawn_future_local(glib::clone!(
|
||||||
self.set_model(model);
|
#[strong(rename_to=obj)]
|
||||||
let page = self.new_image_page(&first);
|
self,
|
||||||
|
async move {
|
||||||
|
let sliding_view = obj.sliding_view().editor();
|
||||||
|
|
||||||
sliding_view.clear_lazy();
|
let model = LpFileModel::from_files(files).await;
|
||||||
sliding_view.append(&page);
|
obj.set_model(model);
|
||||||
self.update_sliding_view(&first);
|
let page = obj.new_image_page(&first);
|
||||||
|
|
||||||
|
sliding_view.clear_lazy();
|
||||||
|
sliding_view.append(&page);
|
||||||
|
obj.update_sliding_view(&first);
|
||||||
|
}
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
log::error!("File list was empty");
|
log::error!("File list was empty");
|
||||||
}
|
}
|
||||||
|
|
@ -474,18 +480,18 @@ impl LpImageView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = LpFileModel::from_file(file.clone());
|
glib::spawn_future_local(glib::clone!(
|
||||||
self.set_model(model);
|
#[strong(rename_to = obj)]
|
||||||
log::debug!("New model created");
|
self,
|
||||||
|
async move {
|
||||||
|
let model = LpFileModel::from_file(file.clone()).await;
|
||||||
|
obj.set_model(model);
|
||||||
|
log::debug!("New model created");
|
||||||
|
|
||||||
self.update_sliding_view(&file);
|
obj.update_sliding_view(&file);
|
||||||
|
|
||||||
// List other files in directory
|
// List other files in directory
|
||||||
if let Some(directory) = directory {
|
if let Some(directory) = directory {
|
||||||
glib::spawn_future_local(glib::clone!(
|
|
||||||
#[weak(rename_to = obj)]
|
|
||||||
self,
|
|
||||||
async move {
|
|
||||||
if let Err(err) = obj.model().load_directory(directory.clone()).await {
|
if let Err(err) = obj.model().load_directory(directory.clone()).await {
|
||||||
log::warn!("Failed to load directory: {}", err.root_cause());
|
log::warn!("Failed to load directory: {}", err.root_cause());
|
||||||
obj.activate_action(
|
obj.activate_action(
|
||||||
|
|
@ -504,8 +510,8 @@ impl LpImageView {
|
||||||
|
|
||||||
obj.update_sliding_view(&file);
|
obj.update_sliding_view(&file);
|
||||||
}
|
}
|
||||||
));
|
}
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move forward or backwards
|
/// Move forward or backwards
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue