mirror of
https://github.com/clangen/musikcube.git
synced 2025-04-01 10:20:29 +00:00
SDK and WebSocket layers now support multiple-predicate queries for both
categories and tracks.
This commit is contained in:
parent
7d70bd694c
commit
3df8105dcd
@ -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(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
@ -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,16 +808,25 @@ 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);
|
||||
|
||||
return context.dataProvider->QueryTracksByCategory(
|
||||
category.c_str(), selectedId, filter.c_str(), 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
|
||||
->QueryCategoryWithPredicate(
|
||||
category.c_str(),
|
||||
predicate.c_str(),
|
||||
predicateId,
|
||||
filter.c_str());
|
||||
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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user