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]
|
[target.'cfg(unix)'.dependencies]
|
||||||
fork = "0.2"
|
fork = "0.2"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
procfs = "0.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# cap-std = "3"
|
# cap-std = "3"
|
||||||
# cap-tempfile = "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
|
// This allows handling paths as groups if possible, such as launching a single video
|
||||||
// player that is passed every path.
|
// player that is passed every path.
|
||||||
let mut groups: HashMap<Mime, Vec<PathBuf>> = HashMap::new();
|
let mut groups: HashMap<Mime, Vec<PathBuf>> = HashMap::new();
|
||||||
for (mime, path) in paths
|
for (mime, path) in paths.iter().map(|path| {
|
||||||
.iter()
|
(
|
||||||
.map(|path| (mime_icon::mime_for_path(path), path.as_ref().to_owned()))
|
mime_icon::mime_for_path(path, None, false),
|
||||||
{
|
path.as_ref().to_owned(),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
groups.entry(mime).or_default().push(path);
|
groups.entry(mime).or_default().push(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use cosmic::widget::icon;
|
use cosmic::widget::icon;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use once_cell::sync::Lazy;
|
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";
|
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()));
|
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();
|
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||||
// Try the shared mime info cache first
|
// Try the shared mime info cache first
|
||||||
let guess = mime_icon_cache
|
let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
|
||||||
.shared_mime_info
|
if remote {
|
||||||
.guess_mime_type()
|
if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
|
||||||
.path(&path)
|
gb.file_name(file_name);
|
||||||
.guess();
|
}
|
||||||
|
} else {
|
||||||
|
gb.path(&path);
|
||||||
|
}
|
||||||
|
if let Some(metadata) = metadata_opt {
|
||||||
|
gb.metadata(metadata.clone());
|
||||||
|
}
|
||||||
|
let guess = gb.guess();
|
||||||
if guess.uncertain() {
|
if guess.uncertain() {
|
||||||
// If uncertain, try mime_guess. This could happen on platforms without shared-mime-info
|
// If uncertain, try mime_guess. This could happen on platforms without shared-mime-info
|
||||||
mime_guess::from_path(&path).first_or_octet_stream()
|
mime_guess::from_path(&path).first_or_octet_stream()
|
||||||
|
|
|
||||||
|
|
@ -963,7 +963,7 @@ impl Operation {
|
||||||
op_sel.selected.push(new_dir.clone());
|
op_sel.selected.push(new_dir.clone());
|
||||||
|
|
||||||
let controller = controller.clone();
|
let controller = controller.clone();
|
||||||
let mime = mime_for_path(path);
|
let mime = mime_for_path(path, None, false);
|
||||||
let password = password.clone();
|
let password = password.clone();
|
||||||
match mime.essence_str() {
|
match mime.essence_str() {
|
||||||
"application/gzip" | "application/x-compressed-tar" => {
|
"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
|
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>) {
|
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
|
|
@ -554,6 +613,8 @@ 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 (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() {
|
||||||
(
|
(
|
||||||
|
|
@ -564,7 +625,7 @@ pub fn item_from_entry(
|
||||||
folder_icon(&path, sizes.list_condensed()),
|
folder_icon(&path, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let mime = mime_for_path(&path);
|
let mime = mime_for_path(&path, Some(&metadata), remote);
|
||||||
//TODO: clean this up, implement for trash
|
//TODO: clean this up, implement for trash
|
||||||
let icon_name_opt = if mime == "application/x-desktop" {
|
let icon_name_opt = if mime == "application/x-desktop" {
|
||||||
let (desktop_name_opt, icon_name_opt) = parse_desktop_file(&path);
|
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?)
|
//TODO: calculate children in the background (and make it cancellable?)
|
||||||
match fs::read_dir(&path) {
|
match fs::read_dir(&path) {
|
||||||
Ok(entries) => entries.count(),
|
Ok(entries) => {
|
||||||
|
children_opt = Some(entries.count());
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to read directory {:?}: {}", path, 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 {
|
Item {
|
||||||
name,
|
name,
|
||||||
display_name,
|
display_name,
|
||||||
metadata: ItemMetadata::Path { metadata, children },
|
metadata: ItemMetadata::Path {
|
||||||
|
metadata,
|
||||||
|
children_opt,
|
||||||
|
},
|
||||||
hidden,
|
hidden,
|
||||||
location_opt: Some(Location::Path(path)),
|
location_opt: Some(Location::Path(path)),
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
icon_handle_list_condensed,
|
icon_handle_list_condensed,
|
||||||
thumbnail_opt: None,
|
thumbnail_opt: if remote {
|
||||||
|
Some(ItemThumbnail::NotImage)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
button_id: widget::Id::unique(),
|
button_id: widget::Id::unique(),
|
||||||
pos_opt: Cell::new(None),
|
pos_opt: Cell::new(None),
|
||||||
rect_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()),
|
folder_icon(&original_path, sizes.list_condensed()),
|
||||||
),
|
),
|
||||||
trash::TrashItemSize::Bytes(_) => {
|
trash::TrashItemSize::Bytes(_) => {
|
||||||
//TODO: do not use original path
|
// This passes remote = true so it does not read from the original path
|
||||||
let mime = mime_for_path(&original_path);
|
let mime = mime_for_path(&original_path, None, true);
|
||||||
(
|
(
|
||||||
mime.clone(),
|
mime.clone(),
|
||||||
mime_icon(mime.clone(), sizes.grid()),
|
mime_icon(mime.clone(), sizes.grid()),
|
||||||
|
|
@ -1316,7 +1380,7 @@ pub enum DirSize {
|
||||||
pub enum ItemMetadata {
|
pub enum ItemMetadata {
|
||||||
Path {
|
Path {
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
children: usize,
|
children_opt: Option<usize>,
|
||||||
},
|
},
|
||||||
Trash {
|
Trash {
|
||||||
metadata: trash::TrashItemMetadata,
|
metadata: trash::TrashItemMetadata,
|
||||||
|
|
@ -1646,16 +1710,23 @@ impl Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match &self.metadata {
|
match &self.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path {
|
||||||
|
metadata,
|
||||||
|
children_opt,
|
||||||
|
} => {
|
||||||
if metadata.is_dir() {
|
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 {
|
let size = match &self.dir_size {
|
||||||
DirSize::Calculating(_) => fl!("calculating"),
|
DirSize::Calculating(_) => fl!("calculating"),
|
||||||
DirSize::Directory(size) => format_size(*size),
|
DirSize::Directory(size) => format_size(*size),
|
||||||
DirSize::NotDirectory => String::new(),
|
DirSize::NotDirectory => String::new(),
|
||||||
DirSize::Error(err) => err.clone(),
|
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 {
|
} else {
|
||||||
details = details.push(widget::text::body(fl!(
|
details = details.push(widget::text::body(fl!(
|
||||||
"item-size",
|
"item-size",
|
||||||
|
|
@ -1769,9 +1840,14 @@ impl Item {
|
||||||
//TODO: translate!
|
//TODO: translate!
|
||||||
//TODO: correct display of folder size?
|
//TODO: correct display of folder size?
|
||||||
match &self.metadata {
|
match &self.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path {
|
||||||
|
metadata,
|
||||||
|
children_opt,
|
||||||
|
} => {
|
||||||
if metadata.is_dir() {
|
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 {
|
} else {
|
||||||
column = column.push(widget::text::body(format!(
|
column = column.push(widget::text::body(format!(
|
||||||
"Size: {}",
|
"Size: {}",
|
||||||
|
|
@ -3501,9 +3577,12 @@ impl Tab {
|
||||||
items.sort_by(|a, b| {
|
items.sort_by(|a, b| {
|
||||||
// entries take precedence over size
|
// entries take precedence over size
|
||||||
let get_size = |x: &Item| match &x.metadata {
|
let get_size = |x: &Item| match &x.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path {
|
||||||
|
metadata,
|
||||||
|
children_opt,
|
||||||
|
} => {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
(true, *children as u64)
|
(true, children_opt.unwrap_or_default() as u64)
|
||||||
} else {
|
} else {
|
||||||
(false, metadata.len())
|
(false, metadata.len())
|
||||||
}
|
}
|
||||||
|
|
@ -4518,13 +4597,20 @@ impl Tab {
|
||||||
};
|
};
|
||||||
|
|
||||||
let size_text = match &item.metadata {
|
let size_text = match &item.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path {
|
||||||
|
metadata,
|
||||||
|
children_opt,
|
||||||
|
} => {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
//TODO: translate
|
//TODO: translate
|
||||||
if *children == 1 {
|
if let Some(children) = children_opt {
|
||||||
format!("{} item", children)
|
if *children == 1 {
|
||||||
|
format!("{} item", children)
|
||||||
|
} else {
|
||||||
|
format!("{} items", children)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("{} items", children)
|
String::new()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
format_size(metadata.len())
|
format_size(metadata.len())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue