Skip to content

Commit

Permalink
Fix deps
Browse files Browse the repository at this point in the history
  • Loading branch information
SamHDev committed Apr 10, 2021
1 parent b7d0fbd commit ec0af03
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 46 deletions.
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# sizeplz
A simple asynchronous recursive directory summarisation tool written in rust.

### Install
#### Download
Head over to the [releases] page and download a binary for your system.

#### Build
Building from source will require the rust chain, which can be installed using [rustup](https://rustup.rs)
```
git clone https://github.com/samhdev/sizeplz.git
cd sizeplz
cargo build
```

1 change: 1 addition & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use clap::{App, Arg};

// create a clang app for arg processing.
pub fn app<'x>() -> App<'x> {
App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
Expand Down
107 changes: 77 additions & 30 deletions src/calc.rs
Original file line number Diff line number Diff line change
@@ -1,133 +1,180 @@
use std::io::{ErrorKind, Result};
use std::path::{Path, PathBuf};

use futures::future::{BoxFuture, FutureExt};
use tokio::fs;
use std::io::{Result, ErrorKind};

use crate::metadata::get_metadata_size;
use crate::util::{convert_os_string_option, convert_time};
use std::path::{PathBuf, Path};
use futures::future::{BoxFuture, FutureExt};

// Get the size of a file from a path
// - catches permissions errors
async fn get_file_size(path: &Path) -> Result<u64> {
// catch
match catch_permission(path, fs::metadata(&path).await)? {
Some(meta) => Ok(get_metadata_size(&meta)),
None => Ok(0)
}
}

// Get the size of a directory
fn get_size(path: PathBuf) -> BoxFuture<'static, Result<u64>> {
// box future bs
async move {
// create result buffer/var
let mut result: u64 = 0;

let mut entries = if let Some(e) = catch_permission(&path, fs::read_dir(&path).await)? { e } else { return Ok(0) };
// get dir entries (while catching for permissions)
let mut entries = if let Some(e) = catch_permission(&path, fs::read_dir(&path).await)? { e } else { return Ok(0); };

// loop over entries
while let Ok(Some(entry)) = entries.next_entry().await {
// get entry path
let entry_path = entry.path();

// add to result
if entry_path.is_file() {
// get file size
result += get_file_size(&entry_path).await?;
} else if entry_path.is_dir() {
// recursive
result += get_size(entry_path).await?;
}
}

// return size
Ok(result)
}.boxed()
}

// Record object.
// Used for storing calc results to formatter.
#[derive(Debug)]
pub struct Record {
// path of the record
pub path: PathBuf,
// name of the record, used for output
pub name: String,
// size of the record.
pub size: u64,
// modified epoch, used in sort
pub modified: u64,
// created epoch, used in sort
pub created: u64,
// if the record is file or record
pub file: bool,
pub children: Vec<Record>
// children of record (if not file)
pub children: Vec<Record>,
}

// Read a folder returning results until depth limit.
pub(crate) fn handle_folder(path: PathBuf, depth: u16) -> BoxFuture<'static, Result<Record>> {
// async bs
async move {
// temp vars
let mut result: u64 = 0;
let mut children = Vec::new();

let mut children_tasks = Vec::new();

// get dir entries (while catching permission errors)
let mut entries = if let Some(e) = catch_permission(&path, fs::read_dir(&path).await)? { e } else {
// return empty record.
return Ok(Record {
name: convert_os_string_option(&path.file_name(), path.to_str().unwrap_or("?")),
path,
size: result,
modified: 0,
created: 0,
file: false,
children
})
children,
});
};

// iterate over entries
while let Ok(Some(entry)) = entries.next_entry().await {
// get path
let entry_path = entry.path();

// if file
if entry_path.is_file() {
let entry_meta = if let Some(x) = catch_permission(&entry_path, fs::metadata(&entry_path).await)? { x }
else { if depth != 0 {
children.push(Record {
path: entry_path.clone(),
name: convert_os_string_option(&entry_path.file_name(), "NAME"),
size: 0,
modified: 0,
created: 0,
file: true,
children: vec![]
})
}; continue; };

let file_size = get_metadata_size(&entry_meta);
result += file_size;
// get file meta (while catching permissions)
let entry_meta =
if let Some(x) = catch_permission(&entry_path, fs::metadata(&entry_path).await)? { x } else {
// return empty record.
if depth != 0 {
children.push(Record {
path: entry_path.clone(),
name: convert_os_string_option(&entry_path.file_name(), "NAME"),
size: 0,
modified: 0,
created: 0,
file: true,
children: vec![],
})
};
continue;
};

// add file size to result.
result += get_metadata_size(&entry_meta);

if depth != 0 {
// create file record.
children.push(Record {
path: entry_path.clone(),
name: convert_os_string_option(&entry_path.file_name(), "NAME"),
size: file_size,
modified: convert_time(entry_meta.modified()?),
created: convert_time(entry_meta.created()?),
file: true,
children: vec![]
children: vec![],
})
};

} else if entry_path.is_dir() {
if depth != 0 {
children_tasks.push(tokio::task::spawn(handle_folder(entry_path.to_path_buf(), depth - 1) ));
// offload recursive task to tokio task.
children_tasks.push(tokio::task::spawn(
handle_folder(entry_path.to_path_buf(), depth - 1)
));
} else {
// add to size.
result += get_size(entry_path.to_path_buf()).await?;
}
}
}

// wait for task completion
for task in children_tasks {
let child = task.await.unwrap()?;
result += child.size;
result += child.size; // add child size to total.
children.push(child);
}


// folder metadata.
let metadata = path.metadata()?;

// create record..
Ok(Record {
name: convert_os_string_option(&path.file_name(), path.to_str().unwrap_or("?")),
path,
size: result,
modified: convert_time(metadata.modified()?),
created: convert_time(metadata.created()?),
file: false,
children
children,
})
}.boxed()
}

// catch permission denied error.
pub fn catch_permission<T>(path: &Path, x: Result<T>) -> Result<Option<T>> {
match x {
Ok(x) => Ok(Some(x)),
Err(e) => match e.kind() {
ErrorKind::PermissionDenied => {println!("Permission Denied: {:?}", path); Ok(None)},
ErrorKind::PermissionDenied => {
println!("Permission Denied: {:?}", path);
Ok(None)
}
_ => Err(e)
}
}
Expand Down
37 changes: 34 additions & 3 deletions src/format.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::calc::Record;
use crate::sort::SortKey;

// units to format sizes in.
#[derive(Clone, PartialEq)]
pub enum FormatUnit {
Bytes,
Expand All @@ -13,6 +14,7 @@ pub enum FormatUnit {
}

impl FormatUnit {
// argument parsing
pub fn parse(x: &str) -> Option<FormatUnit> {
Some(match x.to_ascii_lowercase().trim() {
"b" => FormatUnit::Bytes,
Expand Down Expand Up @@ -52,6 +54,7 @@ impl FormatUnit {
})
}

// suffix to print
pub fn suffix(&self) -> &'static str {
match &self {
FormatUnit::Bytes => "bytes",
Expand All @@ -64,6 +67,7 @@ impl FormatUnit {
}
}

// div factor for calc
pub fn factor(&self) -> f64 {
match &self {
FormatUnit::Bytes => 0x1i64 as f64,
Expand All @@ -76,6 +80,7 @@ impl FormatUnit {
}
}

// when format is auto, get the best format for data.
pub fn autosize<'x>(&'x self, value: &u64) -> &'x Self {
if self == &Self::Auto {
if value < &0x400u64 {
Expand All @@ -96,34 +101,49 @@ impl FormatUnit {
}
}

// calculate with floating point fuck ups.
pub fn calc(&self, value: u64, round: &RoundFactor) -> f64 {
round.calc((value as f64) / self.factor())
}

// calculate without rounding fuck ups.
pub fn string(&self, value: u64, round: &RoundFactor) -> String {
round.string((value as f64) / self.factor())
}

}

#[derive(Clone)]
// struct to move rounding args.
pub struct RoundFactor(f64, usize);

impl RoundFactor {
pub fn parse(p: i8) -> Self {
Self(10f64.powi(p as i32), if p < 0 { 0 } else { p } as usize)
// argument parsing.
pub fn parse(p: i8) -> Self
{
Self(
// div/mul ratio.
10f64.powi(p as i32),
// format arg.
if p < 0 { 0 } else { p } as usize
)
}

// calculate rounding without format.
pub fn calc(&self, value: f64) -> f64 {
(value * self.0).round() / self.0
}

// calculate rounding with format.
pub fn string(&self, value: f64) -> String {
format!("{:.1$}", self.calc(value), self.1)
}
}


// format a single record.
fn format_record(mut record: Record, indent: usize, last: bool, pad: &usize, format: &FormatUnit, round: &RoundFactor, files: &bool, empty: &bool, sort: &SortKey, invert: &bool) {
// tree code.
let mut ind = String::new();
if indent != 0 {
ind.push_str(&"│ ".repeat(indent - 1));
Expand All @@ -134,24 +154,35 @@ fn format_record(mut record: Record, indent: usize, last: bool, pad: &usize, for
}
}

// format autosize
let form = format.autosize(&record.size);

// print
println!("{0}{1: <2$} {3: >4} {4}", ind, record.name, pad, form.string(record.size, round), form.suffix());

// sort children
sort.sort(&mut record.children);
// reverse children
if *invert { record.children.reverse(); }

// calc max width
let mut max_size = 0;
for child in &record.children { if child.name.len() > max_size { max_size = child.name.len(); }}
if max_size > 48 { max_size = 48; }
if max_size > 64 { max_size = 64; }

// print children
let length = record.children.len();
for (i, child) in record.children.into_iter().enumerate() {
// check flags
if child.file && !*files { continue; }
if child.size == 0 && *empty { continue; }

// print
format_record(child, indent + 1, i + 1 == length, &max_size, format, round, files, empty, sort, invert);
}
}

// initial record util func.
pub fn initial_record(record: Record, format: &FormatUnit, round: &RoundFactor, files: &bool, empty: &bool, sort: &SortKey, invert: &bool) {
let len = record.name.len();
format_record(record, 0, true, &len, format, round, files, empty, sort, invert)
Expand Down
Loading

0 comments on commit ec0af03

Please sign in to comment.