add SIZE column and paths_under

This commit is contained in:
🪞👃🪞 2025-03-23 18:14:09 +02:00
parent 1a00ef1ff6
commit 29435bb937
5 changed files with 87 additions and 65 deletions

View file

@ -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'" 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-binary := "--binary-path ./target/coverage/deps/"
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
@ -14,7 +19,3 @@ cov-md-ci:
doc: doc:
cargo doc cargo doc
build-release:
time cargo build -j4 --release
run:
cargo run -- -j16 ~/Music

View file

@ -23,7 +23,7 @@ macro_rules! press {
impl Handle<TuiIn> for Taggart { impl Handle<TuiIn> for Taggart {
fn handle (&mut self, input: &TuiIn) -> Perhaps<bool> { fn handle (&mut self, input: &TuiIn) -> Perhaps<bool> {
let x_min = self.offset; 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(); let event = &*input.event();
match &self.editing { match &self.editing {
None => match event { None => match event {

View file

@ -15,8 +15,6 @@ use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventSt
use clap::{arg, command, value_parser}; use clap::{arg, command, value_parser};
use walkdir::WalkDir; use walkdir::WalkDir;
use xxhash_rust::xxh3::xxh3_64; use xxhash_rust::xxh3::xxh3_64;
use file_type::FileType;
mod keys; mod keys;
mod view; use self::view::*; mod view; use self::view::*;
mod model; pub(crate) use self::model::*; mod model; pub(crate) use self::model::*;

View file

@ -2,6 +2,7 @@ use crate::*;
use std::fs::File; use std::fs::File;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor};
use byte_unit::{Byte, Unit::MB};
pub struct Taggart { pub struct Taggart {
pub _root: PathBuf, pub _root: PathBuf,
@ -10,7 +11,7 @@ pub struct Taggart {
pub offset: usize, pub offset: usize,
pub column: usize, pub column: usize,
pub columns: Columns<Entry>, pub columns: Columns<Entry>,
pub size: Measure<TuiOut>, pub display: Measure<TuiOut>,
pub editing: Option<(usize, String)>, pub editing: Option<(usize, String)>,
} }
@ -31,7 +32,7 @@ pub enum EntryInfo {
}, },
Music { Music {
hash: Arc<str>, hash: Arc<str>,
file_type: Option<&'static FileType>, size: Arc<str>,
artist: Option<Arc<str>>, artist: Option<Arc<str>>,
album: Option<Arc<str>>, album: Option<Arc<str>>,
track: Option<u32>, track: Option<u32>,
@ -46,14 +47,14 @@ pub enum EntryInfo {
}, },
Image { Image {
hash: Arc<str>, hash: Arc<str>,
file_type: Option<&'static FileType>, size: Arc<str>,
title: Option<String>, title: Option<String>,
author: Option<String>, author: Option<String>,
invalid: bool, invalid: bool,
}, },
Unknown { Unknown {
hash: Arc<str>, hash: Arc<str>,
file_type: Option<&'static FileType>, size: Arc<str>,
} }
} }
@ -63,8 +64,8 @@ impl Taggart {
_root: root.as_ref().into(), _root: root.as_ref().into(),
cursor: 0, cursor: 0,
offset: 0, offset: 0,
column: 0, column: 2,
size: Measure::new(), display: Measure::new(),
editing: None, editing: None,
columns: Columns::default(), columns: Columns::default(),
paths, paths,
@ -100,59 +101,70 @@ impl<T> Column<T> {
pub struct Columns<T>(pub Vec<Column<T>>); pub struct Columns<T>(pub Vec<Column<T>>);
fn paths_under (entries: &mut [Entry], index: usize) -> Option<Vec<&mut Entry>> {
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<Entry> { impl Default for Columns<Entry> {
fn default () -> Self { fn default () -> Self {
Self(vec![ Self(vec![
Column::new(&"HASH", 16, |entry: &Entry|entry.hash()), 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(&"FILE", 80, |entry: &Entry|entry.name()),
Column::new(&"ARTIST", 30, |entry: &Entry|entry.artist()) Column::new(&"ARTIST", 30, |entry: &Entry|entry.artist())
.setter(|entries: &mut [Entry], index: usize, value: &str|{ .setter(|entries: &mut [Entry], index: usize, value: &str|{
if let Some(entry) = entries.get_mut(index) { if let Some(entries) = paths_under(entries, index) {
match entry.info { todo!("set artist for whole directory {:#?}", entries);
EntryInfo::Directory { .. } => { } else if let Some(entry) = entries.get_mut(index) {
todo!("set artist for whole directory") entry.set_artist(&value)
},
_ => entry.set_artist(&value)
}
} }
}), }),
Column::new(&"RELEASE", 30, |entry: &Entry|entry.album()) Column::new(&"RELEASE", 30, |entry: &Entry|entry.album())
.setter(|entries: &mut [Entry], index: usize, value: &str|{ .setter(|entries: &mut [Entry], index: usize, value: &str|{
if let Some(entry) = entries.get_mut(index) { if let Some(entries) = paths_under(entries, index) {
match entry.info { todo!("set album for whole directory {:#?}", entries);
EntryInfo::Directory { .. } => { } else if let Some(entry) = entries.get_mut(index) {
todo!("set album for whole directory") entry.set_album(&value)
},
_ => entry.set_album(&value)
}
} }
}), }),
Column::new(&"TRACK", 5, |entry: &Entry|entry.track()) Column::new(&"TRACK", 5, |entry: &Entry|entry.track())
.setter(|entries: &mut [Entry], index: usize, value: &str|{ .setter(|entries: &mut [Entry], index: usize, value: &str|{
if let Some(entry) = entries.get_mut(index) { if let Some(entries) = paths_under(entries, index) {
match entry.info { todo!("set track for whole directory {:#?}", entries);
EntryInfo::Directory { .. } => { } else if let Some(entry) = entries.get_mut(index) {
todo!("set title for whole directory") entry.set_track(&value)
},
_ => entry.set_track(&value)
}
} }
}), }),
Column::new(&"TITLE", 80, |entry: &Entry|entry.title()) Column::new(&"TITLE", 80, |entry: &Entry|entry.title())
.setter(|entries: &mut [Entry], index: usize, value: &str|{ .setter(|entries: &mut [Entry], index: usize, value: &str|{
if let Some(entry) = entries.get_mut(index) { if let Some(entries) = paths_under(entries, index) {
match entry.info { todo!("set title for whole directory {:#?}", entries);
EntryInfo::Directory { .. } => { } else if let Some(entry) = entries.get_mut(index) {
todo!("set track for whole directory") entry.set_title(&value)
},
_ => entry.set_title(&value)
}
} }
}), }),
@ -206,6 +218,13 @@ impl Entry {
_ => None _ => None
} }
} }
pub fn size (&self) -> Option<Arc<str>> {
match self.info {
EntryInfo::Image { ref size, .. } => Some(size.clone()),
EntryInfo::Music { ref size, .. } => Some(size.clone()),
_ => None
}
}
pub fn artist (&self) -> Option<Arc<str>> { pub fn artist (&self) -> Option<Arc<str>> {
match self.info { match self.info {
EntryInfo::Music { ref artist, .. } => artist.clone(), EntryInfo::Music { ref artist, .. } => artist.clone(),
@ -269,10 +288,12 @@ impl EntryInfo {
if probe.file_type().is_some() { if probe.file_type().is_some() {
let file = lofty::read_from_path(path)?; let file = lofty::read_from_path(path)?;
let tag = file.primary_tag(); 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 { Ok(Self::Music {
hash, hash,
file_type: None, size: format!("{:#>8.2}", size).into(),
artist: tag.map(|t|t.artist().map(|t|t.into())).flatten(), artist: tag.map(|t|t.artist().map(|t|t.into())).flatten(),
album: tag.map(|t|t.album().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(), track: tag.map(|t|t.track().map(|t|t.into())).flatten(),
@ -290,7 +311,9 @@ impl EntryInfo {
} }
} }
pub fn new_fallback (path: &Path) -> Usually<Self> { pub fn new_fallback (path: &Path) -> Usually<Self> {
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]; let mut bytes = vec![0;16];
reader.read(&mut bytes)?; reader.read(&mut bytes)?;
// PNG // PNG
@ -298,8 +321,8 @@ impl EntryInfo {
let mut bytes = vec![]; let mut bytes = vec![];
BufReader::new(File::open(path)?).read(&mut bytes)?; BufReader::new(File::open(path)?).read(&mut bytes)?;
return Ok(Self::Image { return Ok(Self::Image {
file_type: None,
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
size: format!("{:#>8.2}", size).into(),
title: None, title: None,
author: None, author: None,
invalid: false, invalid: false,
@ -317,15 +340,15 @@ impl EntryInfo {
bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00)) bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00))
{ {
return Ok(Self::Image { return Ok(Self::Image {
file_type: None,
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
size: format!("{:#>8.2}", size).into(),
title: None, title: None,
author: None, author: None,
invalid: false, invalid: false,
}) })
} }
Ok(Self::Unknown { Ok(Self::Unknown {
file_type: None, size: format!("{:#>8.2}", size).into(),
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
}) })
} }

View file

@ -14,8 +14,8 @@ impl Taggart {
impl Content<TuiOut> for Taggart { impl Content<TuiOut> for Taggart {
fn content (&self) -> impl Render<TuiOut> { fn content (&self) -> impl Render<TuiOut> {
let sizer = Fill::xy(&self.size); let sizer = Fill::xy(&self.display);
let size = format!("{}x{}", self.size.w(), self.size.h()); let size = format!("{}x{}", self.display.w(), self.display.h());
let titlebar = status_bar(Align::w(self.columns.header())); let titlebar = status_bar(Align::w(self.columns.header()));
let size_bar = status_bar(Fill::x(Bsp::a( let size_bar = status_bar(Fill::x(Bsp::a(
Fill::x(Align::w(Tui::bold(true, if self.editing.is_some() { Fill::x(Align::w(Tui::bold(true, if self.editing.is_some() {