/******************************************************************************!
* \file Client.cpp
* \author Sebastien Beaugrand
* \sa http://beaugrand.chez.com/
* \copyright CeCILL 2.1 Free Software license
******************************************************************************/
#include <fstream>
#include "Client.h"
#include "log.h"
/******************************************************************************!
* \fn Client
******************************************************************************/
Client::Client(Input& input, Output& output, const std::string& url)
: mInput(input)
, mOutput(output)
, mHttpClient(url)
, mJsonClient(mHttpClient)
{
std::string tz;
if (std::ifstream("/etc/timezone") >> tz; tz == "Europe/Paris") {
mLang = FR;
} else {
mLang = EN;
}
}
/******************************************************************************!
* \fn Client
******************************************************************************/
Client::~Client()
{
DEBUG("");
}
/******************************************************************************!
* \fn close
******************************************************************************/
void
Client::close()
{
this->loop = false;
mInput.hasEvent = true;
mInput.hasEvent.notify_one();
}
/******************************************************************************!
* \fn currentTitle
******************************************************************************/
void
Client::currentTitle(const Json::Value json)
{
try {
Json::Value error("");
auto pause = json.get("pause", false).asBool();
auto pos = json.get("pos", -1).asInt() + 1;
auto length = json.get("length", -1).asInt() + 1;
auto title = json.get("title", error).asString();
auto album = json.get("album", error).asString();
auto artist = json.get("artist", error).asString();
auto date = json.get("date", error).asString();
int diffmax = -Output::LCD_SHIFT;
if (mShift > 0) {
auto size = album.size();
int diff = size - mShift - Output::LCD_COLS;
if (diff >= 0) {
album = album.substr(mShift);
} else if (size > Output::LCD_COLS) {
album = album.substr(size - Output::LCD_COLS);
}
if (diffmax < diff) {
diffmax = diff;
}
}
if (mShift > 0) {
auto size = title.size();
int diff = size - mShift - Output::LCD_COLS;
if (diff >= 0) {
title = title.substr(mShift);
} else if (size > Output::LCD_COLS) {
title = title.substr(size - Output::LCD_COLS);
}
if (diffmax < diff) {
diffmax = diff;
}
}
if (mShift > 0 && diffmax <= -Output::LCD_SHIFT) {
mShift -= Output::LCD_SHIFT;
}
mOutput.write((pause ? "PAUSE " : "") + artist,
album,
date + ' ' +
std::to_string(pos) + '/' + std::to_string(length),
title);
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
} catch (const Json::LogicError& e) {
ERROR(e.what());
}
}
/******************************************************************************!
* \fn currentAlbum
******************************************************************************/
void
Client::currentAlbum(const Json::Value json)
{
char dateR[3] = { 0 };
if (int d = json["days"].asInt(); d > 0) {
int dInit = d;
if (d > 9) {
d /= 7;
if (d > 9) {
d = dInit / 31;
if (d > 9) {
d /= 12;
dateR[1] = (mLang == FR) ? 'A' : 'Y';
} else {
dateR[1] = 'M';
}
} else {
dateR[1] = (mLang == FR) ? 'S' : 'W';
}
} else {
dateR[1] = (mLang == FR) ? 'J' : 'D';
}
dateR[0] = '0' + d;
}
mOutput.write(json["artist"].asString(),
json["album"].asString(),
json["date"].asString(),
json["abrev"].asString() + ' ' + dateR);
}
/******************************************************************************!
* \fn albumList
******************************************************************************/
void
Client::albumList()
{
Json::Value empty("");
auto line1 = mArtist.get("artist", empty).asString();
auto line2 = mArtist["album"].get(Json::ArrayIndex(mAlbumPos - 1),
empty).asString();
auto line3 = mArtist["album"].get(Json::ArrayIndex(mAlbumPos),
empty).asString();
auto line4 = mArtist["album"].get(Json::ArrayIndex(mAlbumPos + 1),
empty).asString();
try {
line3.at(4) = '>';
} catch (std::out_of_range const& e) {
ERROR(e.what());
}
mOutput.write(line1,
line2,
line3,
line4);
}
/******************************************************************************!
* \fn letters
******************************************************************************/
void
Client::letters(int pos)
{
switch (pos) {
case 0:
mOutput.write(mArtist["artist"].asString(),
" AE",
"FJ KO PT",
" UY");
break;
case 1:
mOutput.write(mArtist["artist"].asString(),
" A",
"B C D",
" E");
break;
case 2:
mOutput.write(mArtist["artist"].asString(),
" F",
"G H I",
" J");
break;
case 3:
mOutput.write(mArtist["artist"].asString(),
" K",
"L M N",
" O");
break;
case 4:
mOutput.write(mArtist["artist"].asString(),
" P",
"Q R S",
" T");
break;
case 5:
mOutput.write(mArtist["artist"].asString(),
" U",
"V W Y",
" Z");
break;
}
}
/******************************************************************************!
* \fn onEvent
******************************************************************************/
State
Client::onEvent(const state::Normal& state, const event::Up&)
{
this->currentTitle(mJsonClient.CallMethod("prev", Json::Value()));
return state;
}
State
Client::onEvent(const state::Normal& state, const event::Down&)
{
this->currentTitle(mJsonClient.CallMethod("next", Json::Value()));
return state;
}
State
Client::onEvent(const state::Normal&, const event::Left&)
{
try {
mArtist = mJsonClient.CallMethod("artist", Json::Value{});
int pos = mArtist.get("pos", Json::Value(-1)).asInt();
if (pos >= 0) {
mAlbumPos = pos;
this->albumList();
} else {
return this->onEvent(state::Album{}, event::Left{});
}
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
}
return state::Album{};
}
State
Client::onEvent(const state::Normal& state, const event::Right&)
{
this->currentTitle(mJsonClient.CallMethod("info", Json::Value()));
return state;
}
State
Client::onEvent(const state::Normal& state, const event::Ok&)
{
this->currentTitle(mJsonClient.CallMethod("ok", Json::Value()));
return state;
}
State
Client::onEvent(const state::Normal& state, const event::Setup&)
{
try {
this->currentAlbum(mJsonClient.CallMethod("rand", Json::Value{}));
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
}
return state;
}
State
Client::onEvent(const state::Album& state, const event::Up&)
{
if (mAlbumPos > 0) {
--mAlbumPos;
}
this->albumList();
return state;
}
State
Client::onEvent(const state::Album& state, const event::Down&)
{
if (mAlbumPos + 1 < mArtist["album"].size()) {
++mAlbumPos;
}
this->albumList();
return state;
}
State
Client::onEvent(const state::Album&, const event::Left&)
{
mArtist["artist"] = "";
mArtistPos = 0;
this->letters(mArtistPos);
return state::Artist{};
}
State
Client::onEvent(const state::Album&, const event::Right&)
{
mShift = 0;
this->currentTitle("info");
return state::Normal{};
}
State
Client::onEvent(const state::Album&, const event::Ok&)
{
try {
Json::Value params;
params["artist"] = mArtist["artist"];
params["pos"] = mAlbumPos;
this->currentTitle(mJsonClient.CallMethod("album", params));
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
}
return state::Normal{};
}
State
Client::onEvent(const state::Album& state, const event::Setup&)
{
return state;
}
State
Client::onEvent(const state::Artist& state, const event::Up&)
{
if (mArtistPos == 0) {
mArtistPos = 1;
} else {
mArtist["artist"] = mArtist["artist"].asString() +
mLetterList.at(mArtistPos - 1);
mArtistPos = 0;
}
this->letters(mArtistPos);
return state;
}
State
Client::onEvent(const state::Artist& state, const event::Down&)
{
if (mArtistPos == 0) {
mArtistPos = 5;
} else {
mArtist["artist"] = mArtist["artist"].asString() +
mLetterList.at(mArtistPos - 1 + 20);
mArtistPos = 0;
}
this->letters(mArtistPos);
return state;
}
State
Client::onEvent(const state::Artist& state, const event::Left&)
{
if (mArtistPos == 0) {
mArtistPos = 2;
} else {
mArtist["artist"] = mArtist["artist"].asString() +
mLetterList.at(mArtistPos - 1 + 5);
mArtistPos = 0;
}
this->letters(mArtistPos);
return state;
}
State
Client::onEvent(const state::Artist& state, const event::Right&)
{
if (mArtistPos == 0) {
mArtistPos = 4;
} else {
mArtist["artist"] = mArtist["artist"].asString() +
mLetterList.at(mArtistPos - 1 + 15);
mArtistPos = 0;
}
this->letters(mArtistPos);
return state;
}
State
Client::onEvent(const state::Artist& state, const event::Ok&)
{
if (mArtistPos == 0) {
mArtistPos = 3;
} else {
mArtist["artist"] = mArtist["artist"].asString() +
mLetterList.at(mArtistPos - 1 + 10);
mArtistPos = 0;
}
this->letters(mArtistPos);
return state;
}
State
Client::onEvent(const state::Artist&, const event::Setup&)
{
try {
Json::Value params;
params["artist"] = mArtist["artist"];
params["pos"] = -1;
mJsonClient.CallMethod("album", params);
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
}
return this->onEvent(state::Normal{}, event::Left{});
}
/******************************************************************************!
* \fn processEvent
******************************************************************************/
void
Client::processEvent(const Event& event)
{
state = std::visit(
[this](const auto& st, const auto& ev) {
return this->onEvent(st, ev);
},
state, event);
}
/******************************************************************************!
* \fn run
******************************************************************************/
int
Client::run()
{
for (;;) {
try {
auto j = mJsonClient.CallMethod("musicDirectory", Json::Value());
if (! j.empty()) {
auto d = j.asString();
DEBUG(d);
mOutput.musicDirectory = d;
}
break;
} catch (jsonrpc::JsonRpcException& e) {
ERROR(e.what());
std::this_thread::sleep_for(std::chrono::seconds(1));
} catch (const Json::LogicError& e) {
ERROR(e.what());
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
this->currentTitle(mJsonClient.CallMethod("info", Json::Value()));
while (this->loop) {
mInput.hasEvent.wait(false);
if (! this->loop) {
return 0;
}
auto key = mInput.key;
mInput.hasEvent = false;
switch (key) {
case Input::KEY_UP:
mShift = 0;
this->processEvent(event::Up{});
break;
case Input::KEY_DOWN:
mShift = 0;
this->processEvent(event::Down{});
break;
case Input::KEY_LEFT:
if (mShift > 0) {
mShift -= Output::LCD_SHIFT;
this->processEvent(event::Right{});
} else {
mShift = 0;
this->processEvent(event::Left{});
}
break;
case Input::KEY_RIGHT:
mShift += Output::LCD_SHIFT;
this->processEvent(event::Right{});
break;
case Input::KEY_OK:
mShift = 0;
this->processEvent(event::Ok{});
break;
case Input::KEY_SETUP:
mShift = 0;
this->processEvent(event::Setup{});
break;
case Input::KEY_BACK:
mShift = 0;
this->currentTitle(mJsonClient.CallMethod("info", Json::Value()));
break;
case Input::KEY_UNDEFINED:
break;
}
}
return 0;
}