use std::{
fs::{remove_dir_all, remove_file},
path::{Path, PathBuf},
};
use anyhow::{Context, Result, bail};
use termusiclib::config::v2::server::{ScanDepth, config_extra::ServerConfigVersionedDefaulted};
use tuirealm::{
Sub, SubClause, SubEventClause,
props::{TableBuilder, TextSpan},
};
use crate::ui::{
components::orx_music_library::{
music_library::OrxMusicLibraryComponent, scanner::library_scan,
},
ids::Id,
model::{Model, UserEvent},
msg::{LIMsg, LINodeReady, LINodeReadySub, LIReloadData, LIReloadPathData, LIReqNode, Msg},
tui_cmd::TuiCmd,
};
fn library_subs() -> Vec<Sub<Id, UserEvent>> {
vec![
Sub::new(
SubEventClause::User(UserEvent::Forward(Msg::Library(LIMsg::Reload(
LIReloadData::default(),
)))),
SubClause::Always,
),
Sub::new(
SubEventClause::User(UserEvent::Forward(Msg::Library(LIMsg::ReloadPath(
LIReloadPathData::default(),
)))),
SubClause::Always,
),
Sub::new(
SubEventClause::User(UserEvent::Forward(Msg::Library(LIMsg::TreeNodeReady(
LINodeReady::default(),
)))),
SubClause::Always,
),
Sub::new(
SubEventClause::User(UserEvent::Forward(Msg::Library(LIMsg::TreeNodeReadySub(
LINodeReadySub::default(),
)))),
SubClause::Always,
),
Sub::new(
SubEventClause::User(UserEvent::Forward(Msg::Library(LIMsg::RequestCurrentPath(
LIReqNode::default(),
)))),
SubClause::Always,
),
]
}
impl Model {
pub fn mount_new_library(&mut self) -> Result<()> {
self.app.mount(
Id::Library,
Box::new(OrxMusicLibraryComponent::new_loading(
self.config_tui.clone(),
self.tx_to_main.clone(),
self.download_tracker.clone(),
)),
library_subs(),
)?;
Ok(())
}
#[inline]
pub fn new_library_scan_dir<P: Into<PathBuf>>(&self, path: P, focus_node: Option<String>) {
library_scan(
self.download_tracker.clone(),
path,
ScanDepth::Limited(2),
self.tx_to_main.clone(),
focus_node,
);
}
#[expect(clippy::unnecessary_debug_formatting)]
pub fn new_library_reload_and_focus<P: Into<PathBuf>>(&mut self, path: P) {
let path = path.into();
if !path.is_absolute() {
debug!("library reload, given path is not absolute! {path:#?}");
}
let config_read = self.config_server.read_recursive();
for dir in &self.config_server.read().settings.player.music_dirs {
let absolute_dir = shellexpand::path::tilde(dir);
if path.starts_with(absolute_dir)
&& let Err(err) = self.db.scan_path(&path, &config_read, false)
{
error!("Error scanning path {:#?}: {err:#?}", path.display());
}
}
let _ = self
.tx_to_main
.send(Msg::Library(LIMsg::ReloadPath(LIReloadPathData {
path,
change_focus: true,
})));
}
pub fn new_library_show_delete_confirm(&mut self, path: PathBuf, focus_node: Option<String>) {
if path.is_file() {
self.mount_confirm_radio(path, focus_node);
} else {
self.mount_confirm_input(
path,
focus_node,
"You're about to delete the whole directory.",
);
}
}
pub fn new_library_delete_node(
&mut self,
path: &Path,
focus_node: Option<String>,
) -> Result<()> {
if path.is_file() {
remove_file(path)?;
} else {
path.canonicalize()?;
remove_dir_all(path)?;
}
let parent = path.parent().expect("Path to have a parent");
self.new_library_scan_dir(parent, focus_node);
self.playlist_update_library_delete();
Ok(())
}
pub fn new_library_update_search(&mut self, input: &str, path: &Path) {
let mut table: TableBuilder = TableBuilder::default();
let all_items = walkdir::WalkDir::new(path).follow_links(true);
let mut idx: usize = 0;
let search = format!("*{}*", input.to_lowercase());
let search = wildmatch::WildMatch::new(&search);
for record in all_items.into_iter().filter_map(std::result::Result::ok) {
let file_name = record.path();
if search.matches(&file_name.to_string_lossy().to_lowercase()) {
if idx > 0 {
table.add_row();
}
idx += 1;
table
.add_col(TextSpan::new(idx.to_string()))
.add_col(TextSpan::new(file_name.to_string_lossy()));
}
}
let table = table.build();
self.general_search_update_show(table);
}
pub fn new_library_switch_root(&mut self, old_path: &Path) {
let mut vec = Vec::new();
let config_server = self.config_server.read();
for dir in &config_server.settings.player.music_dirs {
let absolute_dir = shellexpand::path::tilde(dir).into_owned();
vec.push(absolute_dir);
}
if let Some(dir) = &config_server.music_dir_overwrite {
let absolute_dir = shellexpand::path::tilde(dir).into_owned();
vec.push(absolute_dir);
}
if vec.is_empty() {
return;
}
drop(config_server);
let mut index = 0;
for (idx, dir) in vec.iter().enumerate() {
if old_path == dir {
index = idx + 1;
break;
}
}
if index > vec.len() - 1 {
index = 0;
}
if let Some(dir) = vec.get(index) {
let pathbuf = PathBuf::from(dir);
self.new_library_scan_dir(pathbuf, None);
}
}
pub fn new_library_add_root<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
let path = path.into();
let mut config_server = self.config_server.write();
for dir in &config_server.settings.player.music_dirs {
let absolute_dir = shellexpand::path::tilde(dir);
if absolute_dir == path {
bail!("Same root already exists");
}
}
config_server.settings.player.music_dirs.push(path);
let res = ServerConfigVersionedDefaulted::save_config_path(&config_server.settings);
drop(config_server);
res.context("Error while saving config")?;
self.command(TuiCmd::ReloadConfig);
Ok(())
}
pub fn new_library_remove_root<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
let path = path.into();
let mut config_server = self.config_server.write();
let mut vec = Vec::with_capacity(config_server.settings.player.music_dirs.len());
for dir in &config_server.settings.player.music_dirs {
let absolute_dir = shellexpand::path::tilde(dir);
if absolute_dir == path {
continue;
}
vec.push(dir.clone());
}
if vec.is_empty() {
bail!("At least 1 root music directory should be kept");
}
config_server.settings.player.music_dirs = vec;
let res = ServerConfigVersionedDefaulted::save_config_path(&config_server.settings);
drop(config_server);
self.new_library_switch_root(&path);
res.context("Error while saving config")?;
Ok(())
}
}