From cf18e32e133483dcaa49f81ec1e356d0cd0ccafa Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 23 Mar 2025 19:00:45 +0200 Subject: [PATCH] refactor; EntryInfo -> Metadata --- src/model.rs | 359 ++---------------------------------------- src/model/column.rs | 89 +++++++++++ src/model/entry.rs | 100 ++++++++++++ src/model/metadata.rs | 185 ++++++++++++++++++++++ src/view/table.rs | 2 +- 5 files changed, 384 insertions(+), 351 deletions(-) create mode 100644 src/model/column.rs create mode 100644 src/model/entry.rs create mode 100644 src/model/metadata.rs diff --git a/src/model.rs b/src/model.rs index 457299d..6403b7a 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,9 +1,8 @@ use crate::*; -use std::fs::File; -use std::io::{BufReader, Read}; -use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering}; -use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; -use byte_unit::{Byte, Unit::MB}; + +mod column; pub use self::column::*; +mod entry; pub use self::entry::*; +mod metadata; pub use self::metadata::*; pub struct Taggart { pub _root: PathBuf, @@ -11,71 +10,11 @@ pub struct Taggart { pub cursor: usize, pub offset: usize, pub column: usize, - pub columns: Columns, + pub columns: ColumnsOption>, fn(&mut [Entry], usize, &str)>, pub display: Measure, pub editing: Option<(usize, String)>, } -#[derive(Debug)] -pub struct Entry { - pub path: PathBuf, - pub depth: usize, - pub info: Arc>, -} - -impl Eq for Entry {} -impl PartialEq for Entry { - fn eq (&self, other: &Self) -> bool { - self.path.eq(&other.path) - } -} -impl Ord for Entry { - fn cmp (&self, other: &Self) -> Ordering { - self.path.cmp(&other.path) - } -} -impl PartialOrd for Entry { - fn partial_cmp (&self, other: &Self) -> Option { - self.path.partial_cmp(&other.path) - } -} - -#[derive(Ord, Eq, PartialEq, PartialOrd, Debug)] -pub enum EntryInfo { - Directory { - hash_file: Option<()>, - catalog_file: Option<()>, - artist_file: Option<()>, - release_file: Option<()>, - }, - Music { - hash: Arc, - size: Arc, - artist: Option>, - album: Option>, - track: Option, - title: Option>, - date: Option>, - year: Option, - people: Option>>, - publisher: Option>, - key: Option>, - bpm: Option>, - invalid: bool, - }, - Image { - hash: Arc, - size: Arc, - title: Option, - author: Option, - invalid: bool, - }, - Unknown { - hash: Arc, - size: Arc, - } -} - impl Taggart { pub fn new (root: &impl AsRef, paths: Vec) -> Usually { Ok(Self { @@ -91,37 +30,11 @@ impl Taggart { } } -pub struct Column { - pub title: Arc, - pub width: usize, - pub getter: fn(&T)->Option>, - pub setter: Option, -} - -impl Column { - pub fn new ( - title: &impl AsRef, - width: usize, - getter: fn(&T)->Option>, - ) -> Self { - Self { - width, - title: title.as_ref().into(), - getter, - setter: None, - } - } - fn setter (mut self, setter: fn(&mut [T], usize, &str)) -> Self { - self.setter = Some(setter); - self - } -} - -pub struct Columns(pub Vec>); - -fn paths_under (entries: &mut [Entry], index: usize) -> Option>>> { +pub(crate) fn paths_under ( + entries: &mut [Entry], index: usize +) -> Option>>> { let path = if let Some(Entry { path, info, .. }) = entries.get(index) - && let EntryInfo::Directory { .. } = &*info.read().unwrap() + && let Metadata::Directory { .. } = &*info.read().unwrap() { Some(path.clone()) } else { @@ -139,257 +52,3 @@ fn paths_under (entries: &mut [Entry], index: usize) -> Option { - 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(entries) = paths_under(entries, index) { - for entry in entries.iter() { - entry.write().unwrap().set_artist(&value); - } - } 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(entries) = paths_under(entries, index) { - for entry in entries.iter() { - entry.write().unwrap().set_album(&value); - } - } 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(entries) = paths_under(entries, index) { - for entry in entries.iter() { - entry.write().unwrap().set_track(&value); - } - } 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(entries) = paths_under(entries, index) { - for entry in entries.iter() { - entry.write().unwrap().set_title(&value); - } - } else if let Some(entry) = entries.get_mut(index) { - entry.set_title(&value) - } - }), - - ]) - } -} - -impl Entry { - pub fn new (root: &impl AsRef, path: &impl AsRef, depth: usize) -> Perhaps { - let path = path.as_ref(); - if path.is_dir() { - Self::new_dir(root, &path, depth) - } else if path.is_file() { - Self::new_file(root, &path, depth) - } else { - Ok(None) - } - } - fn new_dir (_: &impl AsRef, path: &Path, depth: usize) -> Perhaps { - Ok(Some(Self { - depth, - path: path.into(), - info: Arc::new(RwLock::new(EntryInfo::Directory { - hash_file: None, - catalog_file: None, - artist_file: None, - release_file: None, - })), - })) - } - fn new_file (_: &impl AsRef, path: &Path, depth: usize) -> Perhaps { - Ok(Some(Self { - depth, - info: Arc::new(RwLock::new(EntryInfo::new(path)?)), - path: path.into(), - })) - } - pub fn is_dir (&self) -> bool { - matches!(&*self.info.read().unwrap(), EntryInfo::Directory { .. }) - } - pub fn is_mus (&self) -> bool { - matches!(&*self.info.read().unwrap(), EntryInfo::Music { .. }) - } - pub fn is_img (&self) -> bool { - matches!(&*self.info.read().unwrap(), EntryInfo::Image { .. }) - } - pub fn hash (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Image { hash, .. } => Some(hash.clone()), - EntryInfo::Music { hash, .. } => Some(hash.clone()), - EntryInfo::Unknown { hash, .. } => Some(hash.clone()), - _ => None - } - } - pub fn size (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Image { size, .. } => Some(size.clone()), - EntryInfo::Music { size, .. } => Some(size.clone()), - EntryInfo::Unknown { size, .. } => Some(size.clone()), - _ => None - } - } - pub fn artist (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Music { artist, .. } => artist.clone(), - _ => None - } - } - pub fn album (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Music { album, .. } => album.clone(), - _ => None - } - } - pub fn title (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Music { title, .. } => title.clone(), - _ => None - } - } - pub fn track (&self) -> Option> { - match &*self.info.read().unwrap() { - EntryInfo::Music { track, .. } => track.map(|t|format!("{t}").into()).clone(), - _ => None - } - } - pub fn set_artist (&self, value: &impl AsRef ) { - self.info.write().unwrap().set_artist(value) - } - pub fn set_album (&self, value: &impl AsRef ) { - self.info.write().unwrap().set_album(value) - } - pub fn set_title (&self, value: &impl AsRef ) { - self.info.write().unwrap().set_title(value) - } - pub fn set_track (&self, value: &impl AsRef ) { - self.info.write().unwrap().set_track(value) - } -} -impl EntryInfo { - pub fn new (path: &Path) -> Usually { - let reader = BufReader::new(File::open(path)?); - let probe = Probe::new(reader) - //.options(ParseOptions::new().parsing_mode(ParsingMode::Strict)) - .guess_file_type()?; - if probe.file_type().is_some() { - let file = lofty::read_from_path(path)?; - let tag = file.primary_tag(); - 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, - 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(), - title: tag.map(|t|t.title().map(|t|t.into())).flatten(), - year: tag.map(|t|t.year().map(|t|t.into())).flatten(), - date: None, - people: None, - publisher: None, - key: None, - bpm: None, - invalid: false, - }) - } else { - Self::new_fallback(path) - } - } - pub fn new_fallback (path: &Path) -> Usually { - 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 - if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) { - let mut bytes = vec![]; - BufReader::new(File::open(path)?).read(&mut bytes)?; - return Ok(Self::Image { - hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), - size: format!("{:#>8.2}", size).into(), - title: None, - author: None, - invalid: false, - }) - } - // JPG - if bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xDB]) - || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE0, - 0x00, 0x10, 0x4A, 0x46, - 0x49, 0x46, 0x00, 0x01]) - || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xEE]) - || (bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE1]) && - bytes.get(6) == Some(&0x45) && bytes.get(7) == Some(&0x78) && - bytes.get(8) == Some(&0x69) && bytes.get(9) == Some(&0x66) && - bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00)) - { - return Ok(Self::Image { - 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 { - size: format!("{:#>8.2}", size).into(), - hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), - }) - } - pub fn set_artist (&mut self, value: &impl AsRef ) { - match self { - EntryInfo::Music { artist, .. } => *artist = Some(value.as_ref().into()), - _ => {} - } - } - pub fn set_album (&mut self, value: &impl AsRef ) { - match self { - EntryInfo::Music { album, .. } => *album = Some(value.as_ref().into()), - _ => {} - } - } - pub fn set_title (&mut self, value: &impl AsRef ) { - match self { - EntryInfo::Music { title, .. } => *title = Some(value.as_ref().into()), - _ => {} - } - } - pub fn set_track (&mut self, value: &impl AsRef ) { - match self { - EntryInfo::Directory { .. } => todo!("set track for whole directory"), - EntryInfo::Music { track, .. } => { - if let Ok(value) = value.as_ref().trim().parse::() { - *track = Some(value) - } - }, - _ => {} - } - } -} diff --git a/src/model/column.rs b/src/model/column.rs new file mode 100644 index 0000000..635e38c --- /dev/null +++ b/src/model/column.rs @@ -0,0 +1,89 @@ +use crate::*; + +pub struct Columns(pub Vec>); + +pub struct Column { + __: std::marker::PhantomData, + pub title: Arc, + pub width: usize, + pub getter: G, + pub setter: Option, +} + +impl ColumnOption>, fn(&mut [T], usize, &str)> { + pub fn new ( + title: &impl AsRef, + width: usize, + getter: fn(&T)->Option>, + ) -> Self { + Self { + width, + title: title.as_ref().into(), + getter, + setter: None, + __: Default::default(), + } + } + fn setter (mut self, setter: fn(&mut [T], usize, &str)) -> Self { + self.setter = Some(setter); + self + } +} + +impl Default for ColumnsOption>, fn(&mut [Entry], usize, &str)> { + 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(entries) = paths_under(entries, index) { + for entry in entries.iter() { + entry.write().unwrap().set_artist(&value); + } + } 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(entries) = paths_under(entries, index) { + for entry in entries.iter() { + entry.write().unwrap().set_album(&value); + } + } 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(entries) = paths_under(entries, index) { + for entry in entries.iter() { + entry.write().unwrap().set_track(&value); + } + } 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(entries) = paths_under(entries, index) { + for entry in entries.iter() { + entry.write().unwrap().set_title(&value); + } + } else if let Some(entry) = entries.get_mut(index) { + entry.set_title(&value) + } + }), + + ]) + } +} diff --git a/src/model/entry.rs b/src/model/entry.rs new file mode 100644 index 0000000..974d6cd --- /dev/null +++ b/src/model/entry.rs @@ -0,0 +1,100 @@ +use crate::*; +use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering}; + +#[derive(Debug)] +pub struct Entry { + pub path: PathBuf, + pub depth: usize, + pub info: Arc>, +} + +impl Entry { + pub fn new (root: &impl AsRef, path: &impl AsRef, depth: usize) -> Perhaps { + let path = path.as_ref(); + if path.is_dir() { + Self::new_dir(root, &path, depth) + } else if path.is_file() { + Self::new_file(root, &path, depth) + } else { + Ok(None) + } + } + fn new_dir (_: &impl AsRef, path: &Path, depth: usize) -> Perhaps { + Ok(Some(Self { + depth, + path: path.into(), + info: Arc::new(RwLock::new(Metadata::Directory { + hash_file: None, + catalog_file: None, + artist_file: None, + release_file: None, + })), + })) + } + fn new_file (_: &impl AsRef, path: &Path, depth: usize) -> Perhaps { + Ok(Some(Self { + depth, + info: Arc::new(RwLock::new(Metadata::new(path)?)), + path: path.into(), + })) + } + pub fn is_dir (&self) -> bool { + matches!(&*self.info.read().unwrap(), Metadata::Directory { .. }) + } + pub fn is_mus (&self) -> bool { + matches!(&*self.info.read().unwrap(), Metadata::Music { .. }) + } + pub fn is_img (&self) -> bool { + matches!(&*self.info.read().unwrap(), Metadata::Image { .. }) + } + pub fn hash (&self) -> Option> { + self.info.read().unwrap().hash() + } + pub fn size (&self) -> Option> { + self.info.read().unwrap().size() + } + pub fn artist (&self) -> Option> { + self.info.read().unwrap().artist() + } + pub fn album (&self) -> Option> { + self.info.read().unwrap().album() + } + pub fn title (&self) -> Option> { + self.info.read().unwrap().title() + } + pub fn track (&self) -> Option> { + self.info.read().unwrap().track() + } + pub fn set_artist (&self, value: &impl AsRef ) { + self.info.write().unwrap().set_artist(value) + } + pub fn set_album (&self, value: &impl AsRef ) { + self.info.write().unwrap().set_album(value) + } + pub fn set_title (&self, value: &impl AsRef ) { + self.info.write().unwrap().set_title(value) + } + pub fn set_track (&self, value: &impl AsRef ) { + self.info.write().unwrap().set_track(value) + } +} + +impl Eq for Entry {} + +impl PartialEq for Entry { + fn eq (&self, other: &Self) -> bool { + self.path.eq(&other.path) + } +} + +impl Ord for Entry { + fn cmp (&self, other: &Self) -> Ordering { + self.path.cmp(&other.path) + } +} + +impl PartialOrd for Entry { + fn partial_cmp (&self, other: &Self) -> Option { + self.path.partial_cmp(&other.path) + } +} diff --git a/src/model/metadata.rs b/src/model/metadata.rs new file mode 100644 index 0000000..cae2ee3 --- /dev/null +++ b/src/model/metadata.rs @@ -0,0 +1,185 @@ +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}; + +#[derive(Ord, Eq, PartialEq, PartialOrd, Debug)] +pub enum Metadata { + Directory { + hash_file: Option<()>, + catalog_file: Option<()>, + artist_file: Option<()>, + release_file: Option<()>, + }, + Music { + hash: Arc, + size: Arc, + artist: Option>, + album: Option>, + track: Option, + title: Option>, + date: Option>, + year: Option, + people: Option>>, + publisher: Option>, + key: Option>, + bpm: Option>, + invalid: bool, + }, + Image { + hash: Arc, + size: Arc, + title: Option, + author: Option, + invalid: bool, + }, + Unknown { + hash: Arc, + size: Arc, + } +} + +impl Metadata { + pub fn new (path: &Path) -> Usually { + let reader = BufReader::new(File::open(path)?); + let probe = Probe::new(reader) + //.options(ParseOptions::new().parsing_mode(ParsingMode::Strict)) + .guess_file_type()?; + if probe.file_type().is_some() { + let file = lofty::read_from_path(path)?; + let tag = file.primary_tag(); + 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, + 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(), + title: tag.map(|t|t.title().map(|t|t.into())).flatten(), + year: tag.map(|t|t.year().map(|t|t.into())).flatten(), + date: None, + people: None, + publisher: None, + key: None, + bpm: None, + invalid: false, + }) + } else { + Self::new_fallback(path) + } + } + pub fn new_fallback (path: &Path) -> Usually { + 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 + if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) { + let mut bytes = vec![]; + BufReader::new(File::open(path)?).read(&mut bytes)?; + return Ok(Self::Image { + hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), + size: format!("{:#>8.2}", size).into(), + title: None, + author: None, + invalid: false, + }) + } + // JPG + if bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xDB]) + || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE0, + 0x00, 0x10, 0x4A, 0x46, + 0x49, 0x46, 0x00, 0x01]) + || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xEE]) + || (bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE1]) && + bytes.get(6) == Some(&0x45) && bytes.get(7) == Some(&0x78) && + bytes.get(8) == Some(&0x69) && bytes.get(9) == Some(&0x66) && + bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00)) + { + return Ok(Self::Image { + 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 { + size: format!("{:#>8.2}", size).into(), + hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), + }) + } + pub fn hash (&self) -> Option> { + match self { + Metadata::Image { hash, .. } => Some(hash.clone()), + Metadata::Music { hash, .. } => Some(hash.clone()), + Metadata::Unknown { hash, .. } => Some(hash.clone()), + _ => None + } + } + pub fn size (&self) -> Option> { + match self { + Metadata::Image { size, .. } => Some(size.clone()), + Metadata::Music { size, .. } => Some(size.clone()), + Metadata::Unknown { size, .. } => Some(size.clone()), + _ => None + } + } + pub fn artist (&self) -> Option> { + match self { + Metadata::Music { artist, .. } => artist.clone(), + _ => None + } + } + pub fn album (&self) -> Option> { + match self { + Metadata::Music { album, .. } => album.clone(), + _ => None + } + } + pub fn title (&self) -> Option> { + match self { + Metadata::Music { title, .. } => title.clone(), + _ => None + } + } + pub fn track (&self) -> Option> { + match self { + Metadata::Music { track, .. } => track.map(|t|format!("{t}").into()).clone(), + _ => None + } + } + pub fn set_artist (&mut self, value: &impl AsRef ) { + match self { + Metadata::Music { artist, .. } => *artist = Some(value.as_ref().into()), + _ => {} + } + } + pub fn set_album (&mut self, value: &impl AsRef ) { + match self { + Metadata::Music { album, .. } => *album = Some(value.as_ref().into()), + _ => {} + } + } + pub fn set_title (&mut self, value: &impl AsRef ) { + match self { + Metadata::Music { title, .. } => *title = Some(value.as_ref().into()), + _ => {} + } + } + pub fn set_track (&mut self, value: &impl AsRef ) { + match self { + Metadata::Directory { .. } => todo!("set track for whole directory"), + Metadata::Music { track, .. } => { + if let Ok(value) = value.as_ref().trim().parse::() { + *track = Some(value) + } + }, + _ => {} + } + } +} diff --git a/src/view/table.rs b/src/view/table.rs index b5a6a88..447b3c1 100644 --- a/src/view/table.rs +++ b/src/view/table.rs @@ -72,7 +72,7 @@ impl<'a> TreeTable<'a> { } } -impl Columns { +impl Columns { pub fn header (&self) -> Arc { let mut output = String::new(); for Column { width, title, .. } in self.0.iter() {