summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrzej Rybczak <electricityispower@gmail.com>2013-07-10 17:20:09 +0200
committerAndrzej Rybczak <electricityispower@gmail.com>2013-07-10 19:25:32 +0200
commit1710f892c5942bfe09f06672aaedad3eab4cd538 (patch)
treea3d4ace8fec6f9bb7e7da5d974d06d71bf5f65cb
parent0092dfe044a40ae10c8a765d18f35bf6dd44e3e4 (diff)
lastfm: rework service architecture a bit and implement getting full artist info
-rw-r--r--doc/bindings3
-rw-r--r--src/actions.cpp24
-rw-r--r--src/actions.h9
-rw-r--r--src/curl_handle.h4
-rw-r--r--src/lastfm.cpp155
-rw-r--r--src/lastfm.h53
-rw-r--r--src/lastfm_service.cpp202
-rw-r--r--src/lastfm_service.h35
8 files changed, 187 insertions, 298 deletions
diff --git a/doc/bindings b/doc/bindings
index 679206ef..f4b846cd 100644
--- a/doc/bindings
+++ b/doc/bindings
@@ -505,9 +505,6 @@
# refetch_lyrics
#
#def_key "`"
-# refetch_artist_info
-#
-#def_key "`"
# add_random_items
#
#def_key "ctrl_p"
diff --git a/src/actions.cpp b/src/actions.cpp
index daaf8004..27c51bc6 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -2072,22 +2072,6 @@ void RefetchLyrics::run()
# endif // HAVE_CURL_CURL_H
}
-bool RefetchArtistInfo::canBeRun() const
-{
-# ifdef HAVE_CURL_CURL_H
- return myScreen == myLastfm;
-# else
- return false;
-# endif // HAVE_CURL_CURL_H
-}
-
-void RefetchArtistInfo::run()
-{
-# ifdef HAVE_CURL_CURL_H
- myLastfm->Refetch();
-# endif // HAVE_CURL_CURL_H
-}
-
bool SetSelectedItemsPriority::canBeRun() const
{
if (Mpd.Version() < 17)
@@ -2161,7 +2145,7 @@ bool ShowArtistInfo::canBeRun() const
void ShowArtistInfo::run()
{
# ifdef HAVE_CURL_CURL_H
- if (myScreen == myLastfm || myLastfm->isDownloading())
+ if (myScreen == myLastfm)
{
myLastfm->switchTo();
return;
@@ -2181,8 +2165,11 @@ void ShowArtistInfo::run()
artist = s->getArtist();
}
- if (!artist.empty() && myLastfm->SetArtistInfoArgs(artist, Config.lastfm_preferred_language))
+ if (!artist.empty())
+ {
+ myLastfm->queueJob(LastFm::ArtistInfo(artist, Config.lastfm_preferred_language));
myLastfm->switchTo();
+ }
# endif // HAVE_CURL_CURL_H
}
@@ -2540,7 +2527,6 @@ void populateActions()
insert_action(new Actions::ToggleLibraryTagType());
insert_action(new Actions::ToggleMediaLibrarySortMode());
insert_action(new Actions::RefetchLyrics());
- insert_action(new Actions::RefetchArtistInfo());
insert_action(new Actions::SetSelectedItemsPriority());
insert_action(new Actions::FilterPlaylistOnPriorities());
insert_action(new Actions::ShowSongInfo());
diff --git a/src/actions.h b/src/actions.h
index afb53823..42112685 100644
--- a/src/actions.h
+++ b/src/actions.h
@@ -942,15 +942,6 @@ protected:
virtual void run();
};
-struct RefetchArtistInfo : public BaseAction
-{
- RefetchArtistInfo() : BaseAction(Type::RefetchArtistInfo, "refetch_artist_info") { }
-
-protected:
- virtual bool canBeRun() const;
- virtual void run();
-};
-
struct SetSelectedItemsPriority : public BaseAction
{
SetSelectedItemsPriority()
diff --git a/src/curl_handle.h b/src/curl_handle.h
index e1b3eea5..072a3509 100644
--- a/src/curl_handle.h
+++ b/src/curl_handle.h
@@ -21,9 +21,7 @@
#ifndef NCMPCPP_CURL_HANDLE_H
#define NCMPCPP_CURL_HANDLE_H
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
+#include "config.h"
#ifdef HAVE_CURL_CURL_H
diff --git a/src/lastfm.cpp b/src/lastfm.cpp
index f6ff5004..e9baabca 100644
--- a/src/lastfm.cpp
+++ b/src/lastfm.cpp
@@ -22,27 +22,12 @@
#ifdef HAVE_CURL_CURL_H
-#ifdef WIN32
-# include <io.h>
-#else
-# include <sys/stat.h>
-#endif // WIN32
-
-#include <cassert>
-#include <cerrno>
-#include <cstring>
-#include <boost/locale/conversion.hpp>
-#include <fstream>
-#include <iostream>
-
-#include "error.h"
#include "helpers.h"
#include "charset.h"
#include "global.h"
#include "statusbar.h"
#include "title.h"
#include "screen_switcher.h"
-#include "utility/string.h"
using Global::MainHeight;
using Global::MainStartY;
@@ -51,7 +36,6 @@ Lastfm *myLastfm;
Lastfm::Lastfm()
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border::None))
-, isReadyToTake(0), isDownloadInProgress(0)
{ }
void Lastfm::resize()
@@ -65,23 +49,13 @@ void Lastfm::resize()
std::wstring Lastfm::title()
{
- return itsTitle;
+ return m_title;
}
void Lastfm::update()
{
- if (isReadyToTake)
- Take();
-}
-
-void Lastfm::Take()
-{
- assert(isReadyToTake);
- pthread_join(itsDownloader, 0);
- w.flush();
- w.refresh();
- isDownloadInProgress = 0;
- isReadyToTake = 0;
+ if (m_worker.valid() && m_worker.is_ready())
+ getResult();
}
void Lastfm::switchTo()
@@ -90,134 +64,27 @@ void Lastfm::switchTo()
if (myScreen != this)
{
SwitchTo::execute(this);
- // get an old info if it waits
- if (isReadyToTake)
- Take();
- Load();
drawHeader();
}
else
switchToPreviousScreen();
}
-void Lastfm::Load()
-{
- if (isDownloadInProgress)
- return;
-
- assert(itsService.get());
- assert(itsService->checkArgs(itsArgs));
-
- SetTitleAndFolder();
-
- w.clear();
- w.reset();
-
- std::string artist = itsArgs.find("artist")->second;
- std::string file = boost::locale::to_lower(artist + ".txt");
- removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
-
- itsFilename = itsFolder + "/" + file;
-
- mkdir(itsFolder.c_str()
-# ifndef WIN32
- , 0755
-# endif // !WIN32
- );
-
- std::ifstream input(itsFilename.c_str());
- if (input.is_open())
- {
- bool first = 1;
- std::string line;
- while (std::getline(input, line))
- {
- if (!first)
- w << '\n';
- w << Charset::utf8ToLocale(line);
- first = 0;
- }
- input.close();
- itsService->colorizeOutput(w);
- }
- else
- {
- w << "Fetching informations... ";
- pthread_create(&itsDownloader, 0, DownloadWrapper, this);
- isDownloadInProgress = 1;
- }
- w.flush();
-}
-
-void Lastfm::SetTitleAndFolder()
-{
- if (dynamic_cast<ArtistInfo *>(itsService.get()))
- {
- itsTitle = L"Artist info - ";
- itsTitle += ToWString(itsArgs.find("artist")->second);
- itsFolder = Config.ncmpcpp_directory + "artists";
- }
-}
-
-void *Lastfm::DownloadWrapper(void *this_ptr)
+void Lastfm::getResult()
{
- static_cast<Lastfm *>(this_ptr)->Download();
- pthread_exit(0);
-}
-
-void Lastfm::Download()
-{
- LastfmService::Result result = itsService->fetch(itsArgs);
-
+ auto result = m_worker.get();
if (result.first)
{
- Save(result.second);
w.clear();
w << Charset::utf8ToLocale(result.second);
- itsService->colorizeOutput(w);
- }
- else
- w << NC::Color::Red << result.second << NC::Color::End;
-
- isReadyToTake = 1;
-}
-
-void Lastfm::Save(const std::string &data)
-{
- std::ofstream output(itsFilename.c_str());
- if (output.is_open())
- {
- output << data;
- output.close();
+ m_service->beautifyOutput(w);
}
else
- std::cerr << "ncmpcpp: couldn't save file \"" << itsFilename << "\"\n";
-}
-
-void Lastfm::Refetch()
-{
- if (remove(itsFilename.c_str()) && errno != ENOENT)
- {
- const char msg[] = "Couldn't remove \"%ls\": %s";
- Statusbar::msg(msg, wideShorten(ToWString(itsFilename), COLS-const_strlen(msg)-25).c_str(), strerror(errno));
- return;
- }
- Load();
-}
-
-bool Lastfm::SetArtistInfoArgs(const std::string &artist, const std::string &lang)
-{
- if (isDownloading())
- return false;
-
- itsService.reset(new ArtistInfo);
- itsArgs.clear();
- itsArgs["artist"] = artist;
- if (!lang.empty())
- itsArgs["lang"] = lang;
-
- return true;
+ w << " " << NC::Color::Red << result.second << NC::Color::End;
+ w.flush();
+ w.refresh();
+ // reset m_worker so it's no longer valid
+ m_worker = boost::unique_future<LastFm::Service::Result>();
}
#endif // HVAE_CURL_CURL_H
-
diff --git a/src/lastfm.h b/src/lastfm.h
index 6f81257b..ceda5a70 100644
--- a/src/lastfm.h
+++ b/src/lastfm.h
@@ -26,11 +26,12 @@
#ifdef HAVE_CURL_CURL_H
#include <memory>
-#include <pthread.h>
+#include <boost/thread/future.hpp>
#include "interfaces.h"
#include "lastfm_service.h"
#include "screen.h"
+#include "utility/wide_string.h"
struct Lastfm: Screen<NC::Scrollpad>, Tabbable
{
@@ -49,37 +50,39 @@ struct Lastfm: Screen<NC::Scrollpad>, Tabbable
virtual bool isMergable() OVERRIDE { return true; }
- // private members
- void Refetch();
-
- bool isDownloading() { return isDownloadInProgress && !isReadyToTake; }
- bool SetArtistInfoArgs(const std::string &artist, const std::string &lang = "");
+ template <typename ServiceT>
+ bool queueJob(ServiceT service)
+ {
+ auto old_service = dynamic_cast<ServiceT *>(m_service.get());
+ // if the same service and arguments were used, leave old info
+ if (old_service != nullptr && *old_service == service)
+ return true;
+
+ // download in progress
+ if (m_worker.valid() && !m_worker.is_ready())
+ return false;
+
+ m_service = std::make_shared<ServiceT>(std::forward<ServiceT>(service));
+ m_worker = boost::async(boost::launch::async, boost::bind(&LastFm::Service::fetch, m_service.get()));
+
+ w.clear();
+ w << "Fetching information...";
+ w.flush();
+ m_title = ToWString(m_service->name());
+
+ return true;
+ }
protected:
virtual bool isLockable() OVERRIDE { return false; }
private:
- std::wstring itsTitle;
-
- std::string itsArtist;
- std::string itsFilename;
-
- std::string itsFolder;
-
- std::auto_ptr<LastfmService> itsService;
- LastfmService::Args itsArgs;
-
- void Load();
- void Save(const std::string &data);
- void SetTitleAndFolder();
+ void getResult();
- void Download();
- static void *DownloadWrapper(void *);
+ std::wstring m_title;
- void Take();
- bool isReadyToTake;
- bool isDownloadInProgress;
- pthread_t itsDownloader;
+ std::shared_ptr<LastFm::Service> m_service;
+ boost::unique_future<LastFm::Service::Result> m_worker;
};
extern Lastfm *myLastfm;
diff --git a/src/lastfm_service.cpp b/src/lastfm_service.cpp
index d0b11f52..87ddf729 100644
--- a/src/lastfm_service.cpp
+++ b/src/lastfm_service.cpp
@@ -23,152 +23,190 @@
#ifdef HAVE_CURL_CURL_H
#include <boost/algorithm/string/trim.hpp>
+#include <boost/locale/conversion.hpp>
+#include <fstream>
+#include "charset.h"
#include "curl_handle.h"
#include "settings.h"
#include "utility/html.h"
#include "utility/string.h"
-const char *LastfmService::baseURL = "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method=";
+namespace {
-const char *LastfmService::msgParseFailed = "Fetched data could not be parsed";
+const char *apiUrl = "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method=";
+const char *msgInvalidResponse = "Invalid response";
-LastfmService::Result LastfmService::fetch(Args &args)
+}
+
+namespace LastFm {
+
+Service::Result Service::fetch()
{
Result result;
+ result.first = false;
- std::string url = baseURL;
+ std::string url = apiUrl;
url += methodName();
- for (Args::const_iterator it = args.begin(); it != args.end(); ++it)
+ for (auto &arg : m_arguments)
{
url += "&";
- url += it->first;
+ url += arg.first;
url += "=";
- url += Curl::escape(it->second);
+ url += Curl::escape(arg.second);
}
std::string data;
CURLcode code = Curl::perform(data, url);
if (code != CURLE_OK)
- {
result.second = curl_easy_strerror(code);
- return result;
- }
-
- if (actionFailed(data))
- {
- stripHtmlTags(data);
- result.second = data;
- return result;
- }
-
- if (!parse(data))
+ else if (actionFailed(data))
+ result.second = msgInvalidResponse;
+ else
{
+ result = processData(data);
+
// if relevant part of data was not found and one of arguments
// was language, try to fetch it again without that parameter.
// otherwise just report failure.
- Args::iterator lang = args.find("lang");
- if (lang != args.end())
+ if (!result.first && !m_arguments["lang"].empty())
{
- args.erase(lang);
- return fetch(args);
- }
- else
- {
- // parse should change data to error msg, if it fails
- result.second = data;
- return result;
+ m_arguments.erase("lang");
+ result = fetch();
}
}
- result.first = true;
- result.second = data;
return result;
}
-bool LastfmService::actionFailed(const std::string &data)
+bool Service::actionFailed(const std::string &data)
{
return data.find("status=\"failed\"") != std::string::npos;
}
-void LastfmService::postProcess(std::string &data)
-{
- stripHtmlTags(data);
- boost::trim(data);
-}
-
/***********************************************************************/
-bool ArtistInfo::checkArgs(const Args &args)
+bool ArtistInfo::argumentsOk()
{
- return args.find("artist") != args.end();
+ return !m_arguments["artist"].empty();
}
-void ArtistInfo::colorizeOutput(NC::Scrollpad &w)
+void ArtistInfo::beautifyOutput(NC::Scrollpad &w)
{
- w.setProperties(NC::Format::Bold, "\n\nSimilar artists:\n", NC::Format::NoBold, 0, boost::regex::literal);
+ w.setProperties(NC::Format::Bold, "\n\nSimilar artists:\n", NC::Format::NoBold, 0);
+ w.setProperties(NC::Format::Bold, "\n\nSimilar tags:\n", NC::Format::NoBold, 0);
w.setProperties(Config.color2, "\n * ", NC::Color::End, 0, boost::regex::literal);
}
-bool ArtistInfo::parse(std::string &data)
+Service::Result ArtistInfo::processData(const std::string &data)
{
size_t a, b;
- bool parse_failed = false;
+ Service::Result result;
+ result.first = false;
- if ((a = data.find("<content>")) != std::string::npos)
+ boost::regex rx("<content>(.*?)</content>");
+ boost::smatch what;
+ if (boost::regex_search(data, what, rx))
{
- a += const_strlen("<content>");
- if ((b = data.find("</content>")) == std::string::npos)
- parse_failed = true;
+ std::string desc = what[1];
+ // if there is a description...
+ if (desc.length() > 0)
+ {
+ // ...locate the link to wiki on last.fm...
+ rx.assign("<link rel=\"original\" href=\"(.*?)\"");
+ if (boost::regex_search(data, what, rx))
+ {
+ // ...try to get the content of it...
+ std::string wiki;
+ CURLcode code = Curl::perform(wiki, what[1]);
+
+ if (code != CURLE_OK)
+ {
+ result.second = curl_easy_strerror(code);
+ return result;
+ }
+ else
+ {
+ // ...and filter it to get the whole description.
+ rx.assign("<div id=\"wiki\">(.*?)</div>");
+ if (boost::regex_search(wiki, what, rx))
+ desc = unescapeHtmlUtf8(what[1]);
+ }
+ }
+ else
+ {
+ // otherwise, get rid of CDATA wrapper.
+ rx.assign("<!\\[CDATA\\[(.*)\\]\\]>");
+ desc = boost::regex_replace(desc, rx, "\\1");
+ }
+ stripHtmlTags(desc);
+ boost::trim(desc);
+ result.second += desc;
+ }
+ else
+ result.second += "No description available for this artist.";
}
else
- parse_failed = true;
-
- if (parse_failed)
{
- data = msgParseFailed;
- return false;
+ result.second = msgInvalidResponse;
+ return result;
}
- if (a == b)
+ auto add_similars = [&result](boost::sregex_iterator &it, const boost::sregex_iterator &last) {
+ for (; it != last; ++it)
+ {
+ std::string name = it->str(1);
+ std::string url = it->str(2);
+ stripHtmlTags(name);
+ stripHtmlTags(url);
+ result.second += "\n * ";
+ result.second += name;
+ result.second += " (";
+ result.second += url;
+ result.second += ")";
+ }
+ };
+
+ a = data.find("<similar>");
+ b = data.find("</similar>");
+ if (a != std::string::npos && b != std::string::npos)
{
- data = "No description available for this artist.";
- return false;
+ rx.assign("<artist>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</artist>");
+ auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx);
+ auto last = boost::sregex_iterator();
+ if (it != last)
+ result.second += "\n\nSimilar artists:\n";
+ add_similars(it, last);
}
- std::vector< std::pair<std::string, std::string> > similars;
- for (size_t i = data.find("<name>"), j, k = data.find("<url>"), l;
- i != std::string::npos; i = data.find("<name>", i), k = data.find("<url>", k))
+ a = data.find("<tags>");
+ b = data.find("</tags>");
+ if (a != std::string::npos && b != std::string::npos)
{
- j = data.find("</name>", i);
- i += const_strlen("<name>");
-
- l = data.find("</url>", k);
- k += const_strlen("<url>");
-
- similars.push_back(std::make_pair(data.substr(i, j-i), data.substr(k, l-k)));
- stripHtmlTags(similars.back().first);
+ rx.assign("<tag>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</tag>");
+ auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx);
+ auto last = boost::sregex_iterator();
+ if (it != last)
+ result.second += "\n\nSimilar tags:\n";
+ add_similars(it, last);
}
- a += const_strlen("<![CDATA[");
- b -= const_strlen("]]>");
- data = data.substr(a, b-a);
-
- postProcess(data);
+ // get artist we look for, it's the one before similar artists
+ rx.assign("<name>.*?</name>.*?<url>(.*?)</url>.*?<similar>");
- data += "\n\nSimilar artists:\n";
- for (size_t i = 1; i < similars.size(); ++i)
+ if (boost::regex_search(data, what, rx))
{
- data += "\n * ";
- data += similars[i].first;
- data += " (";
- data += similars[i].second;
- data += ")";
+ std::string url = what[1];
+ stripHtmlTags(url);
+ result.second += "\n\n";
+ // add only url
+ result.second += url;
}
- data += "\n\n";
- data += similars.front().second;
- return true;
+ result.first = true;
+ return result;
+}
+
}
#endif
diff --git a/src/lastfm_service.h b/src/lastfm_service.h
index 3aa10b8e..a5c439b7 100644
--- a/src/lastfm_service.h
+++ b/src/lastfm_service.h
@@ -30,42 +30,51 @@
#include "scrollpad.h"
-struct LastfmService
+namespace LastFm {
+
+struct Service
{
- typedef std::map<std::string, std::string> Args;
+ typedef std::map<std::string, std::string> Arguments;
typedef std::pair<bool, std::string> Result;
+ Service(Arguments args) : m_arguments(args) { }
+
virtual const char *name() = 0;
- virtual Result fetch(Args &args);
+ virtual Result fetch();
- virtual bool checkArgs(const Args &args) = 0;
- virtual void colorizeOutput(NC::Scrollpad &w) = 0;
+ virtual void beautifyOutput(NC::Scrollpad &w) = 0;
protected:
+ virtual bool argumentsOk() = 0;
virtual bool actionFailed(const std::string &data);
- virtual bool parse(std::string &data) = 0;
- virtual void postProcess(std::string &data);
+ virtual Result processData(const std::string &data) = 0;
virtual const char *methodName() = 0;
- static const char *baseURL;
- static const char *msgParseFailed;
+ Arguments m_arguments;
};
-struct ArtistInfo : public LastfmService
+struct ArtistInfo : public Service
{
+ ArtistInfo(std::string artist, std::string lang)
+ : Service({{"artist", artist}, {"lang", lang}}) { }
+
virtual const char *name() { return "Artist info"; }
- virtual bool checkArgs(const Args &args);
- virtual void colorizeOutput(NC::Scrollpad &w);
+ virtual void beautifyOutput(NC::Scrollpad &w);
+
+ bool operator==(const ArtistInfo &ai) const { return m_arguments == ai.m_arguments; }
protected:
- virtual bool parse(std::string &data);
+ virtual bool argumentsOk();
+ virtual Result processData(const std::string &data);
virtual const char *methodName() { return "artist.getinfo"; }
};
+}
+
#endif // HAVE_CURL_CURL_H
#endif // NCMPCPP_LASTFM_SERVICE_H