add some more functions to -mers and add hooks

This commit is contained in:
Mark 2024-01-20 01:19:19 +01:00
parent b0417a72b6
commit 8620fcd4ec
8 changed files with 1079 additions and 386 deletions

View File

@ -16,7 +16,7 @@ mers_lib = { version = "0.3.2", optional = true }
musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true } musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true }
[features] [features]
default = ["gui"] default = ["gui", "mers", "merscfg"]
# gui: # gui:
# enables the gui mode # enables the gui mode
# merscfg: # merscfg:

View File

@ -0,0 +1,289 @@
gen_escape_str := str -> {
"echo".run_command(("-ne", str)).try((
(s, stdout, stderr) -> if s.eq(0) stdout else ""
e -> ""
))
}
gen_color := mode -> ("\\x1b[", mode, "m").concat.gen_escape_str
// 0: reset | 1: bold, 2: dim, 3: italic, 4: underline, 9: strikethrough
// Colors: FG/BG=3x/4x (bright, if supported: 9x/10x) | 1red 2green 3yellow 4blue 5magenta 6cyan 7white 9default
clr_reset := "0".gen_color
clr_red := "31".gen_color
clr_green := "32".gen_color
clr_yellow := "33".gen_color
clr_blue := "34".gen_color
clr_magenta := "35".gen_color
clr_cyan := "36".gen_color
clr_white := "37".gen_color
clr_default := "39".gen_color
clr_dim := "2".gen_color
clr_top_bar_decorations := "2;34" // dim blue
clr_top_bar_title := "1;37".gen_color // bold white
clr_top_bar_err := "1;31".gen_color // bold red
clr_top_bar_empty := clr_dim // dim
clr_artist := "35".gen_color // magenta
clr_album := "32".gen_color // green
clr_user_input_line_decor := "2;36".gen_color // dim cyan
clr_user_input_line := "1;36".gen_color // bold cyan
clr_unknown_cmd := "31".gen_color // red
clr_buf_timestamp := "2;34".gen_color // dim blue
clr_search := "1;32".gen_color // dim green
// clear terminal and reset cursor and colors
"\\x1b[2J\\x1b[H\\x1b[0m".gen_escape_str.eprint
// save cursor, reset cursor and color
term_before_redraw := "\\x1b[s\\x1b[H\\x1b[0m".gen_escape_str
// restore cursor pos
term_put_cursor_back := "\\x1b[u".gen_escape_str
// erase rest of line in case previous version of this line was longer than new version, then add newline
term_clear_rest_of_screen := "\\x1b[0J".gen_escape_str
line_end := ("\\x1b[0K".gen_escape_str, clr_reset, "\n").concat
str_repeat := (str, count) -> {
out := ""
().loop(() -> if count.gt(0) {
&count = count.subtract(1)
&out = (out, str).concat
} else (()))
out
}
current_queue_index := [List<Int>] ().as_list
reset_cursor_pos := true
term_buf_len := 10
term_buf := ("Welcome! Type `help` for help.").as_list
bprintln := line -> {
time := "date".run_command(("+%T")).try((
(s, stdout, stderr) -> if s.eq(0) stdout.trim else ""
e -> ""
))
&term_buf.push((clr_buf_timestamp, time, " | ", clr_reset, line).concat)
// remove start of term_buf if it gets too long
if term_buf.len.gt(term_buf_len.product(2)) {
&term_buf = term_buf.enumerate.filter_map((i, v) -> if i.lt(term_buf_len) () else (v)).as_list
}
}
custom_input_handler := [()/(String, String, List<String>)] ()
update_ui := () -> {
screen := term_before_redraw
sprintln := line -> &screen = (screen, line, line_end).concat
// top bar
(top_bar_line_1, top_bar_line_2) := ().queue_get_current_song.try((
() -> ((clr_top_bar_empty, "[-]").concat, "")
id -> id.get_song.try((
() -> ((clr_top_bar_err, "[!]").concat, "")
{id: _, title: title, album: album, artist: artist, cover: _} -> {
l1 := (clr_top_bar_title, title).concat
artist := artist.get_artist
album := album.try_allow_unused((
() -> ()
id -> album.get_album
))
l2 := (artist, album).try_allow_unused((
((), ()) -> ""
({id: _, name: artist_name, cover: _, albums: _, singles: _}, ()) -> (clr_dim, "by ", clr_artist, artist_name).concat
((), {id: _, name: album_name, artist: _, cover: _, songs: _}) -> (clr_dim, "on ", clr_album, album_name).concat
({id: _, name: artist_name, cover: _, albums: _, singles: _}, {id: _, name: album_name, artist: _, cover: _, songs: _}) -> (clr_dim, "by ", clr_reset, clr_artist, artist_name, clr_reset, clr_dim, " on ", clr_reset, clr_album, album_name).concat
))
(l1, l2)
}
))
))
(if ().get_playing "⏵" else "⏸", " ", top_bar_line_1).concat.sprintln
top_bar_line_2.sprintln
// term buf
{
buf_ln := 0
().loop(() -> if buf_ln.lt(term_buf_len){
term_buf.get(term_buf.len.subtract(term_buf_len).sum(buf_ln)).try((
() -> "".sprintln
(line) -> line.sprintln
))
&buf_ln = buf_ln.sum(1)
} else (()))
}
// user input line
user_input_line := (clr_user_input_line_decor, custom_input_handler.try((() -> if current_queue_index.len.gt(0) ">> " else " > ", (_, v, _) -> v)), clr_user_input_line).concat
// print screen
(
screen, user_input_line,
if reset_cursor_pos term_clear_rest_of_screen else term_put_cursor_back
).concat.eprint
&reset_cursor_pos = false
}
if false ().update_ui // check
show_queue_recursive := max_depth_layers -> {
stack := ((current_queue_index, -1, false)).as_list
().loop(() -> {
&stack.pop.try((
() -> (())
((index, depth, show)) -> if (max_depth_layers.eq(0), depth.lt(max_depth_layers)).any {
index := [List<Int>] index
line := if show {
o := ""
i := 0
().loop(() -> if i.lt(depth) { &o = (o, " ").concat, &i = i.sum(1) } else (()))
o
} else ""
if show &line = (clr_dim, line).concat
index.queue_get_elem.try((
() -> ()
{enabled: _, song: id} -> id.get_song.try((
() -> &line = (line, clr_reset, "[!] ", id).concat
{id: _, title: title, album: _, artist: _, cover: _} -> &line = (line, clr_reset, title).concat
))
{enabled: _, loop: {total: total, done: done}} -> {
index := index
&index.push(0)
&stack.push((index, depth, false))
&line = (line, "Loop").concat
}
{enabled: _, random: ()} -> {
&line = (line, "Random").concat
index := index
&index.push(0)
&stack.push((index, depth.sum(1), true))
}
{enabled: _, shuffle: ()} -> {
&line = (line, "Shuffle").concat
index := index
&index.push(0)
&stack.push((index, depth, false))
}
{enabled: _, folder: {index: ix, length: length, name: name}} -> {
&line = (line, "[", name, "]").concat
i := length
().loop(() -> {
&i = i.subtract(1)
if i.lt(0) (()) else {
index := index
&index.push(i)
&stack.push((index, depth.sum(1), true))
}
})
}
))
if show line.bprintln
}
))
})
}
// handlers
on_resume := () -> ().update_ui
on_pause := () -> ().update_ui
on_next_song := () -> ().update_ui
on_library_changed := () -> ().update_ui
on_queue_changed := () -> ().update_ui
on_notification_received := (title, content) -> {
("Notification: ", title).concat.bprintln
content.bprintln
().update_ui
}
// add as handlers
on_resume.handle_event_resume
on_pause.handle_event_pause
on_next_song.handle_event_next_song
on_library_changed.handle_event_library_changed
on_queue_changed.handle_event_queue_changed
on_notification_received.handle_event_notification_received
&found_songs := [List<MusicDbId>] ().as_list
().loop(() -> {
().update_ui
line := ().read_line
exit := line.len.eq(0)
line := line.trim
&reset_cursor_pos = true
custom_input_handler.try((
() -> {
if (exit, line.eq("exit")).any {
(())
} else if line.eq("help") {
"===== Help =====".bprintln
"Commands:".bprintln
"- exit, pause, play".bprintln
"- next, search, add song, clear queue, queue random, queue show".bprintln
"- send notif, set buf len, clear".bprintln
} else if line.eq("pause") {
().pause
} else if line.eq("play") {
().resume
} else if line.eq("next") {
().next_song
} else if line.eq("clear") {
&term_buf = ().as_list
} else if line.eq("queue show") {
"== Queue ==".bprintln
0.show_queue_recursive
} else if line.eq("clear queue") {
().queue_clear
} else if line.eq("queue random") {
().queue_clear
(().as_list, 0).queue_add_loop
(0, 0).as_list.queue_add_random
} else if line.eq("send notif") {
&custom_input_handler = ("SN", "Notification: ", ().as_list)
} else if line.eq("set buf len") {
&custom_input_handler = ("SetBufLen", "Length (lines): ", ().as_list)
} else if line.eq("search") {
&custom_input_handler = ("Search", "Song: ", ().as_list)
} else if line.eq("add song") {
if found_songs.len.gt(0) {
&custom_input_handler = ("AddSong", ("(1-", found_songs.len, ") ").concat, ().as_list)
} else {
"Use `search` to find songs first!".bprintln
}
} else if line.len.gt(0) {
(clr_unknown_cmd, "Unknown command: ", line).concat.bprintln
}
}
(id, desc, args) -> {
&custom_input_handler = ()
args := [List<String>] args
if id.eq("SN") {
line.send_server_notification
} else if id.eq("SetBufLen") {
line.parse_int.try((
() -> (clr_unknown_cmd, "not an int").concat.bprintln
n -> &term_buf_len = n,
))
} else if id.eq("Search") {
songs := ().all_songs.filter_map({id: id, title: title, album: _, artist: _, cover: _} -> title.index_of(line).try((() -> (), n -> ((id, title))))).take(term_buf_len)
if (songs.len.gt(0), songs.len.eq(term_buf_len)).all {
&songs = songs.take(term_buf_len.subtract(1))
(clr_search, "Search: ", clr_reset, line, clr_dim, " (found more results than will be displayed)").concat.bprintln
} else {
(clr_search, "Search: ", clr_reset, line).concat.bprintln
}
&found_songs = songs.map((id, _) -> id).as_list
songs.enumerate.for_each((i, (_, song)) -> (clr_dim, i.sum(1), " ", clr_reset, song).concat.bprintln)
} else if id.eq("AddSong") {
line.parse_int.try((
() -> (clr_unknown_cmd, "not an int (must be 1-", found_songs.len, ")").concat.bprintln
n -> found_songs.get(n.subtract(1)).try((
() -> (clr_unknown_cmd, "out of range (must be 1-", found_songs.len, ")").concat.bprintln
(id) -> (current_queue_index, id).queue_add_song
))
))
} else {
("Unknown CIH ID ", id, ".").concat.eprintln
1.panic
}
}
})
clr_reset.eprintln

View File

@ -0,0 +1,22 @@
// define handlers
on_resume := () -> "Resumed".eprintln
on_pause := () -> "Paused".eprintln
on_next_song := () -> "Next song".eprintln
on_library_changed := () -> "Library changed".eprintln
on_queue_changed := () -> "Queue changed".eprintln
on_notification_received := (title, content) -> ("Notif:\n - ", title, " -\n", content).concat.eprintln
// use handlers to handle events
on_resume.handle_event_resume
on_pause.handle_event_pause
on_next_song.handle_event_next_song
on_library_changed.handle_event_library_changed
on_queue_changed.handle_event_queue_changed
on_notification_received.handle_event_notification_received
// because on_resume won't be called if playback was resumed before this client connected
if ().get_playing {
"Resumed (was playing)".eprintln
}
().loop(() -> 60.sleep)

View File

@ -80,6 +80,7 @@ pub fn main(
connection: TcpStream, connection: TcpStream,
get_con: get::Client<TcpStream>, get_con: get::Client<TcpStream>,
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>, event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
) { ) {
let config_dir = super::get_config_file_path(); let config_dir = super::get_config_file_path();
let config_file = config_dir.join("config_gui.toml"); let config_file = config_dir.join("config_gui.toml");
@ -262,6 +263,7 @@ pub fn main(
#[cfg(feature = "merscfg")] #[cfg(feature = "merscfg")]
merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers"), database), merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers"), database),
}, },
after_db_cmd,
)); ));
} }
@ -320,14 +322,18 @@ impl Gui {
scroll_pages_multiplier: f64, scroll_pages_multiplier: f64,
#[cfg(not(feature = "merscfg"))] gui_config: GuiConfig, #[cfg(not(feature = "merscfg"))] gui_config: GuiConfig,
#[cfg(feature = "merscfg")] mut gui_config: GuiConfig, #[cfg(feature = "merscfg")] mut gui_config: GuiConfig,
#[cfg(feature = "merscfg")] after_db_cmd: &Arc<
Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>,
>,
) -> Self { ) -> Self {
let (notif_overlay, notif_sender) = NotifOverlay::new(); let (notif_overlay, notif_sender) = NotifOverlay::new();
let notif_sender_two = notif_sender.clone(); let notif_sender_two = notif_sender.clone();
#[cfg(feature = "merscfg")] #[cfg(feature = "merscfg")]
match gui_config match gui_config.merscfg.load(
.merscfg Arc::clone(&event_sender),
.load(Arc::clone(&event_sender), notif_sender.clone()) notif_sender.clone(),
{ after_db_cmd,
) {
Err(e) => { Err(e) => {
if !matches!(e.kind(), std::io::ErrorKind::NotFound) { if !matches!(e.kind(), std::io::ErrorKind::NotFound) {
eprintln!("Couldn't load merscfg: {e}") eprintln!("Couldn't load merscfg: {e}")

View File

@ -105,7 +105,12 @@ fn main() {
Arc::new(Mutex::new(None)); Arc::new(Mutex::new(None));
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
let sender = Arc::clone(&update_gui_sender); let sender = Arc::clone(&update_gui_sender);
#[cfg(any(feature = "mers", feature = "merscfg"))]
let mers_after_db_updated_action: Arc<
Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>,
> = Arc::new(Mutex::new(None));
let con_thread = { let con_thread = {
let mers_after_db_updated_action = Arc::clone(&mers_after_db_updated_action);
let mode = mode.clone(); let mode = mode.clone();
let database = Arc::clone(&database); let database = Arc::clone(&database);
let mut con = con.try_clone().unwrap(); let mut con = con.try_clone().unwrap();
@ -148,7 +153,13 @@ fn main() {
if let Some(player) = &mut player { if let Some(player) = &mut player {
player.handle_command(&update); player.handle_command(&update);
} }
#[cfg(any(feature = "mers", feature = "merscfg"))]
if let Some(action) = &mut *mers_after_db_updated_action.lock().unwrap() {
database.lock().unwrap().apply_command(update.clone());
action(update);
} else {
database.lock().unwrap().apply_command(update); database.lock().unwrap().apply_command(update);
}
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
if let Some(v) = &*update_gui_sender.lock().unwrap() { if let Some(v) = &*update_gui_sender.lock().unwrap() {
v.send_event(GuiEvent::Refresh).unwrap(); v.send_event(GuiEvent::Refresh).unwrap();
@ -175,6 +186,8 @@ fn main() {
)) ))
.expect("initializing get client connection"), .expect("initializing get client connection"),
sender, sender,
#[cfg(feature = "merscfg")]
&mers_after_db_updated_action,
) )
}; };
} }
@ -191,6 +204,7 @@ fn main() {
mers_lib::prelude_compile::Config::new().bundle_std(), mers_lib::prelude_compile::Config::new().bundle_std(),
&database, &database,
&Arc::new(move |cmd: Command| cmd.to_bytes(&mut *con.lock().unwrap()).unwrap()), &Arc::new(move |cmd: Command| cmd.to_bytes(&mut *con.lock().unwrap()).unwrap()),
&mers_after_db_updated_action,
) )
.infos(); .infos();
let program = mers_lib::prelude_compile::parse(&mut src, &srca) let program = mers_lib::prelude_compile::parse(&mut src, &srca)

View File

@ -139,13 +139,14 @@ impl MersCfg {
notif_sender: Sender< notif_sender: Sender<
Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>,
>, >,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
) -> mers_lib::prelude_extend_config::Config { ) -> mers_lib::prelude_extend_config::Config {
let cmd_es = event_sender.clone(); let cmd_es = event_sender.clone();
let cmd_ga = self.channel_gui_actions.0.clone(); let cmd_ga = self.channel_gui_actions.0.clone();
musicdb_mers::add(cfg, db, &Arc::new(move |cmd| { musicdb_mers::add(cfg, db, &Arc::new(move |cmd| {
cmd_ga.send(cmd).unwrap(); cmd_ga.send(cmd).unwrap();
cmd_es.send_event(GuiEvent::RefreshMers).unwrap(); cmd_es.send_event(GuiEvent::RefreshMers).unwrap();
})) }), after_db_cmd)
.add_var_arc( .add_var_arc(
"is_playing".to_owned(), "is_playing".to_owned(),
Arc::clone(&self.var_is_playing), Arc::clone(&self.var_is_playing),
@ -704,9 +705,10 @@ impl MersCfg {
notif_sender: Sender< notif_sender: Sender<
Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>,
>, >,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
) -> std::io::Result<Result<Result<(), (String, Option<CheckError>)>, CheckError>> { ) -> std::io::Result<Result<Result<(), (String, Option<CheckError>)>, CheckError>> {
let src = mers_lib::prelude_compile::Source::new_from_file(self.source_file.clone())?; let src = mers_lib::prelude_compile::Source::new_from_file(self.source_file.clone())?;
Ok(self.load2(src, event_sender, notif_sender)) Ok(self.load2(src, event_sender, notif_sender, after_db_cmd))
} }
fn load2( fn load2(
&mut self, &mut self,
@ -715,6 +717,7 @@ impl MersCfg {
notif_sender: Sender< notif_sender: Sender<
Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>,
>, >,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
) -> Result<Result<(), (String, Option<CheckError>)>, CheckError> { ) -> Result<Result<(), (String, Option<CheckError>)>, CheckError> {
let srca = Arc::new(src.clone()); let srca = Arc::new(src.clone());
let (mut i1, mut i2, mut i3) = self let (mut i1, mut i2, mut i3) = self
@ -723,6 +726,7 @@ impl MersCfg {
&self.database, &self.database,
event_sender, event_sender,
notif_sender, notif_sender,
after_db_cmd,
) )
.infos(); .infos();
let compiled = mers_lib::prelude_compile::parse(&mut src, &srca)? let compiled = mers_lib::prelude_compile::parse(&mut src, &srca)?

View File

@ -4,7 +4,7 @@ use std::{
}; };
use mers_lib::{ use mers_lib::{
data::{self, Data, MersData, MersType, Type}, data::{self, function::Function, Data, MersData, MersType, Type},
info::Info, info::Info,
prelude_extend_config::Config, prelude_extend_config::Config,
}; };
@ -20,9 +20,10 @@ use musicdb_lib::{
}; };
pub fn add( pub fn add(
cfg: Config, mut cfg: Config,
db: &Arc<Mutex<Database>>, db: &Arc<Mutex<Database>>,
cmd: &Arc<impl Fn(Command) + Sync + Send + 'static>, cmd: &Arc<impl Fn(Command) + Sync + Send + 'static>,
after_db_cmd: &Arc<Mutex<Option<Box<dyn FnMut(Command) + Send + Sync + 'static>>>>,
) -> Config { ) -> Config {
macro_rules! func { macro_rules! func {
($out:expr, $run:expr) => { ($out:expr, $run:expr) => {
@ -35,16 +36,163 @@ pub fn add(
}) })
}; };
} }
cfg.with_list() /// handle commands received from server (for handler functions)
.add_type(MusicDbIdT.to_string(), Ok(Arc::new(MusicDbIdT))) /// `T` can be used to return generated data to avoid calculating something twice if one event may call multiple handlers.
.add_var( fn handle<T>(
handler: &Arc<RwLock<Data>>,
gen: impl FnOnce() -> (Data, T),
) -> Option<(T, Data)> {
if let Some(func) = handler
.read()
.unwrap()
.get()
.as_any()
.downcast_ref::<Function>()
{
let (data, t) = gen();
Some((t, func.run(data)))
} else {
None
}
}
let handler_resume = Arc::new(RwLock::new(Data::empty_tuple()));
let handler_pause = Arc::new(RwLock::new(Data::empty_tuple()));
let handler_next_song = Arc::new(RwLock::new(Data::empty_tuple()));
let handler_queue_changed = Arc::new(RwLock::new(Data::empty_tuple()));
let handler_library_changed = Arc::new(RwLock::new(Data::empty_tuple()));
let handler_notification_received = Arc::new(RwLock::new(Data::empty_tuple()));
{
*after_db_cmd.lock().unwrap() = Some({
let handler_resume = Arc::clone(&handler_resume);
let handler_pause = Arc::clone(&handler_pause);
let handler_next_song = Arc::clone(&handler_next_song);
let handler_queue_changed = Arc::clone(&handler_queue_changed);
let handler_library_changed = Arc::clone(&handler_library_changed);
let handler_notification_received = Arc::clone(&handler_notification_received);
Box::new(move |cmd| match cmd {
Command::Resume => {
handle(&handler_resume, move || (Data::empty_tuple(), ()));
}
Command::Pause | Command::Stop => {
handle(&handler_pause, move || (Data::empty_tuple(), ()));
}
Command::NextSong => {
handle(&handler_next_song, move || (Data::empty_tuple(), ()));
handle(&handler_queue_changed, move || (Data::empty_tuple(), ()));
}
Command::SyncDatabase(..) => {
handle(&handler_library_changed, move || (Data::empty_tuple(), ()));
}
Command::QueueUpdate(..)
| Command::QueueAdd(..)
| Command::QueueInsert(..)
| Command::QueueRemove(..)
| Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => {
handle(&handler_queue_changed, move || (Data::empty_tuple(), ()));
}
Command::AddSong(_)
| Command::AddAlbum(_)
| Command::AddArtist(_)
| Command::AddCover(_)
| Command::ModifySong(_)
| Command::ModifyAlbum(_)
| Command::ModifyArtist(_)
| Command::RemoveSong(_)
| Command::RemoveAlbum(_)
| Command::RemoveArtist(_) => {
handle(&handler_library_changed, move || (Data::empty_tuple(), ()));
}
Command::SetSongDuration(..) => {
handle(&handler_library_changed, move || (Data::empty_tuple(), ()));
}
Command::TagSongFlagSet(..)
| Command::TagSongFlagUnset(..)
| Command::TagAlbumFlagSet(..)
| Command::TagAlbumFlagUnset(..)
| Command::TagArtistFlagSet(..)
| Command::TagArtistFlagUnset(..)
| Command::TagSongPropertySet(..)
| Command::TagSongPropertyUnset(..)
| Command::TagAlbumPropertySet(..)
| Command::TagAlbumPropertyUnset(..)
| Command::TagArtistPropertySet(..)
| Command::TagArtistPropertyUnset(..) => {
handle(&handler_library_changed, move || (Data::empty_tuple(), ()));
}
Command::InitComplete => (),
Command::Save => (),
Command::ErrorInfo(title, body) => {
handle(&handler_notification_received, move || {
(
Data::new(data::tuple::Tuple(vec![
Data::new(data::string::String(title)),
Data::new(data::string::String(body)),
])),
(),
)
});
}
})
});
}
// MusicDb type
cfg = cfg
.with_list()
.add_type(MusicDbIdT.to_string(), Ok(Arc::new(MusicDbIdT)));
// handler setters
for (name, handler, in_type) in [
("resume", handler_resume, Type::empty_tuple()),
("pause", handler_pause, Type::empty_tuple()),
("next_song", handler_next_song, Type::empty_tuple()),
(
"library_changed",
handler_library_changed,
Type::empty_tuple(),
),
("queue_changed", handler_queue_changed, Type::empty_tuple()),
(
"notification_received",
handler_notification_received,
Type::new(data::tuple::TupleT(vec![
Type::new(data::string::StringT),
Type::new(data::string::StringT),
])),
),
] {
cfg = cfg.add_var(
format!("handle_event_{name}"),
func!(
move |a, _| {
if a.types.iter().all(|a| {
Type::newm(vec![Arc::clone(a)]).is_zero_tuple()
|| a.as_any()
.downcast_ref::<data::function::FunctionT>()
.is_some_and(|a| {
(a.0)(&in_type).is_ok_and(|opt| opt.is_zero_tuple())
})
}) {
Ok(Type::empty_tuple())
} else {
Err(format!("Handler function must be `{in_type} -> ()`").into())
}
},
move |a, _| {
*handler.write().unwrap() = a;
Data::empty_tuple()
}
),
);
}
// actions
cfg.add_var(
"send_server_notification".to_owned(), "send_server_notification".to_owned(),
func!( func!(
|a, _| { |a, _| {
if a.is_included_in(&data::string::StringT) { if a.is_included_in(&data::string::StringT) {
Ok(Type::empty_tuple()) Ok(Type::empty_tuple())
} else { } else {
Err(format!("Function argument must be `()`.").into()) Err(format!("Function argument must be `String`.").into())
} }
}, },
{ {
@ -121,6 +269,41 @@ pub fn add(
} }
), ),
) )
.add_var(
"next_song".to_owned(),
func!(
|a, _| {
if a.is_included_in(&Type::empty_tuple()) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `()`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |_, _| {
cmd(Command::NextSong);
Data::empty_tuple()
}
}
),
)
.add_var(
"get_playing".to_owned(),
func!(
|a, _| {
if a.is_included_in(&Type::empty_tuple()) {
Ok(Type::new(data::bool::BoolT))
} else {
Err(format!("Function argument must be `()`.").into())
}
},
{
let db = Arc::clone(db);
move |_, _| Data::new(data::bool::Bool(db.lock().unwrap().playing))
}
),
)
.add_var( .add_var(
"queue_get_current_song".to_owned(), "queue_get_current_song".to_owned(),
func!( func!(
@ -190,6 +373,49 @@ pub fn add(
} }
), ),
) )
.add_var(
"queue_goto".to_owned(),
func!(
|a, _| {
if a.is_included_in(&mers_lib::program::configs::with_list::ListT(Type::new(
data::int::IntT,
))) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `List<Int>`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |a, _| {
cmd(Command::QueueGoto(int_list_to_usize_vec(&a)));
Data::empty_tuple()
}
}
),
)
.add_var(
"queue_clear".to_owned(),
func!(
|a, _| {
if a.is_included_in(&Type::empty_tuple()) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `()`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |_, _| {
cmd(Command::QueueUpdate(
vec![],
QueueContent::Folder(0, vec![], String::new()).into(),
));
Data::empty_tuple()
}
}
),
)
.add_var( .add_var(
"queue_add_song".to_owned(), "queue_add_song".to_owned(),
func!( func!(
@ -221,6 +447,138 @@ pub fn add(
} }
), ),
) )
.add_var(
"queue_add_loop".to_owned(),
func!(
|a, _| {
if a.is_included_in(&data::tuple::TupleT(vec![
Type::new(mers_lib::program::configs::with_list::ListT(Type::new(
data::int::IntT,
))),
Type::new(data::int::IntT),
])) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `(List<Int>, Int)`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |a, _| {
let a = a.get();
let a = &a.as_any().downcast_ref::<data::tuple::Tuple>().unwrap().0;
let path = int_list_to_usize_vec(&a[0]);
let repeat_count = a[1]
.get()
.as_any()
.downcast_ref::<data::int::Int>()
.unwrap()
.0;
cmd(Command::QueueAdd(
path,
vec![QueueContent::Loop(
repeat_count.max(0) as _,
0,
Box::new(QueueContent::Folder(0, vec![], String::new()).into()),
)
.into()],
));
Data::empty_tuple()
}
}
),
)
.add_var(
"queue_add_folder".to_owned(),
func!(
|a, _| {
if a.is_included_in(&data::tuple::TupleT(vec![
Type::new(mers_lib::program::configs::with_list::ListT(Type::new(
data::int::IntT,
))),
Type::new(data::string::StringT),
])) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `(List<Int>, String)`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |a, _| {
let a = a.get();
let a = &a.as_any().downcast_ref::<data::tuple::Tuple>().unwrap().0;
let path = int_list_to_usize_vec(&a[0]);
let name = a[1]
.get()
.as_any()
.downcast_ref::<data::string::String>()
.unwrap()
.0
.clone();
cmd(Command::QueueAdd(
path,
vec![QueueContent::Folder(0, vec![], name).into()],
));
Data::empty_tuple()
}
}
),
)
.add_var(
"queue_add_random".to_owned(),
func!(
|a, _| {
if a.is_included_in(&mers_lib::program::configs::with_list::ListT(Type::new(
data::int::IntT,
))) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `List<Int>`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |a, _| {
let path = int_list_to_usize_vec(&a);
cmd(Command::QueueAdd(
path,
vec![QueueContent::Random(Default::default()).into()],
));
Data::empty_tuple()
}
}
),
)
.add_var(
"queue_add_shuffle".to_owned(),
func!(
|a, _| {
if a.is_included_in(&mers_lib::program::configs::with_list::ListT(Type::new(
data::int::IntT,
))) {
Ok(Type::empty_tuple())
} else {
Err(format!("Function argument must be `List<Int>`.").into())
}
},
{
let cmd = Arc::clone(cmd);
move |a, _| {
let path = int_list_to_usize_vec(&a);
cmd(Command::QueueAdd(
path,
vec![QueueContent::Shuffle {
inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()),
state: musicdb_lib::data::queue::ShuffleState::NotShuffled,
}
.into()],
));
Data::empty_tuple()
}
}
),
)
.add_var( .add_var(
"all_songs".to_owned(), "all_songs".to_owned(),
func!( func!(