2022-02-27 13:16:53 +00:00
|
|
|
use crate::permissions::Permissions;
|
2022-09-25 07:22:24 +00:00
|
|
|
use humansize::{format_size, DECIMAL};
|
2022-02-27 13:16:53 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::cmp::Ordering;
|
2022-09-10 12:38:15 +00:00
|
|
|
use std::os::unix::prelude::MetadataExt;
|
2022-02-27 13:16:53 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-05-20 19:06:25 +00:00
|
|
|
use std::time::UNIX_EPOCH;
|
2022-02-27 13:16:53 +00:00
|
|
|
|
2022-05-20 19:06:25 +00:00
|
|
|
fn to_human_size(size: u64) -> String {
|
2022-09-25 07:22:24 +00:00
|
|
|
format_size(size, DECIMAL)
|
2022-02-27 13:16:53 +00:00
|
|
|
}
|
|
|
|
|
2023-03-19 19:37:04 +00:00
|
|
|
fn mime_essence(
|
|
|
|
path: &Path,
|
|
|
|
is_dir: bool,
|
|
|
|
extension: &str,
|
|
|
|
is_executable: bool,
|
|
|
|
) -> String {
|
2022-02-27 13:16:53 +00:00
|
|
|
if is_dir {
|
|
|
|
String::from("inode/directory")
|
2023-03-19 19:37:04 +00:00
|
|
|
} else if extension.is_empty() && is_executable {
|
|
|
|
String::from("application/x-executable")
|
2022-02-27 13:16:53 +00:00
|
|
|
} else {
|
2022-11-12 20:09:24 +00:00
|
|
|
mime_guess::from_path(path)
|
2022-02-27 13:16:53 +00:00
|
|
|
.first()
|
|
|
|
.map(|m| m.essence_str().to_string())
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct ResolvedNode {
|
|
|
|
pub absolute_path: String,
|
|
|
|
pub extension: String,
|
|
|
|
pub is_dir: bool,
|
|
|
|
pub is_file: bool,
|
|
|
|
pub is_readonly: bool,
|
|
|
|
pub mime_essence: String,
|
|
|
|
pub size: u64,
|
|
|
|
pub human_size: String,
|
2022-05-20 20:19:16 +00:00
|
|
|
pub created: Option<u128>,
|
|
|
|
pub last_modified: Option<u128>,
|
2022-09-10 12:38:15 +00:00
|
|
|
pub uid: u32,
|
|
|
|
pub gid: u32,
|
2022-02-27 13:16:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ResolvedNode {
|
|
|
|
pub fn from(path: PathBuf) -> Self {
|
|
|
|
let extension = path
|
|
|
|
.extension()
|
|
|
|
.map(|e| e.to_string_lossy().to_string())
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2023-03-19 19:37:04 +00:00
|
|
|
let (
|
|
|
|
is_dir,
|
|
|
|
is_file,
|
|
|
|
is_readonly,
|
|
|
|
size,
|
|
|
|
permissions,
|
|
|
|
created,
|
|
|
|
last_modified,
|
|
|
|
uid,
|
|
|
|
gid,
|
|
|
|
) = path
|
|
|
|
.metadata()
|
|
|
|
.map(|m| {
|
|
|
|
(
|
|
|
|
m.is_dir(),
|
|
|
|
m.is_file(),
|
|
|
|
m.permissions().readonly(),
|
|
|
|
m.len(),
|
|
|
|
Permissions::from(&m),
|
|
|
|
m.created()
|
|
|
|
.ok()
|
|
|
|
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
|
|
|
.map(|d| d.as_nanos()),
|
|
|
|
m.modified()
|
|
|
|
.ok()
|
|
|
|
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
|
|
|
.map(|d| d.as_nanos()),
|
|
|
|
m.uid(),
|
|
|
|
m.gid(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.unwrap_or((false, false, false, 0, Default::default(), None, None, 0, 0));
|
|
|
|
|
|
|
|
let is_executable = permissions.user_execute
|
|
|
|
|| permissions.group_execute
|
|
|
|
|| permissions.other_execute;
|
|
|
|
let mime_essence = mime_essence(&path, is_dir, &extension, is_executable);
|
2022-05-20 19:06:25 +00:00
|
|
|
let human_size = to_human_size(size);
|
2022-02-27 13:16:53 +00:00
|
|
|
|
|
|
|
Self {
|
|
|
|
absolute_path: path.to_string_lossy().to_string(),
|
|
|
|
extension,
|
|
|
|
is_dir,
|
|
|
|
is_file,
|
|
|
|
is_readonly,
|
|
|
|
mime_essence,
|
|
|
|
size,
|
|
|
|
human_size,
|
2022-05-20 19:06:25 +00:00
|
|
|
created,
|
|
|
|
last_modified,
|
2022-09-10 12:38:15 +00:00
|
|
|
uid,
|
|
|
|
gid,
|
2022-02-27 13:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct Node {
|
|
|
|
pub parent: String,
|
|
|
|
pub relative_path: String,
|
|
|
|
pub absolute_path: String,
|
|
|
|
pub extension: String,
|
|
|
|
pub is_dir: bool,
|
|
|
|
pub is_file: bool,
|
|
|
|
pub is_symlink: bool,
|
|
|
|
pub is_broken: bool,
|
|
|
|
pub is_readonly: bool,
|
|
|
|
pub mime_essence: String,
|
|
|
|
pub size: u64,
|
|
|
|
pub human_size: String,
|
|
|
|
pub permissions: Permissions,
|
2022-05-20 20:19:16 +00:00
|
|
|
pub created: Option<u128>,
|
|
|
|
pub last_modified: Option<u128>,
|
2022-09-10 12:38:15 +00:00
|
|
|
pub uid: u32,
|
|
|
|
pub gid: u32,
|
2022-05-20 19:06:25 +00:00
|
|
|
|
2022-02-27 13:16:53 +00:00
|
|
|
pub canonical: Option<ResolvedNode>,
|
|
|
|
pub symlink: Option<ResolvedNode>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Node {
|
|
|
|
pub fn new(parent: String, relative_path: String) -> Self {
|
|
|
|
let absolute_path = PathBuf::from(&parent)
|
|
|
|
.join(&relative_path)
|
|
|
|
.to_string_lossy()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let path = PathBuf::from(&absolute_path);
|
|
|
|
|
|
|
|
let extension = path
|
|
|
|
.extension()
|
|
|
|
.map(|e| e.to_string_lossy().to_string())
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
let (is_broken, maybe_canonical_meta) = path
|
|
|
|
.canonicalize()
|
|
|
|
.map(|p| (false, Some(ResolvedNode::from(p))))
|
|
|
|
.unwrap_or_else(|_| (true, None));
|
|
|
|
|
2022-05-20 19:06:25 +00:00
|
|
|
let (
|
|
|
|
is_symlink,
|
|
|
|
is_dir,
|
|
|
|
is_file,
|
|
|
|
is_readonly,
|
|
|
|
size,
|
|
|
|
permissions,
|
|
|
|
created,
|
|
|
|
last_modified,
|
2022-09-10 12:38:15 +00:00
|
|
|
uid,
|
|
|
|
gid,
|
2022-05-20 19:06:25 +00:00
|
|
|
) = path
|
|
|
|
.symlink_metadata()
|
|
|
|
.map(|m| {
|
|
|
|
(
|
|
|
|
m.file_type().is_symlink(),
|
|
|
|
m.is_dir(),
|
|
|
|
m.is_file(),
|
|
|
|
m.permissions().readonly(),
|
|
|
|
m.len(),
|
|
|
|
Permissions::from(&m),
|
|
|
|
m.created()
|
|
|
|
.ok()
|
|
|
|
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
2022-05-20 20:19:16 +00:00
|
|
|
.map(|d| d.as_nanos()),
|
2022-05-20 19:06:25 +00:00
|
|
|
m.modified()
|
|
|
|
.ok()
|
|
|
|
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
2022-05-20 20:19:16 +00:00
|
|
|
.map(|d| d.as_nanos()),
|
2022-09-10 12:38:15 +00:00
|
|
|
m.uid(),
|
|
|
|
m.gid(),
|
2022-05-20 19:06:25 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
(
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
Permissions::default(),
|
|
|
|
None,
|
|
|
|
None,
|
2022-09-10 12:38:15 +00:00
|
|
|
0,
|
|
|
|
0,
|
2022-05-20 19:06:25 +00:00
|
|
|
)
|
|
|
|
});
|
2022-02-27 13:16:53 +00:00
|
|
|
|
2023-03-19 19:37:04 +00:00
|
|
|
let is_executable = permissions.user_execute
|
|
|
|
|| permissions.group_execute
|
|
|
|
|| permissions.other_execute;
|
|
|
|
|
|
|
|
let mime_essence = mime_essence(&path, is_dir, &extension, is_executable);
|
2022-05-20 19:06:25 +00:00
|
|
|
let human_size = to_human_size(size);
|
2022-02-27 13:16:53 +00:00
|
|
|
|
|
|
|
Self {
|
|
|
|
parent,
|
|
|
|
relative_path,
|
|
|
|
absolute_path,
|
|
|
|
extension,
|
|
|
|
is_dir,
|
|
|
|
is_file,
|
|
|
|
is_symlink,
|
|
|
|
is_broken,
|
|
|
|
is_readonly,
|
|
|
|
mime_essence,
|
|
|
|
size,
|
|
|
|
human_size,
|
|
|
|
permissions,
|
2022-05-20 19:06:25 +00:00
|
|
|
created,
|
|
|
|
last_modified,
|
2022-09-10 12:38:15 +00:00
|
|
|
uid,
|
|
|
|
gid,
|
2022-02-27 13:16:53 +00:00
|
|
|
canonical: maybe_canonical_meta.clone(),
|
|
|
|
symlink: if is_symlink {
|
|
|
|
maybe_canonical_meta
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Ord for Node {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
// Notice that the we flip the ordering on costs.
|
|
|
|
// In case of a tie we compare positions - this step is necessary
|
|
|
|
// to make implementations of `PartialEq` and `Ord` consistent.
|
|
|
|
other.relative_path.cmp(&self.relative_path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialOrd for Node {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|