Updated HttpServer to allow querying audio data by either id or

external_id.
This commit is contained in:
casey langen 2017-04-15 23:29:17 -07:00
parent 59946d3cea
commit 8b2752abe5
9 changed files with 183 additions and 58 deletions

View File

@ -141,6 +141,8 @@ namespace request {
namespace fragment {
static const std::string audio = "audio";
static const std::string id = "id";
static const std::string external_id = "external_id";
}
namespace broadcast {

View File

@ -100,6 +100,74 @@ static std::string contentType(const std::string& fn) {
return "application/octet-stream";
}
/* toHex, urlEncode, fromHex, urlDecode are stilen from here:
http://dlib.net/dlib/server/server_http.cpp.html */
static inline unsigned char toHex(unsigned char x) {
return x + (x > 9 ? ('A' - 10) : '0');
}
std::string urlEncode(const std::string& s) {
std::ostringstream os;
for (std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci) {
if ((*ci >= 'a' && *ci <= 'z') ||
(*ci >= 'A' && *ci <= 'Z') ||
(*ci >= '0' && *ci <= '9'))
{ // allowed
os << *ci;
}
else if (*ci == ' ') {
os << '+';
}
else {
os << '%' << toHex(*ci >> 4) << toHex(*ci % 16);
}
}
return os.str();
}
inline unsigned char from_hex(unsigned char ch) {
if (ch <= '9' && ch >= '0') {
ch -= '0';
}
else if (ch <= 'f' && ch >= 'a') {
ch -= 'a' - 10;
}
else if (ch <= 'F' && ch >= 'A') {
ch -= 'A' - 10;
}
else {
ch = 0;
}
return ch;
}
std::string urlDecode(const std::string& str) {
using namespace std;
string result;
string::size_type i;
for (i = 0; i < str.size(); ++i) {
if (str[i] == '+') {
result += ' ';
}
else if (str[i] == '%' && str.size() > i + 2) {
const unsigned char ch1 = from_hex(str[i + 1]);
const unsigned char ch2 = from_hex(str[i + 2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
}
else {
result += str[i];
}
}
return result;
}
static ssize_t fileReadCallback(void *cls, uint64_t pos, char *buf, size_t max) {
Range* range = static_cast<Range*>(cls);
@ -192,6 +260,9 @@ bool HttpServer::Start() {
nullptr,
&HttpServer::HandleRequest,
this,
MHD_OPTION_UNESCAPE_CALLBACK,
&HttpServer::HandleUnescape,
this,
MHD_OPTION_END);
this->running = (httpServer != nullptr);
@ -213,6 +284,12 @@ bool HttpServer::Stop() {
return true;
}
size_t HttpServer::HandleUnescape(void * cls, struct MHD_Connection *c, char *s) {
/* don't do anything. the default implementation will decode the
entire path, which breaks if we have individually decoded segments. */
return strlen(s);
}
int HttpServer::HandleRequest(
void *cls,
struct MHD_Connection *connection,
@ -237,9 +314,18 @@ int HttpServer::HandleRequest(
std::vector<std::string> parts;
boost::split(parts, urlStr, boost::is_any_of("/"));
if (parts.size() > 0) {
if (parts.at(0) == fragment::audio && parts.size() == 2) {
unsigned long long id = std::stoull(parts.at(1));
IRetainedTrack* track = server->context.dataProvider->QueryTrack(id);
if (parts.at(0) == fragment::audio && parts.size() == 3) {
IRetainedTrack* track = nullptr;
if (parts.at(1) == fragment::id) {
unsigned long long id = std::stoull(urlDecode(parts.at(2)));
track = server->context.dataProvider->QueryTrackById(id);
}
else if (parts.at(1) == fragment::external_id) {
std::string externalId = urlDecode(parts.at(2));
track = server->context.dataProvider->QueryTrackByExternalId(externalId.c_str());
}
if (track) {
std::string filename = GetMetadataString(track, key::filename);
track->Release();
@ -267,6 +353,7 @@ int HttpServer::HandleRequest(
fclose(file);
}
}
}
}
}

View File

@ -59,6 +59,11 @@ class HttpServer {
size_t *upload_data_size,
void **con_cls);
static size_t HandleUnescape(
void * cls,
struct MHD_Connection *c,
char *s);
struct MHD_Daemon *httpServer;
Context& context;
volatile bool running;

View File

@ -87,7 +87,7 @@ ITrackList* LocalSimpleDataProvider::QueryTracks(const char* query, int limit, i
return nullptr;
}
IRetainedTrack* LocalSimpleDataProvider::QueryTrack(unsigned long long trackId) {
IRetainedTrack* LocalSimpleDataProvider::QueryTrackById(unsigned long long trackId) {
try {
TrackPtr target(new LibraryTrack(trackId, this->library));
@ -101,7 +101,30 @@ IRetainedTrack* LocalSimpleDataProvider::QueryTrack(unsigned long long trackId)
}
}
catch (...) {
musik::debug::err(TAG, "QueryTrack failed");
musik::debug::err(TAG, "QueryTrackById failed");
}
return nullptr;
}
IRetainedTrack* LocalSimpleDataProvider::QueryTrackByExternalId(const char* externalId) {
if (strlen(externalId)) {
try {
TrackPtr target(new LibraryTrack(0, this->library));
target->SetValue("external_id", externalId);
std::shared_ptr<TrackMetadataQuery> search(
new TrackMetadataQuery(target, this->library));
this->library->Enqueue(search, ILibrary::QuerySynchronous);
if (search->GetStatus() == IQuery::Finished) {
return new RetainedTrack(target);
}
}
catch (...) {
musik::debug::err(TAG, "QueryTrackByExternalId failed");
}
}
return nullptr;

View File

@ -51,7 +51,9 @@ namespace musik { namespace core { namespace db { namespace local {
int limit = -1,
int offset = 0);
virtual musik::core::sdk::IRetainedTrack* QueryTrack(unsigned long long trackId);
virtual musik::core::sdk::IRetainedTrack* QueryTrackById(unsigned long long trackId);
virtual musik::core::sdk::IRetainedTrack* QueryTrackByExternalId(const char* externalId);
virtual musik::core::sdk::ITrackList*
QueryTracksByCategory(

View File

@ -41,59 +41,68 @@ using namespace musik::core::db::local;
using namespace musik::core;
using namespace musik::core::library;
static const std::string ALL_METADATA_QUERY =
"SELECT DISTINCT t.track, t.disc, t.bpm, t.duration, t.filesize, t.year, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id, t.source_id, t.external_id "
"FROM tracks t, albums al, artists alar, artists ar, genres gn "
"WHERE t.id=? AND t.album_id=al.id AND t.album_artist_id=alar.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id ";
static const std::string COLUMNS = "t.track, t.disc, t.bpm, t.duration, t.filesize, t.year, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id, t.source_id, t.external_id";
static const std::string TABLES = "tracks t, albums al, artists alar, artists ar, genres gn";
static const std::string PREDICATE = "t.album_id=al.id AND t.album_artist_id=alar.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id";
static const std::string URI_ONLY_QUERY =
"SELECT DISTINCT filename "
"FROM tracks "
"WHERE t.id=? ";
static const std::string ALL_METADATA_QUERY_BY_ID =
"SELECT DISTINCT " + COLUMNS + " " +
"FROM " + TABLES + " " +
"WHERE t.id=? AND " + PREDICATE;
TrackMetadataQuery::TrackMetadataQuery(TrackPtr target, ILibraryPtr library, Type type) {
static const std::string ALL_METADATA_QUERY_BY_EXTERNAL_ID =
"SELECT DISTINCT " + COLUMNS + " " +
"FROM " + TABLES + " " +
"WHERE t.external_id=? AND " + PREDICATE;
TrackMetadataQuery::TrackMetadataQuery(TrackPtr target, ILibraryPtr library) {
this->result = target;
this->library = library;
this->type = type;
}
bool TrackMetadataQuery::OnRun(Connection& db) {
if (this->type == Type::AllMetadata) {
Statement trackQuery(ALL_METADATA_QUERY.c_str(), db);
trackQuery.BindInt(0, (uint64) this->result->GetId());
bool queryById = this->result->GetId() != 0;
if (trackQuery.Step() == Row) {
result->SetValue(constants::Track::TRACK_NUM, trackQuery.ColumnText(0));
result->SetValue(constants::Track::DISC_NUM, trackQuery.ColumnText(1));
result->SetValue(constants::Track::BPM, trackQuery.ColumnText(2));
result->SetValue(constants::Track::DURATION, trackQuery.ColumnText(3));
result->SetValue(constants::Track::FILESIZE, trackQuery.ColumnText(4));
result->SetValue(constants::Track::YEAR, trackQuery.ColumnText(5));
result->SetValue(constants::Track::TITLE, trackQuery.ColumnText(6));
result->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(7));
result->SetValue(constants::Track::THUMBNAIL_ID, trackQuery.ColumnText(8));
result->SetValue(constants::Track::ALBUM, trackQuery.ColumnText(9));
result->SetValue(constants::Track::ALBUM_ARTIST, trackQuery.ColumnText(10));
result->SetValue(constants::Track::GENRE, trackQuery.ColumnText(11));
result->SetValue(constants::Track::ARTIST, trackQuery.ColumnText(12));
result->SetValue(constants::Track::FILETIME, trackQuery.ColumnText(13));
result->SetValue(constants::Track::GENRE_ID, trackQuery.ColumnText(14));
result->SetValue(constants::Track::ARTIST_ID, trackQuery.ColumnText(15));
result->SetValue(constants::Track::ALBUM_ARTIST_ID, trackQuery.ColumnText(16));
result->SetValue(constants::Track::ALBUM_ID, trackQuery.ColumnText(17));
result->SetValue(constants::Track::SOURCE_ID, trackQuery.ColumnText(18));
result->SetValue(constants::Track::EXTERNAL_ID, trackQuery.ColumnText(19));
return true;
}
const std::string& query = queryById
? ALL_METADATA_QUERY_BY_ID
: ALL_METADATA_QUERY_BY_EXTERNAL_ID;
Statement trackQuery(query.c_str(), db);
if (queryById) {
trackQuery.BindInt(0, (uint64) this->result->GetId());
}
else {
Statement trackQuery(URI_ONLY_QUERY.c_str(), db);
trackQuery.BindInt(0, (uint64) this->result->GetId());
if (trackQuery.Step() == Row) {
result->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(0));
return true;
const std::string& externalId = this->result->GetValue("external_id");
if (!externalId.size()) {
return false;
}
trackQuery.BindText(0, externalId);
}
if (trackQuery.Step() == Row) {
result->SetValue(constants::Track::TRACK_NUM, trackQuery.ColumnText(0));
result->SetValue(constants::Track::DISC_NUM, trackQuery.ColumnText(1));
result->SetValue(constants::Track::BPM, trackQuery.ColumnText(2));
result->SetValue(constants::Track::DURATION, trackQuery.ColumnText(3));
result->SetValue(constants::Track::FILESIZE, trackQuery.ColumnText(4));
result->SetValue(constants::Track::YEAR, trackQuery.ColumnText(5));
result->SetValue(constants::Track::TITLE, trackQuery.ColumnText(6));
result->SetValue(constants::Track::FILENAME, trackQuery.ColumnText(7));
result->SetValue(constants::Track::THUMBNAIL_ID, trackQuery.ColumnText(8));
result->SetValue(constants::Track::ALBUM, trackQuery.ColumnText(9));
result->SetValue(constants::Track::ALBUM_ARTIST, trackQuery.ColumnText(10));
result->SetValue(constants::Track::GENRE, trackQuery.ColumnText(11));
result->SetValue(constants::Track::ARTIST, trackQuery.ColumnText(12));
result->SetValue(constants::Track::FILETIME, trackQuery.ColumnText(13));
result->SetValue(constants::Track::GENRE_ID, trackQuery.ColumnText(14));
result->SetValue(constants::Track::ARTIST_ID, trackQuery.ColumnText(15));
result->SetValue(constants::Track::ALBUM_ARTIST_ID, trackQuery.ColumnText(16));
result->SetValue(constants::Track::ALBUM_ID, trackQuery.ColumnText(17));
result->SetValue(constants::Track::SOURCE_ID, trackQuery.ColumnText(18));
result->SetValue(constants::Track::EXTERNAL_ID, trackQuery.ColumnText(19));
return true;
}
return false;

View File

@ -41,15 +41,9 @@ namespace musik { namespace core { namespace db { namespace local {
class TrackMetadataQuery : public LocalQueryBase {
public:
enum class Type : int {
AllMetadata,
UriOnly
};
TrackMetadataQuery(
musik::core::TrackPtr target,
musik::core::ILibraryPtr library,
Type type = Type::AllMetadata);
musik::core::ILibraryPtr library);
virtual ~TrackMetadataQuery() { }
@ -64,7 +58,6 @@ class TrackMetadataQuery : public LocalQueryBase {
private:
ILibraryPtr library;
TrackPtr result;
Type type;
};
} } } }

View File

@ -48,7 +48,9 @@ namespace musik { namespace core { namespace sdk {
int limit = -1,
int offset = 0) = 0;
virtual IRetainedTrack* QueryTrack(unsigned long long trackId) = 0;
virtual IRetainedTrack* QueryTrackById(unsigned long long trackId) = 0;
virtual IRetainedTrack* QueryTrackByExternalId(const char* externalId) = 0;
virtual ITrackList* QueryTracksByCategory(
const char* categoryType,

View File

@ -120,7 +120,9 @@ namespace musik {
static const char* ArtistId = "visual_artist_id";
static const char* AlbumArtistId = "album_artist_id";
static const char* AlbumId = "album_id";
static const char* SourceId = "source_id";
static const char* ExternalId = "external_id";
}
static const int SdkVersion = 5;
static const int SdkVersion = 6;
} } }