use bytesize::ByteSize;
use sysinfo::{Gid, Groups, Uid, Users};
use crate::error::Result;
use std::{
fs::{self, File, OpenOptions},
path::PathBuf,
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(target_os = "windows")]
use std::os::windows::fs::MetadataExt;
#[derive(Debug)]
pub struct FileInfo<'a> {
pub path: &'a str,
pub arguments: Option<Vec<&'a str>>,
pub bytes: &'a [u8],
pub is_read_only: bool,
pub name: String,
pub size: String,
pub blocks: u64,
pub block_size: u64,
pub device: u64,
pub inode: u64,
pub links: u64,
pub access: FileAccessInfo,
pub date: FileDateInfo,
}
#[derive(Debug)]
pub struct FileAccessInfo {
pub mode: String,
pub uid: String,
pub gid: String,
}
#[derive(Debug)]
pub struct FileDateInfo {
pub access: String,
pub modify: String,
pub change: String,
pub birth: String,
}
impl<'a> FileInfo<'a> {
#[cfg(not(target_os = "windows"))]
pub fn new(path: &'a str, arguments: Option<Vec<&'a str>>, bytes: &'a [u8]) -> Result<Self> {
let metadata = fs::metadata(path)?;
let mode = metadata.permissions().mode();
let users = Users::new_with_refreshed_list();
let groups = Groups::new_with_refreshed_list();
Ok(Self {
path,
arguments,
bytes,
is_read_only: false,
name: PathBuf::from(path)
.file_name()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or_default(),
size: ByteSize(metadata.len()).to_string(),
blocks: metadata.blocks(),
block_size: metadata.blksize(),
device: metadata.dev(),
inode: metadata.ino(),
links: metadata.nlink(),
access: FileAccessInfo {
mode: format!("{:04o}/{}", mode & 0o777, {
let mut s = String::new();
s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
s.push(if mode & 0o100 != 0 { 'x' } else { '-' });
s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
s.push(if mode & 0o010 != 0 { 'x' } else { '-' });
s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
s.push(if mode & 0o001 != 0 { 'x' } else { '-' });
s
}),
uid: format!(
"{}/{}",
metadata.uid(),
Uid::try_from(metadata.uid() as usize)
.ok()
.and_then(|uid| users.get_user_by_id(&uid))
.map(|v| v.name())
.unwrap_or("?")
),
gid: format!(
"{}/{}",
metadata.gid(),
groups
.list()
.iter()
.find(|g| Gid::try_from(metadata.gid() as usize).as_ref() == Ok(g.id()))
.map(|v| v.name())
.unwrap_or("?")
),
},
date: {
fn format_system_time(system_time: SystemTime) -> String {
let datetime: chrono::DateTime<chrono::Local> = system_time.into();
format!("{}", datetime.format("%Y-%m-%d %H:%M:%S.%f %z"))
}
FileDateInfo {
access: format_system_time(metadata.accessed()?),
modify: format_system_time(metadata.modified()?),
change: format_system_time(
UNIX_EPOCH
+ Duration::new(
metadata.ctime().try_into()?,
metadata.ctime_nsec().try_into()?,
),
),
birth: metadata
.created()
.map(format_system_time)
.unwrap_or_else(|_| String::from("not supported")),
}
},
})
}
#[cfg(target_os = "windows")]
pub fn new(path: &'a str, arguments: Option<Vec<&'a str>>, bytes: &'a [u8]) -> Result<Self> {
let metadata = fs::metadata(path)?;
let users = Users::new_with_refreshed_list();
let groups = Groups::new_with_refreshed_list();
Ok(Self {
path,
arguments,
bytes,
is_read_only: false,
name: PathBuf::from(path)
.file_name()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or_default(),
size: ByteSize(metadata.len()).to_string(),
blocks: 0, block_size: 0, device: 0, inode: 0, links: 1, access: FileAccessInfo {
mode: String::from("unsupported"),
uid: String::from("unsupported"),
gid: String::from("unsupported"),
},
date: {
fn format_system_time(system_time: SystemTime) -> String {
let datetime: chrono::DateTime<chrono::Local> = system_time.into();
format!("{}", datetime.format("%Y-%m-%d %H:%M:%S.%f %z"))
}
FileDateInfo {
access: format_system_time(metadata.accessed()?),
modify: format_system_time(metadata.modified()?),
change: String::from("unsupported"),
birth: metadata
.created()
.map(format_system_time)
.unwrap_or_else(|_| String::from("not supported")),
}
},
})
}
pub fn open_file(&mut self) -> Result<File> {
Ok(
match OpenOptions::new().write(true).read(true).open(self.path) {
Ok(v) => v,
Err(_) => {
self.is_read_only = true;
File::open(self.path)?
}
},
)
}
}