mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 09:36:42 +01:00
unify setters and deleters
This commit is contained in:
parent
25ca263cd7
commit
d1889481b1
5 changed files with 86 additions and 163 deletions
12
src/keys.rs
12
src/keys.rs
|
|
@ -47,9 +47,9 @@ impl Handle<TuiIn> for Taggart {
|
||||||
press!(Right) => { self.column_select( 1); },
|
press!(Right) => { self.column_select( 1); },
|
||||||
press!(Char('[')) => { self.column_resize(-1); },
|
press!(Char('[')) => { self.column_resize(-1); },
|
||||||
press!(Char(']')) => { self.column_resize( 1); },
|
press!(Char(']')) => { self.column_resize( 1); },
|
||||||
press!(Char('{')) => { self.column_collapse(true, 1); },
|
press!(Char('{')) => { self.column_collapse(true); self.column_select( 1); },
|
||||||
press!(Char('}')) => { self.column_collapse(false, -1); },
|
press!(Char('}')) => { self.column_collapse(false); self.column_select(-1); },
|
||||||
press!(Delete) => { self.clear() },
|
press!(Delete) => { self.edit_clear() },
|
||||||
press!(Enter) => { self.edit_begin() },
|
press!(Enter) => { self.edit_begin() },
|
||||||
press!(Char(' ')) => { self.open_in_player()?; },
|
press!(Char(' ')) => { self.open_in_player()?; },
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -61,9 +61,6 @@ impl Handle<TuiIn> for Taggart {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Taggart {
|
impl Taggart {
|
||||||
fn clear (&self) {
|
|
||||||
todo!("clear")
|
|
||||||
}
|
|
||||||
fn open_in_player (&self) -> Usually<()> {
|
fn open_in_player (&self) -> Usually<()> {
|
||||||
open(self.entries[self.cursor].path.as_ref())?;
|
open(self.entries[self.cursor].path.as_ref())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -78,9 +75,8 @@ impl Taggart {
|
||||||
let column = &mut self.columns.0[self.column];
|
let column = &mut self.columns.0[self.column];
|
||||||
column.width = ((column.width as isize) + amount).max(0) as usize;
|
column.width = ((column.width as isize) + amount).max(0) as usize;
|
||||||
}
|
}
|
||||||
fn column_collapse (&mut self, value: bool, next: isize) {
|
fn column_collapse (&mut self, value: bool) {
|
||||||
let column = &mut self.columns.0[self.column];
|
let column = &mut self.columns.0[self.column];
|
||||||
column.collapsed = value;
|
column.collapsed = value;
|
||||||
self.column = ((self.column as isize) + next).max(0) as usize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,17 @@ impl Taggart {
|
||||||
&& let Some(setter) = &column.setter
|
&& let Some(setter) = &column.setter
|
||||||
&& self.entries.get_mut(self.cursor).is_some()
|
&& self.entries.get_mut(self.cursor).is_some()
|
||||||
{
|
{
|
||||||
setter(self, self.cursor, value.as_ref());
|
setter(self, self.cursor, Some(value.as_ref()));
|
||||||
self.mode = None;
|
self.mode = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn edit_clear (&mut self) {
|
pub fn edit_clear (&mut self) {
|
||||||
todo!("edit_clear")
|
if self.entries.get_mut(self.cursor).is_some()
|
||||||
|
&& let Some(column) = self.columns.0.get(self.column)
|
||||||
|
&& let Some(setter) = &column.setter
|
||||||
|
{
|
||||||
|
setter(self, self.cursor, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn edit_insert (&mut self, c: char) {
|
pub fn edit_insert (&mut self, c: char) {
|
||||||
if let Some(Mode::Edit { value, index }) = &self.mode {
|
if let Some(Mode::Edit { value, index }) = &self.mode {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct Taggart {
|
||||||
/// Table columns to display
|
/// Table columns to display
|
||||||
pub columns: Columns<
|
pub columns: Columns<
|
||||||
fn(&Entry)->Option<Arc<str>>,
|
fn(&Entry)->Option<Arc<str>>,
|
||||||
fn(&mut Self, usize, &str),
|
fn(&mut Self, usize, Option<&str>),
|
||||||
fn(&Entry)->Option<Style>,
|
fn(&Entry)->Option<Style>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub struct Column<G, S, U> {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Getter<T> = fn(&T)->Option<Arc<str>>;
|
type Getter<T> = fn(&T)->Option<Arc<str>>;
|
||||||
type Setter<T> = fn(&mut T, usize, &str);
|
type Setter<T> = fn(&mut T, usize, Option<&str>);
|
||||||
type Styler<T> = fn(&T)->Option<Style>;
|
type Styler<T> = fn(&T)->Option<Style>;
|
||||||
|
|
||||||
impl<G, S, U> Column<Getter<G>, Setter<S>, Styler<U>> {
|
impl<G, S, U> Column<Getter<G>, Setter<S>, Styler<U>> {
|
||||||
|
|
@ -63,21 +63,16 @@ pub struct Columns<G, S, U>(pub Vec<Column<G, S, U>>);
|
||||||
|
|
||||||
macro_rules! setter {
|
macro_rules! setter {
|
||||||
($set:ident) => {{
|
($set:ident) => {{
|
||||||
fn $set (state: &mut Taggart, index: usize, value: &str) {
|
fn $set (state: &mut Taggart, index: usize, value: Option<&str>) {
|
||||||
if let Some(entries) = entries_under(&mut state.entries, index) {
|
if let Some(entries) = entries_under(&mut state.entries, index) {
|
||||||
for (_path, entry) in entries.into_iter() {
|
for (_path, entry) in entries.into_iter() {
|
||||||
if entry.write().unwrap().$set(&value) {
|
if entry.write().unwrap().$set(value) {
|
||||||
state.count_changes();
|
state.count_changes();
|
||||||
//state.tasks.retain(|t|{(t.path != p) || (t.item.key() != item.key())});
|
|
||||||
//state.tasks.push(Task { path: p, item, });
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if let Some(entry) = state.entries.get_mut(index) {
|
} else if let Some(entry) = state.entries.get_mut(index) {
|
||||||
//let p = entry.path.clone();
|
if entry.$set(value) {
|
||||||
if entry.$set(&value) {
|
|
||||||
state.count_changes();
|
state.count_changes();
|
||||||
//state.tasks.retain(|t|{(t.path != p) || (t.item.key() != item.key())});
|
|
||||||
//state.tasks.push(Task { path: p, item, });
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +82,7 @@ macro_rules! setter {
|
||||||
|
|
||||||
impl Default for Columns<
|
impl Default for Columns<
|
||||||
fn(&Entry)->Option<Arc<str>>,
|
fn(&Entry)->Option<Arc<str>>,
|
||||||
fn(&mut Taggart, usize, &str),
|
fn(&mut Taggart, usize, Option<&str>),
|
||||||
fn(&Entry)->Option<Style>,
|
fn(&Entry)->Option<Style>,
|
||||||
> {
|
> {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering};
|
use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering};
|
||||||
use byte_unit::{Byte, Unit::MB};
|
use byte_unit::{Byte, Unit::MB};
|
||||||
|
|
@ -65,14 +66,11 @@ impl Entry {
|
||||||
Some(format!("{indent}{icon} {name}").into())
|
Some(format!("{indent}{icon} {name}").into())
|
||||||
}
|
}
|
||||||
fn icon (&self) -> &'static str {
|
fn icon (&self) -> &'static str {
|
||||||
if self.is_directory() {
|
match &*self.info.read().unwrap() {
|
||||||
ICON_DIRECTORY
|
Metadata::Directory { .. } => ICON_DIRECTORY,
|
||||||
} else if self.is_image() {
|
Metadata::Image { .. } => ICON_IMAGE,
|
||||||
ICON_IMAGE
|
Metadata::Music { .. } => ICON_MUSIC,
|
||||||
} else if self.is_music() {
|
_ => ICON_UNKNOWN
|
||||||
ICON_MUSIC
|
|
||||||
} else {
|
|
||||||
ICON_UNKNOWN
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_modified (&self) -> bool {
|
pub fn is_modified (&self) -> bool {
|
||||||
|
|
@ -201,162 +199,81 @@ macro_rules! generated_field {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! music_tag_field {
|
macro_rules! music_tag_field {
|
||||||
(string: $get:ident $set:ident $del:ident $key:expr) => {
|
($get:ident $set:ident $del:ident $key:expr; write = $write:expr; parse = $parse:expr;) => {
|
||||||
impl Entry {
|
impl Entry {
|
||||||
pub fn $get (&self) -> Option<Arc<str>> {
|
pub fn $get (&self) -> Option<Arc<str>> {
|
||||||
self.info.read().unwrap().$get()
|
self.info.read().unwrap().$get()
|
||||||
}
|
}
|
||||||
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
|
pub fn $set (&mut self, value: Option<&str>) -> bool {
|
||||||
self.info.write().unwrap().$set(value)
|
self.info.write().unwrap().$set(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
|
/// Get the metadata value if present.
|
||||||
pub fn $get (&self) -> Option<Arc<str>> {
|
pub fn $get (&self) -> Option<Arc<str>> {
|
||||||
|
let write = $write;
|
||||||
if let Metadata::Music { original_tag, modified_tag, .. } = self {
|
if let Metadata::Music { original_tag, modified_tag, .. } = self {
|
||||||
return modified_tag.as_ref()
|
return modified_tag
|
||||||
.map(|tag|tag.read().unwrap().$get().map(|t|t.into()))
|
.as_ref()
|
||||||
|
.map(|tag|tag.read().unwrap().$get().map(|x|write(x)))
|
||||||
.flatten()
|
.flatten()
|
||||||
.or_else(||original_tag.as_ref()
|
.or_else(||original_tag
|
||||||
.map(|tag|tag.$get().map(|t|t.into()))
|
.as_ref()
|
||||||
|
.map(|tag|tag.$get().map(|x|write(x)))
|
||||||
.flatten())
|
.flatten())
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
|
/// Set or delete the metadata value.
|
||||||
let value = value.as_ref().trim();
|
/// Return whether the changes should be recounted.
|
||||||
if let &mut Metadata::Music {
|
pub fn $set (&mut self, raw_value: Option<&str>) -> bool {
|
||||||
ref original_tag,
|
let parse = $parse;
|
||||||
ref mut modified_tag,
|
if let &mut Metadata::Music { ref mut modified_tag, .. } = self {
|
||||||
..
|
//let write = $write;
|
||||||
} = self {
|
if let Some(raw_value) = raw_value {
|
||||||
match (value.len(), &original_tag, &modified_tag) {
|
// Set value
|
||||||
(0, _, Some(new_tag)) => {
|
if let Ok(parsed_value) = parse(raw_value) {
|
||||||
if new_tag.read().unwrap().item_count() <= 1 {
|
match &modified_tag {
|
||||||
// removing last entry removes modified_tag
|
Some(new_tag) => {
|
||||||
*modified_tag = None;
|
// add entry to modified_tag
|
||||||
} else {
|
new_tag.write().unwrap().$set(parsed_value);
|
||||||
|
true
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// first entry creates modified_tag
|
||||||
|
let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types
|
||||||
|
new_tag.$set(parsed_value);
|
||||||
|
*modified_tag = Some(Arc::new(RwLock::new(new_tag)));
|
||||||
|
true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete value
|
||||||
|
match &modified_tag {
|
||||||
|
Some(new_tag) => {
|
||||||
// remove entry from modified_tag
|
// remove entry from modified_tag
|
||||||
new_tag.write().unwrap().$del();
|
new_tag.write().unwrap().$del();
|
||||||
|
if new_tag.read().unwrap().item_count() <= 1 {
|
||||||
|
// removing last entry removes modified_tag
|
||||||
|
*modified_tag = None;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(_, _, Some(new_tag)) => {
|
|
||||||
// add entry to modified_tag
|
|
||||||
new_tag.write().unwrap().$set(value.into());
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(0, _, None) => {
|
|
||||||
// leave modified_tag empty
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
(_, _, None) => {
|
|
||||||
// first entry creates modified_tag
|
|
||||||
let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types
|
|
||||||
new_tag.$set(value.into());
|
|
||||||
*modified_tag = Some(Arc::new(RwLock::new(new_tag)));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(number: $get:ident $set:ident $del:ident $key:expr) => {
|
|
||||||
impl Entry {
|
|
||||||
pub fn $get (&self) -> Option<Arc<str>> {
|
|
||||||
self.info.read().unwrap().$get()
|
|
||||||
}
|
|
||||||
pub fn $del (&mut self) -> bool {
|
|
||||||
self.info.write().unwrap().$del()
|
|
||||||
}
|
|
||||||
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
|
|
||||||
self.info.write().unwrap().$set(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Metadata {
|
|
||||||
pub fn $get (&self) -> Option<Arc<str>> {
|
|
||||||
if let Metadata::Music { original_tag, modified_tag, .. } = self {
|
|
||||||
return modified_tag.as_ref()
|
|
||||||
.map(|tag|tag.read().unwrap().$get().map(|t|format!("{t}").into()))
|
|
||||||
.flatten()
|
|
||||||
.or_else(||original_tag.as_ref()
|
|
||||||
.map(|tag|tag.$get().map(|t|format!("{t}").into()))
|
|
||||||
.flatten())
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
pub fn $del (&mut self) -> bool {
|
|
||||||
if let &mut Metadata::Music { ref original_tag, ref mut modified_tag, .. } = self {
|
|
||||||
let old_value = original_tag.as_ref()
|
|
||||||
.map(|m|m.$get().clone()).flatten();
|
|
||||||
let new_value = modified_tag.as_ref()
|
|
||||||
.map(|m|m.read().unwrap().$get().clone()).flatten();
|
|
||||||
match (old_value, new_value) {
|
|
||||||
(Some(old), Some(new)) => {
|
|
||||||
let mut new_tag = modified_tag.as_ref().unwrap().write().unwrap();
|
|
||||||
new_tag.$set(old);
|
|
||||||
true
|
|
||||||
},
|
|
||||||
(None, Some(new)) => {
|
|
||||||
let mut new_tag = modified_tag.as_ref().unwrap().write().unwrap();
|
|
||||||
new_tag.$del();
|
|
||||||
if new_tag.item_count() == 0 {
|
|
||||||
std::mem::drop(new_tag);
|
|
||||||
*modified_tag = None;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
},
|
|
||||||
(_, None) => {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
|
|
||||||
let value = value.as_ref().trim();
|
|
||||||
if let Ok(numeric_value) = value.parse::<u32>() && let &mut Metadata::Music {
|
|
||||||
ref original_tag,
|
|
||||||
ref mut modified_tag,
|
|
||||||
..
|
|
||||||
} = self {
|
|
||||||
match (value.len(), &original_tag, &modified_tag) {
|
|
||||||
(0, _, Some(new_tag)) => {
|
|
||||||
if new_tag.read().unwrap().item_count() <= 1 {
|
|
||||||
// removing last entry removes modified_tag
|
|
||||||
*modified_tag = None;
|
|
||||||
} else {
|
|
||||||
// remove entry from modified_tag
|
|
||||||
new_tag.write().unwrap().$del();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(_, _, Some(new_tag)) => {
|
|
||||||
// add entry to modified_tag
|
|
||||||
new_tag.write().unwrap().$set(numeric_value);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(0, _, None) => {
|
|
||||||
// leave modified_tag empty
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
(_, _, None) => {
|
|
||||||
// first entry creates modified_tag
|
|
||||||
let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types
|
|
||||||
new_tag.$set(numeric_value);
|
|
||||||
*modified_tag = Some(Arc::new(RwLock::new(new_tag)));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
generated_field!(hash = |self|match self {
|
generated_field!(hash = |self|match self {
|
||||||
Metadata::Image { hash, .. } => Some(hash.clone()),
|
Metadata::Image { hash, .. } => Some(hash.clone()),
|
||||||
Metadata::Music { hash, .. } => Some(hash.clone()),
|
Metadata::Music { hash, .. } => Some(hash.clone()),
|
||||||
|
|
@ -369,8 +286,18 @@ generated_field!(size = |self|match self {
|
||||||
Metadata::Unknown { size, .. } => Some(size.clone()),
|
Metadata::Unknown { size, .. } => Some(size.clone()),
|
||||||
_ => None
|
_ => None
|
||||||
});
|
});
|
||||||
music_tag_field!(string: artist set_artist remove_artist ItemKey::TrackArtist);
|
music_tag_field!(artist set_artist remove_artist ItemKey::TrackArtist;
|
||||||
music_tag_field!(number: year set_year remove_year ItemKey::Year);
|
write = |value: Cow<str>|value.into();
|
||||||
music_tag_field!(string: album set_album remove_album ItemKey::AlbumTitle);
|
parse = |value: &str|Ok::<String, ()>(value.trim().into()););
|
||||||
music_tag_field!(number: track set_track remove_track ItemKey::TrackNumber);
|
music_tag_field!(year set_year remove_year ItemKey::Year;
|
||||||
music_tag_field!(string: title set_title remove_title ItemKey::TrackTitle);
|
write = |value: u32|format!("{value}").into();
|
||||||
|
parse = |value: &str|value.trim().parse::<u32>(););
|
||||||
|
music_tag_field!(album set_album remove_album ItemKey::AlbumTitle;
|
||||||
|
write = |value: Cow<str>|value.into();
|
||||||
|
parse = |value: &str|Ok::<String, ()>(value.trim().into()););
|
||||||
|
music_tag_field!(track set_track remove_track ItemKey::TrackNumber;
|
||||||
|
write = |value: u32|format!("{value}").into();
|
||||||
|
parse = |value: &str|value.trim().parse::<u32>(););
|
||||||
|
music_tag_field!(title set_title remove_title ItemKey::TrackTitle;
|
||||||
|
write = |value: Cow<str>|value.into();
|
||||||
|
parse = |value: &str|Ok::<String, ()>(value.trim().into()););
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue