use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use chrono::{DateTime, SecondsFormat, Utc};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
#[non_exhaustive]
pub enum FileType {
RegularFile,
Directory,
SymbolicLink,
RemoteObject,
BlockDevice,
CharacterDevice,
Fifo,
Socket,
Other,
Unknown,
}
impl FileType {
fn rank(&self) -> u8 {
match self {
Self::RegularFile => 0,
Self::Directory => 1,
Self::SymbolicLink => 2,
Self::RemoteObject => 3,
Self::BlockDevice => 4,
Self::CharacterDevice => 5,
Self::Fifo => 6,
Self::Socket => 7,
Self::Other => 8,
Self::Unknown => 9,
}
}
fn debug_name(&self) -> &'static str {
match self {
Self::RegularFile => "RegularFile",
Self::Directory => "Directory",
Self::SymbolicLink => "SymbolicLink",
Self::RemoteObject => "RemoteObject",
Self::BlockDevice => "BlockDevice",
Self::CharacterDevice => "CharacterDevice",
Self::Fifo => "Fifo",
Self::Socket => "Socket",
Self::Other => "Other",
Self::Unknown => "Unknown",
}
}
fn display_name(&self) -> &'static str {
match self {
Self::RegularFile => "regular file",
Self::Directory => "directory",
Self::SymbolicLink => "symbolic link",
Self::RemoteObject => "remote object",
Self::BlockDevice => "block device",
Self::CharacterDevice => "character device",
Self::Fifo => "fifo",
Self::Socket => "socket",
Self::Other => "other",
Self::Unknown => "unknown",
}
}
}
impl Clone for FileType {
fn clone(&self) -> Self {
match self {
Self::RegularFile => Self::RegularFile,
Self::Directory => Self::Directory,
Self::SymbolicLink => Self::SymbolicLink,
Self::RemoteObject => Self::RemoteObject,
Self::BlockDevice => Self::BlockDevice,
Self::CharacterDevice => Self::CharacterDevice,
Self::Fifo => Self::Fifo,
Self::Socket => Self::Socket,
Self::Other => Self::Other,
Self::Unknown => Self::Unknown,
}
}
}
impl fmt::Debug for FileType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.debug_name())
}
}
impl fmt::Display for FileType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.display_name())
}
}
impl PartialEq for FileType {
fn eq(&self, other: &Self) -> bool {
self.rank() == other.rank()
}
}
impl Eq for FileType {}
impl PartialOrd for FileType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileType {
fn cmp(&self, other: &Self) -> Ordering {
self.rank().cmp(&other.rank())
}
}
impl Hash for FileType {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.rank().hash(state);
}
}
pub struct PortablePermissions {
pub read_only: Option<bool>,
pub executable: Option<bool>,
}
fn permission_state(value: Option<bool>) -> &'static str {
match value {
None => "unknown",
Some(false) => "no",
Some(true) => "yes",
}
}
impl Clone for PortablePermissions {
fn clone(&self) -> Self {
Self {
read_only: self.read_only,
executable: self.executable,
}
}
}
impl fmt::Debug for PortablePermissions {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PortablePermissions")
.field("read_only", &self.read_only)
.field("executable", &self.executable)
.finish()
}
}
impl fmt::Display for PortablePermissions {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"read-only: {}, executable: {}",
permission_state(self.read_only),
permission_state(self.executable)
)
}
}
impl PartialEq for PortablePermissions {
fn eq(&self, other: &Self) -> bool {
self.read_only == other.read_only
&& self.executable == other.executable
}
}
impl Eq for PortablePermissions {}
impl PartialOrd for PortablePermissions {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PortablePermissions {
fn cmp(&self, other: &Self) -> Ordering {
self.read_only
.cmp(&other.read_only)
.then_with(|| self.executable.cmp(&other.executable))
}
}
impl Hash for PortablePermissions {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.read_only.hash(state);
self.executable.hash(state);
}
}
pub struct FileTimes {
pub created: Option<SystemTime>,
pub modified: Option<SystemTime>,
pub accessed: Option<SystemTime>,
pub metadata_changed: Option<SystemTime>,
}
struct FormattedSystemTime<'a>(Option<&'a SystemTime>);
impl fmt::Debug for FormattedSystemTime<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, formatter)
}
}
impl fmt::Display for FormattedSystemTime<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(time) = self.0 else {
return formatter.write_str("unknown");
};
match time.duration_since(UNIX_EPOCH) {
Ok(duration) => {
if let Ok(seconds) = i64::try_from(duration.as_secs()) {
if let Some(datetime) =
DateTime::<Utc>::from_timestamp(seconds, duration.subsec_nanos())
{
return formatter.write_str(
&datetime.to_rfc3339_opts(SecondsFormat::Nanos, true),
);
}
}
write!(
formatter,
"unix+{}.{:09}s",
duration.as_secs(),
duration.subsec_nanos()
)
}
Err(error) => {
let magnitude = error.duration();
let nanoseconds = magnitude.subsec_nanos();
let signed_seconds = if nanoseconds == 0 {
-i128::from(magnitude.as_secs())
} else {
-i128::from(magnitude.as_secs()) - 1
};
let normalized_nanoseconds = if nanoseconds == 0 {
0
} else {
1_000_000_000 - nanoseconds
};
if let Ok(seconds) = i64::try_from(signed_seconds) {
if let Some(datetime) =
DateTime::<Utc>::from_timestamp(seconds, normalized_nanoseconds)
{
return formatter.write_str(
&datetime.to_rfc3339_opts(SecondsFormat::Nanos, true),
);
}
}
write!(
formatter,
"unix-{}.{:09}s",
magnitude.as_secs(),
magnitude.subsec_nanos()
)
}
}
}
}
impl Clone for FileTimes {
fn clone(&self) -> Self {
Self {
created: self.created,
modified: self.modified,
accessed: self.accessed,
metadata_changed: self.metadata_changed,
}
}
}
impl fmt::Debug for FileTimes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FileTimes")
.field("created", &FormattedSystemTime(self.created.as_ref()))
.field("modified", &FormattedSystemTime(self.modified.as_ref()))
.field("accessed", &FormattedSystemTime(self.accessed.as_ref()))
.field(
"metadata_changed",
&FormattedSystemTime(self.metadata_changed.as_ref()),
)
.finish()
}
}
impl fmt::Display for FileTimes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"created: {}, modified: {}, accessed: {}, metadata changed: {}",
FormattedSystemTime(self.created.as_ref()),
FormattedSystemTime(self.modified.as_ref()),
FormattedSystemTime(self.accessed.as_ref()),
FormattedSystemTime(self.metadata_changed.as_ref())
)
}
}
impl PartialEq for FileTimes {
fn eq(&self, other: &Self) -> bool {
self.created == other.created
&& self.modified == other.modified
&& self.accessed == other.accessed
&& self.metadata_changed == other.metadata_changed
}
}
impl Eq for FileTimes {}
impl PartialOrd for FileTimes {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileTimes {
fn cmp(&self, other: &Self) -> Ordering {
self.created
.cmp(&other.created)
.then_with(|| self.modified.cmp(&other.modified))
.then_with(|| self.accessed.cmp(&other.accessed))
.then_with(|| self.metadata_changed.cmp(&other.metadata_changed))
}
}
impl Hash for FileTimes {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.created.hash(state);
self.modified.hash(state);
self.accessed.hash(state);
self.metadata_changed.hash(state);
}
}
pub struct ResourceVersion<L> {
locator: L,
token: Arc<[u8]>,
}
impl<L> ResourceVersion<L> {
#[must_use]
pub fn new(locator: L, token: Arc<[u8]>) -> Self {
Self { locator, token }
}
#[must_use]
pub fn locator(&self) -> &L {
&self.locator
}
#[must_use]
pub fn token(&self) -> &[u8] {
&self.token
}
#[must_use]
pub fn into_parts(self) -> (L, Arc<[u8]>) {
(self.locator, self.token)
}
}
impl<L> Clone for ResourceVersion<L>
where
L: Clone,
{
fn clone(&self) -> Self {
Self {
locator: self.locator.clone(),
token: Arc::clone(&self.token),
}
}
}
impl<L> fmt::Debug for ResourceVersion<L>
where
L: fmt::Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ResourceVersion")
.field("locator", &self.locator)
.field("token_len", &self.token.len())
.finish()
}
}
impl<L> fmt::Display for ResourceVersion<L>
where
L: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{} (opaque version token: {} bytes)",
self.locator,
self.token.len()
)
}
}
impl<L> PartialEq for ResourceVersion<L>
where
L: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.locator == other.locator && self.token.as_ref() == other.token.as_ref()
}
}
impl<L> Eq for ResourceVersion<L> where L: Eq {}
impl<L> PartialOrd for ResourceVersion<L>
where
L: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.locator.partial_cmp(&other.locator) {
Some(Ordering::Equal) => self.token.as_ref().partial_cmp(other.token.as_ref()),
ordering => ordering,
}
}
}
impl<L> Ord for ResourceVersion<L>
where
L: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.locator
.cmp(&other.locator)
.then_with(|| self.token.as_ref().cmp(other.token.as_ref()))
}
}
impl<L> Hash for ResourceVersion<L>
where
L: Hash,
{
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.locator.hash(state);
self.token.as_ref().hash(state);
}
}
#[non_exhaustive]
pub struct FileMetadata<L> {
pub file_type: FileType,
pub byte_len: Option<u64>,
pub permissions: PortablePermissions,
pub times: FileTimes,
pub version: Option<ResourceVersion<L>>,
}
impl<L> FileMetadata<L> {
#[must_use]
pub fn new(
file_type: FileType,
byte_len: Option<u64>,
permissions: PortablePermissions,
times: FileTimes,
version: Option<ResourceVersion<L>>,
) -> Self {
Self {
file_type,
byte_len,
permissions,
times,
version,
}
}
}
impl<L> Clone for FileMetadata<L>
where
L: Clone,
{
fn clone(&self) -> Self {
Self {
file_type: self.file_type.clone(),
byte_len: self.byte_len,
permissions: self.permissions.clone(),
times: self.times.clone(),
version: self.version.clone(),
}
}
}
impl<L> fmt::Debug for FileMetadata<L>
where
L: fmt::Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FileMetadata")
.field("file_type", &self.file_type)
.field("byte_len", &self.byte_len)
.field("permissions", &self.permissions)
.field("times", &self.times)
.field("version", &self.version)
.finish()
}
}
impl<L> fmt::Display for FileMetadata<L>
where
L: fmt::Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"type: {}, byte length: ",
self.file_type
)?;
match self.byte_len {
Some(byte_len) => write!(formatter, "{byte_len} bytes")?,
None => formatter.write_str("unknown")?,
}
write!(
formatter,
", permissions: {}, times: {}, version: ",
self.permissions,
self.times
)?;
match &self.version {
Some(version) => fmt::Display::fmt(version, formatter),
None => formatter.write_str("none"),
}
}
}
impl<L> PartialEq for FileMetadata<L>
where
L: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.file_type == other.file_type
&& self.byte_len == other.byte_len
&& self.permissions == other.permissions
&& self.times == other.times
&& self.version == other.version
}
}
impl<L> Eq for FileMetadata<L> where L: Eq {}
impl<L> PartialOrd for FileMetadata<L>
where
L: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let prefix_ordering = self
.file_type
.cmp(&other.file_type)
.then_with(|| self.byte_len.cmp(&other.byte_len))
.then_with(|| self.permissions.cmp(&other.permissions))
.then_with(|| self.times.cmp(&other.times));
match prefix_ordering {
Ordering::Equal => self.version.partial_cmp(&other.version),
ordering => Some(ordering),
}
}
}
impl<L> Ord for FileMetadata<L>
where
L: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.file_type
.cmp(&other.file_type)
.then_with(|| self.byte_len.cmp(&other.byte_len))
.then_with(|| self.permissions.cmp(&other.permissions))
.then_with(|| self.times.cmp(&other.times))
.then_with(|| self.version.cmp(&other.version))
}
}
impl<L> Hash for FileMetadata<L>
where
L: Hash,
{
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.file_type.hash(state);
self.byte_len.hash(state);
self.permissions.hash(state);
self.times.hash(state);
self.version.hash(state);
}
}