SDK and WebSocket layers now support multiple-predicate queries for both

categories and tracks.
This commit is contained in:
casey langen 2018-01-06 00:59:28 -08:00
parent 7d70bd694c
commit 3df8105dcd
9 changed files with 218 additions and 29 deletions

View File

@ -52,6 +52,7 @@
#include <core/library/LocalLibraryConstants.h>
#include <core/runtime/Message.h>
#include <core/support/Messages.h>
#include <core/support/Common.h>
#include <vector>
#include <map>
@ -64,6 +65,35 @@ using namespace musik::core::library;
using namespace musik::core::runtime;
using namespace musik::core::sdk;
using PredicateList = musik::core::db::local::category::PredicateList;
/* HELPERS */
#ifdef __APPLE__
static __thread char threadLocalBuffer[4096];
#else
static thread_local char threadLocalBuffer[4096];
#endif
static inline std::string getValue(IValue* value) {
threadLocalBuffer[0] = 0;
if (value->GetValue(threadLocalBuffer, sizeof(threadLocalBuffer))) {
return std::string(threadLocalBuffer);
}
return "";
}
static inline PredicateList toPredicateList(IValue** predicates, size_t count) {
PredicateList predicateList;
if (predicates && count) {
for (size_t i = 0; i < count; i++) {
IValue* predicate = predicates[i];
predicateList.push_back({ getValue(predicate), predicate->GetId() });
}
}
return predicateList;
}
/* QUERIES */
class ExternalIdListToTrackListQuery : public TrackListQueryBase {
@ -329,11 +359,7 @@ ITrack* LocalSimpleDataProvider::QueryTrackByExternalId(const char* externalId)
}
ITrackList* LocalSimpleDataProvider::QueryTracksByCategory(
const char* categoryType,
int64_t selectedId,
const char* filter,
int limit,
int offset)
const char* categoryType, int64_t selectedId, const char* filter, int limit, int offset)
{
try {
std::shared_ptr<TrackListQueryBase> search;
@ -368,6 +394,31 @@ ITrackList* LocalSimpleDataProvider::QueryTracksByCategory(
return nullptr;
}
ITrackList* LocalSimpleDataProvider::QueryTracksByCategories(
IValue** categories, size_t categoryCount, const char* filter, int limit, int offset)
{
try {
PredicateList list = toPredicateList(categories, categoryCount);
auto query = std::make_shared<CategoryTrackListQuery>(this->library, list, filter);
if (limit >= 0) {
query->SetLimitAndOffset(limit, offset);
}
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return query->GetSdkResult();
}
}
catch (...) {
musik::debug::err(TAG, "QueryTracksByCategory failed");
}
return nullptr;
}
IValueList* LocalSimpleDataProvider::QueryCategory(const char* type, const char* filter) {
return QueryCategoryWithPredicate(type, "", -1LL, filter);
}
@ -414,10 +465,30 @@ IValueList* LocalSimpleDataProvider::QueryCategoryWithPredicate(
return nullptr;
}
IValueList* LocalSimpleDataProvider::QueryCategoryWithPredicates(
const char* type, IValue** predicates, size_t predicateCount, const char* filter)
{
try {
auto predicateList = toPredicateList(predicates, predicateCount);
auto query = std::make_shared<CategoryListQuery>(
type, predicateList, std::string(filter ? filter : ""));
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return query->GetSdkResult();
}
}
catch (...) {
musik::debug::err(TAG, "QueryCategory failed");
}
return nullptr;
}
IMapList* LocalSimpleDataProvider::QueryAlbums(
const char* categoryIdName,
int64_t categoryIdValue,
const char* filter)
const char* categoryIdName, int64_t categoryIdValue, const char* filter)
{
try {
std::shared_ptr<AlbumListQuery> search(new AlbumListQuery(

View File

@ -65,6 +65,14 @@ namespace musik { namespace core { namespace db { namespace local {
int limit = -1,
int offset = 0) override;
virtual musik::core::sdk::ITrackList*
QueryTracksByCategories(
musik::core::sdk::IValue** categories,
size_t categoryCount,
const char* filter = "",
int limit = -1,
int offset = 0) override;
virtual musik::core::sdk::ITrackList* QueryTracksByExternalId(
const char** externalIds, size_t externalIdCount) override;
@ -82,6 +90,13 @@ namespace musik { namespace core { namespace db { namespace local {
int64_t predicateId,
const char* filter = "") override;
virtual musik::core::sdk::IValueList*
QueryCategoryWithPredicates(
const char* type,
musik::core::sdk::IValue** predicates,
size_t predicateCount,
const char* filter = "") override;
virtual musik::core::sdk::IMapList*
QueryAlbums(const char* filter = "") override;

View File

@ -114,6 +114,8 @@ bool AlbumListQuery::OnRun(Connection& db) {
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{album_list_filter}}", albumFilter);
OutputDebugStringA(query.c_str());
Statement stmt(query.c_str(), db);
Apply(stmt, args);

View File

@ -104,7 +104,7 @@ namespace musik { namespace core { namespace db { namespace local { namespace ca
auto end = REGULAR_PROPERTY_MAP.end();
for (auto p : input) {
if (p.first.size() && p.second > 0) {
if (p.first.size() && p.second != 0 && p.second != -1) {
if (REGULAR_PROPERTY_MAP.find(p.first) != end) {
regular.push_back(p);
}

View File

@ -59,6 +59,13 @@ namespace musik { namespace core { namespace sdk {
int limit = -1,
int offset = 0) = 0;
virtual ITrackList* QueryTracksByCategories(
IValue** categories,
size_t categoryCount,
const char* filter = "",
int limit = -1,
int offset = 0) = 0;
virtual ITrackList* QueryTracksByExternalId(
const char** externalIds, size_t externalIdCount) = 0;
@ -74,6 +81,12 @@ namespace musik { namespace core { namespace sdk {
int64_t predicateId,
const char* filter = "") = 0;
virtual IValueList* QueryCategoryWithPredicates(
const char* type,
IValue** predicates,
size_t predicateCount,
const char* filter = "") = 0;
virtual IMapList* QueryAlbums(const char* filter = "") = 0;
virtual IMapList* QueryAlbums(

View File

@ -124,6 +124,7 @@ namespace key {
static const std::string sort_orders = "sort_orders";
static const std::string predicate_category = "predicate_category";
static const std::string predicate_id = "predicate_id";
static const std::string predicates = "predicates";
static const std::string sdk_version = "sdk_version";
static const std::string api_version = "api_version";
}

View File

@ -41,6 +41,18 @@ __thread char threadLocalBuffer[4096];
thread_local char threadLocalBuffer[4096];
#endif
using namespace musik::core::sdk;
static 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;
}
/* toHex, urlEncode, fromHex, urlDecode are stolen from here:
http://dlib.net/dlib/server/server_http.cpp.html */
static inline unsigned char toHex(unsigned char x) {
@ -85,7 +97,6 @@ std::string urlEncode(const std::string& s) {
return os.str();
}
std::string urlDecode(const std::string& str) {
using namespace std;
string result;
@ -109,3 +120,29 @@ std::string urlDecode(const std::string& str) {
return result;
}
IValue* CreateValue(
const std::string& value, int64_t id, const std::string& type)
{
struct Value : IValue {
int64_t id;
std::string value, type;
Value(const std::string& value, int64_t id, const std::string& type) {
this->value = value;
this->id = id;
this->type = type;
}
virtual int64_t GetId() { return id; }
virtual Class GetClass() { return IResource::Class::Value; }
virtual const char* GetType() { return type.c_str(); }
virtual void Release() { delete this; }
virtual size_t GetValue(char* dst, size_t size) {
return CopyString(value, dst, size);
}
};
return new Value(value, id, type);
}

View File

@ -77,6 +77,13 @@ static std::string GetValueString(musik::core::sdk::IValue* value) {
return std::string(threadLocalBuffer);
}
extern musik::core::sdk::IValue* CreateValue(
const std::string& value, int64_t id, const std::string& type);
extern std::string urlEncode(const std::string& s);
extern std::string urlDecode(const std::string& str);
#ifdef WIN32
static inline std::wstring utf8to16(const char* utf8) {
int size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, 0, 0);
@ -88,6 +95,3 @@ static inline std::wstring utf8to16(const char* utf8) {
return utf16fn;
}
#endif
std::string urlEncode(const std::string& s);
std::string urlDecode(const std::string& str);

View File

@ -84,7 +84,7 @@ static std::shared_ptr<char*> jsonToStringArray(const json& jsonArray) {
template <typename T>
static std::shared_ptr<T> jsonToIntArray(json& arr) {
size_t count = arr.size();
size_t count = arr.is_array() ? arr.size() : 0;
T* idArray = new T[count];
if (count > 0) {
@ -96,6 +96,25 @@ static std::shared_ptr<T> jsonToIntArray(json& arr) {
});
}
static std::shared_ptr<IValue*> jsonToPredicateList(json& arr) {
size_t count = arr.is_array() ? arr.size() : 0;
IValue** valueArray = new IValue*[count];
for (size_t i = 0; i < count; i++) {
valueArray[i] = CreateValue(
arr[i]["category"],
arr[i]["id"],
"category");
}
return std::shared_ptr<IValue*>(valueArray, [count](IValue** del) {
for (size_t i = 0; i < count; i++) {
del[i]->Release();
}
delete[] del;
});
}
static json getEnvironment(Context& context) {
return {
{ prefs::http_server_enabled, context.prefs->GetBool(prefs::http_server_enabled.c_str()) },
@ -720,7 +739,7 @@ void WebSocketServer::RespondWithQueryAlbums(connection_hdl connection, json& re
std::string filter = options.value(key::filter, "");
std::string category = options.value(key::category, "");
uint64_t categoryId = options.value(key::category_id, -1);
int64_t categoryId = options.value<int64_t>(key::category_id, -1);
IMapList* albumList = context.dataProvider
->QueryAlbums(category.c_str(), categoryId, filter.c_str());
@ -789,17 +808,26 @@ ITrackList* WebSocketServer::QueryTracksByCategory(json& request, int& limit, in
if (request.find(message::options) != request.end()) {
json& options = request[message::options];
std::string category = options[key::category];
uint64_t selectedId = options[key::id];
std::string category = options.value(key::category, "");
int64_t selectedId = options.value<int64_t>(key::id, -1);
json& predicates = options.value(key::predicates, json::array());
std::string filter = options.value(key::filter, "");
limit = -1, offset = 0;
this->GetLimitAndOffset(options, limit, offset);
if (predicates.size()) {
auto predicateList = jsonToPredicateList(predicates);
return context.dataProvider->QueryTracksByCategories(
predicateList.get(), predicates.size(), filter.c_str(), limit, offset);
}
else {
return context.dataProvider->QueryTracksByCategory(
category.c_str(), selectedId, filter.c_str(), limit, offset);
}
}
return nullptr;
}
@ -840,16 +868,34 @@ void WebSocketServer::RespondWithQueryCategory(connection_hdl connection, json&
auto& options = request[message::options];
std::string category = options[key::category];
std::string filter = options.value(key::filter, "");
/* single predicate */
std::string predicate = options.value(key::predicate_category, "");
int64_t predicateId = options.value(key::predicate_id, -1LL);
int64_t predicateId = options.value<int64_t>(key::predicate_id, -1LL);
/* multiple predicates */
json& predicates = options.value(key::predicates, json::array());
if (category.size()) {
IValueList* result = context.dataProvider
IValueList* result;
if (predicates.size()) {
auto predicateList = jsonToPredicateList(predicates);
result = context.dataProvider
->QueryCategoryWithPredicates(
category.c_str(),
predicateList.get(),
predicates.size(),
filter.c_str());
}
else {
result = context.dataProvider
->QueryCategoryWithPredicate(
category.c_str(),
predicate.c_str(),
predicateId,
filter.c_str());
}
if (result != nullptr) {
json list = json::array();
@ -928,7 +974,7 @@ void WebSocketServer::RespondWithSavePlaylist(connection_hdl connection, json& r
/* TODO: a lot of copy/paste between this method and RespondWithAppendToPlaylist */
auto& options = request[message::options];
int64_t id = options.value(key::playlist_id, 0);
int64_t id = options.value<int64_t>(key::playlist_id, 0);
std::string name = options.value(key::playlist_name, "");
/* by external id (slower, more reliable) */
@ -1018,7 +1064,7 @@ void WebSocketServer::RespondWithAppendToPlaylist(connection_hdl connection, jso
auto& options = request[message::options];
int offset = options.value(key::offset, -1);
int64_t id = options.value(key::playlist_id, 0);
int64_t id = options.value<int64_t>(key::playlist_id, 0);
if (id) {
/* by external id */
@ -1082,7 +1128,7 @@ void WebSocketServer::RespondWithRemoveTracksFromPlaylist(connection_hdl connect
auto end = options.end();
auto externalIdsIt = options.find(key::external_ids);
auto sortOrdersIt = options.find(key::sort_orders);
int64_t id = options.value(key::playlist_id, 0);
int64_t id = options.value<int64_t>(key::playlist_id, 0);
size_t updated = 0;
bool valid =