1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "AudioLibrary.h"
10 
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "TextureDatabase.h"
14 #include "Util.h"
15 #include "filesystem/Directory.h"
16 #include "messaging/ApplicationMessenger.h"
17 #include "music/Album.h"
18 #include "music/Artist.h"
19 #include "music/MusicDatabase.h"
20 #include "music/MusicThumbLoader.h"
21 #include "music/Song.h"
22 #include "music/tags/MusicInfoTag.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "utils/SortUtils.h"
27 #include "utils/StringUtils.h"
28 #include "utils/URIUtils.h"
29 #include "utils/Variant.h"
30 
31 using namespace MUSIC_INFO;
32 using namespace JSONRPC;
33 using namespace XFILE;
34 using namespace KODI::MESSAGING;
35 
GetProperties(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)36 JSONRPC_STATUS CAudioLibrary::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
37 {
38   CVariant properties = CVariant(CVariant::VariantTypeObject);
39   CMusicDatabase musicdatabase;
40   // Make db connection once if one or more properties needs db access
41   for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array();
42        it != parameterObject["properties"].end_array(); it++)
43   {
44     std::string propertyName = it->asString();
45     if (propertyName == "librarylastupdated" || propertyName == "librarylastcleaned" ||
46         propertyName == "artistlinksupdated" || propertyName == "songslastadded" ||
47         propertyName == "albumslastadded" || propertyName == "artistslastadded" ||
48         propertyName == "songsmodified" || propertyName == "albumsmodified" ||
49         propertyName == "artistsmodified")
50     {
51       if (!musicdatabase.Open())
52         return InternalError;
53       else
54         break;
55     }
56   }
57 
58   for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); it != parameterObject["properties"].end_array(); it++)
59   {
60     std::string propertyName = it->asString();
61     CVariant property;
62     if (propertyName == "missingartistid")
63       property = (int)BLANKARTIST_ID;
64     else if (propertyName == "librarylastupdated")
65       property = musicdatabase.GetLibraryLastUpdated();
66     else if (propertyName == "librarylastcleaned")
67       property = musicdatabase.GetLibraryLastCleaned();
68     else if (propertyName == "artistlinksupdated")
69       property = musicdatabase.GetArtistLinksUpdated();
70     else if (propertyName == "songslastadded")
71       property = musicdatabase.GetSongsLastAdded();
72     else if (propertyName == "albumslastadded")
73       property = musicdatabase.GetAlbumsLastAdded();
74     else if (propertyName == "artistslastadded")
75       property = musicdatabase.GetArtistsLastAdded();
76     else if (propertyName == "genreslastadded")
77       property = musicdatabase.GetGenresLastAdded();
78     else if (propertyName == "songsmodified")
79       property = musicdatabase.GetSongsLastModified();
80     else if (propertyName == "albumsmodified")
81       property = musicdatabase.GetAlbumsLastModified();
82     else if (propertyName == "artistsmodified")
83       property = musicdatabase.GetArtistsLastModified();
84 
85     properties[propertyName] = property;
86   }
87 
88   result = properties;
89   return OK;
90 }
91 
92 
GetArtists(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)93 JSONRPC_STATUS CAudioLibrary::GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
94 {
95   CMusicDatabase musicdatabase;
96   if (!musicdatabase.Open())
97     return InternalError;
98 
99   CMusicDbUrl musicUrl;
100   if (!musicUrl.FromString("musicdb://artists/"))
101     return InternalError;
102 
103   bool allroles = false;
104   if (parameterObject["allroles"].isBoolean())
105     allroles = parameterObject["allroles"].asBoolean();
106 
107   const CVariant &filter = parameterObject["filter"];
108 
109   if (allroles)
110     musicUrl.AddOption("roleid", -1000); //All roles, any negative parameter overrides implicit roleid=1 filter required for backward compatibility
111   else if (filter.isMember("roleid"))
112     musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
113   else if (filter.isMember("role"))
114     musicUrl.AddOption("role", filter["role"].asString());
115   // Only one of (song) genreid/genre, albumid/album or songid/song or rules type filter is allowed by filter syntax
116   if (filter.isMember("genreid"))  //Deprecated. Use "songgenre" or "artistgenre"
117     musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
118   else if (filter.isMember("genre"))
119     musicUrl.AddOption("genre", filter["genre"].asString());
120   if (filter.isMember("songgenreid"))
121     musicUrl.AddOption("genreid", static_cast<int>(filter["songgenreid"].asInteger()));
122   else if (filter.isMember("songgenre"))
123     musicUrl.AddOption("genre", filter["songgenre"].asString());
124   else if (filter.isMember("albumid"))
125     musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger()));
126   else if (filter.isMember("album"))
127     musicUrl.AddOption("album", filter["album"].asString());
128   else if (filter.isMember("songid"))
129     musicUrl.AddOption("songid", static_cast<int>(filter["songid"].asInteger()));
130   else if (filter.isObject())
131   {
132     std::string xsp;
133     if (!GetXspFiltering("artists", filter, xsp))
134       return InvalidParams;
135 
136     musicUrl.AddOption("xsp", xsp);
137   }
138 
139   bool albumArtistsOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS);
140   if (parameterObject["albumartistsonly"].isBoolean())
141     albumArtistsOnly = parameterObject["albumartistsonly"].asBoolean();
142   musicUrl.AddOption("albumartistsonly", albumArtistsOnly);
143 
144   SortDescription sorting;
145   ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
146   if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
147     return InvalidParams;
148 
149   int total;
150   std::set<std::string> fields;
151   if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
152   {
153     for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
154       field != parameterObject["properties"].end_array(); field++)
155       fields.insert(field->asString());
156   }
157 
158   musicdatabase.SetTranslateBlankArtist(false);
159   if (!musicdatabase.GetArtistsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
160     return InternalError;
161 
162   int start, end;
163   HandleLimits(parameterObject, result, total, start, end);
164 
165   return OK;
166 }
167 
GetArtistDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)168 JSONRPC_STATUS CAudioLibrary::GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
169 {
170   int artistID = (int)parameterObject["artistid"].asInteger();
171 
172   CMusicDbUrl musicUrl;
173   if (!musicUrl.FromString("musicdb://artists/"))
174     return InternalError;
175 
176   CMusicDatabase musicdatabase;
177   if (!musicdatabase.Open())
178     return InternalError;
179 
180   musicUrl.AddOption("artistid", artistID);
181 
182   CFileItemList items;
183   CDatabase::Filter filter;
184   if (!musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items) || items.Size() != 1)
185     return InvalidParams;
186 
187   // Add "artist" to "properties" array by default
188   CVariant param = parameterObject;
189   if (!param.isMember("properties"))
190     param["properties"] = CVariant(CVariant::VariantTypeArray);
191   param["properties"].append("artist");
192 
193   //Get roleids, roles etc. if needed
194   JSONRPC_STATUS ret = GetAdditionalArtistDetails(parameterObject, items, musicdatabase);
195   if (ret != OK)
196     return ret;
197 
198   HandleFileItem("artistid", false, "artistdetails", items[0], param, param["properties"], result, false);
199   return OK;
200 }
201 
GetAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)202 JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
203 {
204   CMusicDatabase musicdatabase;
205   if (!musicdatabase.Open())
206     return InternalError;
207 
208   CMusicDbUrl musicUrl;
209   if (!musicUrl.FromString("musicdb://albums/"))
210     return InternalError;
211 
212   if (parameterObject["includesingles"].asBoolean())
213     musicUrl.AddOption("show_singles", true);
214 
215   bool allroles = false;
216   if (parameterObject["allroles"].isBoolean())
217     allroles = parameterObject["allroles"].asBoolean();
218 
219   const CVariant &filter = parameterObject["filter"];
220 
221   if (allroles)
222     musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility
223   else if (filter.isMember("roleid"))
224     musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
225   else if (filter.isMember("role"))
226     musicUrl.AddOption("role", filter["role"].asString());
227   // Only one of genreid/genre, artistid/artist or rules type filter is allowed by filter syntax
228   if (filter.isMember("artistid"))
229     musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger()));
230   else if (filter.isMember("artist"))
231     musicUrl.AddOption("artist", filter["artist"].asString());
232   else if (filter.isMember("genreid"))
233     musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
234   else if (filter.isMember("genre"))
235     musicUrl.AddOption("genre", filter["genre"].asString());
236   else if (filter.isObject())
237   {
238     std::string xsp;
239     if (!GetXspFiltering("albums", filter, xsp))
240       return InvalidParams;
241 
242     musicUrl.AddOption("xsp", xsp);
243   }
244 
245   SortDescription sorting;
246   ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
247   if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
248     return InvalidParams;
249 
250   int total;
251   std::set<std::string> fields;
252   if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
253   {
254     for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
255       field != parameterObject["properties"].end_array(); field++)
256       fields.insert(field->asString());
257   }
258 
259   if (!musicdatabase.GetAlbumsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
260     return InternalError;
261 
262   if (!result.isNull())
263   {
264     bool bFetchArt = fields.find("art") != fields.end();
265     bool bFetchFanart = fields.find("fanart") != fields.end();
266     if (bFetchArt || bFetchFanart)
267     {
268       CThumbLoader* thumbLoader = new CMusicThumbLoader();
269       thumbLoader->OnLoaderStart();
270 
271       std::set<std::string> artfields;
272       if (bFetchArt)
273         artfields.insert("art");
274       if (bFetchFanart)
275         artfields.insert("fanart");
276 
277       for (unsigned int index = 0; index < result["albums"].size(); index++)
278       {
279         CFileItem item;
280         item.GetMusicInfoTag()->SetDatabaseId(result["albums"][index]["albumid"].asInteger32(), MediaTypeAlbum);
281 
282         // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag
283         // CFileItemPtr itemptr(new CFileItem(item));
284         // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["albums"][index], thumbLoader);
285 
286         thumbLoader->FillLibraryArt(item);
287 
288         if (bFetchFanart)
289         {
290           if (item.HasArt("fanart"))
291             result["albums"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart"));
292           else
293             result["albums"][index]["fanart"] = "";
294         }
295         if (bFetchArt)
296         {
297           CGUIListItem::ArtMap artMap = item.GetArt();
298           CVariant artObj(CVariant::VariantTypeObject);
299           for (const auto& artIt : artMap)
300           {
301             if (!artIt.second.empty())
302               artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second);
303           }
304           result["albums"][index]["art"] = artObj;
305         }
306       }
307 
308       delete thumbLoader;
309     }
310   }
311 
312   int start, end;
313   HandleLimits(parameterObject, result, total, start, end);
314 
315   return OK;
316 }
317 
GetAlbumDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)318 JSONRPC_STATUS CAudioLibrary::GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
319 {
320   int albumID = (int)parameterObject["albumid"].asInteger();
321 
322   CMusicDatabase musicdatabase;
323   if (!musicdatabase.Open())
324     return InternalError;
325 
326   CAlbum album;
327   if (!musicdatabase.GetAlbum(albumID, album, false))
328     return InvalidParams;
329 
330   std::string path = StringUtils::Format("musicdb://albums/%li/", albumID);
331 
332   CFileItemPtr albumItem;
333   FillAlbumItem(album, path, albumItem);
334 
335   CFileItemList items;
336   items.Add(albumItem);
337   JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
338   if (ret != OK)
339     return ret;
340 
341   HandleFileItem("albumid", false, "albumdetails", items[0], parameterObject, parameterObject["properties"], result, false);
342 
343   return OK;
344 }
345 
GetSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)346 JSONRPC_STATUS CAudioLibrary::GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
347 {
348   CMusicDatabase musicdatabase;
349   if (!musicdatabase.Open())
350     return InternalError;
351 
352   CMusicDbUrl musicUrl;
353   if (!musicUrl.FromString("musicdb://songs/"))
354     return InternalError;
355 
356   if (parameterObject["singlesonly"].asBoolean())
357     musicUrl.AddOption("singles", true);
358   else if (!parameterObject["includesingles"].asBoolean())
359     musicUrl.AddOption("singles", false);
360 
361   bool allroles = false;
362   if (parameterObject["allroles"].isBoolean())
363     allroles = parameterObject["allroles"].asBoolean();
364 
365   const CVariant &filter = parameterObject["filter"];
366 
367   if (allroles)
368     musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility
369   else if (filter.isMember("roleid"))
370     musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger()));
371   else if (filter.isMember("role"))
372     musicUrl.AddOption("role", filter["role"].asString());
373   // Only one of genreid/genre, artistid/artist, albumid/album or rules type filter is allowed by filter syntax
374   if (filter.isMember("artistid"))
375     musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger()));
376   else if (filter.isMember("artist"))
377     musicUrl.AddOption("artist", filter["artist"].asString());
378   else if (filter.isMember("genreid"))
379     musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger()));
380   else if (filter.isMember("genre"))
381     musicUrl.AddOption("genre", filter["genre"].asString());
382   else if (filter.isMember("albumid"))
383     musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger()));
384   else if (filter.isMember("album"))
385     musicUrl.AddOption("album", filter["album"].asString());
386   else if (filter.isObject())
387   {
388     std::string xsp;
389     if (!GetXspFiltering("songs", filter, xsp))
390       return InvalidParams;
391 
392     musicUrl.AddOption("xsp", xsp);
393   }
394 
395   SortDescription sorting;
396   ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd);
397   if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
398     return InvalidParams;
399 
400   int total;
401   std::set<std::string> fields;
402   if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
403   {
404     for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array();
405       field != parameterObject["properties"].end_array(); field++)
406       fields.insert(field->asString());
407   }
408 
409   if (!musicdatabase.GetSongsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting))
410     return InternalError;
411 
412   if (!result.isNull())
413   {
414     bool bFetchArt = fields.find("art") != fields.end();
415     bool bFetchFanart = fields.find("fanart") != fields.end();
416     bool bFetchThumb = fields.find("thumbnail") != fields.end();
417     if (bFetchArt || bFetchFanart || bFetchThumb)
418     {
419       CThumbLoader* thumbLoader = new CMusicThumbLoader();
420       thumbLoader->OnLoaderStart();
421 
422       std::set<std::string> artfields;
423       if (bFetchArt)
424         artfields.insert("art");
425       if (bFetchFanart)
426         artfields.insert("fanart");
427       if (bFetchThumb)
428         artfields.insert("thumbnail");
429 
430       for (unsigned int index = 0; index < result["songs"].size(); index++)
431       {
432         CFileItem item;
433         // Only needs song and album id (if we have it) set to get art
434         // Getting art is quicker if "albumid" has been fetched
435         item.GetMusicInfoTag()->SetDatabaseId(result["songs"][index]["songid"].asInteger32(), MediaTypeSong);
436         if (result["songs"][index].isMember("albumid"))
437           item.GetMusicInfoTag()->SetAlbumId(result["songs"][index]["albumid"].asInteger32());
438         else
439           item.GetMusicInfoTag()->SetAlbumId(-1);
440 
441         // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag
442         // CFileItemPtr itemptr(new CFileItem(item));
443         // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["songs"][index], thumbLoader);
444 
445         thumbLoader->FillLibraryArt(item);
446 
447         if (bFetchThumb)
448         {
449           if (item.HasArt("thumb"))
450             result["songs"][index]["thumbnail"] = CTextureUtils::GetWrappedImageURL(item.GetArt("thumb"));
451           else
452             result["songs"][index]["thumbnail"] = "";
453         }
454         if (bFetchFanart)
455         {
456           if (item.HasArt("fanart"))
457             result["songs"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart"));
458           else
459             result["songs"][index]["fanart"] = "";
460         }
461         if (bFetchArt)
462         {
463           CGUIListItem::ArtMap artMap = item.GetArt();
464           CVariant artObj(CVariant::VariantTypeObject);
465           for (const auto& artIt : artMap)
466           {
467             if (!artIt.second.empty())
468               artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second);
469           }
470           result["songs"][index]["art"] = artObj;
471         }
472       }
473 
474       delete thumbLoader;
475     }
476   }
477 
478   int start, end;
479   HandleLimits(parameterObject, result, total, start, end);
480 
481   return OK;
482 }
483 
GetSongDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)484 JSONRPC_STATUS CAudioLibrary::GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
485 {
486   int idSong = (int)parameterObject["songid"].asInteger();
487 
488   CMusicDatabase musicdatabase;
489   if (!musicdatabase.Open())
490     return InternalError;
491 
492   CSong song;
493   if (!musicdatabase.GetSong(idSong, song))
494     return InvalidParams;
495 
496   CFileItemList items;
497   CFileItemPtr item = CFileItemPtr(new CFileItem(song));
498   FillItemArtistIDs(song.GetArtistIDArray(), item);
499   items.Add(item);
500 
501   JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
502   if (ret != OK)
503     return ret;
504 
505   HandleFileItem("songid", true, "songdetails", items[0], parameterObject, parameterObject["properties"], result, false);
506   return OK;
507 }
508 
GetRecentlyAddedAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)509 JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
510 {
511   CMusicDatabase musicdatabase;
512   if (!musicdatabase.Open())
513     return InternalError;
514 
515   VECALBUMS albums;
516   if (!musicdatabase.GetRecentlyAddedAlbums(albums))
517     return InternalError;
518 
519   CFileItemList items;
520   for (unsigned int index = 0; index < albums.size(); index++)
521   {
522     std::string path = StringUtils::Format("musicdb://recentlyaddedalbums/%li/", albums[index].idAlbum);
523 
524     CFileItemPtr item;
525     FillAlbumItem(albums[index], path, item);
526     items.Add(item);
527   }
528 
529   JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
530   if (ret != OK)
531     return ret;
532 
533   HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
534   return OK;
535 }
536 
GetRecentlyAddedSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)537 JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
538 {
539   CMusicDatabase musicdatabase;
540   if (!musicdatabase.Open())
541     return InternalError;
542 
543   int amount = (int)parameterObject["albumlimit"].asInteger();
544   if (amount < 0)
545     amount = 0;
546 
547   CFileItemList items;
548   if (!musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", items, (unsigned int)amount))
549     return InternalError;
550 
551   JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
552   if (ret != OK)
553     return ret;
554 
555   HandleFileItemList("songid", true, "songs", items, parameterObject, result);
556   return OK;
557 }
558 
GetRecentlyPlayedAlbums(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)559 JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
560 {
561   CMusicDatabase musicdatabase;
562   if (!musicdatabase.Open())
563     return InternalError;
564 
565   VECALBUMS albums;
566   if (!musicdatabase.GetRecentlyPlayedAlbums(albums))
567     return InternalError;
568 
569   CFileItemList items;
570   for (unsigned int index = 0; index < albums.size(); index++)
571   {
572     std::string path = StringUtils::Format("musicdb://recentlyplayedalbums/%li/", albums[index].idAlbum);
573 
574     CFileItemPtr item;
575     FillAlbumItem(albums[index], path, item);
576     items.Add(item);
577   }
578 
579   JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase);
580   if (ret != OK)
581     return ret;
582 
583   HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
584   return OK;
585 }
586 
GetRecentlyPlayedSongs(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)587 JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
588 {
589   CMusicDatabase musicdatabase;
590   if (!musicdatabase.Open())
591     return InternalError;
592 
593   CFileItemList items;
594   if (!musicdatabase.GetRecentlyPlayedAlbumSongs("musicdb://songs/", items))
595     return InternalError;
596 
597   JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase);
598   if (ret != OK)
599     return ret;
600 
601   HandleFileItemList("songid", true, "songs", items, parameterObject, result);
602   return OK;
603 }
604 
GetGenres(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)605 JSONRPC_STATUS CAudioLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
606 {
607   CMusicDatabase musicdatabase;
608   if (!musicdatabase.Open())
609     return InternalError;
610 
611   // Check if sources for genre wanted
612   bool sourcesneeded(false);
613   std::set<std::string> checkProperties;
614   checkProperties.insert("sourceid");
615   std::set<std::string> additionalProperties;
616   if (CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
617     sourcesneeded = (additionalProperties.find("sourceid") != additionalProperties.end());
618 
619   CFileItemList items;
620   if (!musicdatabase.GetGenresJSON(items, sourcesneeded))
621     return InternalError;
622 
623   HandleFileItemList("genreid", false, "genres", items, parameterObject, result);
624   return OK;
625 }
626 
GetRoles(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)627 JSONRPC_STATUS CAudioLibrary::GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
628 {
629   CMusicDatabase musicdatabase;
630   if (!musicdatabase.Open())
631     return InternalError;
632 
633   CFileItemList items;
634   if (!musicdatabase.GetRolesNav("musicdb://songs/", items))
635     return InternalError;
636 
637   /* need to set strTitle in each item*/
638   for (unsigned int i = 0; i < (unsigned int)items.Size(); i++)
639     items[i]->GetMusicInfoTag()->SetTitle(items[i]->GetLabel());
640 
641   HandleFileItemList("roleid", false, "roles", items, parameterObject, result);
642   return OK;
643 }
644 
GetSources(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)645 JSONRPC_STATUS JSONRPC::CAudioLibrary::GetSources(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
646 {
647   CMusicDatabase musicdatabase;
648   if (!musicdatabase.Open())
649     return InternalError;
650 
651   // Add "file" to "properties" array by default
652   CVariant param = parameterObject;
653   if (!param.isMember("properties"))
654     param["properties"] = CVariant(CVariant::VariantTypeArray);
655   if (!param["properties"].isMember("file"))
656     param["properties"].append("file");
657 
658   CFileItemList items;
659   if (!musicdatabase.GetSources(items))
660     return InternalError;
661 
662   HandleFileItemList("sourceid", true, "sources", items, param, result);
663   return OK;
664 }
665 
GetAvailableArtTypes(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)666 JSONRPC_STATUS CAudioLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
667 {
668   std::string mediaType;
669   int mediaID = -1;
670   if (parameterObject["item"].isMember("albumid"))
671   {
672     mediaType = MediaTypeAlbum;
673     mediaID = parameterObject["item"]["albumid"].asInteger32();
674   }
675   if (parameterObject["item"].isMember("artistid"))
676   {
677     mediaType = MediaTypeArtist;
678     mediaID = parameterObject["item"]["artistid"].asInteger32();
679   }
680   if (mediaID == -1)
681     return InternalError;
682 
683   CMusicDatabase musicdatabase;
684   if (!musicdatabase.Open())
685     return InternalError;
686 
687   CVariant availablearttypes = CVariant(CVariant::VariantTypeArray);
688   for (const auto& artType : musicdatabase.GetAvailableArtTypesForItem(mediaID, mediaType))
689   {
690     availablearttypes.append(artType);
691   }
692   result = CVariant(CVariant::VariantTypeObject);
693   result["availablearttypes"] = availablearttypes;
694 
695   return OK;
696 }
697 
GetAvailableArt(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)698 JSONRPC_STATUS CAudioLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result)
699 {
700   std::string mediaType;
701   int mediaID = -1;
702   if (parameterObject["item"].isMember("albumid"))
703   {
704     mediaType = MediaTypeAlbum;
705     mediaID = parameterObject["item"]["albumid"].asInteger32();
706   }
707   if (parameterObject["item"].isMember("artistid"))
708   {
709     mediaType = MediaTypeArtist;
710     mediaID = parameterObject["item"]["artistid"].asInteger32();
711   }
712   if (mediaID == -1)
713     return InternalError;
714 
715   std::string artType = parameterObject["arttype"].asString();
716   StringUtils::ToLower(artType);
717 
718   CMusicDatabase musicdatabase;
719   if (!musicdatabase.Open())
720     return InternalError;
721 
722   CVariant availableart = CVariant(CVariant::VariantTypeArray);
723   for (const auto& artentry : musicdatabase.GetAvailableArtForItem(mediaID, mediaType, artType))
724   {
725     CVariant item = CVariant(CVariant::VariantTypeObject);
726     item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url);
727     item["arttype"] = artentry.m_aspect;
728     if (!artentry.m_preview.empty())
729       item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview);
730     availableart.append(item);
731   }
732   result = CVariant(CVariant::VariantTypeObject);
733   result["availableart"] = availableart;
734 
735   return OK;
736 }
737 
SetArtistDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)738 JSONRPC_STATUS CAudioLibrary::SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
739 {
740   int id = (int)parameterObject["artistid"].asInteger();
741 
742   CMusicDatabase musicdatabase;
743   if (!musicdatabase.Open())
744     return InternalError;
745 
746   CArtist artist;
747   if (!musicdatabase.GetArtist(id, artist) || artist.idArtist <= 0)
748     return InvalidParams;
749 
750   if (ParameterNotNull(parameterObject, "artist"))
751     artist.strArtist = parameterObject["artist"].asString();
752   if (ParameterNotNull(parameterObject, "instrument"))
753     CopyStringArray(parameterObject["instrument"], artist.instruments);
754   if (ParameterNotNull(parameterObject, "style"))
755     CopyStringArray(parameterObject["style"], artist.styles);
756   if (ParameterNotNull(parameterObject, "mood"))
757     CopyStringArray(parameterObject["mood"], artist.moods);
758   if (ParameterNotNull(parameterObject, "born"))
759     artist.strBorn = parameterObject["born"].asString();
760   if (ParameterNotNull(parameterObject, "formed"))
761     artist.strFormed = parameterObject["formed"].asString();
762   if (ParameterNotNull(parameterObject, "description"))
763     artist.strBiography = parameterObject["description"].asString();
764   if (ParameterNotNull(parameterObject, "genre"))
765     CopyStringArray(parameterObject["genre"], artist.genre);
766   if (ParameterNotNull(parameterObject, "died"))
767     artist.strDied = parameterObject["died"].asString();
768   if (ParameterNotNull(parameterObject, "disbanded"))
769     artist.strDisbanded = parameterObject["disbanded"].asString();
770   if (ParameterNotNull(parameterObject, "yearsactive"))
771     CopyStringArray(parameterObject["yearsactive"], artist.yearsActive);
772   if (ParameterNotNull(parameterObject, "musicbrainzartistid"))
773     artist.strMusicBrainzArtistID = parameterObject["musicbrainzartistid"].asString();
774   if (ParameterNotNull(parameterObject, "sortname"))
775     artist.strSortName = parameterObject["sortname"].asString();
776   if (ParameterNotNull(parameterObject, "type"))
777     artist.strType = parameterObject["type"].asString();
778   if (ParameterNotNull(parameterObject, "gender"))
779     artist.strGender = parameterObject["gender"].asString();
780   if (ParameterNotNull(parameterObject, "disambiguation"))
781     artist.strDisambiguation = parameterObject["disambiguation"].asString();
782 
783   // Update existing art. Any existing artwork that isn't specified in this request stays as is.
784   // If the value is null then the existing art with that type is removed.
785   if (ParameterNotNull(parameterObject, "art"))
786   {
787     // Get current artwork
788     musicdatabase.GetArtForItem(artist.idArtist, MediaTypeArtist, artist.art);
789 
790     std::set<std::string> removedArtwork;
791     CVariant art = parameterObject["art"];
792     for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
793     {
794       if (artIt->second.isString() && !artIt->second.asString().empty())
795         artist.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
796       else if (artIt->second.isNull())
797       {
798         artist.art.erase(artIt->first);
799         removedArtwork.insert(artIt->first);
800       }
801     }
802     // Remove null art now, as not done by update
803     if (!musicdatabase.RemoveArtForItem(artist.idArtist, MediaTypeArtist, removedArtwork))
804       return InternalError;
805   }
806 
807   // Update artist including adding or replacing (but not removing) art
808   if (!musicdatabase.UpdateArtist(artist))
809     return InternalError;
810 
811   CJSONRPCUtils::NotifyItemUpdated();
812   return ACK;
813 }
814 
SetAlbumDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)815 JSONRPC_STATUS CAudioLibrary::SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
816 {
817   int id = (int)parameterObject["albumid"].asInteger();
818 
819   CMusicDatabase musicdatabase;
820   if (!musicdatabase.Open())
821     return InternalError;
822 
823   CAlbum album;
824   // Get current album details, but not songs as we do not want to update them here
825   if (!musicdatabase.GetAlbum(id, album, false) || album.idAlbum <= 0)
826     return InvalidParams;
827 
828   if (ParameterNotNull(parameterObject, "title"))
829     album.strAlbum = parameterObject["title"].asString();
830   if (ParameterNotNull(parameterObject, "displayartist"))
831     album.strArtistDesc = parameterObject["displayartist"].asString();
832   // Set album sort string before processing artist credits
833   if (ParameterNotNull(parameterObject, "sortartist"))
834     album.strArtistSort = parameterObject["sortartist"].asString();
835 
836   // Match up artist names and mbids to make new artist credits
837   // Mbid values only apply if there are names
838   if (ParameterNotNull(parameterObject, "artist"))
839   {
840     std::vector<std::string> artists;
841     std::vector<std::string> mbids;
842     CopyStringArray(parameterObject["artist"], artists);
843     // Check for Musicbrainz ids
844     if (ParameterNotNull(parameterObject, "musicbrainzalbumartistid"))
845       CopyStringArray(parameterObject["musicbrainzalbumartistid"], mbids);
846     // When display artist is not provided and yet artists is changing make by concatenation
847     if (!ParameterNotNull(parameterObject, "displayartist"))
848       album.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
849     album.SetArtistCredits(artists, std::vector<std::string>(), mbids);
850     // On updatealbum artists will be changed
851     album.bArtistSongMerge = true;
852   }
853 
854   if (ParameterNotNull(parameterObject, "description"))
855     album.strReview = parameterObject["description"].asString();
856   if (ParameterNotNull(parameterObject, "genre"))
857     CopyStringArray(parameterObject["genre"], album.genre);
858   if (ParameterNotNull(parameterObject, "theme"))
859     CopyStringArray(parameterObject["theme"], album.themes);
860   if (ParameterNotNull(parameterObject, "mood"))
861     CopyStringArray(parameterObject["mood"], album.moods);
862   if (ParameterNotNull(parameterObject, "style"))
863     CopyStringArray(parameterObject["style"], album.styles);
864   if (ParameterNotNull(parameterObject, "type"))
865     album.strType = parameterObject["type"].asString();
866   if (ParameterNotNull(parameterObject, "albumlabel"))
867     album.strLabel = parameterObject["albumlabel"].asString();
868   if (ParameterNotNull(parameterObject, "rating"))
869     album.fRating = parameterObject["rating"].asFloat();
870   if (ParameterNotNull(parameterObject, "userrating"))
871     album.iUserrating = static_cast<int>(parameterObject["userrating"].asInteger());
872   if (ParameterNotNull(parameterObject, "votes"))
873     album.iVotes = static_cast<int>(parameterObject["votes"].asInteger());
874   if (ParameterNotNull(parameterObject, "year"))
875     album.strReleaseDate = parameterObject["year"].asString();
876   if (ParameterNotNull(parameterObject, "musicbrainzalbumid"))
877     album.strMusicBrainzAlbumID = parameterObject["musicbrainzalbumid"].asString();
878   if (ParameterNotNull(parameterObject, "musicbrainzreleasegroupid"))
879     album.strReleaseGroupMBID = parameterObject["musicbrainzreleasegroupid"].asString();
880   if (ParameterNotNull(parameterObject, "isboxset"))
881     album.bBoxedSet = parameterObject["isboxset"].asBoolean();
882   if (ParameterNotNull(parameterObject, "originaldate"))
883     album.strOrigReleaseDate = parameterObject["originaldate"].asString();
884   if (ParameterNotNull(parameterObject, "releasedate"))
885     album.strReleaseDate = parameterObject["releasedate"].asString();
886   if (ParameterNotNull(parameterObject, "albumstatus"))
887     album.strReleaseStatus = parameterObject["albumstatus"].asString();
888 
889   // Update existing art. Any existing artwork that isn't specified in this request stays as is.
890   // If the value is null then the existing art with that type is removed.
891   if (ParameterNotNull(parameterObject, "art"))
892   {
893     // Get current artwork
894     musicdatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, album.art);
895 
896     std::set<std::string> removedArtwork;
897     CVariant art = parameterObject["art"];
898     for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
899     {
900       if (artIt->second.isString() && !artIt->second.asString().empty())
901         album.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
902       else if (artIt->second.isNull())
903       {
904         album.art.erase(artIt->first);
905         removedArtwork.insert(artIt->first);
906       }
907     }
908     // Remove null art now, as not done by update
909     if (!musicdatabase.RemoveArtForItem(album.idAlbum, MediaTypeAlbum, removedArtwork))
910       return InternalError;
911   }
912 
913   // Update artist including adding or replacing (but not removing) art
914   if (!musicdatabase.UpdateAlbum(album))
915     return InternalError;
916 
917   CJSONRPCUtils::NotifyItemUpdated();
918   return ACK;
919 }
920 
SetSongDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)921 JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
922 {
923   int id = (int)parameterObject["songid"].asInteger();
924 
925   CMusicDatabase musicdatabase;
926   if (!musicdatabase.Open())
927     return InternalError;
928 
929   CSong song;
930   if (!musicdatabase.GetSong(id, song) || song.idSong != id)
931     return InvalidParams;
932 
933   if (ParameterNotNull(parameterObject, "title"))
934     song.strTitle = parameterObject["title"].asString();
935 
936   if (ParameterNotNull(parameterObject, "displayartist"))
937     song.strArtistDesc = parameterObject["displayartist"].asString();
938   // Set album sort string before processing artist credits
939   if (ParameterNotNull(parameterObject, "sortartist"))
940     song.strArtistSort = parameterObject["sortartist"].asString();
941 
942   // Match up artist names and mbids to make new artist credits
943   // Mbid values only apply if there are names
944   bool updateartists = false;
945   if (ParameterNotNull(parameterObject, "artist"))
946   {
947     std::vector<std::string> artists, mbids;
948     updateartists = true;
949     CopyStringArray(parameterObject["artist"], artists);
950     // Check for Musicbrainz ids
951     if (ParameterNotNull(parameterObject, "musicbrainzartistid"))
952       CopyStringArray(parameterObject["musicbrainzartistid"], mbids);
953     // When display artist is not provided and yet artists is changing make by concatenation
954     if (!ParameterNotNull(parameterObject, "displayartist"))
955       song.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
956     song.SetArtistCredits(artists, std::vector<std::string>(), mbids);
957   }
958 
959   if (ParameterNotNull(parameterObject, "genre"))
960     CopyStringArray(parameterObject["genre"], song.genre);
961   if (ParameterNotNull(parameterObject, "year"))
962     song.strReleaseDate = parameterObject["year"].asString();
963   if (ParameterNotNull(parameterObject, "rating"))
964     song.rating = parameterObject["rating"].asFloat();
965   if (ParameterNotNull(parameterObject, "userrating"))
966     song.userrating = static_cast<int>(parameterObject["userrating"].asInteger());
967   if (ParameterNotNull(parameterObject, "track"))
968     song.iTrack = (song.iTrack & 0xffff0000) | ((int)parameterObject["track"].asInteger() & 0xffff);
969   if (ParameterNotNull(parameterObject, "disc"))
970     song.iTrack = (song.iTrack & 0xffff) | ((int)parameterObject["disc"].asInteger() << 16);
971   if (ParameterNotNull(parameterObject, "duration"))
972     song.iDuration = (int)parameterObject["duration"].asInteger();
973   if (ParameterNotNull(parameterObject, "comment"))
974     song.strComment = parameterObject["comment"].asString();
975   if (ParameterNotNull(parameterObject, "musicbrainztrackid"))
976     song.strMusicBrainzTrackID = parameterObject["musicbrainztrackid"].asString();
977   if (ParameterNotNull(parameterObject, "playcount"))
978     song.iTimesPlayed = static_cast<int>(parameterObject["playcount"].asInteger());
979   if (ParameterNotNull(parameterObject, "lastplayed"))
980     song.lastPlayed.SetFromDBDateTime(parameterObject["lastplayed"].asString());
981   if (ParameterNotNull(parameterObject, "mood"))
982     song.strMood = parameterObject["mood"].asString();
983   if (ParameterNotNull(parameterObject, "disctitle"))
984     song.strDiscSubtitle = parameterObject["disctitle"].asString();
985   if (ParameterNotNull(parameterObject, "bpm"))
986     song.iBPM = static_cast<int>(parameterObject["bpm"].asInteger());
987   if (ParameterNotNull(parameterObject, "originaldate"))
988     song.strOrigReleaseDate = parameterObject["originaldate"].asString();
989   if (ParameterNotNull(parameterObject, "albumreleasedate"))
990     song.strReleaseDate = parameterObject["albumreleasedate"].asString();
991 
992   // Update existing art. Any existing artwork that isn't specified in this request stays as is.
993   // If the value is null then the existing art with that type is removed.
994   if (ParameterNotNull(parameterObject, "art"))
995   {
996     // Get current artwork
997     std::map<std::string, std::string> artwork;
998     musicdatabase.GetArtForItem(song.idSong, MediaTypeSong, artwork);
999 
1000     std::set<std::string> removedArtwork;
1001     CVariant art = parameterObject["art"];
1002     for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); artIt++)
1003     {
1004       if (artIt->second.isString() && !artIt->second.asString().empty())
1005         artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString());
1006       else if (artIt->second.isNull())
1007       {
1008         artwork.erase(artIt->first);
1009         removedArtwork.insert(artIt->first);
1010       }
1011     }
1012     //Update artwork, not done in update song
1013     musicdatabase.SetArtForItem(song.idSong, MediaTypeSong, artwork);
1014     if (!musicdatabase.RemoveArtForItem(song.idSong, MediaTypeSong, removedArtwork))
1015       return InternalError;
1016   }
1017 
1018   // Update song (not including artwork)
1019   if (!musicdatabase.UpdateSong(song, updateartists))
1020     return InternalError;
1021 
1022   CJSONRPCUtils::NotifyItemUpdated();
1023   return ACK;
1024 }
1025 
Scan(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1026 JSONRPC_STATUS CAudioLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
1027 {
1028   std::string directory = parameterObject["directory"].asString();
1029   std::string cmd = StringUtils::Format("updatelibrary(music, %s, %s)", StringUtils::Paramify(directory).c_str(), parameterObject["showdialogs"].asBoolean() ? "true" : "false");
1030 
1031   CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1032   return ACK;
1033 }
1034 
Export(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1035 JSONRPC_STATUS CAudioLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
1036 {
1037   std::string cmd;
1038   if (parameterObject["options"].isMember("path"))
1039     cmd = StringUtils::Format("exportlibrary2(music, singlefile, %s, albums, albumartists)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str());
1040   else
1041   {
1042     cmd = "exportlibrary2(music, library, dummy, albums, albumartists";
1043     if (parameterObject["options"]["images"].isBoolean() &&
1044         parameterObject["options"]["images"].asBoolean() == true)
1045       cmd += ", artwork";
1046     if (parameterObject["options"]["overwrite"].isBoolean() &&
1047         parameterObject["options"]["overwrite"].asBoolean() == true)
1048       cmd += ", overwrite";
1049     cmd += ")";
1050   }
1051   CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1052   return ACK;
1053 }
1054 
Clean(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)1055 JSONRPC_STATUS CAudioLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
1056 {
1057   std::string cmd = StringUtils::Format("cleanlibrary(music, %s)", parameterObject["showdialogs"].asBoolean() ? "true" : "false");
1058   CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
1059   return ACK;
1060 }
1061 
FillFileItem(const std::string & strFilename,CFileItemPtr & item,const CVariant & parameterObject)1062 bool CAudioLibrary::FillFileItem(const std::string &strFilename, CFileItemPtr &item, const CVariant &parameterObject /* = CVariant(CVariant::VariantTypeArray) */)
1063 {
1064   CMusicDatabase musicdatabase;
1065   if (strFilename.empty())
1066     return false;
1067 
1068   bool filled = false;
1069   if (musicdatabase.Open())
1070   {
1071     if (CDirectory::Exists(strFilename))
1072     {
1073       CAlbum album;
1074       int albumid = musicdatabase.GetAlbumIdByPath(strFilename);
1075       if (musicdatabase.GetAlbum(albumid, album, false))
1076       {
1077         item->SetFromAlbum(album);
1078         FillItemArtistIDs(album.GetArtistIDArray(), item);
1079 
1080         CFileItemList items;
1081         items.Add(item);
1082 
1083         if (GetAdditionalAlbumDetails(parameterObject, items, musicdatabase) == OK)
1084           filled = true;
1085       }
1086     }
1087     else
1088     {
1089       CSong song;
1090       if (musicdatabase.GetSongByFileName(strFilename, song))
1091       {
1092         item->SetFromSong(song);
1093         FillItemArtistIDs(song.GetArtistIDArray(), item);
1094 
1095         CFileItemList items;
1096         items.Add(item);
1097         if (GetAdditionalSongDetails(parameterObject, items, musicdatabase) == OK)
1098           filled = true;
1099       }
1100     }
1101   }
1102 
1103   if (item->GetLabel().empty())
1104   {
1105     item->SetLabel(CUtil::GetTitleFromPath(strFilename, false));
1106     if (item->GetLabel().empty())
1107       item->SetLabel(URIUtils::GetFileName(strFilename));
1108   }
1109 
1110   return filled;
1111 }
1112 
FillFileItemList(const CVariant & parameterObject,CFileItemList & list)1113 bool CAudioLibrary::FillFileItemList(const CVariant &parameterObject, CFileItemList &list)
1114 {
1115   CMusicDatabase musicdatabase;
1116   if (!musicdatabase.Open())
1117     return false;
1118 
1119   std::string file = parameterObject["file"].asString();
1120   int artistID = (int)parameterObject["artistid"].asInteger(-1);
1121   int albumID = (int)parameterObject["albumid"].asInteger(-1);
1122   int genreID = (int)parameterObject["genreid"].asInteger(-1);
1123 
1124   bool success = false;
1125   CFileItemPtr fileItem(new CFileItem());
1126   if (FillFileItem(file, fileItem, parameterObject))
1127   {
1128     success = true;
1129     list.Add(fileItem);
1130   }
1131 
1132   if (artistID != -1 || albumID != -1 || genreID != -1)
1133     success |= musicdatabase.GetSongsNav("musicdb://songs/", list, genreID, artistID, albumID);
1134 
1135   int songID = (int)parameterObject["songid"].asInteger(-1);
1136   if (songID != -1)
1137   {
1138     CSong song;
1139     if (musicdatabase.GetSong(songID, song))
1140     {
1141       list.Add(CFileItemPtr(new CFileItem(song)));
1142       success = true;
1143     }
1144   }
1145 
1146   if (success)
1147   {
1148     // If we retrieved the list of songs by "artistid"
1149     // we sort by album (and implicitly by track number)
1150     if (artistID != -1)
1151       list.Sort(SortByAlbum, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1152     // If we retrieve the list of songs by "genreid"
1153     // we sort by artist (and implicitly by album and track number)
1154     else if (genreID != -1)
1155       list.Sort(SortByArtist, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1156     // otherwise we sort by track number
1157     else
1158       list.Sort(SortByTrackNumber, SortOrderAscending);
1159 
1160   }
1161 
1162   return success;
1163 }
1164 
FillItemArtistIDs(const std::vector<int> & artistids,CFileItemPtr & item)1165 void CAudioLibrary::FillItemArtistIDs(const std::vector<int>& artistids, CFileItemPtr& item)
1166 {
1167   // Add artistIds as separate property as not part of CMusicInfoTag
1168   CVariant artistidObj(CVariant::VariantTypeArray);
1169   for (const auto& artistid : artistids)
1170     artistidObj.push_back(artistid);
1171 
1172   item->SetProperty("artistid", artistidObj);
1173 }
1174 
FillAlbumItem(const CAlbum & album,const std::string & path,CFileItemPtr & item)1175 void CAudioLibrary::FillAlbumItem(const CAlbum &album, const std::string &path, CFileItemPtr &item)
1176 {
1177   item = CFileItemPtr(new CFileItem(path, album));
1178   // Add album artistIds as separate property as not part of CMusicInfoTag
1179   std::vector<int> artistids = album.GetArtistIDArray();
1180   FillItemArtistIDs(artistids, item);
1181 }
1182 
GetAdditionalDetails(const CVariant & parameterObject,CFileItemList & items)1183 JSONRPC_STATUS CAudioLibrary::GetAdditionalDetails(const CVariant &parameterObject, CFileItemList &items)
1184 {
1185   if (items.IsEmpty())
1186     return OK;
1187 
1188   CMusicDatabase musicdb;
1189   if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeArtist))
1190     return GetAdditionalArtistDetails(parameterObject, items, musicdb);
1191   else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeAlbum))
1192     return GetAdditionalAlbumDetails(parameterObject, items, musicdb);
1193   else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeSong))
1194     return GetAdditionalSongDetails(parameterObject, items, musicdb);
1195 
1196   return OK;
1197 }
1198 
GetAdditionalArtistDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1199 JSONRPC_STATUS CAudioLibrary::GetAdditionalArtistDetails(const CVariant &parameterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1200 {
1201   if (!musicdatabase.Open())
1202     return InternalError;
1203 
1204   std::set<std::string> checkProperties;
1205   checkProperties.insert("roles");
1206   checkProperties.insert("songgenres");
1207   checkProperties.insert("isalbumartist");
1208   checkProperties.insert("sourceid");
1209   std::set<std::string> additionalProperties;
1210   if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1211     return OK;
1212 
1213   if (additionalProperties.find("roles") != additionalProperties.end())
1214   {
1215     for (int i = 0; i < items.Size(); i++)
1216     {
1217       CFileItemPtr item = items[i];
1218       musicdatabase.GetRolesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1219     }
1220   }
1221   if (additionalProperties.find("songgenres") != additionalProperties.end())
1222   {
1223     for (int i = 0; i < items.Size(); i++)
1224     {
1225       CFileItemPtr item = items[i];
1226       musicdatabase.GetGenresByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1227     }
1228   }
1229   if (additionalProperties.find("isalbumartist") != additionalProperties.end())
1230   {
1231     for (int i = 0; i < items.Size(); i++)
1232     {
1233       CFileItemPtr item = items[i];
1234       musicdatabase.GetIsAlbumArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1235     }
1236   }
1237   if (additionalProperties.find("sourceid") != additionalProperties.end())
1238   {
1239     for (int i = 0; i < items.Size(); i++)
1240     {
1241       CFileItemPtr item = items[i];
1242       musicdatabase.GetSourcesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1243     }
1244   }
1245 
1246   return OK;
1247 }
1248 
GetAdditionalAlbumDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1249 JSONRPC_STATUS CAudioLibrary::GetAdditionalAlbumDetails(const CVariant &parameterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1250 {
1251   if (!musicdatabase.Open())
1252     return InternalError;
1253 
1254   std::set<std::string> checkProperties;
1255   checkProperties.insert("songgenres");
1256   checkProperties.insert("sourceid");
1257   std::set<std::string> additionalProperties;
1258   if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1259     return OK;
1260 
1261   if (additionalProperties.find("songgenres") != additionalProperties.end())
1262   {
1263     for (int i = 0; i < items.Size(); i++)
1264     {
1265       CFileItemPtr item = items[i];
1266       musicdatabase.GetGenresByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1267     }
1268   }
1269   if (additionalProperties.find("sourceid") != additionalProperties.end())
1270   {
1271     for (int i = 0; i < items.Size(); i++)
1272     {
1273       CFileItemPtr item = items[i];
1274       musicdatabase.GetSourcesByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get());
1275     }
1276   }
1277 
1278   return OK;
1279 }
1280 
GetAdditionalSongDetails(const CVariant & parameterObject,CFileItemList & items,CMusicDatabase & musicdatabase)1281 JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant &parameterObject, CFileItemList &items, CMusicDatabase &musicdatabase)
1282 {
1283   if (!musicdatabase.Open())
1284     return InternalError;
1285 
1286   std::set<std::string> checkProperties;
1287   checkProperties.insert("genreid");
1288   checkProperties.insert("sourceid");
1289   // Query (songview join songartistview) returns song.strAlbumArtists = CMusicInfoTag.m_strAlbumArtistDesc only
1290   // Actual album artist data, if required,  comes from album_artist and artist tables.
1291   // It may differ from just splitting album artist description string
1292   checkProperties.insert("albumartist");
1293   checkProperties.insert("albumartistid");
1294   checkProperties.insert("musicbrainzalbumartistid");
1295   std::set<std::string> additionalProperties;
1296   if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties))
1297     return OK;
1298 
1299   for (int i = 0; i < items.Size(); i++)
1300   {
1301     CFileItemPtr item = items[i];
1302     if (additionalProperties.find("genreid") != additionalProperties.end())
1303     {
1304       std::vector<int> genreids;
1305       if (musicdatabase.GetGenresBySong(item->GetMusicInfoTag()->GetDatabaseId(), genreids))
1306       {
1307         CVariant genreidObj(CVariant::VariantTypeArray);
1308         for (const auto& genreid : genreids)
1309           genreidObj.push_back(genreid);
1310 
1311         item->SetProperty("genreid", genreidObj);
1312       }
1313     }
1314     if (additionalProperties.find("sourceid") != additionalProperties.end())
1315     {
1316       musicdatabase.GetSourcesBySong(item->GetMusicInfoTag()->GetDatabaseId(), item->GetPath(), item.get());
1317     }
1318     if (item->GetMusicInfoTag()->GetAlbumId() > 0)
1319     {
1320       if (additionalProperties.find("albumartist") != additionalProperties.end() ||
1321           additionalProperties.find("albumartistid") != additionalProperties.end() ||
1322           additionalProperties.find("musicbrainzalbumartistid") != additionalProperties.end())
1323       {
1324         musicdatabase.GetArtistsByAlbum(item->GetMusicInfoTag()->GetAlbumId(), item.get());
1325       }
1326     }
1327   }
1328 
1329   return OK;
1330 }
1331 
CheckForAdditionalProperties(const CVariant & properties,const std::set<std::string> & checkProperties,std::set<std::string> & foundProperties)1332 bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties)
1333 {
1334   if (!properties.isArray() || properties.empty())
1335     return false;
1336 
1337   std::set<std::string> checkingProperties = checkProperties;
1338   for (CVariant::const_iterator_array itr = properties.begin_array(); itr != properties.end_array() && !checkingProperties.empty(); itr++)
1339   {
1340     if (!itr->isString())
1341       continue;
1342 
1343     std::string property = itr->asString();
1344     if (checkingProperties.find(property) != checkingProperties.end())
1345     {
1346       checkingProperties.erase(property);
1347       foundProperties.insert(property);
1348     }
1349   }
1350 
1351   return !foundProperties.empty();
1352 }
1353