diff --git a/Justfile b/Justfile index 14fe25d..713cc8f 100644 --- a/Justfile +++ b/Justfile @@ -1,3 +1,8 @@ +run: + cargo run -- -j16 ~/Music +build-release: + time cargo build -j4 --release + covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cov/cargo-test-%p-%m.profraw'" grcov-binary := "--binary-path ./target/coverage/deps/" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" @@ -14,7 +19,3 @@ cov-md-ci: doc: cargo doc -build-release: - time cargo build -j4 --release -run: - cargo run -- -j16 ~/Music diff --git a/src/keys.rs b/src/keys.rs index b7f7d52..ac83715 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -23,7 +23,7 @@ macro_rules! press { impl Handle for Taggart { fn handle (&mut self, input: &TuiIn) -> Perhaps { let x_min = self.offset; - let x_max = self.offset + self.size.h().saturating_sub(1); + let x_max = self.offset + self.display.h().saturating_sub(1); let event = &*input.event(); match &self.editing { None => match event { diff --git a/src/main.rs b/src/main.rs index beb168e..2f15b80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,6 @@ use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventSt use clap::{arg, command, value_parser}; use walkdir::WalkDir; use xxhash_rust::xxh3::xxh3_64; -use file_type::FileType; - mod keys; mod view; use self::view::*; mod model; pub(crate) use self::model::*; diff --git a/src/model.rs b/src/model.rs index 21caaec..77d72ab 100644 --- a/src/model.rs +++ b/src/model.rs @@ -2,16 +2,17 @@ use crate::*; use std::fs::File; use std::io::{BufReader, Read}; use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; +use byte_unit::{Byte, Unit::MB}; pub struct Taggart { - pub _root: PathBuf, - pub paths: Vec, - pub cursor: usize, - pub offset: usize, - pub column: usize, - pub columns: Columns, - pub size: Measure, - pub editing: Option<(usize, String)>, + pub _root: PathBuf, + pub paths: Vec, + pub cursor: usize, + pub offset: usize, + pub column: usize, + pub columns: Columns, + pub display: Measure, + pub editing: Option<(usize, String)>, } #[derive(Ord, Eq, PartialEq, PartialOrd, Debug)] @@ -31,7 +32,7 @@ pub enum EntryInfo { }, Music { hash: Arc, - file_type: Option<&'static FileType>, + size: Arc, artist: Option>, album: Option>, track: Option, @@ -46,14 +47,14 @@ pub enum EntryInfo { }, Image { hash: Arc, - file_type: Option<&'static FileType>, + size: Arc, title: Option, author: Option, invalid: bool, }, Unknown { hash: Arc, - file_type: Option<&'static FileType>, + size: Arc, } } @@ -63,8 +64,8 @@ impl Taggart { _root: root.as_ref().into(), cursor: 0, offset: 0, - column: 0, - size: Measure::new(), + column: 2, + display: Measure::new(), editing: None, columns: Columns::default(), paths, @@ -100,59 +101,70 @@ impl Column { pub struct Columns(pub Vec>); +fn paths_under (entries: &mut [Entry], index: usize) -> Option> { + let path = if let Some(Entry { + path, info: EntryInfo::Directory { .. }, .. + }) = entries.get(index) { + Some(path.clone()) + } else { + None + }; + if let Some(path) = path { + let mut others = vec![]; + for other in entries.iter_mut() { + if other.path.starts_with(&path) && !other.is_dir() { + others.push(other); + } + } + Some(others) + } else { + None + } +} + impl Default for Columns { fn default () -> Self { Self(vec![ Column::new(&"HASH", 16, |entry: &Entry|entry.hash()), + Column::new(&"SIZE", 8, |entry: &Entry|entry.size()), + Column::new(&"FILE", 80, |entry: &Entry|entry.name()), Column::new(&"ARTIST", 30, |entry: &Entry|entry.artist()) .setter(|entries: &mut [Entry], index: usize, value: &str|{ - if let Some(entry) = entries.get_mut(index) { - match entry.info { - EntryInfo::Directory { .. } => { - todo!("set artist for whole directory") - }, - _ => entry.set_artist(&value) - } + if let Some(entries) = paths_under(entries, index) { + todo!("set artist for whole directory {:#?}", entries); + } else if let Some(entry) = entries.get_mut(index) { + entry.set_artist(&value) } }), Column::new(&"RELEASE", 30, |entry: &Entry|entry.album()) .setter(|entries: &mut [Entry], index: usize, value: &str|{ - if let Some(entry) = entries.get_mut(index) { - match entry.info { - EntryInfo::Directory { .. } => { - todo!("set album for whole directory") - }, - _ => entry.set_album(&value) - } + if let Some(entries) = paths_under(entries, index) { + todo!("set album for whole directory {:#?}", entries); + } else if let Some(entry) = entries.get_mut(index) { + entry.set_album(&value) } }), Column::new(&"TRACK", 5, |entry: &Entry|entry.track()) .setter(|entries: &mut [Entry], index: usize, value: &str|{ - if let Some(entry) = entries.get_mut(index) { - match entry.info { - EntryInfo::Directory { .. } => { - todo!("set title for whole directory") - }, - _ => entry.set_track(&value) - } + if let Some(entries) = paths_under(entries, index) { + todo!("set track for whole directory {:#?}", entries); + } else if let Some(entry) = entries.get_mut(index) { + entry.set_track(&value) } }), Column::new(&"TITLE", 80, |entry: &Entry|entry.title()) .setter(|entries: &mut [Entry], index: usize, value: &str|{ - if let Some(entry) = entries.get_mut(index) { - match entry.info { - EntryInfo::Directory { .. } => { - todo!("set track for whole directory") - }, - _ => entry.set_title(&value) - } + if let Some(entries) = paths_under(entries, index) { + todo!("set title for whole directory {:#?}", entries); + } else if let Some(entry) = entries.get_mut(index) { + entry.set_title(&value) } }), @@ -206,6 +218,13 @@ impl Entry { _ => None } } + pub fn size (&self) -> Option> { + match self.info { + EntryInfo::Image { ref size, .. } => Some(size.clone()), + EntryInfo::Music { ref size, .. } => Some(size.clone()), + _ => None + } + } pub fn artist (&self) -> Option> { match self.info { EntryInfo::Music { ref artist, .. } => artist.clone(), @@ -269,10 +288,12 @@ impl EntryInfo { if probe.file_type().is_some() { let file = lofty::read_from_path(path)?; let tag = file.primary_tag(); - let hash = hex::encode(xxh3_64(std::fs::read(path)?.as_slice()).to_be_bytes()).into(); + let data = std::fs::read(path)?; + let hash = hex::encode(xxh3_64(data.as_slice()).to_be_bytes()).into(); + let size = Byte::from_u64(data.len() as u64).get_adjusted_unit(MB); Ok(Self::Music { hash, - file_type: None, + size: format!("{:#>8.2}", size).into(), artist: tag.map(|t|t.artist().map(|t|t.into())).flatten(), album: tag.map(|t|t.album().map(|t|t.into())).flatten(), track: tag.map(|t|t.track().map(|t|t.into())).flatten(), @@ -290,7 +311,9 @@ impl EntryInfo { } } pub fn new_fallback (path: &Path) -> Usually { - let mut reader = BufReader::new(File::open(path)?); + let file = File::open(path)?; + let size = Byte::from_u64(file.metadata()?.len() as u64).get_adjusted_unit(MB); + let mut reader = BufReader::new(file); let mut bytes = vec![0;16]; reader.read(&mut bytes)?; // PNG @@ -298,11 +321,11 @@ impl EntryInfo { let mut bytes = vec![]; BufReader::new(File::open(path)?).read(&mut bytes)?; return Ok(Self::Image { - file_type: None, - hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), - title: None, - author: None, - invalid: false, + hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), + size: format!("{:#>8.2}", size).into(), + title: None, + author: None, + invalid: false, }) } // JPG @@ -317,16 +340,16 @@ impl EntryInfo { bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00)) { return Ok(Self::Image { - file_type: None, - hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), - title: None, - author: None, - invalid: false, + hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), + size: format!("{:#>8.2}", size).into(), + title: None, + author: None, + invalid: false, }) } Ok(Self::Unknown { - file_type: None, - hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), + size: format!("{:#>8.2}", size).into(), + hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), }) } } diff --git a/src/view.rs b/src/view.rs index 54206de..29e69c6 100644 --- a/src/view.rs +++ b/src/view.rs @@ -14,8 +14,8 @@ impl Taggart { impl Content for Taggart { fn content (&self) -> impl Render { - let sizer = Fill::xy(&self.size); - let size = format!("{}x{}", self.size.w(), self.size.h()); + let sizer = Fill::xy(&self.display); + let size = format!("{}x{}", self.display.w(), self.display.h()); let titlebar = status_bar(Align::w(self.columns.header())); let size_bar = status_bar(Fill::x(Bsp::a( Fill::x(Align::w(Tui::bold(true, if self.editing.is_some() {