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 "FileItemHandler.h"
10 
11 #include "AudioLibrary.h"
12 #include "FileOperations.h"
13 #include "ServiceBroker.h"
14 #include "TextureDatabase.h"
15 #include "Util.h"
16 #include "VideoLibrary.h"
17 #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h" // EPG_TAG_INVALID_UID
18 #include "filesystem/Directory.h"
19 #include "filesystem/File.h"
20 #include "music/MusicThumbLoader.h"
21 #include "music/tags/MusicInfoTag.h"
22 #include "pictures/PictureInfoTag.h"
23 #include "pvr/PVRManager.h"
24 #include "pvr/channels/PVRChannel.h"
25 #include "pvr/epg/EpgInfoTag.h"
26 #include "pvr/recordings/PVRRecording.h"
27 #include "pvr/recordings/PVRRecordings.h"
28 #include "pvr/timers/PVRTimerInfoTag.h"
29 #include "pvr/timers/PVRTimers.h"
30 #include "utils/ISerializable.h"
31 #include "utils/SortUtils.h"
32 #include "utils/URIUtils.h"
33 #include "utils/Variant.h"
34 #include "video/VideoDatabase.h"
35 #include "video/VideoInfoTag.h"
36 #include "video/VideoThumbLoader.h"
37 
38 #include <map>
39 #include <string.h>
40 
41 using namespace MUSIC_INFO;
42 using namespace JSONRPC;
43 using namespace XFILE;
44 
GetField(const std::string & field,const CVariant & info,const CFileItemPtr & item,CVariant & result,bool & fetchedArt,CThumbLoader * thumbLoader)45 bool CFileItemHandler::GetField(const std::string &field, const CVariant &info, const CFileItemPtr &item, CVariant &result, bool &fetchedArt, CThumbLoader *thumbLoader /* = NULL */)
46 {
47   if (result.isMember(field) && !result[field].empty())
48     return true;
49 
50   // overwrite serialized values
51   if (item)
52   {
53     if (field == "mimetype" && item->GetMimeType().empty())
54     {
55       item->FillInMimeType(false);
56       result[field] = item->GetMimeType();
57       return true;
58     }
59 
60     if (item->HasPVRChannelInfoTag())
61     {
62       // Translate PVR.Details.Broadcast -> List.Item.Base format
63       if (field == "cast")
64       {
65         // string -> Video.Cast
66         const std::vector<std::string> actors =
67             StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR);
68 
69         result[field] = CVariant(CVariant::VariantTypeArray);
70         for (const auto& actor : actors)
71         {
72           CVariant actorVar;
73           actorVar["name"] = actor;
74           result[field].push_back(actorVar);
75         }
76         return true;
77       }
78       else if (field == "director" || field == "writer")
79       {
80         // string -> Array.String
81         result[field] = StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR);
82         return true;
83       }
84       else if (field == "isrecording")
85       {
86         result[field] = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(
87             *item->GetPVRChannelInfoTag());
88         return true;
89       }
90     }
91 
92     if (item->HasEPGInfoTag())
93     {
94       if (field == "hastimer")
95       {
96         const std::shared_ptr<PVR::CPVRTimerInfoTag> timer =
97             CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag());
98         result[field] = (timer != nullptr);
99         return true;
100       }
101       else if (field == "hasreminder")
102       {
103         const std::shared_ptr<PVR::CPVRTimerInfoTag> timer =
104             CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag());
105         result[field] = (timer && timer->IsReminder());
106         return true;
107       }
108       else if (field == "hastimerrule")
109       {
110         const std::shared_ptr<PVR::CPVRTimerInfoTag> timer =
111             CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag());
112         result[field] = (timer && (timer->GetTimerRuleId() != PVR_TIMER_NO_PARENT));
113         return true;
114       }
115       else if (field == "hasrecording")
116       {
117         const std::shared_ptr<PVR::CPVRRecording> recording =
118             CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(
119                 item->GetEPGInfoTag());
120         result[field] = (recording != nullptr);
121         return true;
122       }
123       else if (field == "recording")
124       {
125         const std::shared_ptr<PVR::CPVRRecording> recording =
126             CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(
127                 item->GetEPGInfoTag());
128         result[field] = recording ? recording->m_strFileNameAndPath : "";
129         return true;
130       }
131     }
132   }
133 
134   // check for serialized values
135   if (info.isMember(field) && !info[field].isNull())
136   {
137     result[field] = info[field];
138     return true;
139   }
140 
141   // check if the field requires special handling
142   if (item)
143   {
144     if (item->IsAlbum())
145     {
146       if (field == "albumlabel")
147       {
148         result[field] = item->GetProperty("album_label");
149         return true;
150       }
151       if (item->HasProperty("album_" + field + "_array"))
152       {
153         result[field] = item->GetProperty("album_" + field + "_array");
154         return true;
155       }
156       if (item->HasProperty("album_" + field))
157       {
158         result[field] = item->GetProperty("album_" + field);
159         return true;
160       }
161     }
162 
163     if (item->HasProperty("artist_" + field + "_array"))
164     {
165       result[field] = item->GetProperty("artist_" + field + "_array");
166       return true;
167     }
168     if (item->HasProperty("artist_" + field))
169     {
170       result[field] = item->GetProperty("artist_" + field);
171       return true;
172     }
173 
174     if (field == "art")
175     {
176       if (thumbLoader && !item->GetProperty("libraryartfilled").asBoolean() && !fetchedArt &&
177           ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) ||
178            (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1)))
179       {
180         thumbLoader->FillLibraryArt(*item);
181         fetchedArt = true;
182       }
183 
184       CGUIListItem::ArtMap artMap = item->GetArt();
185       CVariant artObj(CVariant::VariantTypeObject);
186       for (const auto& artIt : artMap)
187       {
188         if (!artIt.second.empty())
189           artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second);
190       }
191 
192       result["art"] = artObj;
193       return true;
194     }
195 
196     if (field == "thumbnail")
197     {
198       if (thumbLoader != NULL && !item->HasArt("thumb") && !fetchedArt &&
199         ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1)))
200       {
201         thumbLoader->FillLibraryArt(*item);
202         fetchedArt = true;
203       }
204       else if (item->HasPictureInfoTag() && !item->HasArt("thumb"))
205         item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(item->GetPath()));
206 
207       if (item->HasArt("thumb"))
208         result["thumbnail"] = CTextureUtils::GetWrappedImageURL(item->GetArt("thumb"));
209       else
210         result["thumbnail"] = "";
211 
212       return true;
213     }
214 
215     if (field == "fanart")
216     {
217       if (thumbLoader != NULL && !item->HasArt("fanart") && !fetchedArt &&
218         ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1)))
219       {
220         thumbLoader->FillLibraryArt(*item);
221         fetchedArt = true;
222       }
223 
224       if (item->HasArt("fanart"))
225         result["fanart"] = CTextureUtils::GetWrappedImageURL(item->GetArt("fanart"));
226       else
227         result["fanart"] = "";
228 
229       return true;
230     }
231 
232     if (item->HasVideoInfoTag() && item->GetVideoContentType() == VIDEODB_CONTENT_TVSHOWS)
233     {
234       if (item->GetVideoInfoTag()->m_iSeason < 0 && field == "season")
235       {
236         result[field] = (int)item->GetProperty("totalseasons").asInteger();
237         return true;
238       }
239       if (field == "watchedepisodes")
240       {
241         result[field] = (int)item->GetProperty("watchedepisodes").asInteger();
242         return true;
243       }
244     }
245 
246     if (item->HasProperty(field))
247     {
248       result[field] = item->GetProperty(field);
249       return true;
250     }
251   }
252 
253   return false;
254 }
255 
FillDetails(const ISerializable * info,const CFileItemPtr & item,std::set<std::string> & fields,CVariant & result,CThumbLoader * thumbLoader)256 void CFileItemHandler::FillDetails(const ISerializable *info, const CFileItemPtr &item, std::set<std::string> &fields, CVariant &result, CThumbLoader *thumbLoader /* = NULL */)
257 {
258   if (info == NULL || fields.empty())
259     return;
260 
261   CVariant serialization;
262   info->Serialize(serialization);
263 
264   bool fetchedArt = false;
265 
266   std::set<std::string> originalFields = fields;
267 
268   for (const auto& fieldIt : originalFields)
269   {
270     if (GetField(fieldIt, serialization, item, result, fetchedArt, thumbLoader) &&
271         result.isMember(fieldIt) && !result[fieldIt].empty())
272       fields.erase(fieldIt);
273   }
274 }
275 
HandleFileItemList(const char * ID,bool allowFile,const char * resultname,CFileItemList & items,const CVariant & parameterObject,CVariant & result,bool sortLimit)276 void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant &parameterObject, CVariant &result, bool sortLimit /* = true */)
277 {
278   HandleFileItemList(ID, allowFile, resultname, items, parameterObject, result, items.Size(), sortLimit);
279 }
280 
HandleFileItemList(const char * ID,bool allowFile,const char * resultname,CFileItemList & items,const CVariant & parameterObject,CVariant & result,int size,bool sortLimit)281 void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant &parameterObject, CVariant &result, int size, bool sortLimit /* = true */)
282 {
283   int start, end;
284   HandleLimits(parameterObject, result, size, start, end);
285 
286   if (sortLimit)
287     Sort(items, parameterObject);
288   else
289   {
290     start = 0;
291     end = items.Size();
292   }
293 
294   CThumbLoader *thumbLoader = NULL;
295   if (end - start > 0)
296   {
297     if (items.Get(start)->HasVideoInfoTag())
298       thumbLoader = new CVideoThumbLoader();
299     else if (items.Get(start)->HasMusicInfoTag())
300       thumbLoader = new CMusicThumbLoader();
301 
302     if (thumbLoader != NULL)
303       thumbLoader->OnLoaderStart();
304   }
305 
306   std::set<std::string> fields;
307   if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
308   {
309     for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); field != parameterObject["properties"].end_array(); field++)
310       fields.insert(field->asString());
311   }
312 
313   result[resultname].reserve(static_cast<size_t>(end - start));
314   for (int i = start; i < end; i++)
315   {
316     CFileItemPtr item = items.Get(i);
317     HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, true, thumbLoader);
318   }
319 
320   delete thumbLoader;
321 }
322 
HandleFileItem(const char * ID,bool allowFile,const char * resultname,const CFileItemPtr & item,const CVariant & parameterObject,const CVariant & validFields,CVariant & result,bool append,CThumbLoader * thumbLoader)323 void CFileItemHandler::HandleFileItem(const char* ID,
324                                       bool allowFile,
325                                       const char* resultname,
326                                       const CFileItemPtr& item,
327                                       const CVariant& parameterObject,
328                                       const CVariant& validFields,
329                                       CVariant& result,
330                                       bool append /* = true */,
331                                       CThumbLoader* thumbLoader /* = NULL */)
332 {
333   std::set<std::string> fields;
334   if (parameterObject.isMember("properties") && parameterObject["properties"].isArray())
335   {
336     for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); field != parameterObject["properties"].end_array(); field++)
337       fields.insert(field->asString());
338   }
339 
340   HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, append, thumbLoader);
341 }
342 
HandleFileItem(const char * ID,bool allowFile,const char * resultname,const CFileItemPtr & item,const CVariant & parameterObject,const std::set<std::string> & validFields,CVariant & result,bool append,CThumbLoader * thumbLoader)343 void CFileItemHandler::HandleFileItem(const char* ID,
344                                       bool allowFile,
345                                       const char* resultname,
346                                       const CFileItemPtr& item,
347                                       const CVariant& parameterObject,
348                                       const std::set<std::string>& validFields,
349                                       CVariant& result,
350                                       bool append /* = true */,
351                                       CThumbLoader* thumbLoader /* = NULL */)
352 {
353   CVariant object;
354   std::set<std::string> fields(validFields.begin(), validFields.end());
355 
356   if (item.get())
357   {
358     std::set<std::string>::const_iterator fileField = fields.find("file");
359     if (fileField != fields.end())
360     {
361       if (allowFile)
362       {
363         if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->GetPath().empty())
364           object["file"] = item->GetVideoInfoTag()->GetPath().c_str();
365         if (item->HasMusicInfoTag() && !item->GetMusicInfoTag()->GetURL().empty())
366           object["file"] = item->GetMusicInfoTag()->GetURL().c_str();
367         if (item->HasPVRTimerInfoTag() && !item->GetPVRTimerInfoTag()->m_strFileNameAndPath.empty())
368           object["file"] = item->GetPVRTimerInfoTag()->m_strFileNameAndPath.c_str();
369 
370         if (!object.isMember("file"))
371           object["file"] = item->GetDynPath().c_str();
372       }
373       fields.erase(fileField);
374     }
375 
376     fileField = fields.find("mediapath");
377     if (fileField != fields.end())
378     {
379       object["mediapath"] = item->GetPath().c_str();
380       fields.erase(fileField);
381     }
382 
383     fileField = fields.find("dynpath");
384     if (fileField != fields.end())
385     {
386       object["dynpath"] = item->GetDynPath().c_str();
387       fields.erase(fileField);
388     }
389 
390     if (ID)
391     {
392       if (item->HasPVRChannelInfoTag() && item->GetPVRChannelInfoTag()->ChannelID() > 0)
393          object[ID] = item->GetPVRChannelInfoTag()->ChannelID();
394       else if (item->HasEPGInfoTag() && item->GetEPGInfoTag()->DatabaseID() > 0)
395         object[ID] = item->GetEPGInfoTag()->DatabaseID();
396       else if (item->HasPVRRecordingInfoTag() && item->GetPVRRecordingInfoTag()->m_iRecordingId > 0)
397          object[ID] = item->GetPVRRecordingInfoTag()->m_iRecordingId;
398       else if (item->HasPVRTimerInfoTag() && item->GetPVRTimerInfoTag()->m_iTimerId > 0)
399          object[ID] = item->GetPVRTimerInfoTag()->m_iTimerId;
400       else if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > 0)
401         object[ID] = item->GetMusicInfoTag()->GetDatabaseId();
402       else if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > 0)
403         object[ID] = item->GetVideoInfoTag()->m_iDbId;
404 
405       if (StringUtils::CompareNoCase(ID, "id") == 0)
406       {
407         if (item->HasPVRChannelInfoTag())
408           object["type"] = "channel";
409         else if (item->HasMusicInfoTag())
410         {
411           std::string type = item->GetMusicInfoTag()->GetType();
412           if (type == MediaTypeAlbum || type == MediaTypeSong || type == MediaTypeArtist)
413             object["type"] = type;
414           else if (!item->m_bIsFolder)
415             object["type"] = MediaTypeSong;
416         }
417         else if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_type.empty())
418         {
419           std::string type = item->GetVideoInfoTag()->m_type;
420           if (type == MediaTypeMovie || type == MediaTypeTvShow || type == MediaTypeEpisode || type == MediaTypeMusicVideo)
421             object["type"] = type;
422         }
423         else if (item->HasPictureInfoTag())
424           object["type"] = "picture";
425 
426         if (!object.isMember("type"))
427           object["type"] = "unknown";
428 
429         if (fields.find("filetype") != fields.end())
430         {
431           if (item->m_bIsFolder)
432             object["filetype"] = "directory";
433           else
434             object["filetype"] = "file";
435         }
436       }
437     }
438 
439     bool deleteThumbloader = false;
440     if (thumbLoader == NULL)
441     {
442       if (item->HasVideoInfoTag())
443         thumbLoader = new CVideoThumbLoader();
444       else if (item->HasMusicInfoTag())
445         thumbLoader = new CMusicThumbLoader();
446 
447       if (thumbLoader != NULL)
448       {
449         deleteThumbloader = true;
450         thumbLoader->OnLoaderStart();
451       }
452     }
453 
454     if (item->HasPVRChannelInfoTag())
455       FillDetails(item->GetPVRChannelInfoTag().get(), item, fields, object, thumbLoader);
456     if (item->HasEPGInfoTag())
457       FillDetails(item->GetEPGInfoTag().get(), item, fields, object, thumbLoader);
458     if (item->HasPVRRecordingInfoTag())
459       FillDetails(item->GetPVRRecordingInfoTag().get(), item, fields, object, thumbLoader);
460     if (item->HasPVRTimerInfoTag())
461       FillDetails(item->GetPVRTimerInfoTag().get(), item, fields, object, thumbLoader);
462     if (item->HasVideoInfoTag())
463       FillDetails(item->GetVideoInfoTag(), item, fields, object, thumbLoader);
464     if (item->HasMusicInfoTag())
465       FillDetails(item->GetMusicInfoTag(), item, fields, object, thumbLoader);
466     if (item->HasPictureInfoTag())
467       FillDetails(item->GetPictureInfoTag(), item, fields, object, thumbLoader);
468 
469     FillDetails(item.get(), item, fields, object, thumbLoader);
470 
471     if (deleteThumbloader)
472       delete thumbLoader;
473 
474     object["label"] = item->GetLabel().c_str();
475   }
476   else
477     object = CVariant(CVariant::VariantTypeNull);
478 
479   if (resultname)
480   {
481     if (append)
482       result[resultname].append(object);
483     else
484       result[resultname] = object;
485   }
486 }
487 
FillFileItemList(const CVariant & parameterObject,CFileItemList & list)488 bool CFileItemHandler::FillFileItemList(const CVariant &parameterObject, CFileItemList &list)
489 {
490   CAudioLibrary::FillFileItemList(parameterObject, list);
491   CVideoLibrary::FillFileItemList(parameterObject, list);
492   CFileOperations::FillFileItemList(parameterObject, list);
493 
494   std::string file = parameterObject["file"].asString();
495   if (!file.empty() && (URIUtils::IsURL(file) || (CFile::Exists(file) && !CDirectory::Exists(file))))
496   {
497     bool added = false;
498     for (int index = 0; index < list.Size(); index++)
499     {
500       if (list[index]->GetDynPath() == file ||
501           list[index]->GetMusicInfoTag()->GetURL() == file || list[index]->GetVideoInfoTag()->GetPath() == file)
502       {
503         added = true;
504         break;
505       }
506     }
507 
508     if (!added)
509     {
510       CFileItemPtr item = CFileItemPtr(new CFileItem(file, false));
511       if (item->IsPicture())
512       {
513         CPictureInfoTag picture;
514         picture.Load(item->GetPath());
515         *item->GetPictureInfoTag() = picture;
516       }
517       if (item->GetLabel().empty())
518       {
519         item->SetLabel(CUtil::GetTitleFromPath(file, false));
520         if (item->GetLabel().empty())
521           item->SetLabel(URIUtils::GetFileName(file));
522       }
523       list.Add(item);
524     }
525   }
526 
527   return (list.Size() > 0);
528 }
529 
Sort(CFileItemList & items,const CVariant & parameterObject)530 void CFileItemHandler::Sort(CFileItemList &items, const CVariant &parameterObject)
531 {
532   SortDescription sorting;
533   if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
534     return;
535 
536   items.Sort(sorting);
537 }
538