1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include "pch.hpp"
36 #include "LocalMetadataProxy.h"
37 
38 #include <musikcore/debug.h>
39 #include <musikcore/db/ScopedTransaction.h>
40 #include <musikcore/library/query/AlbumListQuery.h>
41 #include <musikcore/library/query/AllCategoriesQuery.h>
42 #include <musikcore/library/query/AppendPlaylistQuery.h>
43 #include <musikcore/library/query/CategoryListQuery.h>
44 #include <musikcore/library/query/CategoryTrackListQuery.h>
45 #include <musikcore/library/query/DeletePlaylistQuery.h>
46 #include <musikcore/library/query/SearchTrackListQuery.h>
47 #include <musikcore/library/query/GetPlaylistQuery.h>
48 #include <musikcore/library/query/SavePlaylistQuery.h>
49 #include <musikcore/library/query/TrackMetadataQuery.h>
50 #include <musikcore/library/query/TrackListQueryBase.h>
51 #include <musikcore/library/QueryRegistry.h>
52 #include <musikcore/library/LibraryFactory.h>
53 #include <musikcore/library/track/LibraryTrack.h>
54 #include <musikcore/library/LocalLibraryConstants.h>
55 #include <musikcore/runtime/Message.h>
56 #include <musikcore/support/Messages.h>
57 #include <musikcore/support/Common.h>
58 #include <vector>
59 #include <map>
60 
61 #pragma warning(push, 0)
62 #include <nlohmann/json.hpp>
63 #pragma warning(pop)
64 
65 #define TAG "LocalMetadataProxy"
66 
67 using namespace musik::core;
68 using namespace musik::core::db;
69 using namespace musik::core::library::query;
70 using namespace musik::core::library;
71 using namespace musik::core::runtime;
72 using namespace musik::core::sdk;
73 
74 using PredicateList = musik::core::library::query::category::PredicateList;
75 
76 /* HELPERS */
77 
78 #ifdef __APPLE__
79 static __thread char threadLocalBuffer[4096];
80 #else
81 static thread_local char threadLocalBuffer[4096];
82 #endif
83 
getValue(IValue * value)84 static inline std::string getValue(IValue* value) {
85     threadLocalBuffer[0] = 0;
86     if (value->GetValue(threadLocalBuffer, sizeof(threadLocalBuffer))) {
87         return std::string(threadLocalBuffer);
88     }
89     return "";
90 }
91 
toPredicateList(IValue ** predicates,size_t count)92 static inline PredicateList toPredicateList(IValue** predicates, size_t count) {
93     PredicateList predicateList;
94     if (predicates && count) {
95         for (size_t i = 0; i < count; i++) {
96             auto predicate = predicates[i];
97             if (predicate) {
98                 predicateList.push_back({ getValue(predicate), predicate->GetId() });
99             }
100         }
101     }
102     return predicateList;
103 }
104 
105 /* QUERIES */
106 
107 class ExternalIdListToTrackListQuery : public TrackListQueryBase {
108     public:
ExternalIdListToTrackListQuery(ILibraryPtr library,const char ** externalIds,size_t externalIdCount)109         ExternalIdListToTrackListQuery(
110             ILibraryPtr library,
111             const char** externalIds,
112             size_t externalIdCount)
113         {
114             this->library = library;
115             this->externalIds = externalIds;
116             this->externalIdCount = externalIdCount;
117         }
118 
GetResult()119         std::shared_ptr<TrackList> GetResult() noexcept override {
120             return this->result;
121         }
122 
GetHeaders()123         Headers GetHeaders() noexcept override {
124             return Headers();
125         }
126 
GetDurations()127         Durations GetDurations() noexcept override {
128             return Durations();
129         }
130 
GetQueryHash()131         size_t GetQueryHash() noexcept override {
132             return 0;
133         }
134 
135     protected:
OnRun(musik::core::db::Connection & db)136          bool OnRun(musik::core::db::Connection& db) override {
137             std::string sql = "SELECT id, external_id FROM tracks WHERE external_id IN(";
138             for (size_t i = 0; i < externalIdCount; i++) {
139                 sql += (i == 0) ? "?" : ",?";
140             }
141             sql += ");";
142 
143             Statement query(sql.c_str(), db);
144 
145             for (size_t i = 0; i < externalIdCount; i++) {
146                 query.BindText((int) i, externalIds[i]);
147             }
148 
149             /* gotta eat up some memory to preserve the input order. map the
150             external id to the id so we can ensure we return the list in the
151             same order it was requested. this is faster than executing one
152             query per ID (we do this because WHERE IN() does not preserve input
153             ordering... */
154             struct Record { int64_t id; std::string externalId; };
155             std::map<std::string, int64_t> records;
156 
157             while (query.Step() == Row) {
158                 records[query.ColumnText(1)] = query.ColumnInt64(0);
159             }
160 
161             /* order the output here... */
162             this->result = std::make_shared<TrackList>(this->library);
163             auto end = records.end();
164             for (size_t i = 0; i < externalIdCount; i++) {
165                 auto r = records.find(externalIds[i]);
166                 if (r != end) {
167                     this->result->Add(r->second);
168                 }
169             }
170 
171             return true;
172         }
173 
Name()174         std::string Name() override {
175             return "ExternalIdListToTrackListQuery";
176         }
177 
178     private:
179         ILibraryPtr library;
180         const char** externalIds;
181         size_t externalIdCount;
182         std::shared_ptr<TrackList> result;
183 };
184 
185 class RemoveFromPlaylistQuery : public QueryBase {
186     public:
RemoveFromPlaylistQuery(ILibraryPtr library,int64_t playlistId,const char ** externalIds,const int * sortOrders,size_t count)187         RemoveFromPlaylistQuery(
188             ILibraryPtr library,
189             int64_t playlistId,
190             const char** externalIds,
191             const int* sortOrders,
192             size_t count)
193         {
194             this->library = library;
195             this->playlistId = playlistId;
196             this->externalIds = externalIds;
197             this->sortOrders = sortOrders;
198             this->count = count;
199             this->updated = 0;
200         }
201 
GetResult()202         size_t GetResult() noexcept {
203             return this->updated;
204         }
205 
206     protected:
OnRun(musik::core::db::Connection & db)207         bool OnRun(musik::core::db::Connection& db) override {
208             this->updated = 0;
209 
210             ScopedTransaction transaction(db);
211 
212             {
213                 Statement deleteStmt(
214                     "DELETE FROM playlist_tracks "
215                     "WHERE playlist_id=? AND track_external_id=? AND sort_order=?",
216                     db);
217 
218                 for (size_t i = 0; i < count; i++) {
219                     const auto id = this->externalIds[i];
220                     const auto o = this->sortOrders[i];
221 
222                     deleteStmt.ResetAndUnbind();
223                     deleteStmt.BindInt64(0, this->playlistId);
224                     deleteStmt.BindText(1, this->externalIds[i]);
225                     deleteStmt.BindInt32(2, this->sortOrders[i]);
226                     if (deleteStmt.Step() == Done) {
227                         ++this->updated;
228                     }
229                 }
230             }
231 
232             bool error = false;
233 
234             {
235                 Statement playlistTracks(
236                     "SELECT track_external_id, sort_order FROM playlist_tracks "
237                     "WHERE playlist_id=? ORDER BY sort_order ASC",
238                     db);
239 
240                 Statement updateStmt(
241                     "UPDATE playlist_tracks "
242                     "SET sort_order=? "
243                     "WHERE playlist_id=? AND track_external_id=? AND sort_order=?",
244                     db);
245 
246                 int order = 0;
247 
248                 playlistTracks.BindInt64(0, this->playlistId);
249                 while (playlistTracks.Step() == Row) {
250                     updateStmt.ResetAndUnbind();
251                     updateStmt.BindInt32(0, order++);
252                     updateStmt.BindInt64(1, this->playlistId);
253                     updateStmt.BindText(2, playlistTracks.ColumnText(0));
254                     updateStmt.BindInt32(3, playlistTracks.ColumnInt32(1));
255                     if (updateStmt.Step() != Done) {
256                         error = true;
257                         break;
258                     }
259                 }
260             }
261 
262             if (!error) {
263                 transaction.CommitAndRestart();
264             }
265             else {
266                 this->updated = 0;
267             }
268 
269             if (this->updated > 0) {
270                 this->library->GetMessageQueue().Broadcast(
271                     Message::Create(nullptr, message::PlaylistModified, playlistId));
272             }
273 
274             return true;
275         }
276 
Name()277         std::string Name() override {
278             return "RemoveFromPlaylistQuery";
279         }
280 
281     private:
282         ILibraryPtr library;
283         int64_t playlistId;
284         const char** externalIds;
285         const int* sortOrders;
286         size_t count;
287         size_t updated;
288         std::shared_ptr<TrackList> result;
289 };
290 
291 /* DATA PROVIDER */
292 
LocalMetadataProxy(musik::core::ILibraryPtr library)293 LocalMetadataProxy::LocalMetadataProxy(musik::core::ILibraryPtr library)
294 : library(library) {
295 
296 }
297 
Release()298 void LocalMetadataProxy::Release() noexcept {
299     delete this;
300 }
301 
QueryTracks(const char * query,int limit,int offset)302 ITrackList* LocalMetadataProxy::QueryTracks(const char* query, int limit, int offset) {
303     try {
304         auto search = std::make_shared<SearchTrackListQuery>(
305             this->library,
306             SearchTrackListQuery::MatchType::Substring,
307             std::string(query ? query : ""),
308             TrackSortType::Album);
309 
310         if (limit >= 0) {
311             search->SetLimitAndOffset(limit, offset);
312         }
313 
314         this->library->EnqueueAndWait(search);
315 
316         if (search->GetStatus() == IQuery::Finished) {
317             return search->GetSdkResult();
318         }
319     }
320     catch (...) {
321         musik::debug::error(TAG, "QueryTracks failed");
322     }
323 
324     return nullptr;
325 }
326 
QueryTrackById(int64_t trackId)327 ITrack* LocalMetadataProxy::QueryTrackById(int64_t trackId) {
328     try {
329         const auto target = std::make_shared<LibraryTrack>(trackId, this->library);
330         const auto search = std::make_shared<TrackMetadataQuery>(target, this->library);
331         this->library->EnqueueAndWait(search);
332         if (search->GetStatus() == IQuery::Finished) {
333             return search->Result()->GetSdkValue();
334         }
335     }
336     catch (...) {
337         musik::debug::error(TAG, "QueryTrackById failed");
338     }
339 
340     return nullptr;
341 }
342 
QueryTrackByExternalId(const char * externalId)343 ITrack* LocalMetadataProxy::QueryTrackByExternalId(const char* externalId) {
344     if (strlen(externalId)) {
345         try {
346             auto target = std::make_shared<LibraryTrack>(0, this->library);
347             target->SetValue("external_id", externalId);
348             auto search = std::make_shared<TrackMetadataQuery>(target, this->library);
349             this->library->EnqueueAndWait(search);
350             if (search->GetStatus() == IQuery::Finished) {
351                 return search->Result()->GetSdkValue();
352             }
353         }
354         catch (...) {
355             musik::debug::error(TAG, "QueryTrackByExternalId failed");
356         }
357     }
358 
359     return nullptr;
360 }
361 
QueryTracksByCategory(const char * categoryType,int64_t selectedId,const char * filter,int limit,int offset)362 ITrackList* LocalMetadataProxy::QueryTracksByCategory(
363     const char* categoryType, int64_t selectedId, const char* filter, int limit, int offset)
364 {
365     try {
366         std::shared_ptr<TrackListQueryBase> search;
367 
368         if (std::string(categoryType) == constants::Playlists::TABLE_NAME) {
369             search = std::make_shared<GetPlaylistQuery>(this->library, selectedId);
370         }
371         else {
372             if (categoryType && strlen(categoryType) && selectedId > 0) {
373                 search = std::make_shared<CategoryTrackListQuery>(
374                     this->library, categoryType, selectedId, filter);
375             }
376             else {
377                 search = std::make_shared<CategoryTrackListQuery>(this->library, filter);
378             }
379         }
380 
381         if (limit >= 0) {
382             search->SetLimitAndOffset(limit, offset);
383         }
384 
385         this->library->EnqueueAndWait(search);
386 
387         if (search->GetStatus() == IQuery::Finished) {
388             return search->GetSdkResult();
389         }
390     }
391     catch (...) {
392         musik::debug::error(TAG, "QueryTracksByCategory failed");
393     }
394 
395     return nullptr;
396 }
397 
QueryTracksByCategories(IValue ** categories,size_t categoryCount,const char * filter,int limit,int offset)398 ITrackList* LocalMetadataProxy::QueryTracksByCategories(
399     IValue** categories, size_t categoryCount, const char* filter, int limit, int offset)
400 {
401     try {
402         PredicateList list = toPredicateList(categories, categoryCount);
403 
404         auto query = std::make_shared<CategoryTrackListQuery>(this->library, list, filter);
405 
406         if (limit >= 0) {
407             query->SetLimitAndOffset(limit, offset);
408         }
409 
410         this->library->EnqueueAndWait(query);
411 
412         if (query->GetStatus() == IQuery::Finished) {
413             return query->GetSdkResult();
414         }
415     }
416     catch (...) {
417         musik::debug::error(TAG, "QueryTracksByCategory failed");
418     }
419 
420     return nullptr;
421 }
422 
QueryCategory(const char * type,const char * filter)423 IValueList* LocalMetadataProxy::QueryCategory(const char* type, const char* filter) {
424     return QueryCategoryWithPredicate(type, "", -1LL, filter);
425 }
426 
ListCategories()427 IValueList* LocalMetadataProxy::ListCategories() {
428     try {
429         auto query = std::make_shared<AllCategoriesQuery>();
430         this->library->EnqueueAndWait(query);
431 
432         if (query->GetStatus() == IQuery::Finished) {
433             return query->GetSdkResult();
434         }
435     }
436     catch (...) {
437         musik::debug::error(TAG, "ListCategories failed");
438     }
439 
440     return nullptr;
441 }
442 
443 
QueryCategoryWithPredicate(const char * type,const char * predicateType,int64_t predicateId,const char * filter)444 IValueList* LocalMetadataProxy::QueryCategoryWithPredicate(
445     const char* type, const char* predicateType, int64_t predicateId, const char* filter)
446 {
447     try {
448         const std::string field = predicateType ? predicateType : "";
449         const category::PredicateList predicates = { { field, predicateId } };
450 
451         auto search = std::make_shared<CategoryListQuery>(
452             CategoryListQuery::MatchType::Substring,
453             type,
454             predicates,
455             std::string(filter ? filter : ""));
456 
457         this->library->EnqueueAndWait(search);
458 
459         if (search->GetStatus() == IQuery::Finished) {
460             return search->GetSdkResult();
461         }
462     }
463     catch (...) {
464         musik::debug::error(TAG, "QueryCategory failed");
465     }
466 
467     return nullptr;
468 }
469 
QueryCategoryWithPredicates(const char * type,IValue ** predicates,size_t predicateCount,const char * filter)470 IValueList* LocalMetadataProxy::QueryCategoryWithPredicates(
471     const char* type, IValue** predicates, size_t predicateCount, const char* filter)
472 {
473     try {
474         auto predicateList = toPredicateList(predicates, predicateCount);
475 
476         auto query = std::make_shared<CategoryListQuery>(
477             CategoryListQuery::MatchType::Substring,
478             type,
479             predicateList,
480             std::string(filter ? filter : ""));
481 
482         this->library->EnqueueAndWait(query);
483 
484         if (query->GetStatus() == IQuery::Finished) {
485             return query->GetSdkResult();
486         }
487     }
488     catch (...) {
489         musik::debug::error(TAG, "QueryCategory failed");
490     }
491 
492     return nullptr;
493 }
494 
QueryAlbums(const char * categoryIdName,int64_t categoryIdValue,const char * filter)495 IMapList* LocalMetadataProxy::QueryAlbums(
496     const char* categoryIdName, int64_t categoryIdValue, const char* filter)
497 {
498     try {
499         auto search = std::make_shared<AlbumListQuery>(
500             std::string(categoryIdName ? categoryIdName : ""),
501             categoryIdValue,
502             std::string(filter ? filter : ""));
503 
504         this->library->EnqueueAndWait(search);
505 
506         if (search->GetStatus() == IQuery::Finished) {
507             return search->GetSdkResult();
508         }
509     }
510     catch (...) {
511         musik::debug::error(TAG, "QueryAlbums failed");
512     }
513 
514     return nullptr;
515 }
516 
QueryAlbums(const char * filter)517 IMapList* LocalMetadataProxy::QueryAlbums(const char* filter) {
518     return this->QueryAlbums(nullptr, -1, filter);
519 }
520 
521 template <typename TrackListType>
savePlaylist(ILibraryPtr library,TrackListType trackList,const char * playlistName,const int64_t playlistId)522 static uint64_t savePlaylist(
523     ILibraryPtr library,
524     TrackListType trackList,
525     const char* playlistName,
526     const int64_t playlistId)
527 {
528     try {
529         /* replacing (and optionally renaming) an existing playlist */
530         if (playlistId != 0) {
531             std::shared_ptr<SavePlaylistQuery> query =
532                 SavePlaylistQuery::Replace(library, playlistId, trackList);
533 
534             library->EnqueueAndWait(query);
535 
536             if (query->GetStatus() == IQuery::Finished) {
537                 if (strlen(playlistName)) {
538                     query = SavePlaylistQuery::Rename(library, playlistId, playlistName);
539 
540                     library->EnqueueAndWait(query);
541 
542                     if (query->GetStatus() == IQuery::Finished) {
543                         return playlistId;
544                     }
545                 }
546                 else {
547                     return playlistId;
548                 }
549             }
550         }
551         else {
552             std::shared_ptr<SavePlaylistQuery> query =
553                 SavePlaylistQuery::Save(library, playlistName, trackList);
554 
555             library->EnqueueAndWait(query);
556 
557             if (query->GetStatus() == IQuery::Finished) {
558                 return query->GetPlaylistId();
559             }
560         }
561     }
562     catch (...) {
563         musik::debug::error(TAG, "SavePlaylist failed");
564     }
565 
566     return 0;
567 }
568 
SavePlaylistWithIds(int64_t * trackIds,size_t trackIdCount,const char * playlistName,const int64_t playlistId)569 int64_t LocalMetadataProxy::SavePlaylistWithIds(
570     int64_t* trackIds,
571     size_t trackIdCount,
572     const char* playlistName,
573     const int64_t playlistId)
574 {
575     if (playlistId == 0 && (!playlistName || !strlen(playlistName))) {
576         return 0;
577     }
578 
579     std::shared_ptr<TrackList> trackList =
580         std::make_shared<TrackList>(this->library, trackIds, trackIdCount);
581 
582     return savePlaylist(this->library, trackList, playlistName, playlistId);
583 }
584 
SavePlaylistWithExternalIds(const char ** externalIds,size_t externalIdCount,const char * playlistName,const int64_t playlistId)585 int64_t LocalMetadataProxy::SavePlaylistWithExternalIds(
586     const char** externalIds,
587     size_t externalIdCount,
588     const char* playlistName,
589     const int64_t playlistId)
590 {
591     if (playlistId == 0 && (!playlistName || !strlen(playlistName))) {
592         return 0;
593     }
594 
595     try {
596         using Query = ExternalIdListToTrackListQuery;
597 
598         std::shared_ptr<Query> query =
599             std::make_shared<Query>(this->library, externalIds, externalIdCount);
600 
601         library->EnqueueAndWait(query);
602 
603         if (query->GetStatus() == IQuery::Finished) {
604             return savePlaylist(this->library, query->GetResult(), playlistName, playlistId);
605         }
606     }
607     catch (...) {
608         musik::debug::error(TAG, "SavePlaylistWithExternalIds failed");
609     }
610 
611     return 0;
612 }
613 
SavePlaylistWithTrackList(ITrackList * trackList,const char * playlistName,const int64_t playlistId)614 int64_t LocalMetadataProxy::SavePlaylistWithTrackList(
615     ITrackList* trackList,
616     const char* playlistName,
617     const int64_t playlistId)
618 {
619     if (playlistId == 0 && (!playlistName || !strlen(playlistName))) {
620         return 0;
621     }
622 
623     return savePlaylist(this->library, trackList, playlistName, playlistId);
624 }
625 
RenamePlaylist(const int64_t playlistId,const char * name)626 bool LocalMetadataProxy::RenamePlaylist(const int64_t playlistId, const char* name)
627 {
628     if (strlen(name)) {
629         try {
630             std::shared_ptr<SavePlaylistQuery> query =
631                 SavePlaylistQuery::Rename(library, playlistId, name);
632 
633             this->library->EnqueueAndWait(query);
634 
635             if (query->GetStatus() == IQuery::Finished) {
636                 return true;
637             }
638         }
639         catch (...) {
640             musik::debug::error(TAG, "RenamePlaylist failed");
641         }
642     }
643 
644     return false;
645 }
646 
DeletePlaylist(const int64_t playlistId)647 bool LocalMetadataProxy::DeletePlaylist(const int64_t playlistId) {
648     try {
649         std::shared_ptr<DeletePlaylistQuery> query =
650             std::make_shared<DeletePlaylistQuery>(library, playlistId);
651 
652         this->library->EnqueueAndWait(query);
653 
654         if (query->GetStatus() == IQuery::Finished) {
655             return true;
656         }
657     }
658     catch (...) {
659         musik::debug::error(TAG, "DeletePlaylist failed");
660     }
661 
662     return false;
663 }
664 
665 template <typename TrackListType>
appendToPlaylist(ILibraryPtr library,const int64_t playlistId,TrackListType trackList,int offset)666 static bool appendToPlaylist(
667     ILibraryPtr library,
668     const int64_t playlistId,
669     TrackListType trackList,
670     int offset)
671 {
672     try {
673         std::shared_ptr<AppendPlaylistQuery> query =
674             std::make_shared<AppendPlaylistQuery>(
675                 library, playlistId, trackList, offset);
676 
677         library->EnqueueAndWait(query);
678 
679         if (query->GetStatus() == IQuery::Finished) {
680             return true;
681         }
682     }
683     catch (...) {
684         musik::debug::error(TAG, "AppendToPlaylist failed");
685     }
686 
687     return false;
688 }
689 
AppendToPlaylistWithIds(const int64_t playlistId,const int64_t * ids,size_t idCount,int offset)690 bool LocalMetadataProxy::AppendToPlaylistWithIds(
691     const int64_t playlistId,
692     const int64_t* ids,
693     size_t idCount,
694     int offset)
695 {
696     std::shared_ptr<TrackList> trackList =
697         std::make_shared<TrackList>(this->library, ids, idCount);
698 
699     return appendToPlaylist(this->library, playlistId, trackList, offset);
700 }
701 
AppendToPlaylistWithExternalIds(const int64_t playlistId,const char ** externalIds,size_t externalIdCount,int offset)702 bool LocalMetadataProxy::AppendToPlaylistWithExternalIds(
703     const int64_t playlistId,
704     const char** externalIds,
705     size_t externalIdCount,
706     int offset)
707 {
708     using Query = ExternalIdListToTrackListQuery;
709 
710     try {
711         std::shared_ptr<Query> query =
712             std::make_shared<Query>(this->library, externalIds, externalIdCount);
713 
714         library->EnqueueAndWait(query);
715 
716         if (query->GetStatus() == IQuery::Finished) {
717             return appendToPlaylist(this->library, playlistId, query->GetResult(), offset);
718         }
719     }
720     catch (...) {
721         musik::debug::error(TAG, "AppendToPlaylistWithExternalIds failed");
722     }
723 
724     return 0;
725 
726 }
727 
AppendToPlaylistWithTrackList(const int64_t playlistId,ITrackList * trackList,int offset)728 bool LocalMetadataProxy::AppendToPlaylistWithTrackList(
729     const int64_t playlistId, ITrackList* trackList, int offset)
730 {
731     return appendToPlaylist(this->library, playlistId, trackList, offset);
732 }
733 
RemoveTracksFromPlaylist(const int64_t playlistId,const char ** externalIds,const int * sortOrders,int count)734 size_t LocalMetadataProxy::RemoveTracksFromPlaylist(
735     const int64_t playlistId,
736     const char** externalIds,
737     const int* sortOrders,
738     int count)
739 {
740     try {
741         auto query = std::make_shared<RemoveFromPlaylistQuery>(
742             this->library, playlistId, externalIds, sortOrders, count);
743 
744         library->EnqueueAndWait(query);
745 
746         if (query->GetStatus() == IQuery::Finished) {
747             return query->GetResult();
748         }
749     }
750     catch (...) {
751         musik::debug::error(TAG, "RemoveTracksFromPlaylist failed");
752     }
753 
754     return 0;
755 }
756 
QueryTracksByExternalId(const char ** externalIds,size_t externalIdCount)757 ITrackList* LocalMetadataProxy::QueryTracksByExternalId(
758     const char** externalIds, size_t externalIdCount)
759 {
760     try {
761         auto query = std::make_shared<ExternalIdListToTrackListQuery>(
762             this->library, externalIds, externalIdCount);
763 
764         library->EnqueueAndWait(query);
765 
766         if (query->GetStatus() == IQuery::Finished) {
767             return query->GetSdkResult();
768         }
769     }
770     catch (...) {
771         musik::debug::error(TAG, "QueryTracksByExternalId failed");
772     }
773 
774     return nullptr;
775 }
776 
SendRawQuery(const char * query,IAllocator & allocator,char ** resultData,int * resultSize)777 bool LocalMetadataProxy::SendRawQuery(
778     const char* query, IAllocator& allocator, char** resultData, int* resultSize)
779 {
780     if (!resultData || !resultSize) {
781         return false;
782     }
783 
784     try {
785         nlohmann::json json = nlohmann::json::parse(query);
786         auto localLibrary = LibraryFactory::Instance().DefaultLocalLibrary();
787         std::string name = json["name"];
788         auto libraryQuery = QueryRegistry::CreateLocalQueryFor(name, query, localLibrary);
789         if (libraryQuery) {
790             localLibrary->EnqueueAndWait(libraryQuery);
791             if (libraryQuery->GetStatus() == IQuery::Finished) {
792                 std::string result = libraryQuery->SerializeResult();
793                 *resultData = static_cast<char*>(allocator.Allocate(result.size() + 1));
794                 if (*resultData) {
795                     *resultSize = (int) result.size() + 1;
796                     strncpy(*resultData, result.c_str(), *resultSize);
797                     return true;
798                 }
799                 else {
800                     musik::debug::error(TAG, "SendRawQuery failed: memory allocation failed");
801                 }
802             }
803             else {
804                 musik::debug::error(TAG, "SendRawQuery failed: query returned failure");
805             }
806         }
807         else {
808             musik::debug::error(TAG, "SendRawQuery failed: could not find query in registry");
809         }
810     }
811     catch (...) {
812         musik::debug::error(TAG, "SendRawQuery failed: exception thrown");
813     }
814     return false;
815 }