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 ¶meterObject, 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 ¶meterObject, 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 ¶meterObject, 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 ¶meterObject)
531 {
532 SortDescription sorting;
533 if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes))
534 return;
535
536 items.Sort(sorting);
537 }
538