2024-02-18 22:12:41 +01:00
|
|
|
#![cfg_attr(nightly, feature(test))]
|
|
|
|
#[cfg(nightly)]
|
|
|
|
extern crate test;
|
|
|
|
|
2024-01-22 23:01:34 +01:00
|
|
|
mod tui;
|
|
|
|
|
2024-01-12 21:15:59 +01:00
|
|
|
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf};
|
2023-04-10 00:13:18 +02:00
|
|
|
|
2023-04-14 16:21:25 +02:00
|
|
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
2023-04-10 00:13:18 +02:00
|
|
|
use structopt::StructOpt;
|
|
|
|
|
|
|
|
use musichoard::{
|
2024-03-16 16:57:50 +01:00
|
|
|
external::{
|
|
|
|
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
|
|
|
|
library::beets::{
|
|
|
|
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
|
|
|
|
BeetsLibrary,
|
|
|
|
},
|
|
|
|
},
|
2024-03-09 19:11:59 +01:00
|
|
|
interface::{
|
|
|
|
database::{IDatabase, NullDatabase},
|
|
|
|
library::{ILibrary, NullLibrary},
|
2023-04-14 16:21:25 +02:00
|
|
|
},
|
2024-01-12 21:34:01 +01:00
|
|
|
MusicHoardBuilder, NoDatabase, NoLibrary,
|
2023-04-13 14:09:59 +02:00
|
|
|
};
|
|
|
|
|
2024-02-03 14:32:13 +01:00
|
|
|
use tui::{App, EventChannel, EventHandler, EventListener, Tui, Ui};
|
2023-04-10 00:13:18 +02:00
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
|
|
|
struct Opt {
|
2024-01-12 21:15:59 +01:00
|
|
|
#[structopt(flatten)]
|
|
|
|
lib_opt: LibOpt,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
db_opt: DbOpt,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
|
|
|
struct LibOpt {
|
2024-01-11 23:27:01 +01:00
|
|
|
#[structopt(long = "ssh", help = "Beets SSH URI")]
|
2023-04-14 16:21:25 +02:00
|
|
|
beets_ssh_uri: Option<OsString>,
|
|
|
|
|
2024-08-24 23:10:59 +02:00
|
|
|
#[structopt(long = "beets-bin", help = "Beets binary path")]
|
|
|
|
beets_bin_path: Option<OsString>,
|
|
|
|
|
|
|
|
#[structopt(long = "beets-config", help = "Beets config file path")]
|
2023-04-14 16:21:25 +02:00
|
|
|
beets_config_file_path: Option<OsString>,
|
2023-04-10 00:13:18 +02:00
|
|
|
|
2024-01-11 23:27:01 +01:00
|
|
|
#[structopt(long = "no-library", help = "Do not connect to the library")]
|
|
|
|
no_library: bool,
|
2024-01-12 21:15:59 +01:00
|
|
|
}
|
2024-01-11 23:27:01 +01:00
|
|
|
|
2024-01-12 21:15:59 +01:00
|
|
|
#[derive(StructOpt)]
|
|
|
|
struct DbOpt {
|
2023-04-10 00:13:18 +02:00
|
|
|
#[structopt(
|
|
|
|
long = "database",
|
2024-01-11 23:27:01 +01:00
|
|
|
help = "Database file path",
|
2023-04-14 16:21:25 +02:00
|
|
|
default_value = "database.json"
|
2023-04-10 00:13:18 +02:00
|
|
|
)]
|
|
|
|
database_file_path: PathBuf,
|
2024-01-11 23:27:01 +01:00
|
|
|
|
|
|
|
#[structopt(long = "no-database", help = "Do not read from/write to the database")]
|
|
|
|
no_database: bool,
|
2023-04-10 00:13:18 +02:00
|
|
|
}
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
fn with<Database: IDatabase, Library: ILibrary>(builder: MusicHoardBuilder<Database, Library>) {
|
2024-03-01 09:00:52 +01:00
|
|
|
let music_hoard = builder.build().expect("failed to initialise MusicHoard");
|
2023-04-13 14:09:59 +02:00
|
|
|
|
|
|
|
// 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();
|
2023-05-10 22:52:03 +02:00
|
|
|
let listener = EventListener::new(channel.sender());
|
|
|
|
let handler = EventHandler::new(channel.receiver());
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2024-02-03 14:32:13 +01:00
|
|
|
let app = App::new(music_hoard);
|
|
|
|
let ui = Ui;
|
2023-04-13 14:09:59 +02:00
|
|
|
|
|
|
|
// Run the TUI application.
|
2024-02-03 14:32:13 +01:00
|
|
|
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
fn with_database<Library: ILibrary>(
|
|
|
|
db_opt: DbOpt,
|
|
|
|
builder: MusicHoardBuilder<NoDatabase, Library>,
|
|
|
|
) {
|
2024-01-12 21:15:59 +01:00
|
|
|
if db_opt.no_database {
|
|
|
|
with(builder.set_database(NullDatabase));
|
2024-01-11 23:27:01 +01:00
|
|
|
} else {
|
|
|
|
// Create an empty database file if it does not exist.
|
|
|
|
match OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.create_new(true)
|
2024-01-12 21:15:59 +01:00
|
|
|
.open(&db_opt.database_file_path)
|
2024-01-11 23:27:01 +01:00
|
|
|
{
|
|
|
|
Ok(f) => {
|
|
|
|
drop(f);
|
2024-01-12 21:15:59 +01:00
|
|
|
JsonDatabase::new(JsonDatabaseFileBackend::new(&db_opt.database_file_path))
|
2024-01-12 21:34:01 +01:00
|
|
|
.save(&vec![])
|
2024-01-11 23:27:01 +01:00
|
|
|
.expect("failed to create empty database");
|
|
|
|
}
|
|
|
|
Err(e) => match e.kind() {
|
|
|
|
io::ErrorKind::AlreadyExists => {}
|
|
|
|
_ => panic!("failed to access database file"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-01-12 21:15:59 +01:00
|
|
|
let db_exec = JsonDatabaseFileBackend::new(&db_opt.database_file_path);
|
|
|
|
with(builder.set_database(JsonDatabase::new(db_exec)));
|
2024-01-11 23:27:01 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
fn with_library(lib_opt: LibOpt, db_opt: DbOpt, builder: MusicHoardBuilder<NoDatabase, NoLibrary>) {
|
2024-01-12 21:15:59 +01:00
|
|
|
if lib_opt.no_library {
|
|
|
|
with_database(db_opt, builder.set_library(NullLibrary));
|
|
|
|
} else if let Some(uri) = lib_opt.beets_ssh_uri {
|
|
|
|
let uri = uri.into_string().expect("invalid SSH URI");
|
|
|
|
let beets_config_file_path = lib_opt
|
|
|
|
.beets_config_file_path
|
|
|
|
.map(|s| s.into_string())
|
|
|
|
.transpose()
|
|
|
|
.expect("failed to extract beets config file path");
|
2024-08-24 23:10:59 +02:00
|
|
|
let lib_exec = match lib_opt.beets_bin_path {
|
|
|
|
Some(beets_bin) => {
|
|
|
|
let bin = beets_bin.into_string().expect("invalid beets binary path");
|
|
|
|
BeetsLibrarySshExecutor::bin(uri, bin)
|
|
|
|
}
|
|
|
|
None => BeetsLibrarySshExecutor::new(uri),
|
|
|
|
}
|
|
|
|
.expect("failed to initialise beets")
|
|
|
|
.config(beets_config_file_path);
|
2024-01-12 21:15:59 +01:00
|
|
|
with_database(db_opt, builder.set_library(BeetsLibrary::new(lib_exec)));
|
|
|
|
} else {
|
2024-08-24 23:10:59 +02:00
|
|
|
let lib_exec = match lib_opt.beets_bin_path {
|
|
|
|
Some(beets_bin) => BeetsLibraryProcessExecutor::bin(beets_bin),
|
|
|
|
None => BeetsLibraryProcessExecutor::default(),
|
|
|
|
}
|
|
|
|
.config(lib_opt.beets_config_file_path);
|
2024-01-12 21:15:59 +01:00
|
|
|
with_database(db_opt, builder.set_library(BeetsLibrary::new(lib_exec)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let opt = Opt::from_args();
|
|
|
|
let builder = MusicHoardBuilder::default();
|
|
|
|
with_library(opt.lib_opt, opt.db_opt, builder);
|
|
|
|
}
|
|
|
|
|
2023-04-13 14:09:59 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
#[macro_use]
|
2024-03-16 16:57:50 +01:00
|
|
|
mod testmod;
|