polybar-dwm/src/interfaces/mpd.cpp
2016-05-20 05:34:07 +02:00

390 lines
9.3 KiB
C++

#include <cstring>
#include <string>
#include <assert.h>
#include <thread>
#include "lemonbuddy.hpp"
#include "services/logger.hpp"
#include "interfaces/mpd.hpp"
#include "utils/math.hpp"
namespace mpd
{
std::shared_ptr<Connection> conn;
std::shared_ptr<Connection> &Connection::get()
{
if (!conn)
conn = std::make_shared<Connection>();
return conn;
}
// Base
void Connection::connect() throw (ClientError)
{
assert(!this->connection);
try {
this->connection.reset(mpd_connection_new(this->host.c_str(), this->port, this->timeout * 1000));
this->check_errors();
if (!this->password.empty()) {
this->noidle();
assert(!this->mpd_command_list_active);
mpd_run_password(this->connection.get(), this->password.c_str());
this->check_errors();
}
this->mpd_fd = mpd_connection_get_fd(this->connection.get());
this->check_errors();
} catch(ClientError &e) {
this->disconnect();
throw e;
}
}
void Connection::disconnect()
{
this->connection.reset();
this->mpd_idle = false;
this->mpd_command_list_active = false;
}
bool Connection::connected()
{
return this->connection.get() != nullptr;
}
bool Connection::retry_connection(int interval)
{
if (this->connected())
return true;
while (true) {
try {
this->connect();
return true;
} catch (Exception &e) {
get_logger()->debug(e.what());
}
std::this_thread::sleep_for(
std::chrono::duration<double>(interval));
}
}
void Connection::idle()
{
this->check_connection();
if (!this->mpd_idle) {
mpd_send_idle(this->connection.get());
this->check_errors();
}
this->mpd_idle = true;
}
int Connection::noidle()
{
this->check_connection();
int flags = 0;
if (this->mpd_idle && mpd_send_noidle(this->connection.get())) {
this->mpd_idle = false;
flags = mpd_recv_idle(this->connection.get(), true);
mpd_response_finish(this->connection.get());
this->check_errors();
}
return flags;
}
void Connection::check_connection() throw(ClientError)
{
if (!this->connection)
throw ClientError("Not connected to MPD server", MPD_ERROR_STATE, false);
}
void Connection::check_prerequisites()
{
this->check_connection();
this->noidle();
}
void Connection::check_prerequisites_commands_list()
{
this->noidle();
assert(!this->mpd_command_list_active);
this->check_prerequisites();
}
void Connection::check_errors() throw(ClientError, ServerError)
{
auto connection = this->connection.get();
mpd_error code = mpd_connection_get_error(connection);
if (code == MPD_ERROR_SUCCESS)
return;
std::string msg = mpd_connection_get_error_message(connection);
if (code == MPD_ERROR_SERVER)
throw ServerError(msg,
mpd_connection_get_server_error(connection),
mpd_connection_clear_error(connection));
else
throw ClientError(msg, code, mpd_connection_clear_error(connection));
}
// Commands
void Connection::play()
{
try {
this->check_prerequisites_commands_list();
mpd_run_play(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::pause(bool state)
{
try {
this->check_prerequisites_commands_list();
mpd_run_pause(this->connection.get(), state);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::toggle()
{
try {
this->check_prerequisites_commands_list();
mpd_run_toggle_pause(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::stop()
{
try {
this->check_prerequisites_commands_list();
mpd_run_stop(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::prev()
{
try {
this->check_prerequisites_commands_list();
mpd_run_previous(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::next()
{
try {
this->check_prerequisites_commands_list();
mpd_run_next(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::seek(int percentage)
{
try {
auto status = this->get_status();
if (status->total_time == 0)
return;
if (percentage < 0)
percentage = 0;
else if (percentage > 100)
percentage = 100;
int pos = float(status->total_time) * percentage / 100.0f + 0.5f;
this->check_prerequisites_commands_list();
mpd_run_seek_id(this->connection.get(), status->song_id, pos);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::repeat(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_repeat(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::random(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_random(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::single(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_single(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
// Status
std::unique_ptr<Status> Connection::get_status()
{
this->check_prerequisites();
mpd_status *status = mpd_run_status(this->connection.get());
this->check_errors();
return std::make_unique<Status>(status);
}
Status::Status(struct mpd_status *status) {
this->set(std::unique_ptr<struct mpd_status, StatusDeleter> {status});
}
void Status::set(std::unique_ptr<struct mpd_status, StatusDeleter> status)
{
this->status.swap(status);
this->song_id = mpd_status_get_song_id(this->status.get());
this->random = mpd_status_get_random(this->status.get());
this->repeat = mpd_status_get_repeat(this->status.get());
this->single = mpd_status_get_single(this->status.get());
this->elapsed_time = mpd_status_get_elapsed_time(this->status.get());
this->total_time = mpd_status_get_total_time(this->status.get());
this->updated_at = std::chrono::system_clock::now();
}
void Status::update(int event)
{
auto status = Connection::get()->get_status();
if (event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS)) {
this->set(std::move(status->status));
this->elapsed_time_ms = this->elapsed_time * 1000;
this->song = Connection::get()->get_song();
auto mpd_state = mpd_status_get_state(this->status.get());
switch (mpd_state) {
case MPD_STATE_PAUSE: this->state = PAUSED; break;
case MPD_STATE_PLAY: this->state = PLAYING; break;
case MPD_STATE_STOP: this->state = STOPPED; break;
default: this->state = UNKNOWN;
}
}
}
void Status::update_timer()
{
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - this->updated_at);
this->elapsed_time_ms += dur.count();
this->elapsed_time = this->elapsed_time_ms / 1000 + 0.5f;
this->updated_at = std::chrono::system_clock::now();
}
unsigned Status::get_total_time() {
return this->total_time;
}
unsigned Status::get_elapsed_time() {
return this->elapsed_time;
}
unsigned Status::get_elapsed_percentage()
{
if (this->total_time == 0) return 0;
return (int) float(this->elapsed_time) / float(this->total_time) * 100 + 0.5f;
}
std::string Status::get_formatted_elapsed()
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", this->elapsed_time / 60, this->elapsed_time % 60);
return std::string(buffer);
}
std::string Status::get_formatted_total()
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", this->total_time / 60, this->total_time % 60);
return std::string(buffer);
}
// Song
std::unique_ptr<Song> Connection::get_song()
{
this->check_prerequisites_commands_list();
mpd_send_current_song(this->connection.get());
mpd_song *song = mpd_recv_song(this->connection.get());
mpd_response_finish(this->connection.get());
this->check_errors();
if (song == nullptr)
return std::make_unique<Song>();
else
return std::make_unique<Song>(song);
}
Song::Song(struct mpd_song *song) {
this->song = std::shared_ptr<struct mpd_song>(song, mpd_song_free);
}
std::string Song::get_artist()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_ARTIST, 0);
}
std::string Song::get_album()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_ALBUM, 0);
}
std::string Song::get_title()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_TITLE, 0);
}
unsigned Song::get_duration()
{
assert(this->song);
return mpd_song_get_duration(this->song.get());
}
}