extern crate dirs;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rpassword;
extern crate rspotify;
extern crate serde;
extern crate spoterm;
extern crate toml;
use std::fs;
use std::io;
use std::path;
use termion::event::{Event, Key};
use termion::input::{MouseTerminal, TermRead};
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, Paragraph, SelectableList, Tabs, Text, Widget};
use tui::Terminal;
use log::LevelFilter;
use log4rs::append::file::FileAppender;
use log4rs::config;
use log4rs::config::Appender;
use log4rs::encode::pattern::PatternEncoder;
use spoterm::config::UserConfig;
use spoterm::event;
use spoterm::spoterm::SpotermClient;
use spoterm::ui::UI;
fn init_spoterm_config_if_needed() -> Result<(), failure::Error> {
let config_dir = dirs::home_dir()
.expect("can not find home directory")
.join(".spoterm");
if !config_dir.exists() {
fs::create_dir_all(config_dir.clone())?;
}
let config = config_dir.join("config.toml");
if !config.exists() {
println!("config.toml not found and input your <CLIENT ID> and <CLIENT SECRET>");
let client_id = rpassword::read_password_from_tty(Some("Client ID: "))?;
let client_secret = rpassword::read_password_from_tty(Some("Client Secret: "))?;
let user_config = UserConfig::new()
.client_id(client_id)
.client_secret(client_secret);
fs::write(config.as_path(), toml::to_string(&user_config)?)?;
}
Ok(())
}
fn get_spotify_client_id_and_secret() -> Result<(String, String), Box<std::error::Error>> {
let config = dirs::home_dir()
.expect("can not find home directory")
.join(".spoterm")
.join("config.toml");
let config_content = fs::read_to_string(config.to_str().expect("can not read config file"))?;
let user_config: UserConfig = toml::from_str(&config_content)?;
Ok((
user_config.profile.client_id,
user_config.profile.client_secret,
))
}
fn main() -> Result<(), Box<std::error::Error>> {
init_spoterm_config_if_needed()?;
let logfile = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build("log/output.log")?;
let config = config::Config::builder()
.appender(Appender::builder().build("logfile", Box::new(logfile)))
.build(
config::Root::builder()
.appender("logfile")
.build(LevelFilter::Info),
)?;
log4rs::init_config(config)?;
let (client_id, client_secret) = get_spotify_client_id_and_secret()?;
let mut spoterm = SpotermClient::new(client_id, client_secret);
spoterm.request_device();
spoterm.request_current_user_recently_played();
spoterm.request_current_playback();
spoterm.request_current_user_saved_tracks();
spoterm.set_selected_device();
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;
let event_handler = event::EventHandler::new();
loop {
let content_ui = &mut spoterm.contents.uis[spoterm.selected_menu_tab_id];
content_ui.set_data(&spoterm.spotify_data);
content_ui.set_filter(spoterm.contents.filter.clone());
if spoterm.contents.input_mode {
match event_handler.next()? {
event::Event::KeyInput(key) => match key {
Key::Char('\n') => {
spoterm.contents.input_mode = false;
}
Key::Char(c) => {
spoterm.contents.filter.push(c);
}
Key::Backspace => {
spoterm.contents.filter.pop();
}
_ => {}
},
event::Event::Tick => {
spoterm.fetch_api_result();
spoterm.set_selected_device();
}
event::Event::APIUpdate => {
spoterm.request_device();
spoterm.request_current_playback();
spoterm.request_current_user_recently_played();
spoterm.request_current_user_saved_tracks();
spoterm.request_check_unknown_saved_tracks();
}
_ => {}
}
} else {
match event_handler.next()? {
event::Event::KeyInput(key) => match key {
Key::Char('q') => {
break;
}
Key::Char('p') | Key::Char(' ') => {
spoterm.pause();
spoterm.request_current_playback();
}
Key::Char('/') => {
spoterm.contents.input_mode = true;
}
Key::Down | Key::Char('j') => {
content_ui.key_down();
}
Key::Up | Key::Char('k') => {
content_ui.key_up();
}
Key::Char('f') => {
spoterm.request_save_current_playback();
}
Key::Char('+') => {
spoterm.request_volume(true);
}
Key::Char('-') => {
spoterm.request_volume(false);
}
Key::Char('S') => {
spoterm.shuffle();
spoterm.request_current_playback();
}
Key::Char('r') => {
spoterm.request_repeat();
}
Key::Char('>') => {
spoterm.request_next_track();
spoterm.request_current_playback();
}
Key::Char('<') => {
spoterm.request_seek_to_zero_or_previous_track();
spoterm.request_current_playback();
}
Key::Char('\n') => {
content_ui.key_enter();
}
Key::Right | Key::Char('l') => {
spoterm.move_to_next_menu_tab();
}
Key::Left | Key::Char('h') => {
spoterm.move_to_previous_menu_tab();
}
_ => {}
},
event::Event::Tick => {
spoterm.fetch_api_result();
spoterm.set_selected_device();
}
event::Event::APIUpdate => {
spoterm.request_device();
spoterm.request_current_playback();
spoterm.request_current_user_recently_played();
spoterm.request_current_user_saved_tracks();
spoterm.request_check_unknown_saved_tracks();
}
_ => {}
}
}
let filter = spoterm.contents.filter.clone();
terminal.draw(|mut f| {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints(
[
Constraint::Length(3),
Constraint::Length(5),
Constraint::Length(3),
Constraint::Length(3),
]
.as_ref(),
)
.split(size);
Tabs::default()
.block(Block::default().borders(Borders::ALL).title("Menu"))
.titles(&spoterm.menu_tabs)
.select(spoterm.selected_menu_tab_id)
.style(Style::default().fg(Color::Cyan))
.highlight_style(Style::default().fg(Color::Red))
.render(&mut f, chunks[0]);
List::new(spoterm.player_items().into_iter())
.block(Block::default().borders(Borders::ALL).title("Player"))
.render(&mut f, chunks[1]);
let filter_title = if spoterm.contents.input_mode {
"Filter(Entering.... Quit: Enter)"
} else {
"Filter(Filter Mode: /)"
};
Paragraph::new([Text::raw(filter)].iter())
.style((Style::default().fg(Color::White)))
.block(Block::default().borders(Borders::ALL).title(filter_title))
.render(&mut f, chunks[2]);
spoterm.contents.uis[spoterm.selected_menu_tab_id].render(&mut f, chunks[3]);
});
}
Ok(())
}