mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 17:46:42 +01:00
add SIZE column and paths_under
This commit is contained in:
parent
1a00ef1ff6
commit
29435bb937
5 changed files with 87 additions and 65 deletions
9
Justfile
9
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'"
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
103
src/model.rs
103
src/model.rs
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue