add missing file mod + add lib.rs
This commit is contained in:
parent
2ca3c55092
commit
fff0d1b0dd
318
src/file.rs
Normal file
318
src/file.rs
Normal 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
3
src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod file;
|
||||
|
||||
pub use file::Directory;
|
||||
@ -1,9 +1,7 @@
|
||||
use clap::Parser;
|
||||
use env_logger::Env;
|
||||
|
||||
mod file;
|
||||
|
||||
use file::Directory;
|
||||
use rs_optimg::Directory;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user