#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileKind {
Regular,
Symlink,
Directory,
Device,
Unknown,
}
#[derive(Debug, Clone)]
pub struct FileMetadata {
pub path: PathBuf,
pub size_bytes: u64,
pub kind: FileKind,
pub modified: Option<SystemTime>,
pub created: Option<SystemTime>,
pub readable: bool,
pub writable: bool,
}
impl FileMetadata {
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn size_mib(&self) -> f64 {
self.size_bytes as f64 / (1024.0 * 1024.0)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.size_bytes == 0
}
#[must_use]
pub fn is_regular(&self) -> bool {
self.kind == FileKind::Regular
}
}
pub struct FileMetadataReader;
impl FileMetadataReader {
pub fn probe(path: impl AsRef<Path>) -> std::io::Result<FileMetadata> {
let path = path.as_ref();
let meta = std::fs::metadata(path)?;
let kind = if meta.is_file() {
FileKind::Regular
} else if meta.is_dir() {
FileKind::Directory
} else if meta.file_type().is_symlink() {
FileKind::Symlink
} else {
FileKind::Unknown
};
let readable = std::fs::File::open(path).is_ok();
let writable = !meta.permissions().readonly();
Ok(FileMetadata {
path: path.to_path_buf(),
size_bytes: meta.len(),
kind,
modified: meta.modified().ok(),
created: meta.created().ok(),
readable,
writable,
})
}
pub fn probe_no_follow(path: impl AsRef<Path>) -> std::io::Result<FileMetadata> {
let path = path.as_ref();
let meta = std::fs::symlink_metadata(path)?;
let kind = if meta.is_file() {
FileKind::Regular
} else if meta.is_dir() {
FileKind::Directory
} else if meta.file_type().is_symlink() {
FileKind::Symlink
} else {
FileKind::Unknown
};
let readable = std::fs::File::open(path).is_ok();
let writable = !meta.permissions().readonly();
Ok(FileMetadata {
path: path.to_path_buf(),
size_bytes: meta.len(),
kind,
modified: meta.modified().ok(),
created: meta.created().ok(),
readable,
writable,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn write_temp(content: &[u8]) -> (tempfile::NamedTempFile, PathBuf) {
let mut f = tempfile::NamedTempFile::new().expect("failed to create temp file");
f.write_all(content).expect("failed to write");
let p = f.path().to_path_buf();
(f, p)
}
#[test]
fn test_probe_regular_file() {
let (_f, path) = write_temp(b"hello");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert_eq!(meta.kind, FileKind::Regular);
}
#[test]
fn test_probe_size() {
let data = b"0123456789";
let (_f, path) = write_temp(data);
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert_eq!(meta.size_bytes, data.len() as u64);
}
#[test]
fn test_probe_is_not_empty() {
let (_f, path) = write_temp(b"x");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(!meta.is_empty());
}
#[test]
fn test_probe_empty_file() {
let (_f, path) = write_temp(b"");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(meta.is_empty());
}
#[test]
fn test_probe_is_regular() {
let (_f, path) = write_temp(b"data");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(meta.is_regular());
}
#[test]
fn test_probe_directory() {
let dir = tempfile::tempdir().expect("failed to create temp file");
let meta = FileMetadataReader::probe(dir.path()).expect("operation should succeed");
assert_eq!(meta.kind, FileKind::Directory);
}
#[test]
fn test_probe_readable() {
let (_f, path) = write_temp(b"read me");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(meta.readable);
}
#[test]
fn test_probe_path_stored() {
let (_f, path) = write_temp(b"path check");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert_eq!(meta.path, path);
}
#[test]
fn test_size_mib() {
let (_f, path) = write_temp(&vec![0u8; 1024 * 1024]);
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
let mib = meta.size_mib();
assert!((mib - 1.0).abs() < 0.001);
}
#[test]
fn test_size_mib_small() {
let (_f, path) = write_temp(b"small");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(meta.size_mib() < 1.0);
}
#[test]
fn test_probe_nonexistent_returns_error() {
let result = FileMetadataReader::probe("/nonexistent/path/file.mp4");
assert!(result.is_err());
}
#[test]
fn test_file_kind_variants() {
assert_ne!(FileKind::Regular, FileKind::Directory);
assert_ne!(FileKind::Symlink, FileKind::Unknown);
assert_ne!(FileKind::Device, FileKind::Regular);
}
#[test]
fn test_probe_no_follow_regular_file() {
let (_f, path) = write_temp(b"no follow");
let meta = FileMetadataReader::probe_no_follow(&path).expect("operation should succeed");
assert_eq!(meta.kind, FileKind::Regular);
}
#[test]
fn test_modified_timestamp_present() {
let (_f, path) = write_temp(b"ts check");
let meta = FileMetadataReader::probe(&path).expect("operation should succeed");
assert!(meta.modified.is_some());
}
#[test]
fn test_probe_directory_size_zero_or_nonzero() {
let dir = tempfile::tempdir().expect("failed to create temp file");
let meta = FileMetadataReader::probe(dir.path()).expect("operation should succeed");
assert_eq!(meta.kind, FileKind::Directory);
}
}