use std::fs::OpenOptions; use std::path::PathBuf; use std::{ffi::OsString, io}; use musichoard::database::NullDatabase; use musichoard::library::{ILibrary, NullLibrary}; use musichoard::{Collection, MusicHoardBuilder}; use ratatui::{backend::CrosstermBackend, Terminal}; use structopt::StructOpt; use musichoard::{ database::{ json::{backend::JsonDatabaseFileBackend, JsonDatabase}, IDatabase, }, library::beets::{ executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor}, BeetsLibrary, }, NoLibrary, }; mod tui; use tui::ui::Ui; use tui::{event::EventChannel, handler::EventHandler, listener::EventListener, Tui}; #[derive(StructOpt)] struct Opt { #[structopt(long = "ssh", help = "Beets SSH URI")] beets_ssh_uri: Option, #[structopt(long = "beets", help = "Beets config file path")] beets_config_file_path: Option, #[structopt(long = "no-library", help = "Do not connect to the library")] no_library: bool, #[structopt( long = "database", help = "Database file path", default_value = "database.json" )] database_file_path: PathBuf, #[structopt(long = "no-database", help = "Do not read from/write to the database")] no_database: bool, } fn with(builder: MusicHoardBuilder) { let music_hoard = builder.build(); // Initialize the terminal user interface. let backend = CrosstermBackend::new(io::stdout()); let terminal = Terminal::new(backend).expect("failed to initialise terminal"); let channel = EventChannel::new(); let listener = EventListener::new(channel.sender()); let handler = EventHandler::new(channel.receiver()); let ui = Ui::new(music_hoard).expect("failed to initialise ui"); // Run the TUI application. Tui::run(terminal, ui, handler, listener).expect("failed to run tui"); } fn with_database(opt: Opt, builder: MusicHoardBuilder) { if opt.no_library { with(builder.set_library(NullLibrary)); } else if let Some(uri) = opt.beets_ssh_uri { let uri = uri.into_string().expect("invalid SSH URI"); let beets_config_file_path = opt .beets_config_file_path .map(|s| s.into_string()) .transpose() .expect("failed to extract beets config file path"); let lib_exec = BeetsLibrarySshExecutor::new(uri) .expect("failed to initialise beets") .config(beets_config_file_path); with(builder.set_library(BeetsLibrary::new(lib_exec))); } else { let lib_exec = BeetsLibraryProcessExecutor::default().config(opt.beets_config_file_path); with(builder.set_library(BeetsLibrary::new(lib_exec))); } } fn main() { let opt = Opt::from_args(); let builder = MusicHoardBuilder::default(); if opt.no_database { with_database(opt, builder.set_database(NullDatabase)); } else { // Create an empty database file if it does not exist. match OpenOptions::new() .write(true) .create_new(true) .open(&opt.database_file_path) { Ok(f) => { drop(f); JsonDatabase::new(JsonDatabaseFileBackend::new(&opt.database_file_path)) .save::(&vec![]) .expect("failed to create empty database"); } Err(e) => match e.kind() { io::ErrorKind::AlreadyExists => {} _ => panic!("failed to access database file"), }, } let db_exec = JsonDatabaseFileBackend::new(&opt.database_file_path); with_database(opt, builder.set_database(JsonDatabase::new(db_exec))); }; } #[cfg(test)] #[macro_use] mod testlib; #[cfg(test)] mod tests { use once_cell::sync::Lazy; use musichoard::*; pub static COLLECTION: Lazy> = Lazy::new(|| collection!()); }