refactor: move and add functions
This commit is contained in:
committed by
Henrik Friedrichsen
parent
04cbe8ac20
commit
c36d3cf272
@@ -1,12 +1,11 @@
|
|||||||
use crate::{command, ipc, mpris, queue, spotify};
|
use std::path::Path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cursive::event::EventTrigger;
|
use cursive::event::EventTrigger;
|
||||||
|
use cursive::theme::Theme;
|
||||||
use cursive::traits::Nameable;
|
use cursive::traits::Nameable;
|
||||||
use cursive::{Cursive, CursiveRunner};
|
use cursive::{Cursive, CursiveRunner};
|
||||||
use librespot_core::authentication::Credentials;
|
|
||||||
use librespot_core::cache::Cache;
|
|
||||||
use log::{error, info, trace};
|
use log::{error, info, trace};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -14,27 +13,20 @@ use signal_hook::{consts::SIGHUP, consts::SIGTERM, iterator::Signals};
|
|||||||
|
|
||||||
use crate::command::{Command, JumpMode};
|
use crate::command::{Command, JumpMode};
|
||||||
use crate::commands::CommandManager;
|
use crate::commands::CommandManager;
|
||||||
use crate::config::{self, cache_path, set_configuration_base_path, Config};
|
use crate::config::{cache_path, Config};
|
||||||
use crate::events::{Event, EventManager};
|
use crate::events::{Event, EventManager};
|
||||||
use crate::ext_traits::CursiveExt;
|
use crate::ext_traits::CursiveExt;
|
||||||
|
use crate::ipc::IpcSocket;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
|
use crate::queue::Queue;
|
||||||
use crate::spotify::{PlayerEvent, Spotify};
|
use crate::spotify::{PlayerEvent, Spotify};
|
||||||
use crate::ui::contextmenu::ContextMenu;
|
use crate::ui::contextmenu::ContextMenu;
|
||||||
|
use crate::ui::create_cursive;
|
||||||
use crate::{authentication, ui};
|
use crate::{authentication, ui};
|
||||||
|
use crate::{command, ipc, queue, spotify};
|
||||||
|
|
||||||
fn credentials_prompt(error_message: Option<String>) -> Result<Credentials, String> {
|
#[cfg(feature = "mpris")]
|
||||||
if let Some(message) = error_message {
|
use crate::mpris::{self, MprisManager};
|
||||||
let mut siv = cursive::default();
|
|
||||||
let dialog = cursive::views::Dialog::around(cursive::views::TextView::new(format!(
|
|
||||||
"Connection error:\n{message}"
|
|
||||||
)))
|
|
||||||
.button("Ok", |s| s.quit());
|
|
||||||
siv.add_layer(dialog);
|
|
||||||
siv.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
authentication::create_credentials()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up the global logger to log to `filename`.
|
/// Set up the global logger to log to `filename`.
|
||||||
pub fn setup_logging(filename: &Path) -> Result<(), fern::InitError> {
|
pub fn setup_logging(filename: &Path) -> Result<(), fern::InitError> {
|
||||||
@@ -77,14 +69,24 @@ lazy_static!(
|
|||||||
pub struct Application {
|
pub struct Application {
|
||||||
/// The Spotify library, which is obtained from the Spotify API using rspotify.
|
/// The Spotify library, which is obtained from the Spotify API using rspotify.
|
||||||
library: Arc<Library>,
|
library: Arc<Library>,
|
||||||
|
/// The music queue which controls playback order.
|
||||||
|
queue: Arc<Queue>,
|
||||||
/// Internally shared
|
/// Internally shared
|
||||||
spotify: Spotify,
|
spotify: Spotify,
|
||||||
/// The configuration provided in the config file.
|
/// The configuration provided in the config file.
|
||||||
config: Arc<Config>,
|
configuration: Arc<Config>,
|
||||||
/// Internally shared
|
/// Internally shared
|
||||||
event_manager: EventManager,
|
event_manager: EventManager,
|
||||||
|
/// An IPC implementation using the D-Bus MPRIS protocol, used to control and inspect ncspot.
|
||||||
|
#[cfg(feature = "mpris")]
|
||||||
|
mpris_manager: MprisManager,
|
||||||
|
/// An IPC implementation using a Unix domain socket, used to control and inspect ncspot.
|
||||||
|
#[cfg(unix)]
|
||||||
|
ipc: IpcSocket,
|
||||||
/// The object to render to the terminal.
|
/// The object to render to the terminal.
|
||||||
cursive: CursiveRunner<Cursive>,
|
cursive: CursiveRunner<Cursive>,
|
||||||
|
/// The theme used to draw the user interface.
|
||||||
|
theme: Rc<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
@@ -94,97 +96,75 @@ impl Application {
|
|||||||
///
|
///
|
||||||
/// * `configuration_base_path` - Path to the configuration directory
|
/// * `configuration_base_path` - Path to the configuration directory
|
||||||
/// * `configuration_file_path` - Relative path to the configuration file inside the base path
|
/// * `configuration_file_path` - Relative path to the configuration file inside the base path
|
||||||
pub fn new(
|
pub fn new(configuration_file_path: Option<String>) -> Result<Self, String> {
|
||||||
configuration_base_path: Option<PathBuf>,
|
// Things here may cause the process to abort; we must do them before creating curses
|
||||||
configuration_file_path: Option<PathBuf>,
|
// windows otherwise the error message will not be seen by a user
|
||||||
) -> Result<Self, String> {
|
|
||||||
set_configuration_base_path(configuration_base_path);
|
|
||||||
|
|
||||||
// Things here may cause the process to abort; we must do them before creating curses windows
|
let configuration = Arc::new(Config::new(configuration_file_path));
|
||||||
// otherwise the error message will not be seen by a user
|
let credentials = authentication::get_credentials(&configuration)?;
|
||||||
let config = Arc::new(Config::new(
|
let theme = configuration.build_theme();
|
||||||
configuration_file_path.unwrap_or("config.toml".into()),
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut credentials = {
|
|
||||||
let cache = Cache::new(Some(config::cache_path("librespot")), None, None, None)
|
|
||||||
.expect("Could not create librespot cache");
|
|
||||||
let cached_credentials = cache.credentials();
|
|
||||||
match cached_credentials {
|
|
||||||
Some(c) => {
|
|
||||||
info!("Using cached credentials");
|
|
||||||
c
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
info!("Attempting to resolve credentials via username/password commands");
|
|
||||||
let creds = config.values().credentials.clone().unwrap_or_default();
|
|
||||||
|
|
||||||
match (creds.username_cmd, creds.password_cmd) {
|
|
||||||
(Some(username_cmd), Some(password_cmd)) => {
|
|
||||||
authentication::credentials_eval(&username_cmd, &password_cmd)?
|
|
||||||
}
|
|
||||||
_ => credentials_prompt(None)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Err(error) = spotify::Spotify::test_credentials(credentials.clone()) {
|
|
||||||
let error_msg = format!("{error}");
|
|
||||||
credentials = credentials_prompt(Some(error_msg))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Connecting to Spotify..");
|
println!("Connecting to Spotify..");
|
||||||
|
|
||||||
// DON'T USE STDOUT AFTER THIS CALL!
|
// DON'T USE STDOUT AFTER THIS CALL!
|
||||||
let backend = cursive::backends::try_default().map_err(|e| e.to_string())?;
|
let mut cursive = create_cursive().map_err(|error| error.to_string())?;
|
||||||
let buffered_backend = Box::new(cursive_buffered_backend::BufferedBackend::new(backend));
|
|
||||||
|
|
||||||
let mut cursive = cursive::CursiveRunner::new(cursive::Cursive::new(), buffered_backend);
|
cursive.set_theme(theme.clone());
|
||||||
cursive.set_window_title("ncspot");
|
|
||||||
|
|
||||||
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
||||||
|
|
||||||
let spotify = spotify::Spotify::new(event_manager.clone(), credentials, config.clone());
|
let spotify =
|
||||||
|
spotify::Spotify::new(event_manager.clone(), credentials, configuration.clone());
|
||||||
|
|
||||||
let library = Arc::new(Library::new(
|
let library = Arc::new(Library::new(
|
||||||
&event_manager,
|
event_manager.clone(),
|
||||||
spotify.clone(),
|
spotify.clone(),
|
||||||
config.clone(),
|
configuration.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
library,
|
|
||||||
spotify,
|
|
||||||
config,
|
|
||||||
event_manager,
|
|
||||||
cursive,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), String> {
|
|
||||||
let theme = self.config.build_theme();
|
|
||||||
self.cursive.set_theme(theme.clone());
|
|
||||||
|
|
||||||
let queue = Arc::new(queue::Queue::new(
|
let queue = Arc::new(queue::Queue::new(
|
||||||
self.spotify.clone(),
|
spotify.clone(),
|
||||||
self.config.clone(),
|
configuration.clone(),
|
||||||
self.library.clone(),
|
library.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(feature = "mpris")]
|
#[cfg(feature = "mpris")]
|
||||||
let mpris_manager = mpris::MprisManager::new(
|
let mpris_manager = mpris::MprisManager::new(
|
||||||
self.event_manager.clone(),
|
event_manager.clone(),
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
self.library.clone(),
|
library.clone(),
|
||||||
self.spotify.clone(),
|
spotify.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let ipc = ipc::IpcSocket::new(
|
||||||
|
ASYNC_RUNTIME.handle(),
|
||||||
|
cache_path("ncspot.sock"),
|
||||||
|
event_manager.clone(),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
library,
|
||||||
|
queue,
|
||||||
|
spotify,
|
||||||
|
configuration,
|
||||||
|
event_manager,
|
||||||
|
#[cfg(feature = "mpris")]
|
||||||
|
mpris_manager,
|
||||||
|
#[cfg(unix)]
|
||||||
|
ipc,
|
||||||
|
cursive,
|
||||||
|
theme: Rc::new(theme),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Result<(), String> {
|
||||||
let mut cmd_manager = CommandManager::new(
|
let mut cmd_manager = CommandManager::new(
|
||||||
self.spotify.clone(),
|
self.spotify.clone(),
|
||||||
queue.clone(),
|
self.queue.clone(),
|
||||||
self.library.clone(),
|
self.library.clone(),
|
||||||
self.config.clone(),
|
self.configuration.clone(),
|
||||||
self.event_manager.clone(),
|
self.event_manager.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -196,31 +176,35 @@ impl Application {
|
|||||||
|
|
||||||
let search = ui::search::SearchView::new(
|
let search = ui::search::SearchView::new(
|
||||||
self.event_manager.clone(),
|
self.event_manager.clone(),
|
||||||
queue.clone(),
|
self.queue.clone(),
|
||||||
self.library.clone(),
|
self.library.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let libraryview = ui::library::LibraryView::new(queue.clone(), self.library.clone());
|
let libraryview = ui::library::LibraryView::new(self.queue.clone(), self.library.clone());
|
||||||
|
|
||||||
let queueview = ui::queue::QueueView::new(queue.clone(), self.library.clone());
|
let queueview = ui::queue::QueueView::new(self.queue.clone(), self.library.clone());
|
||||||
|
|
||||||
#[cfg(feature = "cover")]
|
#[cfg(feature = "cover")]
|
||||||
let coverview =
|
let coverview = ui::cover::CoverView::new(
|
||||||
ui::cover::CoverView::new(queue.clone(), self.library.clone(), &self.config);
|
self.queue.clone(),
|
||||||
|
self.library.clone(),
|
||||||
|
&self.configuration,
|
||||||
|
);
|
||||||
|
|
||||||
let status = ui::statusbar::StatusBar::new(queue.clone(), Arc::clone(&self.library));
|
let status = ui::statusbar::StatusBar::new(self.queue.clone(), Arc::clone(&self.library));
|
||||||
|
|
||||||
let mut layout = ui::layout::Layout::new(status, &self.event_manager, theme)
|
let mut layout =
|
||||||
.screen("search", search.with_name("search"))
|
ui::layout::Layout::new(status, &self.event_manager, Rc::clone(&self.theme))
|
||||||
.screen("library", libraryview.with_name("library"))
|
.screen("search", search.with_name("search"))
|
||||||
.screen("queue", queueview);
|
.screen("library", libraryview.with_name("library"))
|
||||||
|
.screen("queue", queueview);
|
||||||
|
|
||||||
#[cfg(feature = "cover")]
|
#[cfg(feature = "cover")]
|
||||||
layout.add_screen("cover", coverview.with_name("cover"));
|
layout.add_screen("cover", coverview.with_name("cover"));
|
||||||
|
|
||||||
// initial screen is library
|
// initial screen is library
|
||||||
let initial_screen = self
|
let initial_screen = self
|
||||||
.config
|
.configuration
|
||||||
.values()
|
.values()
|
||||||
.initial_screen
|
.initial_screen
|
||||||
.clone()
|
.clone()
|
||||||
@@ -235,8 +219,8 @@ impl Application {
|
|||||||
let cmd_key = |cfg: Arc<Config>| cfg.values().command_key.unwrap_or(':');
|
let cmd_key = |cfg: Arc<Config>| cfg.values().command_key.unwrap_or(':');
|
||||||
|
|
||||||
{
|
{
|
||||||
let c = self.config.clone();
|
let c = self.configuration.clone();
|
||||||
let config_clone = Arc::clone(&self.config);
|
let config_clone = Arc::clone(&self.configuration);
|
||||||
self.cursive.set_on_post_event(
|
self.cursive.set_on_post_event(
|
||||||
EventTrigger::from_fn(move |event| {
|
EventTrigger::from_fn(move |event| {
|
||||||
event == &cursive::event::Event::Char(cmd_key(c.clone()))
|
event == &cursive::event::Event::Char(cmd_key(c.clone()))
|
||||||
@@ -316,16 +300,6 @@ impl Application {
|
|||||||
let mut signals =
|
let mut signals =
|
||||||
Signals::new([SIGTERM, SIGHUP]).expect("could not register signal handler");
|
Signals::new([SIGTERM, SIGHUP]).expect("could not register signal handler");
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
let ipc = {
|
|
||||||
ipc::IpcSocket::new(
|
|
||||||
ASYNC_RUNTIME.handle(),
|
|
||||||
cache_path("ncspot.sock"),
|
|
||||||
self.event_manager.clone(),
|
|
||||||
)
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
};
|
|
||||||
|
|
||||||
// cursive event loop
|
// cursive event loop
|
||||||
while self.cursive.is_running() {
|
while self.cursive.is_running() {
|
||||||
self.cursive.step();
|
self.cursive.step();
|
||||||
@@ -345,17 +319,17 @@ impl Application {
|
|||||||
self.spotify.update_status(state.clone());
|
self.spotify.update_status(state.clone());
|
||||||
|
|
||||||
#[cfg(feature = "mpris")]
|
#[cfg(feature = "mpris")]
|
||||||
mpris_manager.update();
|
self.mpris_manager.update();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
ipc.publish(&state, queue.get_current());
|
self.ipc.publish(&state, self.queue.get_current());
|
||||||
|
|
||||||
if state == PlayerEvent::FinishedTrack {
|
if state == PlayerEvent::FinishedTrack {
|
||||||
queue.next(false);
|
self.queue.next(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Queue(event) => {
|
Event::Queue(event) => {
|
||||||
queue.handle_event(event);
|
self.queue.handle_event(event);
|
||||||
}
|
}
|
||||||
Event::SessionDied => self.spotify.start_worker(None),
|
Event::SessionDied => self.spotify.start_worker(None),
|
||||||
Event::IpcInput(input) => match command::parse(&input) {
|
Event::IpcInput(input) => match command::parse(&input) {
|
||||||
|
|||||||
@@ -3,13 +3,71 @@ use std::process::Command;
|
|||||||
use cursive::traits::Resizable;
|
use cursive::traits::Resizable;
|
||||||
use cursive::view::Nameable;
|
use cursive::view::Nameable;
|
||||||
use cursive::views::*;
|
use cursive::views::*;
|
||||||
use cursive::{Cursive, CursiveExt};
|
use cursive::Cursive;
|
||||||
|
|
||||||
use librespot_core::authentication::Credentials as RespotCredentials;
|
use librespot_core::authentication::Credentials as RespotCredentials;
|
||||||
|
use librespot_core::cache::Cache;
|
||||||
use librespot_protocol::authentication::AuthenticationType;
|
use librespot_protocol::authentication::AuthenticationType;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::config::{self, Config};
|
||||||
|
use crate::spotify::Spotify;
|
||||||
|
use crate::ui::create_cursive;
|
||||||
|
|
||||||
|
/// Get credentials for use with librespot. This first tries to get cached credentials. If no cached
|
||||||
|
/// credentials are available, it will either try to get them from the user configured commands, or
|
||||||
|
/// if that fails, it will prompt the user on stdout.
|
||||||
|
pub fn get_credentials(configuration: &Config) -> Result<RespotCredentials, String> {
|
||||||
|
let mut credentials = {
|
||||||
|
let cache = Cache::new(Some(config::cache_path("librespot")), None, None, None)
|
||||||
|
.expect("Could not create librespot cache");
|
||||||
|
let cached_credentials = cache.credentials();
|
||||||
|
match cached_credentials {
|
||||||
|
Some(c) => {
|
||||||
|
info!("Using cached credentials");
|
||||||
|
c
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
info!("Attempting to resolve credentials via username/password commands");
|
||||||
|
let creds = configuration
|
||||||
|
.values()
|
||||||
|
.credentials
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
match (creds.username_cmd, creds.password_cmd) {
|
||||||
|
(Some(username_cmd), Some(password_cmd)) => {
|
||||||
|
credentials_eval(&username_cmd, &password_cmd)?
|
||||||
|
}
|
||||||
|
_ => credentials_prompt(None)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Err(error) = Spotify::test_credentials(credentials.clone()) {
|
||||||
|
let error_msg = format!("{error}");
|
||||||
|
credentials = credentials_prompt(Some(error_msg))?;
|
||||||
|
}
|
||||||
|
Ok(credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn credentials_prompt(error_message: Option<String>) -> Result<RespotCredentials, String> {
|
||||||
|
if let Some(message) = error_message {
|
||||||
|
let mut siv = create_cursive().unwrap();
|
||||||
|
let dialog = cursive::views::Dialog::around(cursive::views::TextView::new(format!(
|
||||||
|
"Connection error:\n{message}"
|
||||||
|
)))
|
||||||
|
.button("Ok", |s| s.quit());
|
||||||
|
siv.add_layer(dialog);
|
||||||
|
siv.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
create_credentials()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_credentials() -> Result<RespotCredentials, String> {
|
pub fn create_credentials() -> Result<RespotCredentials, String> {
|
||||||
let mut login_cursive = Cursive::default();
|
let mut login_cursive = create_cursive().unwrap();
|
||||||
let info_buf = TextContent::new("Please login to Spotify\n");
|
let info_buf = TextContent::new("Please login to Spotify\n");
|
||||||
let info_view = Dialog::around(TextView::new_with_content(info_buf))
|
let info_view = Dialog::around(TextView::new_with_content(info_buf))
|
||||||
.button("Login", move |s| {
|
.button("Login", move |s| {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ lazy_static! {
|
|||||||
/// The complete configuration (state + user configuration) of ncspot.
|
/// The complete configuration (state + user configuration) of ncspot.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The configuration file path.
|
/// The configuration file path.
|
||||||
filename: PathBuf,
|
filename: String,
|
||||||
/// Configuration set by the user, read only.
|
/// Configuration set by the user, read only.
|
||||||
values: RwLock<ConfigValues>,
|
values: RwLock<ConfigValues>,
|
||||||
/// Runtime state which can't be edited by the user, read/write.
|
/// Runtime state which can't be edited by the user, read/write.
|
||||||
@@ -191,8 +191,10 @@ pub struct Config {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Generate the configuration from the user configuration file and the runtime state file.
|
/// Generate the configuration from the user configuration file and the runtime state file.
|
||||||
pub fn new(filename: PathBuf) -> Self {
|
/// `filename` can be used to look for a differently named configuration file.
|
||||||
let values = load(&filename.to_string_lossy()).unwrap_or_else(|e| {
|
pub fn new(filename: Option<String>) -> Self {
|
||||||
|
let filename = filename.unwrap_or("config.toml".to_owned());
|
||||||
|
let values = load(&filename).unwrap_or_else(|e| {
|
||||||
eprintln!("could not load config: {e}");
|
eprintln!("could not load config: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
@@ -256,7 +258,7 @@ impl Config {
|
|||||||
|
|
||||||
/// Reload the configuration file.
|
/// Reload the configuration file.
|
||||||
pub fn reload(&self) {
|
pub fn reload(&self) {
|
||||||
let cfg = load(&self.filename.to_string_lossy()).expect("could not reload config");
|
let cfg = load(&self.filename).expect("could not reload config");
|
||||||
*self.values.write().expect("can't writelock config values") = cfg
|
*self.values.write().expect("can't writelock config values") = cfg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ pub fn program_arguments() -> clap::Command {
|
|||||||
.short('c')
|
.short('c')
|
||||||
.long("config")
|
.long("config")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.value_parser(PathBufValueParser::new())
|
|
||||||
.help("Filename of config file in basepath")
|
.help("Filename of config file in basepath")
|
||||||
.default_value("config.toml"),
|
.default_value("config.toml"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub struct Library {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Library {
|
impl Library {
|
||||||
pub fn new(ev: &EventManager, spotify: Spotify, cfg: Arc<Config>) -> Self {
|
pub fn new(ev: EventManager, spotify: Spotify, cfg: Arc<Config>) -> Self {
|
||||||
let current_user = spotify.api.current_user();
|
let current_user = spotify.api.current_user();
|
||||||
let user_id = current_user.as_ref().map(|u| u.id.id().to_string());
|
let user_id = current_user.as_ref().map(|u| u.id.id().to_string());
|
||||||
let display_name = current_user.as_ref().and_then(|u| u.display_name.clone());
|
let display_name = current_user.as_ref().and_then(|u| u.display_name.clone());
|
||||||
@@ -56,7 +56,7 @@ impl Library {
|
|||||||
is_done: Arc::new(RwLock::new(false)),
|
is_done: Arc::new(RwLock::new(false)),
|
||||||
user_id,
|
user_id,
|
||||||
display_name,
|
display_name,
|
||||||
ev: ev.clone(),
|
ev,
|
||||||
spotify,
|
spotify,
|
||||||
cfg,
|
cfg,
|
||||||
};
|
};
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -8,6 +8,7 @@ extern crate serde;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use application::{setup_logging, Application};
|
use application::{setup_logging, Application};
|
||||||
|
use config::set_configuration_base_path;
|
||||||
use ncspot::program_arguments;
|
use ncspot::program_arguments;
|
||||||
|
|
||||||
mod application;
|
mod application;
|
||||||
@@ -51,11 +52,12 @@ fn main() -> Result<(), String> {
|
|||||||
setup_logging(filename).expect("logger could not be initialized");
|
setup_logging(filename).expect("logger could not be initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the configuration base path. All configuration files are read/written relative to this
|
||||||
|
// path.
|
||||||
|
set_configuration_base_path(matches.get_one::<PathBuf>("basepath").cloned());
|
||||||
|
|
||||||
// Create the application.
|
// Create the application.
|
||||||
let mut application = Application::new(
|
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;
|
||||||
matches.get_one::<PathBuf>("basepath").cloned(),
|
|
||||||
matches.get_one::<PathBuf>("config").cloned(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Start the application event loop.
|
// Start the application event loop.
|
||||||
application.run()
|
application.run()
|
||||||
|
|||||||
12
src/queue.rs
12
src/queue.rs
@@ -1,14 +1,14 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use log::{debug, error, info};
|
use log::{debug, info};
|
||||||
#[cfg(feature = "notify")]
|
#[cfg(feature = "notify")]
|
||||||
use notify_rust::{Hint, Notification, Urgency};
|
use notify_rust::{Hint, Notification, Urgency};
|
||||||
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use strum_macros::Display;
|
use strum_macros::Display;
|
||||||
|
|
||||||
use crate::config::{Config, NotificationFormat, PlaybackState};
|
use crate::config::{Config, PlaybackState};
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::model::playable::Playable;
|
use crate::model::playable::Playable;
|
||||||
use crate::spotify::PlayerEvent;
|
use crate::spotify::PlayerEvent;
|
||||||
@@ -321,10 +321,10 @@ impl Queue {
|
|||||||
.notification_format
|
.notification_format
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let default_title = NotificationFormat::default().title.unwrap();
|
let default_title = crate::config::NotificationFormat::default().title.unwrap();
|
||||||
let title = format.title.unwrap_or_else(|| default_title.clone());
|
let title = format.title.unwrap_or_else(|| default_title.clone());
|
||||||
|
|
||||||
let default_body = NotificationFormat::default().body.unwrap();
|
let default_body = crate::config::NotificationFormat::default().body.unwrap();
|
||||||
let body = format.body.unwrap_or_else(|| default_body.clone());
|
let body = format.body.unwrap_or_else(|| default_body.clone());
|
||||||
|
|
||||||
let summary_txt = Playable::format(track, &title, &self.library);
|
let summary_txt = Playable::format(track, &title, &self.library);
|
||||||
@@ -503,7 +503,7 @@ pub fn send_notification(summary_txt: &str, body_txt: &str, cover_url: Option<St
|
|||||||
let path = crate::utils::cache_path_for_url(u.to_string());
|
let path = crate::utils::cache_path_for_url(u.to_string());
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
if let Err(e) = crate::utils::download(u, path.clone()) {
|
if let Err(e) = crate::utils::download(u, path.clone()) {
|
||||||
error!("Failed to download cover: {}", e);
|
log::error!("Failed to download cover: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.icon(path.to_str().unwrap());
|
n.icon(path.to_str().unwrap());
|
||||||
@@ -521,6 +521,6 @@ pub fn send_notification(summary_txt: &str, body_txt: &str, cover_url: Option<St
|
|||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
info!("Created notification: {}", handle.id());
|
info!("Created notification: {}", handle.id());
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to send notification cover: {}", e),
|
Err(e) => log::error!("Failed to send notification cover: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use cursive::align::HAlign;
|
use cursive::align::HAlign;
|
||||||
@@ -30,11 +31,11 @@ pub struct Layout {
|
|||||||
screenchange: bool,
|
screenchange: bool,
|
||||||
last_size: Vec2,
|
last_size: Vec2,
|
||||||
ev: events::EventManager,
|
ev: events::EventManager,
|
||||||
theme: Theme,
|
theme: Rc<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
pub fn new<T: IntoBoxedView>(status: T, ev: &events::EventManager, theme: Theme) -> Layout {
|
pub fn new<T: IntoBoxedView>(status: T, ev: &events::EventManager, theme: Rc<Theme>) -> Layout {
|
||||||
let style = ColorStyle::new(
|
let style = ColorStyle::new(
|
||||||
ColorType::Color(*theme.palette.custom("cmdline_bg").unwrap()),
|
ColorType::Color(*theme.palette.custom("cmdline_bg").unwrap()),
|
||||||
ColorType::Color(*theme.palette.custom("cmdline").unwrap()),
|
ColorType::Color(*theme.palette.custom("cmdline").unwrap()),
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use cursive::{Cursive, CursiveRunner};
|
||||||
|
use ncspot::BIN_NAME;
|
||||||
|
|
||||||
pub mod album;
|
pub mod album;
|
||||||
pub mod artist;
|
pub mod artist;
|
||||||
pub mod browse;
|
pub mod browse;
|
||||||
@@ -19,3 +22,14 @@ pub mod tabview;
|
|||||||
|
|
||||||
#[cfg(feature = "cover")]
|
#[cfg(feature = "cover")]
|
||||||
pub mod cover;
|
pub mod cover;
|
||||||
|
|
||||||
|
/// Create a CursiveRunner which implements the drawing logic and event loop.
|
||||||
|
pub fn create_cursive() -> Result<CursiveRunner<Cursive>, Box<dyn std::error::Error>> {
|
||||||
|
let backend = cursive::backends::try_default()?;
|
||||||
|
let buffered_backend = Box::new(cursive_buffered_backend::BufferedBackend::new(backend));
|
||||||
|
let mut cursive_runner = CursiveRunner::new(cursive::Cursive::new(), buffered_backend);
|
||||||
|
|
||||||
|
cursive_runner.set_window_title(BIN_NAME);
|
||||||
|
|
||||||
|
Ok(cursive_runner)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user