Use gio to read display names for gvfs mounts, fixes #174

This commit is contained in:
Jeremy Soller 2025-05-15 10:00:44 -06:00
parent f07a106d0f
commit d0fb166cf6
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA

View file

@ -524,11 +524,18 @@ fn hidden_attribute(metadata: &Metadata) -> bool {
metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN
} }
#[derive(Clone, Copy, Debug)]
pub enum FsKind {
Local,
Remote,
Gvfs,
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn remote_fs(metadata: &Metadata) -> bool { fn fs_kind(metadata: &Metadata) -> FsKind {
//TODO: method to reload remote filesystems dynamically //TODO: method to reload remote filesystems dynamically
//TODO: fix for https://github.com/eminence/procfs/issues/262 //TODO: fix for https://github.com/eminence/procfs/issues/262
static DEVICES: Lazy<HashMap<u64, bool>> = Lazy::new(|| { static DEVICES: Lazy<HashMap<u64, FsKind>> = Lazy::new(|| {
let mut devices = HashMap::new(); let mut devices = HashMap::new();
match procfs::process::Process::myself() { match procfs::process::Process::myself() {
Ok(process) => match process.mountinfo() { Ok(process) => match process.mountinfo() {
@ -549,19 +556,13 @@ fn remote_fs(metadata: &Metadata) -> bool {
}; };
let dev = libc::makedev(major, minor); let dev = libc::makedev(major, minor);
//TODO: make sure this list is exhaustive //TODO: make sure this list is exhaustive
let remote = [ let kind = match mount_info.fs_type.as_str() {
"cifs", "cifs" | "fuse.rclone" | "fuse.sshfs" | "nfs" | "nfs4" | "smb"
//TODO: check with GVFS? | "smb2" => FsKind::Remote,
"fuse.gvfsd-fuse", "fuse.gvfsd-fuse" => FsKind::Gvfs,
"fuse.rclone", _ => FsKind::Local,
"fuse.sshfs", };
"nfs", devices.insert(dev, kind);
"nfs4",
"smb",
"smb2",
]
.contains(&mount_info.fs_type.as_str());
devices.insert(dev, remote);
} }
} }
Err(err) => { Err(err) => {
@ -574,13 +575,13 @@ fn remote_fs(metadata: &Metadata) -> bool {
} }
devices devices
}); });
DEVICES.get(&metadata.dev()).map_or(false, |x| *x) DEVICES.get(&metadata.dev()).map_or(FsKind::Local, |x| *x)
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
fn remote_fs(_metadata: &Metadata) -> bool { fn fs_kind(_metadata: &Metadata) -> FsKind {
//TODO: support BSD, macOS, Windows? //TODO: support BSD, macOS, Windows?
false FsKind::Local
} }
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) { pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
@ -613,7 +614,47 @@ pub fn item_from_entry(
let hidden = name.starts_with(".") || hidden_attribute(&metadata); let hidden = name.starts_with(".") || hidden_attribute(&metadata);
let remote = remote_fs(&metadata); let remote = match fs_kind(&metadata) {
FsKind::Local => false,
FsKind::Remote => true,
#[cfg(feature = "gvfs")]
FsKind::Gvfs => {
let file = gio::File::for_path(&path);
match gio::prelude::FileExt::query_info(
&file,
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
) {
Ok(info) => {
display_name = Item::display_name(&info.display_name());
}
Err(err) => {
log::warn!("failed to get GIO info for {:?}: {}", path, err);
}
}
match gio::prelude::FileExt::query_filesystem_info(
&file,
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
) {
Ok(info) => info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE),
Err(err) => {
log::warn!("failed to get GIO filesystem info for {:?}: {}", path, err);
true
}
}
}
#[cfg(not(feature = "gvfs"))]
FsKind::Gvfs => {
log::info!(
"gvfs feature not enabled, info may be inaccurate for {:?}",
path
);
true
}
};
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) =
if metadata.is_dir() { if metadata.is_dir() {
@ -1198,6 +1239,20 @@ impl Location {
} }
} }
pub fn ancestors(&self) -> Vec<(Location, String)> {
let mut ancestors = Vec::new();
if let Some(path) = self.path_opt() {
for ancestor in path.ancestors() {
let (name, found_home) = folder_name(ancestor);
ancestors.push((self.with_path(ancestor.to_path_buf()), name));
if found_home {
break;
}
}
}
ancestors
}
pub fn path_opt(&self) -> Option<&PathBuf> { pub fn path_opt(&self) -> Option<&PathBuf> {
match self { match self {
Self::Desktop(path, ..) => Some(path), Self::Desktop(path, ..) => Some(path),
@ -1247,6 +1302,31 @@ impl Location {
}; };
(parent_item_opt, items) (parent_item_opt, items)
} }
pub fn title(&self) -> String {
match self {
Self::Desktop(path, _, _) => {
let (name, _) = folder_name(path);
name
}
Self::Path(path) => {
let (name, _) = folder_name(path);
name
}
Self::Search(path, term, ..) => {
//TODO: translate
let (name, _) = folder_name(path);
format!("Search \"{}\": {}", term, name)
}
Self::Trash => {
fl!("trash")
}
Self::Recents => {
fl!("recents")
}
Self::Network(_uri, display_name) => display_name.clone(),
}
}
} }
pub struct TaskWrapper(pub cosmic::Task<Message>); pub struct TaskWrapper(pub cosmic::Task<Message>);
@ -1946,6 +2026,8 @@ impl fmt::Debug for SearchContextWrapper {
pub struct Tab { pub struct Tab {
//TODO: make more items private //TODO: make more items private
pub location: Location, pub location: Location,
pub location_ancestors: Vec<(Location, String)>,
pub location_title: String,
pub location_context_menu_point: Option<Point>, pub location_context_menu_point: Option<Point>,
pub location_context_menu_index: Option<usize>, pub location_context_menu_index: Option<usize>,
pub context_menu: Option<Point>, pub context_menu: Option<Point>,
@ -2011,7 +2093,11 @@ fn folder_name<P: AsRef<Path>>(path: P) -> (String, bool) {
found_home = true; found_home = true;
fl!("home") fl!("home")
} else { } else {
name.to_string_lossy().to_string() // This is not optimized but it helps ensure the same display names
match item_from_path(path, IconSizes::default()) {
Ok(item) => item.display_name,
Err(_err) => name.to_string_lossy().to_string(),
}
} }
} }
None => { None => {
@ -2040,9 +2126,14 @@ fn parse_hidden_file(path: &PathBuf) -> Vec<String> {
impl Tab { impl Tab {
pub fn new(location: Location, config: TabConfig) -> Self { pub fn new(location: Location, config: TabConfig) -> Self {
let location = location.normalize();
let location_ancestors = location.ancestors();
let location_title = location.title();
let history = vec![location.clone()]; let history = vec![location.clone()];
Self { Self {
location: location.normalize(), location,
location_ancestors,
location_title,
context_menu: None, context_menu: None,
location_context_menu_point: None, location_context_menu_point: None,
location_context_menu_index: None, location_context_menu_index: None,
@ -2081,28 +2172,8 @@ impl Tab {
} }
pub fn title(&self) -> String { pub fn title(&self) -> String {
match &self.location { //TODO: is it possible to return a &str?
Location::Desktop(path, _, _) => { self.location_title.clone()
let (name, _) = folder_name(path);
name
}
Location::Path(path) => {
let (name, _) = folder_name(path);
name
}
Location::Search(path, term, ..) => {
//TODO: translate
let (name, _) = folder_name(path);
format!("Search \"{}\": {}", term, name)
}
Location::Trash => {
fl!("trash")
}
Location::Recents => {
fl!("recents")
}
Location::Network(_uri, display_name) => display_name.clone(),
}
} }
pub fn items_opt(&self) -> Option<&Vec<Item>> { pub fn items_opt(&self) -> Option<&Vec<Item>> {
@ -2395,6 +2466,8 @@ impl Tab {
pub fn change_location(&mut self, location: &Location, history_i_opt: Option<usize>) { pub fn change_location(&mut self, location: &Location, history_i_opt: Option<usize>) {
self.location = location.normalize(); self.location = location.normalize();
self.location_ancestors = self.location.ancestors();
self.location_title = self.location.title();
self.context_menu = None; self.context_menu = None;
self.edit_location = None; self.edit_location = None;
self.items_opt = None; self.items_opt = None;