Fixed URL signature and the ability to create new sessions. Also added

the ability to store, load and clear sessions.
This commit is contained in:
casey langen 2018-04-30 21:59:28 -07:00
parent e4a92dae4d
commit 1e2582fb1d
7 changed files with 369 additions and 238 deletions

View File

@ -44,6 +44,8 @@ namespace musik { namespace core { namespace io {
template <typename T>
class HttpClient {
public:
enum class Thread { Current, Background };
using HttpHeaders = std::unordered_map<std::string, std::string>;
using Callback = std::function<void(HttpClient<T>* caller, int, CURLcode)>;
using DecoratorCallback = std::function<void(CURL*)>;
@ -61,6 +63,7 @@ namespace musik { namespace core { namespace io {
HttpClient<T>& Headers(HeaderCallback headersCb);
HttpClient<T>& Decorator(DecoratorCallback decoratorCb);
HttpClient<T>& Canceled(CanceledCallback canceledCb);
HttpClient<T>& Mode(Thread mode);
const T& Stream() const { return this->ostream; }
const HttpHeaders& ResponseHeaders() const { return this->responseHeaders; }
@ -79,6 +82,8 @@ namespace musik { namespace core { namespace io {
static size_t CurlHeaderCallback(char *buffer, size_t size, size_t nitems, void *userdata);
static std::string DefaultUserAgent();
void RunOnCurrentThread(Callback callback);
std::recursive_mutex mutex;
std::shared_ptr<std::thread> thread;
@ -89,6 +94,7 @@ namespace musik { namespace core { namespace io {
DecoratorCallback decoratorCb;
CanceledCallback canceledCallback;
bool cancel;
Thread mode{ Thread::Background };
CURL* curl;
};
@ -219,26 +225,36 @@ namespace musik { namespace core { namespace io {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}
this->thread.reset(new std::thread([callback, this] {
if (this->cancel) {
if (this->canceledCallback) {
this->canceledCallback(this);
}
}
else {
CURLcode curlCode = curl_easy_perform(this->curl);
int httpStatus = 0;
curl_easy_getinfo(this->curl, CURLINFO_RESPONSE_CODE, &httpStatus);
if (callback) {
callback(this, httpStatus, curlCode);
}
}
}));
if (mode == Thread::Background) {
this->thread.reset(new std::thread([callback, this] {
this->RunOnCurrentThread(callback);
}));
}
else {
this->RunOnCurrentThread(callback);
}
return *this;
}
template <typename T>
void HttpClient<T>::RunOnCurrentThread(Callback callback) {
CURLcode curlCode = curl_easy_perform(this->curl);
if (this->cancel) {
if (this->canceledCallback) {
this->canceledCallback(this);
}
}
int httpStatus = 0;
curl_easy_getinfo(this->curl, CURLINFO_RESPONSE_CODE, &httpStatus);
if (callback) {
callback(this, httpStatus, curlCode);
}
}
template <typename T>
void HttpClient<T>::Wait() {
std::unique_lock<std::recursive_mutex> lock(this->mutex);
@ -254,6 +270,12 @@ namespace musik { namespace core { namespace io {
return *this;
}
template <typename T>
HttpClient<T>& HttpClient<T>::Mode(Thread mode) {
this->mode = mode;
return *this;
}
template <typename T>
HttpClient<T>& HttpClient<T>::Header(const std::string& key, const std::string& value) {
this->requestHeaders[key] = value;

View File

@ -42,7 +42,7 @@
#include <boost/format.hpp>
#ifdef WIN32
/* nothing special for Win32 */
#include <shellapi.h>
#elif __APPLE__
#include <mach-o/dyld.h>
#else
@ -57,69 +57,7 @@
#include <sys/sysctl.h>
#endif
std::string musik::core::GetPluginDirectory() {
std::string path(GetApplicationDirectory());
path.append("/plugins/");
return path;
}
std::string musik::core::GetApplicationDirectory() {
std::string result;
#ifdef WIN32
wchar_t widePath[2048];
int length = GetModuleFileName(NULL, widePath, 2048);
if (length != 0 && length < 2048) {
result.assign(GetPath(u16to8(widePath).c_str()));
}
#elif __APPLE__
char pathbuf[PATH_MAX + 1];
uint32_t bufsize = sizeof(pathbuf);
_NSGetExecutablePath(pathbuf, &bufsize);
result.assign(pathbuf);
size_t last = result.find_last_of("/");
result = result.substr(0, last); /* remove filename component */
#else
char pathbuf[PATH_MAX + 1] = { 0 };
#ifdef __FreeBSD__
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t bufsize = sizeof(pathbuf);
sysctl(mib, 4, pathbuf, &bufsize, nullptr, 0);
#else
std::string pathToProc = boost::str(boost::format("/proc/%d/exe") % (int) getpid());
readlink(pathToProc.c_str(), pathbuf, PATH_MAX);
#endif
result.assign(pathbuf);
size_t last = result.find_last_of("/");
result = result.substr(0, last); /* remove filename component */
#endif
return result;
}
std::string musik::core::GetHomeDirectory() {
std::string directory;
#ifdef WIN32
DWORD bufferSize = GetEnvironmentVariable(L"USERPROFILE", 0, 0);
wchar_t *buffer = new wchar_t[bufferSize + 2];
GetEnvironmentVariable(L"USERPROFILE", buffer, bufferSize);
directory.assign(u16to8(buffer));
delete[] buffer;
#else
directory = std::string(std::getenv("HOME"));
#endif
return directory;
}
static std::string GetDataDirectoryRoot() {
static std::string getDataDirectoryRoot() {
std::string directory;
#ifdef WIN32
@ -135,163 +73,241 @@ static std::string GetDataDirectoryRoot() {
return directory;
}
std::string musik::core::GetDataDirectory(bool create) {
std::string directory =
#ifdef WIN32
GetDataDirectoryRoot() + std::string("/musikcube/");
#else
GetDataDirectoryRoot() + std::string("/.musikcube/");
#endif
if (create) {
boost::filesystem::path path(directory);
if (!boost::filesystem::exists(path)) {
boost::filesystem::create_directories(path);
}
}
return directory;
}
inline void silentDelete(const std::string fn) {
static inline void silentDelete(const std::string fn) {
boost::system::error_code ec;
boost::filesystem::remove(boost::filesystem::path(fn), ec);
}
void musik::core::RemoveOldDlls() {
#ifdef WIN32
std::string path = GetPluginDirectory();
silentDelete(path + "libcurl.dll");
silentDelete(path + "crypto-41.dll");
silentDelete(path + "ssl-43.dll");
silentDelete(path + "tls-15.dll");
#endif
}
namespace musik { namespace core {
void musik::core::MigrateOldDataDirectory() {
std::string oldDirectory =
#ifdef WIN32
GetDataDirectoryRoot() + std::string("/mC2/");
#else
GetDataDirectoryRoot() + std::string("/.mC2/");
#endif
std::string newDirectory = GetDataDirectory(false);
boost::filesystem::path oldPath(oldDirectory);
boost::filesystem::path newPath(newDirectory);
if (boost::filesystem::exists(oldPath) &&
!boost::filesystem::exists(newPath))
{
boost::filesystem::rename(oldPath, newPath);
}
}
std::string musik::core::GetPath(const std::string &sFile) {
std::string sPath;
int length;
#ifdef WIN32
wchar_t widePath[2048];
wchar_t *szFile = NULL;
length = GetFullPathName(u8to16(sFile).c_str(), 2048, widePath, &szFile);
if(length != 0 && length < 2048) {
sPath.assign(u16to8(widePath).c_str());
if(szFile!=0) {
std::string sTheFile = u16to8(szFile);
sPath.assign(sPath.substr(0,length-sTheFile.length()));
}
}
else {
sPath.assign(sFile);
}
#else
char* szDir;
sPath.assign(getcwd((char*)szDir, (size_t) length));
#endif
return sPath;
}
int64_t musik::core::Checksum(char *data,unsigned int bytes) {
int64_t sum = 0;
for(unsigned int i = 0; i < bytes; ++i) {
char ch = *(data + i);
sum += (int64_t) ch;
}
return sum;
}
size_t musik::core::CopyString(const std::string& src, char* dst, size_t size) {
size_t len = src.size() + 1; /* space for the null terminator */
if (dst) {
size_t copied = src.copy(dst, size - 1);
dst[copied] = '\0';
return copied + 1;
}
return len;
}
bool musik::core::FileToByteArray(const std::string& path, char** target, int& size, bool nullTerminate) {
#ifdef WIN32
std::wstring u16fn = u8to16(path);
FILE* file = _wfopen(u16fn.c_str(), L"rb");
#else
FILE* file = fopen(path.c_str(), "rb");
#endif
*target = nullptr;
size = 0;
if (!file) {
return false;
std::string GetPluginDirectory() {
std::string path(GetApplicationDirectory());
path.append("/plugins/");
return path;
}
bool success = false;
std::string GetApplicationDirectory() {
std::string result;
if (fseek(file, 0L, SEEK_END) == 0) {
long fileSize = ftell(file);
if (fileSize == -1) {
goto close_and_return;
#ifdef WIN32
wchar_t widePath[2048];
int length = GetModuleFileName(NULL, widePath, 2048);
if (length != 0 && length < 2048) {
result.assign(GetPath(u16to8(widePath).c_str()));
}
#elif __APPLE__
char pathbuf[PATH_MAX + 1];
uint32_t bufsize = sizeof(pathbuf);
_NSGetExecutablePath(pathbuf, &bufsize);
result.assign(pathbuf);
size_t last = result.find_last_of("/");
result = result.substr(0, last); /* remove filename component */
#else
char pathbuf[PATH_MAX + 1] = { 0 };
#ifdef __FreeBSD__
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t bufsize = sizeof(pathbuf);
sysctl(mib, 4, pathbuf, &bufsize, nullptr, 0);
#else
std::string pathToProc = boost::str(boost::format("/proc/%d/exe") % (int) getpid());
readlink(pathToProc.c_str(), pathbuf, PATH_MAX);
#endif
result.assign(pathbuf);
size_t last = result.find_last_of("/");
result = result.substr(0, last); /* remove filename component */
#endif
return result;
}
std::string GetHomeDirectory() {
std::string directory;
#ifdef WIN32
DWORD bufferSize = GetEnvironmentVariable(L"USERPROFILE", 0, 0);
wchar_t *buffer = new wchar_t[bufferSize + 2];
GetEnvironmentVariable(L"USERPROFILE", buffer, bufferSize);
directory.assign(u16to8(buffer));
delete[] buffer;
#else
directory = std::string(std::getenv("HOME"));
#endif
return directory;
}
std::string GetDataDirectory(bool create) {
std::string directory =
#ifdef WIN32
getDataDirectoryRoot() + std::string("/musikcube/");
#else
getDataDirectoryRoot() + std::string("/.musikcube/");
#endif
if (create) {
boost::filesystem::path path(directory);
if (!boost::filesystem::exists(path)) {
boost::filesystem::create_directories(path);
}
}
if (fseek(file, 0L, SEEK_SET) != 0) {
goto close_and_return;
return directory;
}
void RemoveOldDlls() {
#ifdef WIN32
std::string path = GetPluginDirectory();
silentDelete(path + "libcurl.dll");
silentDelete(path + "crypto-41.dll");
silentDelete(path + "ssl-43.dll");
silentDelete(path + "tls-15.dll");
#endif
}
void MigrateOldDataDirectory() {
std::string oldDirectory =
#ifdef WIN32
getDataDirectoryRoot() + std::string("/mC2/");
#else
getDataDirectoryRoot() + std::string("/.mC2/");
#endif
std::string newDirectory = GetDataDirectory(false);
boost::filesystem::path oldPath(oldDirectory);
boost::filesystem::path newPath(newDirectory);
if (boost::filesystem::exists(oldPath) &&
!boost::filesystem::exists(newPath))
{
boost::filesystem::rename(oldPath, newPath);
}
}
std::string GetPath(const std::string &sFile) {
std::string sPath;
int length;
#ifdef WIN32
wchar_t widePath[2048];
wchar_t *szFile = NULL;
length = GetFullPathName(u8to16(sFile).c_str(), 2048, widePath, &szFile);
if(length != 0 && length < 2048) {
sPath.assign(u16to8(widePath).c_str());
if(szFile!=0) {
std::string sTheFile = u16to8(szFile);
sPath.assign(sPath.substr(0,length-sTheFile.length()));
}
}
else {
sPath.assign(sFile);
}
#else
char* szDir;
sPath.assign(getcwd((char*)szDir, (size_t) length));
#endif
return sPath;
}
int64_t Checksum(char *data,unsigned int bytes) {
int64_t sum = 0;
for(unsigned int i = 0; i < bytes; ++i) {
char ch = *(data + i);
sum += (int64_t) ch;
}
return sum;
}
size_t CopyString(const std::string& src, char* dst, size_t size) {
size_t len = src.size() + 1; /* space for the null terminator */
if (dst) {
size_t copied = src.copy(dst, size - 1);
dst[copied] = '\0';
return copied + 1;
}
return len;
}
bool FileToByteArray(const std::string& path, char** target, int& size, bool nullTerminate) {
#ifdef WIN32
std::wstring u16fn = u8to16(path);
FILE* file = _wfopen(u16fn.c_str(), L"rb");
#else
FILE* file = fopen(path.c_str(), "rb");
#endif
*target = nullptr;
size = 0;
if (!file) {
return false;
}
*target = (char*)malloc(sizeof(char) * (fileSize + (nullTerminate ? 1 : 0)));
size = fread(*target, sizeof(char), fileSize, file);
bool success = false;
if (size == fileSize) {
if (nullTerminate) {
(*target)[size] = 0;
if (fseek(file, 0L, SEEK_END) == 0) {
long fileSize = ftell(file);
if (fileSize == -1) {
goto close_and_return;
}
success = true;
if (fseek(file, 0L, SEEK_SET) != 0) {
goto close_and_return;
}
*target = (char*)malloc(sizeof(char) * (fileSize + (nullTerminate ? 1 : 0)));
size = fread(*target, sizeof(char), fileSize, file);
if (size == fileSize) {
if (nullTerminate) {
(*target)[size] = 0;
}
success = true;
}
}
close_and_return:
fclose(file);
if (!success) {
free(*target);
}
return success;
}
close_and_return:
fclose(file);
std::string NormalizeDir(std::string path) {
path = boost::filesystem::path(path).make_preferred().string();
if (!success) {
free(*target);
std::string sep(1, boost::filesystem::path::preferred_separator);
if (path.size() && path.substr(path.size() - 1, 1) != sep) {
path += sep;
}
return path;
}
return success;
}
std::string musik::core::NormalizeDir(std::string path) {
path = boost::filesystem::path(path).make_preferred().string();
std::string sep(1, boost::filesystem::path::preferred_separator);
if (path.size() && path.substr(path.size() - 1, 1) != sep) {
path += sep;
void OpenFile(const std::string& path) {
#ifdef WIN32
ShellExecuteA(nullptr, nullptr, path.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
#elif __APPLE__
string command = "open " + path;
system(command.c_str());
#else
string command = "xdg-open " + path;
system(command.c_str());
#endif
}
return path;
}
} }

View File

@ -39,18 +39,19 @@
namespace musik { namespace core {
std::string GetHomeDirectory();
std::string GetApplicationDirectory();
std::string GetDataDirectory(bool create = true);
std::string GetPath(const std::string &sFile);
std::string GetPluginDirectory();
std::string NormalizeDir(std::string path);
int64_t Checksum(char *data,unsigned int bytes);
size_t CopyString(const std::string& src, char* dst, size_t size);
bool FileToByteArray(const std::string& path, char** target, int& size, bool nullTerminate = false);
extern std::string GetHomeDirectory();
extern std::string GetApplicationDirectory();
extern std::string GetDataDirectory(bool create = true);
extern std::string GetPath(const std::string &sFile);
extern std::string GetPluginDirectory();
extern std::string NormalizeDir(std::string path);
extern void OpenFile(const std::string& path);
extern int64_t Checksum(char *data,unsigned int bytes);
extern size_t CopyString(const std::string& src, char* dst, size_t size);
extern bool FileToByteArray(const std::string& path, char** target, int& size, bool nullTerminate = false);
/* renames ~/.mC2 -> ~/.musikcube */
void MigrateOldDataDirectory();
void RemoveOldDlls();
extern void MigrateOldDataDirectory();
extern void RemoveOldDlls();
} }

View File

@ -38,29 +38,45 @@
#include "LastFm.h"
#include <curl/curl.h>
#include <openssl/md5.h>
#include <core/support/Preferences.h>
#include <core/io/HttpClient.h>
#include <core/support/PreferenceKeys.h>
#include <app/util/PreferenceKeys.h>
#include <json.hpp>
#include <sstream>
#include <map>
/* http://www.last.fm/group/Last.fm+Web+Services/forum/21604/_/522900 -- it's ok to
put our key in the code */
static const std::string API_KEY = "502c69bd3f9946e8e0beee4fcb28c4cd";
static const std::string API_KEY = "8b7b7369cd92bbcc071c5f8a1de1d287";
static const std::string API_SECRET = "6dc09da925fe5c115b90320213c53b46";
static const std::string URL_BASE = "http://ws.audioscrobbler.com/2.0/";
static const std::string GET_TOKEN = "auth.gettoken";
static const std::string GET_TOKEN = "auth.getToken";
static const std::string GET_SESSION = "auth.getSession";
static const std::string ACCOUNT_LINK_URL_BASE = "http://www.last.fm/api/auth/?api_key=" + API_KEY + "&token=";
using namespace musik;
using namespace musik::core::prefs;
using LastFmClient = musik::core::io::HttpClient<std::stringstream>;
using Preferences = musik::core::Preferences;
using Prefs = std::shared_ptr<Preferences>;
static std::unique_ptr<LastFmClient> createClient() {
return LastFmClient::Create(std::stringstream());
}
static void validate(musik::cube::lastfm::Session& session) {
session.valid =
session.sessionId.size() &&
session.username.size() &&
session.token.size();
}
static std::string generateSignedUrl(
const std::string& method,
std::map<std::string, std::string>&& params = { })
{
params["format"] = "json";
params["method"] = method;
params["api_key"] = API_KEY;
@ -74,6 +90,8 @@ static std::string generateSignedUrl(
first = false;
}
toHash += API_SECRET;
/* compute the sum */
unsigned char rawDigest[MD5_DIGEST_LENGTH];
MD5((const unsigned char*)toHash.c_str(), toHash.length(), rawDigest);
@ -85,19 +103,24 @@ static std::string generateSignedUrl(
}
hexDigest[32] = 0;
url += "&api_sig=" + std::string(hexDigest);
url += "&format=json&api_sig=" + std::string(hexDigest);
return url;
}
static inline Prefs settings() {
return Preferences::ForComponent(components::Settings);
}
namespace musik { namespace cube { namespace lastfm {
const std::string InitiateLink() {
const std::string CreateAccountLinkToken() {
std::string url = generateSignedUrl(GET_TOKEN);
std::string token;
auto client = createClient();
client->Url(url)
.Mode(LastFmClient::Thread::Current)
.Run([&token](LastFmClient* client, int statusCode, CURLcode curlCode) {
if (statusCode == 200) {
try {
@ -108,10 +131,64 @@ namespace musik { namespace cube { namespace lastfm {
/* not much we can do... */
}
}
})
.Wait();
});
return token.size() ? (ACCOUNT_LINK_URL_BASE + token) : "";
return token;
}
extern Session CreateSession(const std::string& token) {
std::string url = generateSignedUrl(GET_SESSION, { { "token", token } });
Session session;
session.token = token;
auto client = createClient();
client->Url(url)
.Mode(LastFmClient::Thread::Current)
.Run([&session](LastFmClient* client, int statusCode, CURLcode curlCode) {
if (statusCode == 200) {
try {
auto json = nlohmann::json::parse(client->Stream().str());
auto subscriber = json["session"];
session.username = subscriber.value("name", "");
session.sessionId = subscriber.value("key", "");
}
catch (...) {
/* not much we can do... */
}
}
});
validate(session);
return session;
}
const std::string CreateLinkUrl(const std::string& token) {
return ACCOUNT_LINK_URL_BASE + token;
}
Session LoadSession() {
namespace keys = cube::prefs::keys;
auto prefs = settings();
Session session;
session.token = prefs->GetString(keys::LastFmToken);
session.sessionId = prefs->GetString(keys::LastFmSessionId);
session.username = prefs->GetString(keys::LastFmUsername);
validate(session);
return session;
}
void SaveSession(const Session& session) {
namespace keys = cube::prefs::keys;
auto prefs = settings();
prefs->SetString(keys::LastFmToken.c_str(), session.token.c_str());
prefs->SetString(keys::LastFmSessionId.c_str(), session.sessionId.c_str());
prefs->SetString(keys::LastFmUsername.c_str(), session.username.c_str());
}
void ClearSession() {
Session session;
SaveSession(session);
}
} } }

View File

@ -37,5 +37,15 @@
#include "stdafx.h"
namespace musik { namespace cube { namespace lastfm {
extern const std::string InitiateLink();
struct Session {
bool valid{ false };
std::string username, token, sessionId;
};
extern Session LoadSession();
extern void SaveSession(const Session& session);
extern const std::string CreateAccountLinkToken();
extern const std::string CreateLinkUrl(const std::string& token);
extern Session CreateSession(const std::string& token);
extern void ClearSession();
} } }

View File

@ -51,6 +51,9 @@ namespace musik { namespace cube { namespace prefs {
const std::string keys::LastBrowseDirectoryRoot = "LastBrowseDirectoryRoot";
const std::string keys::LastCategoryFilter = "LastCategoryFilter";
const std::string keys::LastTrackFilter = "LastTrackFilter";
const std::string keys::LastFmToken = "LastFmToken";
const std::string keys::LastFmSessionId = "LastFmSessionId";
const std::string keys::LastFmUsername = "LastFmUsername";
} } }

View File

@ -53,7 +53,9 @@ namespace musik { namespace cube { namespace prefs {
extern const std::string LastBrowseDirectoryRoot;
extern const std::string LastCategoryFilter;
extern const std::string LastTrackFilter;
extern const std::string LastFmToken;
extern const std::string LastFmSessionId;
extern const std::string LastFmUsername;
}
} } }