Reduce features used on remote filesystems
This attempts to detect remote filesystems on Linux using the /proc/self/mountinfo file and checking the filesystem against a hardcoded list of remote filesystems. Remote filesystems will not thumbnail, read file data to determine mime types, or calculate directory sizes.
This commit is contained in:
parent
8ced8b0551
commit
dd98622cfa
6 changed files with 372 additions and 243 deletions
430
Cargo.lock
generated
430
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -105,6 +105,9 @@ debug = true
|
|||
[target.'cfg(unix)'.dependencies]
|
||||
fork = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
# cap-std = "3"
|
||||
# cap-tempfile = "3"
|
||||
|
|
|
|||
10
src/app.rs
10
src/app.rs
|
|
@ -597,10 +597,12 @@ impl App {
|
|||
// This allows handling paths as groups if possible, such as launching a single video
|
||||
// player that is passed every path.
|
||||
let mut groups: HashMap<Mime, Vec<PathBuf>> = HashMap::new();
|
||||
for (mime, path) in paths
|
||||
.iter()
|
||||
.map(|path| (mime_icon::mime_for_path(path), path.as_ref().to_owned()))
|
||||
{
|
||||
for (mime, path) in paths.iter().map(|path| {
|
||||
(
|
||||
mime_icon::mime_for_path(path, None, false),
|
||||
path.as_ref().to_owned(),
|
||||
)
|
||||
}) {
|
||||
groups.entry(mime).or_default().push(path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use cosmic::widget::icon;
|
||||
use mime_guess::Mime;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, path::Path, sync::Mutex};
|
||||
use std::{collections::HashMap, fs, path::Path, sync::Mutex};
|
||||
|
||||
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
|
||||
|
||||
|
|
@ -50,14 +50,26 @@ impl MimeIconCache {
|
|||
}
|
||||
static MIME_ICON_CACHE: Lazy<Mutex<MimeIconCache>> = Lazy::new(|| Mutex::new(MimeIconCache::new()));
|
||||
|
||||
pub fn mime_for_path<P: AsRef<Path>>(path: P) -> Mime {
|
||||
pub fn mime_for_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata_opt: Option<&fs::Metadata>,
|
||||
remote: bool,
|
||||
) -> Mime {
|
||||
let path = path.as_ref();
|
||||
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||
// Try the shared mime info cache first
|
||||
let guess = mime_icon_cache
|
||||
.shared_mime_info
|
||||
.guess_mime_type()
|
||||
.path(&path)
|
||||
.guess();
|
||||
let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
|
||||
if remote {
|
||||
if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
|
||||
gb.file_name(file_name);
|
||||
}
|
||||
} else {
|
||||
gb.path(&path);
|
||||
}
|
||||
if let Some(metadata) = metadata_opt {
|
||||
gb.metadata(metadata.clone());
|
||||
}
|
||||
let guess = gb.guess();
|
||||
if guess.uncertain() {
|
||||
// If uncertain, try mime_guess. This could happen on platforms without shared-mime-info
|
||||
mime_guess::from_path(&path).first_or_octet_stream()
|
||||
|
|
|
|||
|
|
@ -963,7 +963,7 @@ impl Operation {
|
|||
op_sel.selected.push(new_dir.clone());
|
||||
|
||||
let controller = controller.clone();
|
||||
let mime = mime_for_path(path);
|
||||
let mime = mime_for_path(path, None, false);
|
||||
let password = password.clone();
|
||||
match mime.essence_str() {
|
||||
"application/gzip" | "application/x-compressed-tar" => {
|
||||
|
|
|
|||
144
src/tab.rs
144
src/tab.rs
|
|
@ -524,6 +524,65 @@ fn hidden_attribute(metadata: &Metadata) -> bool {
|
|||
metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn remote_fs(metadata: &Metadata) -> bool {
|
||||
//TODO: method to reload remote filesystems dynamically
|
||||
//TODO: fix for https://github.com/eminence/procfs/issues/262
|
||||
static DEVICES: Lazy<HashMap<u64, bool>> = Lazy::new(|| {
|
||||
let mut devices = HashMap::new();
|
||||
match procfs::process::Process::myself() {
|
||||
Ok(process) => match process.mountinfo() {
|
||||
Ok(mount_infos) => {
|
||||
for mount_info in mount_infos.iter() {
|
||||
let mut parts = mount_info.majmin.split(':');
|
||||
let Some(major_str) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
let Some(minor_str) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(major) = major_str.parse::<libc::c_uint>() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(minor) = minor_str.parse::<libc::c_uint>() else {
|
||||
continue;
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to get mount info: {err}");
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!("failed to get process info: {err}");
|
||||
}
|
||||
}
|
||||
devices
|
||||
});
|
||||
DEVICES.get(&metadata.dev()).map_or(false, |x| *x)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn remote_fs(_metadata: &Metadata) -> bool {
|
||||
//TODO: support BSD, macOS, Windows?
|
||||
false
|
||||
}
|
||||
|
||||
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
||||
Ok(ok) => ok,
|
||||
|
|
@ -554,6 +613,8 @@ pub fn item_from_entry(
|
|||
|
||||
let hidden = name.starts_with(".") || hidden_attribute(&metadata);
|
||||
|
||||
let remote = remote_fs(&metadata);
|
||||
|
||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) =
|
||||
if metadata.is_dir() {
|
||||
(
|
||||
|
|
@ -564,7 +625,7 @@ pub fn item_from_entry(
|
|||
folder_icon(&path, sizes.list_condensed()),
|
||||
)
|
||||
} else {
|
||||
let mime = mime_for_path(&path);
|
||||
let mime = mime_for_path(&path, Some(&metadata), remote);
|
||||
//TODO: clean this up, implement for trash
|
||||
let icon_name_opt = if mime == "application/x-desktop" {
|
||||
let (desktop_name_opt, icon_name_opt) = parse_desktop_file(&path);
|
||||
|
|
@ -598,36 +659,39 @@ pub fn item_from_entry(
|
|||
}
|
||||
};
|
||||
|
||||
let children = if metadata.is_dir() {
|
||||
let mut children_opt = None;
|
||||
let mut dir_size = DirSize::NotDirectory;
|
||||
if metadata.is_dir() && !remote {
|
||||
dir_size = DirSize::Calculating(Controller::default());
|
||||
//TODO: calculate children in the background (and make it cancellable?)
|
||||
match fs::read_dir(&path) {
|
||||
Ok(entries) => entries.count(),
|
||||
Ok(entries) => {
|
||||
children_opt = Some(entries.count());
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to read directory {:?}: {}", path, err);
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let dir_size = if metadata.is_dir() {
|
||||
DirSize::Calculating(Controller::default())
|
||||
} else {
|
||||
DirSize::NotDirectory
|
||||
};
|
||||
}
|
||||
|
||||
Item {
|
||||
name,
|
||||
display_name,
|
||||
metadata: ItemMetadata::Path { metadata, children },
|
||||
metadata: ItemMetadata::Path {
|
||||
metadata,
|
||||
children_opt,
|
||||
},
|
||||
hidden,
|
||||
location_opt: Some(Location::Path(path)),
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
icon_handle_list_condensed,
|
||||
thumbnail_opt: None,
|
||||
thumbnail_opt: if remote {
|
||||
Some(ItemThumbnail::NotImage)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
button_id: widget::Id::unique(),
|
||||
pos_opt: Cell::new(None),
|
||||
rect_opt: Cell::new(None),
|
||||
|
|
@ -832,8 +896,8 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
|||
folder_icon(&original_path, sizes.list_condensed()),
|
||||
),
|
||||
trash::TrashItemSize::Bytes(_) => {
|
||||
//TODO: do not use original path
|
||||
let mime = mime_for_path(&original_path);
|
||||
// This passes remote = true so it does not read from the original path
|
||||
let mime = mime_for_path(&original_path, None, true);
|
||||
(
|
||||
mime.clone(),
|
||||
mime_icon(mime.clone(), sizes.grid()),
|
||||
|
|
@ -1316,7 +1380,7 @@ pub enum DirSize {
|
|||
pub enum ItemMetadata {
|
||||
Path {
|
||||
metadata: Metadata,
|
||||
children: usize,
|
||||
children_opt: Option<usize>,
|
||||
},
|
||||
Trash {
|
||||
metadata: trash::TrashItemMetadata,
|
||||
|
|
@ -1646,16 +1710,23 @@ impl Item {
|
|||
}
|
||||
}
|
||||
match &self.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
ItemMetadata::Path {
|
||||
metadata,
|
||||
children_opt,
|
||||
} => {
|
||||
if metadata.is_dir() {
|
||||
details = details.push(widget::text::body(fl!("items", items = children)));
|
||||
if let Some(children) = children_opt {
|
||||
details = details.push(widget::text::body(fl!("items", items = children)));
|
||||
}
|
||||
let size = match &self.dir_size {
|
||||
DirSize::Calculating(_) => fl!("calculating"),
|
||||
DirSize::Directory(size) => format_size(*size),
|
||||
DirSize::NotDirectory => String::new(),
|
||||
DirSize::Error(err) => err.clone(),
|
||||
};
|
||||
details = details.push(widget::text::body(fl!("item-size", size = size)));
|
||||
if !size.is_empty() {
|
||||
details = details.push(widget::text::body(fl!("item-size", size = size)));
|
||||
}
|
||||
} else {
|
||||
details = details.push(widget::text::body(fl!(
|
||||
"item-size",
|
||||
|
|
@ -1769,9 +1840,14 @@ impl Item {
|
|||
//TODO: translate!
|
||||
//TODO: correct display of folder size?
|
||||
match &self.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
ItemMetadata::Path {
|
||||
metadata,
|
||||
children_opt,
|
||||
} => {
|
||||
if metadata.is_dir() {
|
||||
column = column.push(widget::text::body(format!("Items: {}", children)));
|
||||
if let Some(children) = children_opt {
|
||||
column = column.push(widget::text::body(format!("Items: {}", children)));
|
||||
}
|
||||
} else {
|
||||
column = column.push(widget::text::body(format!(
|
||||
"Size: {}",
|
||||
|
|
@ -3501,9 +3577,12 @@ impl Tab {
|
|||
items.sort_by(|a, b| {
|
||||
// entries take precedence over size
|
||||
let get_size = |x: &Item| match &x.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
ItemMetadata::Path {
|
||||
metadata,
|
||||
children_opt,
|
||||
} => {
|
||||
if metadata.is_dir() {
|
||||
(true, *children as u64)
|
||||
(true, children_opt.unwrap_or_default() as u64)
|
||||
} else {
|
||||
(false, metadata.len())
|
||||
}
|
||||
|
|
@ -4518,13 +4597,20 @@ impl Tab {
|
|||
};
|
||||
|
||||
let size_text = match &item.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
ItemMetadata::Path {
|
||||
metadata,
|
||||
children_opt,
|
||||
} => {
|
||||
if metadata.is_dir() {
|
||||
//TODO: translate
|
||||
if *children == 1 {
|
||||
format!("{} item", children)
|
||||
if let Some(children) = children_opt {
|
||||
if *children == 1 {
|
||||
format!("{} item", children)
|
||||
} else {
|
||||
format!("{} items", children)
|
||||
}
|
||||
} else {
|
||||
format!("{} items", children)
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
format_size(metadata.len())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue