add missing file mod + add lib.rs

This commit is contained in:
rmanach 2025-10-24 10:08:44 +02:00
parent 2ca3c55092
commit fff0d1b0dd
3 changed files with 322 additions and 3 deletions

318
src/file.rs Normal file
View File

@ -0,0 +1,318 @@
use chrono::{DateTime, Utc};
use mime_guess::from_path;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use walkdir::WalkDir;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FileImgMimetype {
Jpeg,
Png,
}
impl FileImgMimetype {
pub fn value(&self) -> &'static str {
match self {
FileImgMimetype::Jpeg => "image/jpeg",
FileImgMimetype::Png => "image/png",
}
}
}
/// Categorized files by their size in megabytes.
///
/// * TINY: [0,1[ Mb
/// * MEDIUM: [1,2[ Mb
/// * LARGE: [2,5[ Mb
/// * FAT: [5,inf[ Mb
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FileSizeRange {
Tiny,
Medium,
Large,
Fat,
}
impl FileSizeRange {
pub fn from_size(size: f64) -> Self {
if size < 1.0 {
return FileSizeRange::Tiny;
}
if size < 2.0 {
return FileSizeRange::Medium;
}
if size < 5.0 {
return FileSizeRange::Large;
}
FileSizeRange::Fat
}
pub fn value(&self) -> &'static str {
match self {
FileSizeRange::Tiny => "Tiny",
FileSizeRange::Medium => "Medium",
FileSizeRange::Fat => "Fat",
FileSizeRange::Large => "Large",
}
}
}
/// Handle file main attributes.
///
/// Example:
/// ```rs
/// file = File.from_directory("dir-path", "my-file-name.png")
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct File {
pub directory: String,
pub name: String,
pub path: String,
pub mimetype: String,
pub size: f64,
pub modified: DateTime<Utc>,
}
impl File {
pub fn from_directory(directory: &str, name: &str) -> Result<Self, std::io::Error> {
let path = Path::new(directory)
.join(name)
.to_string_lossy()
.into_owned();
let mimetype = from_path(&path)
.first()
.map_or("application/octet-stream".to_string(), |m| m.to_string());
let size = fs::metadata(&path)?.len() as f64 / 1_048_576.0;
let modified = fs::metadata(&path)?.modified()?.into();
Ok(File {
directory: directory.to_string(),
name: name.to_string(),
path,
mimetype,
size,
modified,
})
}
}
/// Group a bunch of `File`. That's all.
/// Only useful to provide number of file and the whole size in Mb quickly.
#[derive(Debug, Clone)]
pub struct FileGroup {
files: HashMap<String, File>,
size: f64,
nb_files: usize,
}
impl Default for FileGroup {
fn default() -> Self {
Self::new()
}
}
impl FileGroup {
pub fn new() -> Self {
FileGroup {
files: HashMap::new(),
size: 0.0,
nb_files: 0,
}
}
pub fn add(&mut self, file: File) {
if self.files.get(&file.path).is_none() {
self.files.insert(file.path.clone(), file.clone());
self.nb_files += 1;
self.size += file.size;
}
}
pub fn len(&self) -> usize {
self.nb_files
}
pub fn join(&mut self, right: &FileGroup) {
for (filepath, file) in &right.files {
if self.files.get(filepath).is_none() {
self.files.insert(filepath.clone(), file.clone());
self.size += file.size;
self.nb_files += 1;
}
}
}
pub fn get_size(&self) -> f64 {
self.size
}
pub fn format_size(size: f64) -> String {
if size < 1000.0 {
return format!("{:.2} Mb", size);
}
format!("{:.2} Gb", size / 1024.0)
}
pub fn get_size_formatted(&self) -> String {
Self::format_size(self.size)
}
pub fn get_files(&self) -> Vec<File> {
self.files.values().cloned().collect()
}
}
/// Represents a directory path grouping files by mimetype and size range.
///
/// Example:
/// ```rs
/// directory = Directory.from_path("my-path")
/// fg = directory.get_file_group() # collect all files
///
/// // collect all tiny files of the directory
/// fg_tiny = directory.get_file_group(None, FileSizeRange::TINY)
///
/// // collect all JPEG files
/// fg_jpeg = directory.get_file_group(FileImgMimetype::JPEG, None)
/// ```
#[derive(Debug, Clone)]
pub struct Directory {
pub path: String,
pub nb_files: usize,
pub details: HashMap<String, HashMap<FileSizeRange, FileGroup>>,
}
impl Directory {
pub fn from_path(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
if !std::path::Path::new(path).is_dir() {
return Err(format!("Directory path: {} must be a directory", path).into());
}
let mut nb_files = 0;
let mut details: HashMap<String, HashMap<FileSizeRange, FileGroup>> = HashMap::new();
for entry in WalkDir::new(path).into_iter().filter_map(Result::ok) {
if entry.file_type().is_file() {
let file_path = entry.path().to_string_lossy().into_owned();
let file = match File::from_directory(
entry.path().parent().unwrap().to_string_lossy().as_ref(),
entry.file_name().to_string_lossy().as_ref(),
) {
Ok(file) => file,
Err(e) => {
log::error!("error accessing {}, err: {}", file_path, e);
continue;
}
};
let mimetype = file.mimetype.clone();
let size_range = FileSizeRange::from_size(file.size);
details
.entry(mimetype.clone())
.or_default()
.entry(size_range)
.or_default()
.add(file);
nb_files += 1;
}
}
Ok(Directory {
path: path.to_string(),
nb_files,
details,
})
}
pub fn len(&self) -> usize {
self.nb_files
}
pub fn show(&self) {
let mut data = vec![format!("directory ({}) details:", self.path)];
for (mimetype, group) in &self.details {
let mut nb_files = 0;
let mut size = 0.0;
let mut to_display = vec![format!("* {}", mimetype)];
for file_range in group.keys() {
let file_group = &group[file_range];
to_display.push(format!(
"\t{:<8}{:<8}{}",
file_range.value(),
file_group.len(),
file_group.get_size_formatted()
));
nb_files += file_group.len();
size += file_group.get_size();
}
to_display[0] = format!(
"* {} ({} files, {})",
mimetype,
nb_files,
FileGroup::format_size(size)
);
data.push(to_display.join("\n"));
}
println!("{}", data.join("\n"));
}
pub fn get_file_group(
&self,
mimetype: Option<&FileImgMimetype>,
size_range: Option<&FileSizeRange>,
) -> FileGroup {
let mut file_group = FileGroup::new();
match (mimetype, size_range) {
(None, None) => {
return self.get_all();
}
(Some(mime), None) => {
if let Some(dict_file_range) = self.details.get(mime.value()) {
for fg in dict_file_range.values() {
file_group.join(fg);
}
}
}
(None, Some(size_range)) => {
for dict_file_range in self.details.values() {
if let Some(fg) = dict_file_range.get(size_range) {
file_group.join(fg);
}
}
}
(Some(mime), Some(size_range)) => {
if let Some(dict_file_range) = self.details.get(mime.value())
&& let Some(fg) = dict_file_range.get(size_range)
{
file_group.join(fg);
}
}
}
file_group
}
pub fn get_all(&self) -> FileGroup {
let mut file_group = FileGroup::new();
for details in self.details.values() {
for fg in details.values() {
file_group.join(fg);
}
}
file_group
}
}

3
src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
mod file;
pub use file::Directory;

View File

@ -1,9 +1,7 @@
use clap::Parser;
use env_logger::Env;
mod file;
use file::Directory;
use rs_optimg::Directory;
#[derive(Parser)]
struct Args {