From d0fb166cf6e425b836e77c0ca9bd630664e346a7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 15 May 2025 10:00:44 -0600 Subject: [PATCH] Use gio to read display names for gvfs mounts, fixes #174 --- src/tab.rs | 159 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index da30659..cccd5b0 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -524,11 +524,18 @@ fn hidden_attribute(metadata: &Metadata) -> bool { metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN } +#[derive(Clone, Copy, Debug)] +pub enum FsKind { + Local, + Remote, + Gvfs, +} + #[cfg(target_os = "linux")] -fn remote_fs(metadata: &Metadata) -> bool { +fn fs_kind(metadata: &Metadata) -> FsKind { //TODO: method to reload remote filesystems dynamically //TODO: fix for https://github.com/eminence/procfs/issues/262 - static DEVICES: Lazy> = Lazy::new(|| { + static DEVICES: Lazy> = Lazy::new(|| { let mut devices = HashMap::new(); match procfs::process::Process::myself() { Ok(process) => match process.mountinfo() { @@ -549,19 +556,13 @@ fn remote_fs(metadata: &Metadata) -> bool { }; let dev = libc::makedev(major, minor); //TODO: make sure this list is exhaustive - let remote = [ - "cifs", - //TODO: check with GVFS? - "fuse.gvfsd-fuse", - "fuse.rclone", - "fuse.sshfs", - "nfs", - "nfs4", - "smb", - "smb2", - ] - .contains(&mount_info.fs_type.as_str()); - devices.insert(dev, remote); + let kind = match mount_info.fs_type.as_str() { + "cifs" | "fuse.rclone" | "fuse.sshfs" | "nfs" | "nfs4" | "smb" + | "smb2" => FsKind::Remote, + "fuse.gvfsd-fuse" => FsKind::Gvfs, + _ => FsKind::Local, + }; + devices.insert(dev, kind); } } Err(err) => { @@ -574,13 +575,13 @@ fn remote_fs(metadata: &Metadata) -> bool { } 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"))] -fn remote_fs(_metadata: &Metadata) -> bool { +fn fs_kind(_metadata: &Metadata) -> FsKind { //TODO: support BSD, macOS, Windows? - false + FsKind::Local } pub fn parse_desktop_file(path: &Path) -> (Option, Option) { @@ -613,7 +614,47 @@ pub fn item_from_entry( 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) = 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> { match self { Self::Desktop(path, ..) => Some(path), @@ -1247,6 +1302,31 @@ impl Location { }; (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); @@ -1946,6 +2026,8 @@ impl fmt::Debug for SearchContextWrapper { pub struct Tab { //TODO: make more items private pub location: Location, + pub location_ancestors: Vec<(Location, String)>, + pub location_title: String, pub location_context_menu_point: Option, pub location_context_menu_index: Option, pub context_menu: Option, @@ -2011,7 +2093,11 @@ fn folder_name>(path: P) -> (String, bool) { found_home = true; fl!("home") } 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 => { @@ -2040,9 +2126,14 @@ fn parse_hidden_file(path: &PathBuf) -> Vec { impl Tab { 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()]; Self { - location: location.normalize(), + location, + location_ancestors, + location_title, context_menu: None, location_context_menu_point: None, location_context_menu_index: None, @@ -2081,28 +2172,8 @@ impl Tab { } pub fn title(&self) -> String { - match &self.location { - Location::Desktop(path, _, _) => { - 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(), - } + //TODO: is it possible to return a &str? + self.location_title.clone() } pub fn items_opt(&self) -> Option<&Vec> { @@ -2395,6 +2466,8 @@ impl Tab { pub fn change_location(&mut self, location: &Location, history_i_opt: Option) { self.location = location.normalize(); + self.location_ancestors = self.location.ancestors(); + self.location_title = self.location.title(); self.context_menu = None; self.edit_location = None; self.items_opt = None;