[FileItem][Cleanup] Add getter/setter for start/end offset
[xbmc.git] / xbmc / filesystem / PluginDirectory.cpp
blob16e27322d7cc50dacb88c62653b032f711d6463a
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "PluginDirectory.h"
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "addons/AddonInstaller.h"
15 #include "addons/AddonManager.h"
16 #include "addons/IAddon.h"
17 #include "addons/PluginSource.h"
18 #include "interfaces/generic/RunningScriptObserver.h"
19 #include "messaging/ApplicationMessenger.h"
20 #include "settings/Settings.h"
21 #include "settings/SettingsComponent.h"
22 #include "utils/URIUtils.h"
23 #include "utils/log.h"
24 #include "video/VideoInfoTag.h"
26 #include <mutex>
28 using namespace XFILE;
29 using namespace ADDON;
30 using namespace KODI::MESSAGING;
32 namespace
34 const unsigned int maxPluginResolutions = 5;
36 /*!
37 \brief Get the plugin path from a CFileItem.
39 \param item CFileItem where to get the path.
40 \return The plugin path if found otherwise an empty string.
42 std::string GetOriginalPluginPath(const CFileItem& item)
44 std::string currentPath = item.GetPath();
45 if (URIUtils::IsPlugin(currentPath))
46 return currentPath;
48 currentPath = item.GetDynPath();
49 if (URIUtils::IsPlugin(currentPath))
50 return currentPath;
52 return std::string();
54 } // unnamed namespace
56 CPluginDirectory::CPluginDirectory() : m_cancelled(false)
58 m_listItems = new CFileItemList;
59 m_fileResult = new CFileItem;
62 CPluginDirectory::~CPluginDirectory(void)
64 delete m_listItems;
65 delete m_fileResult;
68 bool CPluginDirectory::StartScript(const std::string& strPath, bool resume)
70 CURL url(strPath);
72 ADDON::AddonPtr addon;
73 // try the plugin type first, and if not found, try an unknown type
74 if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN,
75 OnlyEnabled::CHOICE_YES) &&
76 !CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_UNKNOWN,
77 OnlyEnabled::CHOICE_YES) &&
78 !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon,
79 InstallModalPrompt::CHOICE_YES))
81 CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
82 return false;
85 // clear out our status variables
86 m_fileResult->Reset();
87 m_listItems->Clear();
88 m_listItems->SetPath(strPath);
89 m_listItems->SetLabel(addon->Name());
90 m_cancelled = false;
91 m_success = false;
92 m_totalItems = 0;
94 // run the script
95 return RunScript(this, addon, strPath, resume);
98 bool CPluginDirectory::GetResolvedPluginResult(CFileItem& resultItem)
100 std::string lastResolvedPath;
101 if (resultItem.HasProperty("ForceResolvePlugin") &&
102 resultItem.GetProperty("ForceResolvePlugin").asBoolean())
104 // ensures that a plugin have the callback to resolve the paths in any case
105 // also when the same items in the playlist are played more times
106 lastResolvedPath = GetOriginalPluginPath(resultItem);
108 else
110 lastResolvedPath = resultItem.GetDynPath();
113 if (!lastResolvedPath.empty())
115 // we try to resolve recursively up to n. (maxPluginResolutions) nested plugin paths
116 // to avoid deadlocks (plugin:// paths can resolve to plugin:// paths)
117 for (unsigned int i = 0; URIUtils::IsPlugin(lastResolvedPath) && i < maxPluginResolutions; ++i)
119 bool resume = resultItem.GetStartOffset() == STARTOFFSET_RESUME;
121 // we modify the item so that it becomes a real URL
122 if (!XFILE::CPluginDirectory::GetPluginResult(lastResolvedPath, resultItem, resume) ||
123 resultItem.GetDynPath() ==
124 resultItem.GetPath()) // GetPluginResult resolved to an empty path
126 return false;
128 lastResolvedPath = resultItem.GetDynPath();
130 // if after the maximum allowed resolution attempts the item is still a plugin just return, it isn't playable
131 if (URIUtils::IsPlugin(resultItem.GetDynPath()))
132 return false;
135 return true;
138 bool CPluginDirectory::GetPluginResult(const std::string& strPath, CFileItem &resultItem, bool resume)
140 CURL url(strPath);
141 CPluginDirectory newDir;
143 bool success = newDir.StartScript(strPath, resume);
145 if (success)
146 { // update the play path and metadata, saving the old one as needed
147 if (!resultItem.HasProperty("original_listitem_url"))
148 resultItem.SetProperty("original_listitem_url", resultItem.GetPath());
149 resultItem.SetDynPath(newDir.m_fileResult->GetPath());
150 resultItem.SetMimeType(newDir.m_fileResult->GetMimeType());
151 resultItem.SetContentLookup(newDir.m_fileResult->ContentLookup());
153 if (resultItem.HasProperty("OverrideInfotag") &&
154 resultItem.GetProperty("OverrideInfotag").asBoolean())
155 resultItem.UpdateInfo(*newDir.m_fileResult);
156 else
157 resultItem.MergeInfo(*newDir.m_fileResult);
159 if (newDir.m_fileResult->HasVideoInfoTag() && newDir.m_fileResult->GetVideoInfoTag()->GetResumePoint().IsSet())
160 resultItem.SetStartOffset(
161 STARTOFFSET_RESUME); // resume point set in the resume item, so force resume
164 return success;
167 bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems)
169 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
170 CPluginDirectory* dir = GetScriptFromHandle(handle);
171 if (!dir)
172 return false;
174 CFileItemPtr pItem(new CFileItem(*item));
175 dir->m_listItems->Add(pItem);
176 dir->m_totalItems = totalItems;
178 return !dir->m_cancelled;
181 bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems)
183 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
184 CPluginDirectory* dir = GetScriptFromHandle(handle);
185 if (!dir)
186 return false;
188 CFileItemList pItemList;
189 pItemList.Copy(*items);
190 dir->m_listItems->Append(pItemList);
191 dir->m_totalItems = totalItems;
193 return !dir->m_cancelled;
196 void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc)
198 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
199 CPluginDirectory* dir = GetScriptFromHandle(handle);
200 if (!dir)
201 return;
203 // set cache to disc
204 dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER);
206 dir->m_success = success;
207 dir->m_listItems->SetReplaceListing(replaceListing);
209 if (!dir->m_listItems->HasSortDetails())
210 dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%L", "%D"));
212 // set the event to mark that we're done
213 dir->SetDone();
216 void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const std::string &labelMask, const std::string &label2Mask)
218 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
219 CPluginDirectory* dir = GetScriptFromHandle(handle);
220 if (!dir)
221 return;
223 //! @todo Add all sort methods and fix which labels go on the right or left
224 switch(sortMethod)
226 case SORT_METHOD_LABEL:
227 case SORT_METHOD_LABEL_IGNORE_THE:
229 dir->m_listItems->AddSortMethod(SortByLabel, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
230 break;
232 case SORT_METHOD_TITLE:
233 case SORT_METHOD_TITLE_IGNORE_THE:
235 dir->m_listItems->AddSortMethod(SortByTitle, 556, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
236 break;
238 case SORT_METHOD_ARTIST:
239 case SORT_METHOD_ARTIST_IGNORE_THE:
241 dir->m_listItems->AddSortMethod(SortByArtist, 557, LABEL_MASKS(labelMask, "%A", labelMask, "%A"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
242 break;
244 case SORT_METHOD_ALBUM:
245 case SORT_METHOD_ALBUM_IGNORE_THE:
247 dir->m_listItems->AddSortMethod(SortByAlbum, 558, LABEL_MASKS(labelMask, "%B", labelMask, "%B"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
248 break;
250 case SORT_METHOD_DATE:
252 dir->m_listItems->AddSortMethod(SortByDate, 552, LABEL_MASKS(labelMask, "%J", labelMask, "%J"));
253 break;
255 case SORT_METHOD_BITRATE:
257 dir->m_listItems->AddSortMethod(SortByBitrate, 623, LABEL_MASKS(labelMask, "%X", labelMask, "%X"));
258 break;
260 case SORT_METHOD_SIZE:
262 dir->m_listItems->AddSortMethod(SortBySize, 553, LABEL_MASKS(labelMask, "%I", labelMask, "%I"));
263 break;
265 case SORT_METHOD_FILE:
267 dir->m_listItems->AddSortMethod(SortByFile, 561, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
268 break;
270 case SORT_METHOD_TRACKNUM:
272 dir->m_listItems->AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
273 break;
275 case SORT_METHOD_DURATION:
276 case SORT_METHOD_VIDEO_RUNTIME:
278 dir->m_listItems->AddSortMethod(SortByTime, 180, LABEL_MASKS(labelMask, "%D", labelMask, "%D"));
279 break;
281 case SORT_METHOD_VIDEO_RATING:
282 case SORT_METHOD_SONG_RATING:
284 dir->m_listItems->AddSortMethod(SortByRating, 563, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
285 break;
287 case SORT_METHOD_YEAR:
289 dir->m_listItems->AddSortMethod(SortByYear, 562, LABEL_MASKS(labelMask, "%Y", labelMask, "%Y"));
290 break;
292 case SORT_METHOD_GENRE:
294 dir->m_listItems->AddSortMethod(SortByGenre, 515, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
295 break;
297 case SORT_METHOD_COUNTRY:
299 dir->m_listItems->AddSortMethod(SortByCountry, 574, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
300 break;
302 case SORT_METHOD_VIDEO_TITLE:
304 dir->m_listItems->AddSortMethod(SortByTitle, 369, LABEL_MASKS(labelMask, "%M", labelMask, "%M"));
305 break;
307 case SORT_METHOD_VIDEO_SORT_TITLE:
308 case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE:
310 dir->m_listItems->AddSortMethod(SortBySortTitle, 556, LABEL_MASKS(labelMask, "%M", labelMask, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
311 break;
313 case SORT_METHOD_VIDEO_ORIGINAL_TITLE:
314 case SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE:
316 dir->m_listItems->AddSortMethod(
317 SortByOriginalTitle, 20376, LABEL_MASKS(labelMask, "%M", labelMask, "%M"),
318 CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
319 CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
320 ? SortAttributeIgnoreArticle
321 : SortAttributeNone);
322 break;
324 case SORT_METHOD_MPAA_RATING:
326 dir->m_listItems->AddSortMethod(SortByMPAA, 20074, LABEL_MASKS(labelMask, "%O", labelMask, "%O"));
327 break;
329 case SORT_METHOD_STUDIO:
330 case SORT_METHOD_STUDIO_IGNORE_THE:
332 dir->m_listItems->AddSortMethod(SortByStudio, 572, LABEL_MASKS(labelMask, "%U", labelMask, "%U"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
333 break;
335 case SORT_METHOD_PROGRAM_COUNT:
337 dir->m_listItems->AddSortMethod(SortByProgramCount, 567, LABEL_MASKS(labelMask, "%C", labelMask, "%C"));
338 break;
340 case SORT_METHOD_UNSORTED:
342 dir->m_listItems->AddSortMethod(SortByNone, 571, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
343 break;
345 case SORT_METHOD_NONE:
347 dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
348 break;
350 case SORT_METHOD_DRIVE_TYPE:
352 dir->m_listItems->AddSortMethod(SortByDriveType, 564, LABEL_MASKS()); // Preformatted
353 break;
355 case SORT_METHOD_PLAYLIST_ORDER:
357 std::string strTrack=CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT);
358 dir->m_listItems->AddSortMethod(SortByPlaylistOrder, 559, LABEL_MASKS(strTrack, "%D"));
359 break;
361 case SORT_METHOD_EPISODE:
363 dir->m_listItems->AddSortMethod(SortByEpisodeNumber, 20359, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
364 break;
366 case SORT_METHOD_PRODUCTIONCODE:
368 //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
369 dir->m_listItems->AddSortMethod(SortByProductionCode, 20368, LABEL_MASKS(labelMask, "%P", labelMask, "%P"));
370 break;
372 case SORT_METHOD_LISTENERS:
374 dir->m_listItems->AddSortMethod(SortByListeners, 20455, LABEL_MASKS(labelMask, "%W"));
375 break;
377 case SORT_METHOD_DATEADDED:
379 dir->m_listItems->AddSortMethod(SortByDateAdded, 570, LABEL_MASKS(labelMask, "%a"));
380 break;
382 case SORT_METHOD_FULLPATH:
384 dir->m_listItems->AddSortMethod(SortByPath, 573, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
385 break;
387 case SORT_METHOD_LABEL_IGNORE_FOLDERS:
389 dir->m_listItems->AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
390 break;
392 case SORT_METHOD_LASTPLAYED:
394 dir->m_listItems->AddSortMethod(SortByLastPlayed, 568, LABEL_MASKS(labelMask, "%G"));
395 break;
397 case SORT_METHOD_PLAYCOUNT:
399 dir->m_listItems->AddSortMethod(SortByPlaycount, 567, LABEL_MASKS(labelMask, "%V", labelMask, "%V"));
400 break;
402 case SORT_METHOD_CHANNEL:
404 dir->m_listItems->AddSortMethod(SortByChannel, 19029, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
405 break;
408 default:
409 break;
413 bool CPluginDirectory::GetDirectory(const CURL& url, CFileItemList& items)
415 const std::string pathToUrl(url.Get());
416 bool success = StartScript(pathToUrl, false);
418 // append the items to the list
419 items.Assign(*m_listItems, true); // true to keep the current items
420 m_listItems->Clear();
421 return success;
424 bool CPluginDirectory::RunScriptWithParams(const std::string& strPath, bool resume)
426 CURL url(strPath);
427 if (url.GetHostName().empty()) // called with no script - should never happen
428 return false;
430 AddonPtr addon;
431 if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN,
432 OnlyEnabled::CHOICE_YES) &&
433 !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon,
434 InstallModalPrompt::CHOICE_YES))
436 CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
437 return false;
440 return ExecuteScript(addon, strPath, resume) >= 0;
443 void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem)
445 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
446 CPluginDirectory* dir = GetScriptFromHandle(handle);
447 if (!dir)
448 return;
450 dir->m_success = success;
451 *dir->m_fileResult = *resultItem;
453 // set the event to mark that we're done
454 dir->SetDone();
457 std::string CPluginDirectory::GetSetting(int handle, const std::string &strID)
459 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
460 CPluginDirectory* dir = GetScriptFromHandle(handle);
461 if (dir && dir->GetAddon())
462 return dir->GetAddon()->GetSetting(strID);
463 else
464 return "";
467 void CPluginDirectory::SetSetting(int handle, const std::string &strID, const std::string &value)
469 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
470 CPluginDirectory* dir = GetScriptFromHandle(handle);
471 if (dir && dir->GetAddon())
472 dir->GetAddon()->UpdateSetting(strID, value);
475 void CPluginDirectory::SetContent(int handle, const std::string &strContent)
477 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
478 CPluginDirectory* dir = GetScriptFromHandle(handle);
479 if (dir)
480 dir->m_listItems->SetContent(strContent);
483 void CPluginDirectory::SetProperty(int handle, const std::string &strProperty, const std::string &strValue)
485 std::unique_lock<CCriticalSection> lock(GetScriptsLock());
486 CPluginDirectory* dir = GetScriptFromHandle(handle);
487 if (!dir)
488 return;
489 if (strProperty == "fanart_image")
490 dir->m_listItems->SetArt("fanart", strValue);
491 else
492 dir->m_listItems->SetProperty(strProperty, strValue);
495 void CPluginDirectory::CancelDirectory()
497 m_cancelled = true;
500 float CPluginDirectory::GetProgress() const
502 if (m_totalItems > 0)
503 return (m_listItems->Size() * 100.0f) / m_totalItems;
504 return 0.0f;
507 bool CPluginDirectory::IsMediaLibraryScanningAllowed(const std::string& content, const std::string& strPath)
509 if (content.empty())
510 return false;
512 CURL url(strPath);
513 if (url.GetHostName().empty())
514 return false;
515 AddonPtr addon;
516 if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN,
517 OnlyEnabled::CHOICE_YES))
519 CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
520 return false;
522 CPluginSource* plugin = dynamic_cast<CPluginSource*>(addon.get());
523 if (!plugin)
524 return false;
526 auto& paths = plugin->MediaLibraryScanPaths();
527 if (paths.empty())
528 return false;
529 auto it = paths.find(content);
530 if (it == paths.end())
531 return false;
532 const std::string& path = url.GetFileName();
533 for (const auto& p : it->second)
534 if (p.empty() || p == "/" || URIUtils::PathHasParent(path, p))
535 return true;
536 return false;
539 bool CPluginDirectory::CheckExists(const std::string& content, const std::string& strPath)
541 if (!IsMediaLibraryScanningAllowed(content, strPath))
542 return false;
543 // call the plugin at specified path with option "kodi_action=check_exists"
544 // url exists if the plugin returns any fileitem with setResolvedUrl
545 CURL url(strPath);
546 url.SetOption("kodi_action", "check_exists");
547 CFileItem item;
548 return CPluginDirectory::GetPluginResult(url.Get(), item, false);