Modernize lyrics screen

This commit is contained in:
Andrzej Rybczak
2016-11-20 14:49:56 +01:00
parent c6f5933bf9
commit b018efceb6
10 changed files with 377 additions and 410 deletions

1
NEWS
View File

@@ -7,6 +7,7 @@ ncmpcpp-0.8 (????-??-??)
* Added support for fetching lyrics from genius.com. * Added support for fetching lyrics from genius.com.
* Added support for fetching lyrics from tekstowo.pl. * Added support for fetching lyrics from tekstowo.pl.
* The list of lyrics fetchers can now be set via configuration file. * The list of lyrics fetchers can now be set via configuration file.
* Lyrics can now be fetched for songs with no tags.
ncmpcpp-0.7.7 (2016-10-31) ncmpcpp-0.7.7 (2016-10-31)
* Fixed compilation on 32bit platforms. * Fixed compilation on 32bit platforms.

View File

@@ -145,17 +145,6 @@ PKG_CHECK_MODULES([ICU], [icu-uc], [
) )
], [[]]) ], [[]])
dnl =============================
dnl = checking for boost.thread =
dnl =============================
AC_DEFINE([BOOST_THREAD_VERSION], [3], [require boost.thread v3])
AC_CHECK_HEADERS([boost/thread.hpp], ,
AC_MSG_ERROR([boost/thread.hpp is missing or your boost version is too old (boost.thread v3 is required)])
)
AC_CHECK_LIB(boost_thread$BOOST_LIB_SUFFIX, main, LIBS="$LIBS -lboost_thread$BOOST_LIB_SUFFIX",
AC_MSG_ERROR([no boost.thread library found])
)
dnl ================================ dnl ================================
dnl = checking for various headers = dnl = checking for various headers =
dnl ================================ dnl ================================
@@ -189,16 +178,6 @@ if test "$ax_cv_lib_readline_history" = "no"; then
AC_MSG_WARN([readline library has no history functionality]) AC_MSG_WARN([readline library has no history functionality])
fi fi
dnl ========================
dnl = checking for pthread =
dnl ========================
AC_CHECK_HEADERS([pthread.h],
AC_CHECK_LIB(pthread, pthread_create, LIBS="$LIBS -lpthread",
AC_MSG_ERROR([pthread.h found but there is no pthread library to make use of])
),
AC_MSG_ERROR([no pthread.h header header file found])
)
dnl ======================== dnl ========================
dnl = checking for ncurses = dnl = checking for ncurses =
dnl ======================== dnl ========================

View File

@@ -1148,7 +1148,7 @@ bool ToggleLyricsFetcher::canBeRun()
void ToggleLyricsFetcher::run() void ToggleLyricsFetcher::run()
{ {
# ifdef HAVE_CURL_CURL_H # ifdef HAVE_CURL_CURL_H
myLyrics->ToggleFetcher(); myLyrics->toggleFetcher();
# endif // HAVE_CURL_CURL_H # endif // HAVE_CURL_CURL_H
} }
@@ -1601,7 +1601,7 @@ bool EditLyrics::canBeRun()
void EditLyrics::run() void EditLyrics::run()
{ {
myLyrics->Edit(); myLyrics->edit();
} }
bool JumpToBrowser::canBeRun() bool JumpToBrowser::canBeRun()
@@ -2309,7 +2309,7 @@ bool RefetchLyrics::canBeRun()
void RefetchLyrics::run() void RefetchLyrics::run()
{ {
# ifdef HAVE_CURL_CURL_H # ifdef HAVE_CURL_CURL_H
myLyrics->Refetch(); myLyrics->refetchCurrent();
# endif // HAVE_CURL_CURL_H # endif // HAVE_CURL_CURL_H
} }
@@ -2444,8 +2444,24 @@ void ShowArtistInfo::run()
# endif // HAVE_CURL_CURL_H # endif // HAVE_CURL_CURL_H
} }
bool ShowLyrics::canBeRun()
{
if (myScreen == myLyrics)
{
m_song = nullptr;
return true;
}
else
{
m_song = currentSong(myScreen);
return m_song != nullptr;
}
}
void ShowLyrics::run() void ShowLyrics::run()
{ {
if (m_song != nullptr)
myLyrics->fetch(*m_song);
myLyrics->switchTo(); myLyrics->switchTo();
} }

View File

@@ -1240,7 +1240,10 @@ struct ShowLyrics: BaseAction
ShowLyrics(): BaseAction(Type::ShowLyrics, "show_lyrics") { } ShowLyrics(): BaseAction(Type::ShowLyrics, "show_lyrics") { }
private: private:
virtual bool canBeRun() override;
virtual void run() override; virtual void run() override;
const MPD::Song *m_song;
}; };
struct Quit: BaseAction struct Quit: BaseAction

View File

@@ -23,7 +23,6 @@
#ifdef HAVE_CURL_CURL_H #ifdef HAVE_CURL_CURL_H
#include <cstdlib> #include <cstdlib>
#include <pthread.h>
namespace namespace
{ {
@@ -65,4 +64,3 @@ std::string Curl::escape(const std::string &s)
} }
#endif // HAVE_CURL_CURL_H #endif // HAVE_CURL_CURL_H

View File

@@ -18,6 +18,9 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/ ***************************************************************************/
#include <boost/algorithm/string/classification.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
@@ -42,22 +45,152 @@
using Global::MainHeight; using Global::MainHeight;
using Global::MainStartY; using Global::MainStartY;
#ifdef HAVE_CURL_CURL_H
LyricsFetcher *Lyrics::itsFetcher = nullptr;
std::queue<MPD::Song *> Lyrics::itsToDownload;
pthread_mutex_t Lyrics::itsDIBLock = PTHREAD_MUTEX_INITIALIZER;
size_t Lyrics::itsWorkersNumber = 0;
#endif // HAVE_CURL_CURL_H
Lyrics *myLyrics; Lyrics *myLyrics;
namespace {
std::string removeExtension(std::string filename)
{
size_t dot = filename.rfind('.');
if (dot != std::string::npos)
filename.resize(dot);
return filename;
}
std::string lyricsFilename(const MPD::Song &s)
{
std::string filename;
if (Config.store_lyrics_in_song_dir && !s.isStream())
{
if (s.isFromDatabase())
filename = Config.mpd_music_dir + "/";
filename += removeExtension(s.getURI());
removeExtension(filename);
}
else
{
std::string artist = s.getArtist();
std::string title = s.getTitle();
if (artist.empty() || title.empty())
filename = removeExtension(s.getName());
else
filename = artist + " - " + title;
removeInvalidCharsFromFilename(filename, Config.generate_win32_compatible_filenames);
filename = Config.lyrics_directory + "/" + filename;
}
filename += ".txt";
return filename;
}
bool loadLyrics(NC::Scrollpad &w, const std::string &filename)
{
std::ifstream input(filename);
if (input.is_open())
{
std::string line;
bool first_line = true;
while (std::getline(input, line))
{
// Remove carriage returns as they mess up the display.
boost::remove_erase(line, '\r');
if (!first_line)
w << '\n';
w << Charset::utf8ToLocale(line);
first_line = false;
}
return true;
}
else
return false;
}
bool saveLyrics(const std::string &filename, const std::string &lyrics)
{
std::ofstream output(filename);
if (output.is_open())
{
output << lyrics;
output.close();
return true;
}
else
return false;
}
boost::optional<std::string> downloadLyrics(
const MPD::Song &s,
std::shared_ptr<Shared<NC::Buffer>> shared_buffer,
LyricsFetcher *current_fetcher)
{
std::string s_artist = Curl::escape(s.getArtist());
std::string s_title = Curl::escape(s.getTitle());
// If artist or title is empty, use filename. This should give reasonable
// results for google search based lyrics fetchers.
if (s_artist.empty() || s_title.empty())
{
s_artist.clear();
s_title = s.getName();
// Get rid of underscores to improve search results.
std::replace_if(s_title.begin(), s_title.end(), boost::is_any_of("-_"), ' ');
size_t dot = s_title.rfind('.');
if (dot != std::string::npos)
s_title.resize(dot);
s_title = Curl::escape(s_title);
}
auto fetch_lyrics = [&](auto &fetcher_) {
{
if (shared_buffer)
{
auto buf = shared_buffer->acquire();
*buf << "Fetching lyrics from "
<< NC::Format::Bold
<< fetcher_->name()
<< NC::Format::NoBold << "... ";
}
}
auto result_ = fetcher_->fetch(s_artist, s_title);
if (result_.first == false)
{
if (shared_buffer)
{
auto buf = shared_buffer->acquire();
*buf << NC::Color::Red
<< result_.second
<< NC::Color::End
<< '\n';
}
}
return result_;
};
LyricsFetcher::Result fetcher_result;
if (current_fetcher == nullptr)
{
for (auto &fetcher : Config.lyrics_fetchers)
{
fetcher_result = fetch_lyrics(fetcher);
if (fetcher_result.first)
break;
}
}
else
fetcher_result = fetch_lyrics(current_fetcher);
boost::optional<std::string> result;
if (fetcher_result.first)
result = std::move(fetcher_result.second);
return result;
}
}
Lyrics::Lyrics() Lyrics::Lyrics()
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border())) : Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
, Reload(0), , m_refresh_window(false)
#ifdef HAVE_CURL_CURL_H , m_scroll_begin(0)
isReadyToTake(0), isDownloadInProgress(0), , m_fetcher(nullptr)
#endif // HAVE_CURL_CURL_H , m_shared_queue(std::make_pair(false, std::queue<MPD::Song>{}))
itsScrollBegin(0)
{ } { }
void Lyrics::resize() void Lyrics::resize()
@@ -71,23 +204,42 @@ void Lyrics::resize()
void Lyrics::update() void Lyrics::update()
{ {
# ifdef HAVE_CURL_CURL_H if (m_worker.valid())
if (isReadyToTake)
Take();
if (isDownloadInProgress)
{ {
auto buffer = m_shared_buffer->acquire();
if (!buffer->empty())
{
w << *buffer;
buffer->clear();
m_refresh_window = true;
}
if (m_worker.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
auto lyrics = m_worker.get();
if (lyrics)
{
w.clear();
w << Charset::utf8ToLocale(*lyrics);
std::string filename = lyricsFilename(m_song);
if (!saveLyrics(filename, *lyrics))
Statusbar::printf("Couldn't save lyrics as \"%1%\": %2%",
filename, strerror(errno));
}
else
w << "\nLyrics were not found.\n";
m_refresh_window = true;
// Reset worker so it's no longer valid.
m_worker = std::future<boost::optional<std::string>>();
}
}
if (m_refresh_window)
{
m_refresh_window = false;
w.flush(); w.flush();
w.refresh(); w.refresh();
} }
# endif // HAVE_CURL_CURL_H
if (Reload)
{
drawHeader();
itsScrollBegin = 0;
Load();
Reload = 0;
}
} }
void Lyrics::switchTo() void Lyrics::switchTo()
@@ -95,31 +247,9 @@ void Lyrics::switchTo()
using Global::myScreen; using Global::myScreen;
if (myScreen != this) if (myScreen != this)
{ {
# ifdef HAVE_CURL_CURL_H SwitchTo::execute(this);
// take lyrics if they were downloaded m_scroll_begin = 0;
if (isReadyToTake) drawHeader();
Take();
if (isDownloadInProgress || itsWorkersNumber > 0)
{
Statusbar::print("Lyrics are being downloaded...");
return;
}
# endif // HAVE_CURL_CURL_H
auto s = currentSong(myScreen);
if (!s)
return;
if (SetSong(*s))
{
SwitchTo::execute(this);
itsScrollBegin = 0;
Load();
drawHeader();
}
else
Statusbar::print("Song must have both artist and title tag set");
} }
else else
switchToPreviousScreen(); switchToPreviousScreen();
@@ -128,328 +258,143 @@ void Lyrics::switchTo()
std::wstring Lyrics::title() std::wstring Lyrics::title()
{ {
std::wstring result = L"Lyrics: "; std::wstring result = L"Lyrics: ";
result += Scroller( result += Scroller(
Format::stringify<wchar_t>(Format::parse(L"%a - %t"), &itsSong), Format::stringify<wchar_t>(Format::parse(L"{%a - %t}|{%f}"), &m_song),
itsScrollBegin, m_scroll_begin,
COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()) COLS - result.length() - (Config.design == Design::Alternative
); ? 2
: Global::VolumeState.length()));
return result; return result;
} }
#ifdef HAVE_CURL_CURL_H void Lyrics::fetch(const MPD::Song &s)
void Lyrics::DownloadInBackground(const MPD::Song &s)
{ {
if (s.empty() || s.getArtist().empty() || s.getTitle().empty()) if (!m_worker.valid() || s != m_song)
return;
std::string filename = GenerateFilename(s);
std::ifstream f(filename.c_str());
if (f.is_open())
{ {
f.close();
return;
}
Statusbar::printf("Fetching lyrics for \"%1%\"...",
Format::stringify<char>(Config.song_status_format, &s)
);
MPD::Song *s_copy = new MPD::Song(s);
pthread_mutex_lock(&itsDIBLock);
if (itsWorkersNumber == itsMaxWorkersNumber)
itsToDownload.push(s_copy);
else
{
++itsWorkersNumber;
pthread_t t;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
}
pthread_mutex_unlock(&itsDIBLock);
}
void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
{
MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
DownloadInBackgroundImplHelper(*s);
delete s;
while (true)
{
pthread_mutex_lock(&itsDIBLock);
if (itsToDownload.empty())
{
pthread_mutex_unlock(&itsDIBLock);
break;
}
else
{
s = itsToDownload.front();
itsToDownload.pop();
pthread_mutex_unlock(&itsDIBLock);
}
DownloadInBackgroundImplHelper(*s);
delete s;
}
pthread_mutex_lock(&itsDIBLock);
--itsWorkersNumber;
pthread_mutex_unlock(&itsDIBLock);
pthread_exit(0);
}
void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
{
std::string artist = Curl::escape(s.getArtist());
std::string title = Curl::escape(s.getTitle());
LyricsFetcher::Result result;
if (itsFetcher == nullptr)
{
for (auto &fetcher : Config.lyrics_fetchers)
{
result = fetcher->fetch(artist, title);
if (result.first)
break;
}
}
else
itsFetcher->fetch(artist, title);
if (result.first)
Save(GenerateFilename(s), result.second);
}
void *Lyrics::Download()
{
std::string artist = Curl::escape(itsSong.getArtist());
std::string title_ = Curl::escape(itsSong.getTitle());
auto fetch_lyrics = [&](auto &fetcher_) {
w << "Fetching lyrics from "
<< NC::Format::Bold
<< fetcher_->name()
<< NC::Format::NoBold << "... ";
auto result_ = fetcher_->fetch(artist, title_);
if (result_.first == false)
{
w << NC::Color::Red
<< result_.second
<< NC::Color::End
<< '\n';
}
return result_;
};
LyricsFetcher::Result result;
if (itsFetcher == nullptr)
{
for (auto &fetcher : Config.lyrics_fetchers)
{
result = fetch_lyrics(fetcher);
if (result.first)
break;
}
}
else
result = fetch_lyrics(itsFetcher);
if (result.first)
{
Save(itsFilename, result.second);
w.clear(); w.clear();
w << Charset::utf8ToLocale(result.second); m_song = s;
} if (loadLyrics(w, lyricsFilename(m_song)))
else m_refresh_window = true;
w << '\n' << "Lyrics weren't found.";
isReadyToTake = 1;
pthread_exit(0);
}
void *Lyrics::DownloadWrapper(void *this_ptr)
{
return static_cast<Lyrics *>(this_ptr)->Download();
}
#endif // HAVE_CURL_CURL_H
std::string Lyrics::GenerateFilename(const MPD::Song &s)
{
std::string filename;
if (Config.store_lyrics_in_song_dir)
{
if (s.isFromDatabase())
{
filename = Config.mpd_music_dir;
filename += "/";
filename += s.getURI();
}
else else
filename = s.getURI();
// replace song's extension with .txt
size_t dot = filename.rfind('.');
assert(dot != std::string::npos);
filename.resize(dot);
filename += ".txt";
}
else
{
std::string file = s.getArtist();
file += " - ";
file += s.getTitle();
file += ".txt";
removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
filename = Config.lyrics_directory;
filename += "/";
filename += file;
}
return filename;
}
void Lyrics::Load()
{
# ifdef HAVE_CURL_CURL_H
if (isDownloadInProgress)
return;
# endif // HAVE_CURL_CURL_H
assert(!itsSong.getArtist().empty());
assert(!itsSong.getTitle().empty());
itsFilename = GenerateFilename(itsSong);
w.clear();
w.reset();
std::ifstream input(itsFilename.c_str());
if (input.is_open())
{
bool first = 1;
std::string line;
while (std::getline(input, line))
{ {
// Remove carriage returns as they mess up the display. m_shared_buffer = std::make_shared<Shared<NC::Buffer>>();
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); m_worker = std::async(
if (!first) std::launch::async,
w << '\n'; std::bind(downloadLyrics, m_song, m_shared_buffer, m_fetcher));
w << Charset::utf8ToLocale(line);
first = 0;
} }
w.flush();
if (Reload)
w.refresh();
}
else
{
# ifdef HAVE_CURL_CURL_H
pthread_create(&itsDownloader, 0, DownloadWrapper, this);
isDownloadInProgress = 1;
# else
w << "Local lyrics not found. As ncmpcpp has been compiled without curl support, you can put appropriate lyrics into " << Config.lyrics_directory << " directory (file syntax is \"$ARTIST - $TITLE.txt\") or recompile ncmpcpp with curl support.";
w.flush();
# endif
} }
} }
void Lyrics::Edit() void Lyrics::refetchCurrent()
{
std::string filename = lyricsFilename(m_song);
if (std::remove(filename.c_str()) == -1 && errno != ENOENT)
{
const char msg[] = "Couldn't remove \"%1%\": %2%";
Statusbar::printf(msg, wideShorten(filename, COLS - const_strlen(msg) - 25),
strerror(errno));
}
else
{
// Get rid of current worker so fetch can restart the process.
m_worker = std::future<boost::optional<std::string>>();
fetch(m_song);
}
}
void Lyrics::edit()
{ {
assert(Global::myScreen == this);
if (Config.external_editor.empty()) if (Config.external_editor.empty())
{ {
Statusbar::print("Proper external_editor variable has to be set in configuration file"); Statusbar::print("external_editor variable has to be set in configuration file");
return; return;
} }
Statusbar::print("Opening lyrics in external editor..."); Statusbar::print("Opening lyrics in external editor...");
GNUC_UNUSED int res; GNUC_UNUSED int res;
std::string command;
std::string filename = lyricsFilename(m_song);
if (Config.use_console_editor) if (Config.use_console_editor)
{ {
res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str()); command = "/bin/sh -c \"" + Config.external_editor + " \\\"" + filename + "\\\"\"";
Load(); res = system(command.c_str());
// below is needed as screen gets cleared, but apparently fetch(m_song);
// ncurses doesn't know about it, so we need to reload main screen // Reset ncurses state to refresh the screen.
endwin(); endwin();
initscr(); initscr();
curs_set(0); curs_set(0);
} }
else else
res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
}
bool Lyrics::SetSong(const MPD::Song &s)
{
if (!s.getArtist().empty() && !s.getTitle().empty())
{ {
itsSong = s; command = "nohup " + Config.external_editor
return true; + " \"" + filename + "\" > /dev/null 2>&1 &";
} res = system(command.c_str());
else
return false;
}
#ifdef HAVE_CURL_CURL_H
void Lyrics::Save(const std::string &filename, const std::string &lyrics)
{
std::ofstream output(filename.c_str());
if (output.is_open())
{
output << lyrics;
output.close();
} }
} }
void Lyrics::Refetch() void Lyrics::toggleFetcher()
{ {
if (remove(itsFilename.c_str()) && errno != ENOENT) if (m_fetcher != nullptr)
{
const char msg[] = "Couldn't remove \"%1%\": %2%";
Statusbar::printf(msg, wideShorten(itsFilename, COLS-const_strlen(msg)-25), strerror(errno));
return;
}
Load();
}
void Lyrics::ToggleFetcher()
{
if (itsFetcher != nullptr)
{ {
auto fetcher = std::find_if(Config.lyrics_fetchers.begin(), auto fetcher = std::find_if(Config.lyrics_fetchers.begin(),
Config.lyrics_fetchers.end(), Config.lyrics_fetchers.end(),
[](auto &f) { return f.get() == itsFetcher; }); [this](auto &f) { return f.get() == m_fetcher; });
assert(fetcher != Config.lyrics_fetchers.end()); assert(fetcher != Config.lyrics_fetchers.end());
++fetcher; ++fetcher;
if (fetcher != Config.lyrics_fetchers.end()) if (fetcher != Config.lyrics_fetchers.end())
itsFetcher = fetcher->get(); m_fetcher = fetcher->get();
else else
itsFetcher = nullptr; m_fetcher = nullptr;
} }
else else
{ {
assert(!Config.lyrics_fetchers.empty()); assert(!Config.lyrics_fetchers.empty());
itsFetcher = Config.lyrics_fetchers[0].get(); m_fetcher = Config.lyrics_fetchers[0].get();
} }
if (itsFetcher != nullptr) if (m_fetcher != nullptr)
Statusbar::printf("Using lyrics fetcher: %s", itsFetcher->name()); Statusbar::printf("Using lyrics fetcher: %s", m_fetcher->name());
else else
Statusbar::print("Using all lyrics fetchers"); Statusbar::print("Using all lyrics fetchers");
} }
void Lyrics::Take() void Lyrics::fetchInBackground(const MPD::Song &s)
{ {
assert(isReadyToTake); auto consumer = [this] {
pthread_join(itsDownloader, 0); std::string lyrics_file;
w.flush(); while (true)
w.refresh(); {
isDownloadInProgress = 0; MPD::Song qs;
isReadyToTake = 0; {
} auto queue = m_shared_queue.acquire();
#endif // HAVE_CURL_CURL_H assert(queue->first);
if (queue->second.empty())
{
queue->first = false;
break;
}
lyrics_file = lyricsFilename(queue->second.front());
if (!boost::filesystem::exists(lyrics_file))
qs = queue->second.front();
queue->second.pop();
}
if (!qs.empty())
{
auto lyrics = downloadLyrics(qs, nullptr, m_fetcher);
if (lyrics)
saveLyrics(lyrics_file, *lyrics);
}
}
};
auto queue = m_shared_queue.acquire();
queue->second.push(s);
// Start the consumer if it's not running.
if (!queue->first)
{
std::thread t(consumer);
t.detach();
queue->first = true;
}
}

View File

@@ -21,7 +21,9 @@
#ifndef NCMPCPP_LYRICS_H #ifndef NCMPCPP_LYRICS_H
#define NCMPCPP_LYRICS_H #define NCMPCPP_LYRICS_H
#include <pthread.h> #include <boost/optional.hpp>
#include <future>
#include <memory>
#include <queue> #include <queue>
#include "interfaces.h" #include "interfaces.h"
@@ -29,6 +31,40 @@
#include "screen.h" #include "screen.h"
#include "song.h" #include "song.h"
template <typename ResourceT>
struct Shared
{
struct Resource
{
Resource(std::mutex &mutex, ResourceT &resource)
: m_lock(std::unique_lock<std::mutex>(mutex)), m_resource(resource)
{ }
ResourceT &operator*() { return m_resource; }
const ResourceT &operator*() const { return m_resource; }
ResourceT *operator->() { return &m_resource; }
const ResourceT *operator->() const { return &m_resource; }
private:
std::unique_lock<std::mutex> m_lock;
ResourceT &m_resource;
};
Shared(){ }
template <typename ValueT>
Shared(ValueT &&value)
: m_resource(std::forward<ValueT>(value))
{ }
Resource acquire() { return Resource(m_mutex, m_resource); }
private:
std::mutex m_mutex;
ResourceT m_resource;
};
struct Lyrics: Screen<NC::Scrollpad>, Tabbable struct Lyrics: Screen<NC::Scrollpad>, Tabbable
{ {
Lyrics(); Lyrics();
@@ -44,53 +80,26 @@ struct Lyrics: Screen<NC::Scrollpad>, Tabbable
virtual bool isLockable() override { return false; } virtual bool isLockable() override { return false; }
virtual bool isMergable() override { return true; } virtual bool isMergable() override { return true; }
// private members
bool SetSong(const MPD::Song &s);
void Edit();
# ifdef HAVE_CURL_CURL_H
void Refetch();
static void ToggleFetcher(); // other members
static void DownloadInBackground(const MPD::Song &s); void fetch(const MPD::Song &s);
# endif // HAVE_CURL_CURL_H void refetchCurrent();
void edit();
bool Reload; void toggleFetcher();
void fetchInBackground(const MPD::Song &s);
private: private:
void Load(); bool m_refresh_window;
size_t m_scroll_begin;
# ifdef HAVE_CURL_CURL_H
static void *DownloadInBackgroundImpl(void *song_ptr); std::shared_ptr<Shared<NC::Buffer>> m_shared_buffer;
static void DownloadInBackgroundImplHelper(const MPD::Song &s);
// lock for allowing exclusive access to itsToDownload and itsWorkersNumber MPD::Song m_song;
static pthread_mutex_t itsDIBLock; LyricsFetcher *m_fetcher;
// storage for songs for which lyrics are scheduled to be downloaded std::future<boost::optional<std::string>> m_worker;
static std::queue<MPD::Song *> itsToDownload;
// current worker threads (ie. downloading lyrics) Shared<std::pair<bool, std::queue<MPD::Song>>> m_shared_queue;
static size_t itsWorkersNumber;
// maximum number of worker threads. if it's reached, next lyrics requests
// are put into itsToDownload queue.
static const size_t itsMaxWorkersNumber = 1;
void *Download();
static void *DownloadWrapper(void *);
static void Save(const std::string &filename, const std::string &lyrics);
void Take();
bool isReadyToTake;
bool isDownloadInProgress;
pthread_t itsDownloader;
static LyricsFetcher *itsFetcher;
# endif // HAVE_CURL_CURL_H
size_t itsScrollBegin;
MPD::Song itsSong;
std::string itsFilename;
static std::string GenerateFilename(const MPD::Song &s);
}; };
extern Lyrics *myLyrics; extern Lyrics *myLyrics;

View File

@@ -46,18 +46,30 @@ struct Scrollpad: public Window
void flush(); void flush();
void reset(); void reset();
bool setProperties(Color begin, const std::string &s, Color end, size_t flags, size_t id = -2); bool setProperties(Color begin, const std::string &s, Color end,
bool setProperties(Format begin, const std::string &s, Format end, size_t flags, size_t id = -2); size_t flags, size_t id = -2);
bool setProperties(Format begin, const std::string &s, Format end,
size_t flags, size_t id = -2);
void removeProperties(size_t id = -2); void removeProperties(size_t id = -2);
Scrollpad &operator<<(int n) { return write(n); }
Scrollpad &operator<<(long int n) { return write(n); }
Scrollpad &operator<<(unsigned int n) { return write(n); }
Scrollpad &operator<<(unsigned long int n) { return write(n); }
Scrollpad &operator<<(char c) { return write(c); }
Scrollpad &operator<<(const char *s) { return write(s); }
Scrollpad &operator<<(const std::string &s) { return write(s); }
Scrollpad &operator<<(Color color) { return write(color); }
Scrollpad &operator<<(Format format) { return write(format); }
private:
template <typename ItemT> template <typename ItemT>
Scrollpad &operator<<(const ItemT &item) Scrollpad &write(ItemT &&item)
{ {
m_buffer << item; m_buffer << std::forward<ItemT>(item);
return *this; return *this;
} }
private:
Buffer m_buffer; Buffer m_buffer;
size_t m_beginning; size_t m_beginning;

View File

@@ -574,7 +574,7 @@ void Status::Changes::songID(int song_id)
# ifdef HAVE_CURL_CURL_H # ifdef HAVE_CURL_CURL_H
if (Config.fetch_lyrics_in_background) if (Config.fetch_lyrics_in_background)
Lyrics::DownloadInBackground(s); myLyrics->fetchInBackground(s);
# endif // HAVE_CURL_CURL_H # endif // HAVE_CURL_CURL_H
drawTitle(s); drawTitle(s);
@@ -582,11 +582,10 @@ void Status::Changes::songID(int song_id)
if (Config.autocenter_mode) if (Config.autocenter_mode)
myPlaylist->locateSong(s); myPlaylist->locateSong(s);
if (Config.now_playing_lyrics && isVisible(myLyrics) && myLyrics->previousScreen() == myPlaylist) if (Config.now_playing_lyrics
{ && isVisible(myLyrics)
if (myLyrics->SetSong(s)) && myLyrics->previousScreen() == myPlaylist)
myLyrics->Reload = 1; myLyrics->fetch(s);
}
} }
} }
elapsedTime(false); elapsedTime(false);

View File

@@ -89,7 +89,12 @@ public:
++it; ++it;
} }
} }
bool empty() const
{
return m_string.empty() && m_properties.empty();
}
void clear() void clear()
{ {
m_string.clear(); m_string.clear();