#[cfg(feature = "mpris")]
mod mpris;
mod ueberzug;
mod update;
mod view;
mod youtube_options;
use crate::{
config::Termusic,
song::Song,
ui::{Application, Id, Msg},
};
use crate::player::GStreamer;
use crate::songtag::SongTag;
use crate::ui::{SearchLyricState, Status};
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
#[cfg(feature = "cover")]
use std::process::Child;
use std::sync::mpsc::{self, Receiver, Sender};
#[cfg(feature = "cover")]
use std::sync::RwLock;
use std::time::{Duration, Instant};
use tui_realm_treeview::Tree;
use tuirealm::terminal::TerminalBridge;
use tuirealm::NoUserEvent;
use youtube_options::YoutubeOptions;
pub const MAX_DEPTH: usize = 3;
pub enum UpdateComponents {
DownloadRunning, DownloadSuccess,
DownloadCompleted(Option<String>),
DownloadErrDownload(String),
DownloadErrEmbedData,
MessageShow((String, String)),
MessageHide((String, String)),
YoutubeSearchSuccess(YoutubeOptions),
YoutubeSearchFail(String),
}
pub struct Model {
pub quit: bool,
pub redraw: bool,
last_redraw: Instant,
pub app: Application<Id, Msg, NoUserEvent>,
pub terminal: TerminalBridge,
pub path: PathBuf,
pub tree: Tree,
pub playlist_items: VecDeque<Song>,
pub config: Termusic,
pub player: GStreamer,
pub status: Option<Status>,
pub yanked_node_id: Option<String>,
pub current_song: Option<Song>,
pub tageditor_song: Option<Song>,
pub time_pos: u64,
pub lyric_line: String,
youtube_options: YoutubeOptions,
pub sender: Sender<UpdateComponents>,
receiver: Receiver<UpdateComponents>,
pub sender_playlist_items: Sender<VecDeque<Song>>,
receiver_playlist_items: Receiver<VecDeque<Song>>,
#[cfg(feature = "cover")]
ueberzug: RwLock<Option<Child>>,
pub songtag_options: Vec<SongTag>,
pub sender_songtag: Sender<SearchLyricState>,
pub receiver_songtag: Receiver<SearchLyricState>,
pub viuer_supported: bool,
}
impl Model {
pub fn new(config: &Termusic) -> Self {
let full_path = shellexpand::tilde(&config.music_dir);
let p: &Path = Path::new(full_path.as_ref());
let tree = Tree::new(Self::library_dir_tree(p, MAX_DEPTH));
let player = GStreamer::new();
let (tx, rx): (Sender<UpdateComponents>, Receiver<UpdateComponents>) = mpsc::channel();
let (tx2, rx2): (Sender<VecDeque<Song>>, Receiver<VecDeque<Song>>) = mpsc::channel();
let (tx3, rx3): (Sender<SearchLyricState>, Receiver<SearchLyricState>) = mpsc::channel();
let viuer_supported = (viuer::KittySupport::Local == viuer::get_kitty_support())
|| viuer::is_iterm_supported();
Self {
app: Self::init_app(&tree),
quit: false,
redraw: true,
last_redraw: Instant::now(),
tree,
path: p.to_path_buf(),
terminal: TerminalBridge::new().expect("Could not initialize terminal"),
playlist_items: VecDeque::with_capacity(100),
config: config.clone(),
player,
yanked_node_id: None,
status: None,
current_song: None,
tageditor_song: None,
time_pos: 0,
lyric_line: String::new(),
youtube_options: YoutubeOptions::new(),
sender: tx,
receiver: rx,
sender_playlist_items: tx2,
receiver_playlist_items: rx2,
#[cfg(feature = "cover")]
ueberzug: RwLock::new(None),
songtag_options: vec![],
sender_songtag: tx3,
receiver_songtag: rx3,
viuer_supported,
}
}
pub fn init_config(&mut self) {
let full_path = match &self.config.music_dir_from_cli {
Some(music_dir) => shellexpand::tilde(&music_dir).to_string(),
None => shellexpand::tilde(&self.config.music_dir).to_string(),
};
let p: &Path = Path::new(&full_path);
self.library_scan_dir(p);
self.player.set_volume(self.config.volume);
}
pub fn init_terminal(&mut self) {
let _ = self.terminal.enable_raw_mode();
let _ = self.terminal.enter_alternate_screen();
let _ = self.terminal.clear_screen();
}
pub fn finalize_terminal(&mut self) {
let _ = self.terminal.disable_raw_mode();
let _ = self.terminal.leave_alternate_screen();
let _ = self.terminal.clear_screen();
}
pub fn since_last_redraw(&self) -> Duration {
self.last_redraw.elapsed()
}
pub fn force_redraw(&mut self) {
self.redraw = true;
}
pub fn run(&mut self) {
match self.status {
Some(Status::Stopped) => {
if self.playlist_items.is_empty() {
return;
}
self.status = Some(Status::Running);
self.player_next();
}
None => self.status = Some(Status::Stopped),
Some(Status::Running | Status::Paused) => {}
}
}
}