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 "PluginDirectory.h"
10 
11 #include "Application.h"
12 #include "FileItem.h"
13 #include "ServiceBroker.h"
14 #include "URL.h"
15 #include "addons/AddonInstaller.h"
16 #include "addons/AddonManager.h"
17 #include "addons/IAddon.h"
18 #include "addons/PluginSource.h"
19 #include "dialogs/GUIDialogBusy.h"
20 #include "dialogs/GUIDialogProgress.h"
21 #include "guilib/GUIComponent.h"
22 #include "guilib/GUIWindowManager.h"
23 #include "interfaces/generic/ScriptInvocationManager.h"
24 #include "messaging/ApplicationMessenger.h"
25 #include "settings/Settings.h"
26 #include "settings/SettingsComponent.h"
27 #include "threads/SingleLock.h"
28 #include "threads/SystemClock.h"
29 #include "utils/StringUtils.h"
30 #include "utils/URIUtils.h"
31 #include "utils/log.h"
32 #include "video/VideoInfoTag.h"
33 
34 using namespace XFILE;
35 using namespace ADDON;
36 using namespace KODI::MESSAGING;
37 
38 std::map<int, CPluginDirectory *> CPluginDirectory::globalHandles;
39 int CPluginDirectory::handleCounter = 0;
40 CCriticalSection CPluginDirectory::m_handleLock;
41 
CScriptObserver(int scriptId,CEvent & event)42 CPluginDirectory::CScriptObserver::CScriptObserver(int scriptId, CEvent &event) :
43   CThread("scriptobs"), m_scriptId(scriptId), m_event(event)
44 {
45   Create();
46 }
47 
Process()48 void CPluginDirectory::CScriptObserver::Process()
49 {
50   while (!m_bStop)
51   {
52     if (!CScriptInvocationManager::GetInstance().IsRunning(m_scriptId))
53     {
54       m_event.Set();
55       break;
56     }
57     CThread::Sleep(20);
58   }
59 }
60 
Abort()61 void CPluginDirectory::CScriptObserver::Abort()
62 {
63   // will wait until thread exits
64   StopThread();
65 }
66 
CPluginDirectory()67 CPluginDirectory::CPluginDirectory()
68   : m_fetchComplete(true)
69   , m_cancelled(false)
70 {
71   m_listItems = new CFileItemList;
72   m_fileResult = new CFileItem;
73 }
74 
~CPluginDirectory(void)75 CPluginDirectory::~CPluginDirectory(void)
76 {
77   delete m_listItems;
78   delete m_fileResult;
79 }
80 
getNewHandle(CPluginDirectory * cp)81 int CPluginDirectory::getNewHandle(CPluginDirectory *cp)
82 {
83   CSingleLock lock(m_handleLock);
84   int handle = ++handleCounter;
85   globalHandles[handle] = cp;
86   return handle;
87 }
88 
reuseHandle(int handle,CPluginDirectory * cp)89 void CPluginDirectory::reuseHandle(int handle, CPluginDirectory* cp)
90 {
91   CSingleLock lock(m_handleLock);
92   globalHandles[handle] = cp;
93 }
94 
removeHandle(int handle)95 void CPluginDirectory::removeHandle(int handle)
96 {
97   CSingleLock lock(m_handleLock);
98   if (!globalHandles.erase(handle))
99     CLog::Log(LOGWARNING, "Attempt to erase invalid handle %i", handle);
100 }
101 
dirFromHandle(int handle)102 CPluginDirectory *CPluginDirectory::dirFromHandle(int handle)
103 {
104   CSingleLock lock(m_handleLock);
105   std::map<int, CPluginDirectory *>::iterator i = globalHandles.find(handle);
106   if (i != globalHandles.end())
107     return i->second;
108   CLog::Log(LOGWARNING, "Attempt to use invalid handle %i", handle);
109   return NULL;
110 }
111 
StartScript(const std::string & strPath,bool retrievingDir,bool resume)112 bool CPluginDirectory::StartScript(const std::string& strPath, bool retrievingDir, bool resume)
113 {
114   CURL url(strPath);
115 
116   // try the plugin type first, and if not found, try an unknown type
117   if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), m_addon, ADDON_PLUGIN,
118                                               OnlyEnabled::YES) &&
119       !CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), m_addon, ADDON_UNKNOWN,
120                                               OnlyEnabled::YES) &&
121       !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), m_addon,
122                                                    InstallModalPrompt::PROMPT))
123   {
124     CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
125     return false;
126   }
127 
128   // get options
129   std::string options = url.GetOptions();
130   url.SetOptions(""); // do this because we can then use the url to generate the basepath
131                       // which is passed to the plugin (and represents the share)
132 
133   std::string basePath(url.Get());
134   // reset our wait event, and grab a new handle
135   m_fetchComplete.Reset();
136   int handle = CScriptInvocationManager::GetInstance().GetReusablePluginHandle(m_addon->LibPath());
137 
138   if (handle < 0)
139     handle = getNewHandle(this);
140   else
141     reuseHandle(handle, this);
142 
143   // clear out our status variables
144   m_fileResult->Reset();
145   m_listItems->Clear();
146   m_listItems->SetPath(strPath);
147   m_listItems->SetLabel(m_addon->Name());
148   m_cancelled = false;
149   m_success = false;
150   m_totalItems = 0;
151 
152   // setup our parameters to send the script
153   std::string strHandle = StringUtils::Format("%i", handle);
154   std::vector<std::string> argv;
155   argv.push_back(basePath);
156   argv.push_back(strHandle);
157   argv.push_back(options);
158 
159   std::string strResume = "resume:false";
160   if (resume)
161     strResume = "resume:true";
162   argv.push_back(strResume);
163 
164   // run the script
165   CLog::Log(LOGDEBUG, "%s - calling plugin %s('%s','%s','%s','%s')", __FUNCTION__, m_addon->Name().c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str(), argv[3].c_str());
166   bool success = false;
167   std::string file = m_addon->LibPath();
168   bool reuseLanguageInvoker = false;
169   if (m_addon->ExtraInfo().find("reuselanguageinvoker") != m_addon->ExtraInfo().end())
170     reuseLanguageInvoker = m_addon->ExtraInfo().at("reuselanguageinvoker") == "true";
171 
172   int id = CScriptInvocationManager::GetInstance().ExecuteAsync(file, m_addon, argv,
173                                                                 reuseLanguageInvoker, handle);
174   if (id >= 0)
175   { // wait for our script to finish
176     std::string scriptName = m_addon->Name();
177     success = WaitOnScriptResult(file, id, scriptName, retrievingDir);
178   }
179   else
180     CLog::Log(LOGERROR, "Unable to run plugin %s", m_addon->Name().c_str());
181 
182   // free our handle
183   removeHandle(handle);
184 
185   return success;
186 }
187 
GetPluginResult(const std::string & strPath,CFileItem & resultItem,bool resume)188 bool CPluginDirectory::GetPluginResult(const std::string& strPath, CFileItem &resultItem, bool resume)
189 {
190   CURL url(strPath);
191   CPluginDirectory newDir;
192 
193   bool success = newDir.StartScript(strPath, false, resume);
194 
195   if (success)
196   { // update the play path and metadata, saving the old one as needed
197     if (!resultItem.HasProperty("original_listitem_url"))
198       resultItem.SetProperty("original_listitem_url", resultItem.GetPath());
199     resultItem.SetDynPath(newDir.m_fileResult->GetPath());
200     resultItem.SetMimeType(newDir.m_fileResult->GetMimeType());
201     resultItem.SetContentLookup(newDir.m_fileResult->ContentLookup());
202     resultItem.MergeInfo(*newDir.m_fileResult);
203     if (newDir.m_fileResult->HasVideoInfoTag() && newDir.m_fileResult->GetVideoInfoTag()->GetResumePoint().IsSet())
204       resultItem.m_lStartOffset = STARTOFFSET_RESUME; // resume point set in the resume item, so force resume
205   }
206 
207   return success;
208 }
209 
AddItem(int handle,const CFileItem * item,int totalItems)210 bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems)
211 {
212   CSingleLock lock(m_handleLock);
213   CPluginDirectory *dir = dirFromHandle(handle);
214   if (!dir)
215     return false;
216 
217   CFileItemPtr pItem(new CFileItem(*item));
218   dir->m_listItems->Add(pItem);
219   dir->m_totalItems = totalItems;
220 
221   return !dir->m_cancelled;
222 }
223 
AddItems(int handle,const CFileItemList * items,int totalItems)224 bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems)
225 {
226   CSingleLock lock(m_handleLock);
227   CPluginDirectory *dir = dirFromHandle(handle);
228   if (!dir)
229     return false;
230 
231   CFileItemList pItemList;
232   pItemList.Copy(*items);
233   dir->m_listItems->Append(pItemList);
234   dir->m_totalItems = totalItems;
235 
236   return !dir->m_cancelled;
237 }
238 
EndOfDirectory(int handle,bool success,bool replaceListing,bool cacheToDisc)239 void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc)
240 {
241   CSingleLock lock(m_handleLock);
242   CPluginDirectory *dir = dirFromHandle(handle);
243   if (!dir)
244     return;
245 
246   // set cache to disc
247   dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER);
248 
249   dir->m_success = success;
250   dir->m_listItems->SetReplaceListing(replaceListing);
251 
252   if (!dir->m_listItems->HasSortDetails())
253     dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%L", "%D"));
254 
255   // set the event to mark that we're done
256   dir->m_fetchComplete.Set();
257 }
258 
AddSortMethod(int handle,SORT_METHOD sortMethod,const std::string & labelMask,const std::string & label2Mask)259 void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const std::string &labelMask, const std::string &label2Mask)
260 {
261   CSingleLock lock(m_handleLock);
262   CPluginDirectory *dir = dirFromHandle(handle);
263   if (!dir)
264     return;
265 
266   //! @todo Add all sort methods and fix which labels go on the right or left
267   switch(sortMethod)
268   {
269     case SORT_METHOD_LABEL:
270     case SORT_METHOD_LABEL_IGNORE_THE:
271       {
272         dir->m_listItems->AddSortMethod(SortByLabel, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
273         break;
274       }
275     case SORT_METHOD_TITLE:
276     case SORT_METHOD_TITLE_IGNORE_THE:
277       {
278         dir->m_listItems->AddSortMethod(SortByTitle, 556, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
279         break;
280       }
281     case SORT_METHOD_ARTIST:
282     case SORT_METHOD_ARTIST_IGNORE_THE:
283       {
284         dir->m_listItems->AddSortMethod(SortByArtist, 557, LABEL_MASKS(labelMask, "%A", labelMask, "%A"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
285         break;
286       }
287     case SORT_METHOD_ALBUM:
288     case SORT_METHOD_ALBUM_IGNORE_THE:
289       {
290         dir->m_listItems->AddSortMethod(SortByAlbum, 558, LABEL_MASKS(labelMask, "%B", labelMask, "%B"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
291         break;
292       }
293     case SORT_METHOD_DATE:
294       {
295         dir->m_listItems->AddSortMethod(SortByDate, 552, LABEL_MASKS(labelMask, "%J", labelMask, "%J"));
296         break;
297       }
298     case SORT_METHOD_BITRATE:
299       {
300         dir->m_listItems->AddSortMethod(SortByBitrate, 623, LABEL_MASKS(labelMask, "%X", labelMask, "%X"));
301         break;
302       }
303     case SORT_METHOD_SIZE:
304       {
305         dir->m_listItems->AddSortMethod(SortBySize, 553, LABEL_MASKS(labelMask, "%I", labelMask, "%I"));
306         break;
307       }
308     case SORT_METHOD_FILE:
309       {
310         dir->m_listItems->AddSortMethod(SortByFile, 561, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
311         break;
312       }
313     case SORT_METHOD_TRACKNUM:
314       {
315         dir->m_listItems->AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
316         break;
317       }
318     case SORT_METHOD_DURATION:
319     case SORT_METHOD_VIDEO_RUNTIME:
320       {
321         dir->m_listItems->AddSortMethod(SortByTime, 180, LABEL_MASKS(labelMask, "%D", labelMask, "%D"));
322         break;
323       }
324     case SORT_METHOD_VIDEO_RATING:
325     case SORT_METHOD_SONG_RATING:
326       {
327         dir->m_listItems->AddSortMethod(SortByRating, 563, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
328         break;
329       }
330     case SORT_METHOD_YEAR:
331       {
332         dir->m_listItems->AddSortMethod(SortByYear, 562, LABEL_MASKS(labelMask, "%Y", labelMask, "%Y"));
333         break;
334       }
335     case SORT_METHOD_GENRE:
336       {
337         dir->m_listItems->AddSortMethod(SortByGenre, 515, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
338         break;
339       }
340     case SORT_METHOD_COUNTRY:
341       {
342         dir->m_listItems->AddSortMethod(SortByCountry, 574, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
343         break;
344       }
345     case SORT_METHOD_VIDEO_TITLE:
346       {
347         dir->m_listItems->AddSortMethod(SortByTitle, 369, LABEL_MASKS(labelMask, "%M", labelMask, "%M"));
348         break;
349       }
350     case SORT_METHOD_VIDEO_SORT_TITLE:
351     case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE:
352       {
353         dir->m_listItems->AddSortMethod(SortBySortTitle, 556, LABEL_MASKS(labelMask, "%M", labelMask, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
354         break;
355       }
356     case SORT_METHOD_MPAA_RATING:
357       {
358         dir->m_listItems->AddSortMethod(SortByMPAA, 20074, LABEL_MASKS(labelMask, "%O", labelMask, "%O"));
359         break;
360       }
361     case SORT_METHOD_STUDIO:
362     case SORT_METHOD_STUDIO_IGNORE_THE:
363       {
364         dir->m_listItems->AddSortMethod(SortByStudio, 572, LABEL_MASKS(labelMask, "%U", labelMask, "%U"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
365         break;
366       }
367     case SORT_METHOD_PROGRAM_COUNT:
368       {
369         dir->m_listItems->AddSortMethod(SortByProgramCount, 567, LABEL_MASKS(labelMask, "%C", labelMask, "%C"));
370         break;
371       }
372     case SORT_METHOD_UNSORTED:
373       {
374         dir->m_listItems->AddSortMethod(SortByNone, 571, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
375         break;
376       }
377     case SORT_METHOD_NONE:
378       {
379         dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
380         break;
381       }
382     case SORT_METHOD_DRIVE_TYPE:
383       {
384         dir->m_listItems->AddSortMethod(SortByDriveType, 564, LABEL_MASKS()); // Preformatted
385         break;
386       }
387     case SORT_METHOD_PLAYLIST_ORDER:
388       {
389         std::string strTrack=CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT);
390         dir->m_listItems->AddSortMethod(SortByPlaylistOrder, 559, LABEL_MASKS(strTrack, "%D"));
391         break;
392       }
393     case SORT_METHOD_EPISODE:
394       {
395         dir->m_listItems->AddSortMethod(SortByEpisodeNumber, 20359, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
396         break;
397       }
398     case SORT_METHOD_PRODUCTIONCODE:
399       {
400         //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
401         dir->m_listItems->AddSortMethod(SortByProductionCode, 20368, LABEL_MASKS(labelMask, "%P", labelMask, "%P"));
402         break;
403       }
404     case SORT_METHOD_LISTENERS:
405       {
406        dir->m_listItems->AddSortMethod(SortByListeners, 20455, LABEL_MASKS(labelMask, "%W"));
407        break;
408       }
409     case SORT_METHOD_DATEADDED:
410       {
411         dir->m_listItems->AddSortMethod(SortByDateAdded, 570, LABEL_MASKS(labelMask, "%a"));
412         break;
413       }
414     case SORT_METHOD_FULLPATH:
415       {
416         dir->m_listItems->AddSortMethod(SortByPath, 573, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
417         break;
418       }
419     case SORT_METHOD_LABEL_IGNORE_FOLDERS:
420       {
421         dir->m_listItems->AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
422         break;
423       }
424     case SORT_METHOD_LASTPLAYED:
425       {
426         dir->m_listItems->AddSortMethod(SortByLastPlayed, 568, LABEL_MASKS(labelMask, "%G"));
427         break;
428       }
429     case SORT_METHOD_PLAYCOUNT:
430       {
431         dir->m_listItems->AddSortMethod(SortByPlaycount, 567, LABEL_MASKS(labelMask, "%V", labelMask, "%V"));
432         break;
433       }
434     case SORT_METHOD_CHANNEL:
435       {
436         dir->m_listItems->AddSortMethod(SortByChannel, 19029, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
437         break;
438       }
439 
440     default:
441       break;
442   }
443 }
444 
GetDirectory(const CURL & url,CFileItemList & items)445 bool CPluginDirectory::GetDirectory(const CURL& url, CFileItemList& items)
446 {
447   const std::string pathToUrl(url.Get());
448   bool success = StartScript(pathToUrl, true, false);
449 
450   // append the items to the list
451   items.Assign(*m_listItems, true); // true to keep the current items
452   m_listItems->Clear();
453   return success;
454 }
455 
RunScriptWithParams(const std::string & strPath,bool resume)456 bool CPluginDirectory::RunScriptWithParams(const std::string& strPath, bool resume)
457 {
458   CURL url(strPath);
459   if (url.GetHostName().empty()) // called with no script - should never happen
460     return false;
461 
462   AddonPtr addon;
463   if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN,
464                                               OnlyEnabled::YES) &&
465       !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon,
466                                                    InstallModalPrompt::PROMPT))
467   {
468     CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
469     return false;
470   }
471 
472   // options
473   std::string options = url.GetOptions();
474   url.SetOptions(""); // do this because we can then use the url to generate the basepath
475                       // which is passed to the plugin (and represents the share)
476 
477   std::string basePath(url.Get());
478 
479   // setup our parameters to send the script
480   std::string strHandle = StringUtils::Format("%i", -1);
481   std::vector<std::string> argv;
482   argv.push_back(basePath);
483   argv.push_back(strHandle);
484   argv.push_back(options);
485 
486   std::string strResume = "resume:false";
487   if (resume)
488     strResume = "resume:true";
489   argv.push_back(strResume);
490 
491   // run the script
492   CLog::Log(LOGDEBUG, "%s - calling plugin %s('%s','%s','%s','%s')", __FUNCTION__, addon->Name().c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str(), argv[3].c_str());
493   if (CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon, argv) >= 0)
494     return true;
495   else
496     CLog::Log(LOGERROR, "Unable to run plugin %s", addon->Name().c_str());
497 
498   return false;
499 }
500 
WaitOnScriptResult(const std::string & scriptPath,int scriptId,const std::string & scriptName,bool retrievingDir)501 bool CPluginDirectory::WaitOnScriptResult(const std::string &scriptPath, int scriptId, const std::string &scriptName, bool retrievingDir)
502 {
503   // CPluginDirectory::GetDirectory can be called from the main and other threads.
504   // If called form the main thread, we need to bring up the BusyDialog in order to
505   // keep the render loop alive
506   if (g_application.IsCurrentThread())
507   {
508     if (!m_fetchComplete.WaitMSec(20))
509     {
510       CScriptObserver scriptObs(scriptId, m_fetchComplete);
511 
512       CGUIDialogProgress* progress = nullptr;
513       CGUIWindowManager& wm = CServiceBroker::GetGUI()->GetWindowManager();
514       if (wm.IsModalDialogTopmost(WINDOW_DIALOG_PROGRESS))
515         progress = wm.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
516 
517       if (progress != nullptr)
518       {
519         if (!progress->WaitOnEvent(m_fetchComplete))
520           m_cancelled = true;
521       }
522       else if (!CGUIDialogBusy::WaitOnEvent(m_fetchComplete, 200))
523         m_cancelled = true;
524 
525       scriptObs.Abort();
526     }
527   }
528   else
529   {
530     // Wait for directory fetch to complete, end, or be cancelled
531     while (!m_cancelled
532         && CScriptInvocationManager::GetInstance().IsRunning(scriptId)
533         && !m_fetchComplete.WaitMSec(20));
534 
535     // Give the script 30 seconds to exit before we attempt to stop it
536     XbmcThreads::EndTime timer(30000);
537     while (!timer.IsTimePast()
538           && CScriptInvocationManager::GetInstance().IsRunning(scriptId)
539           && !m_fetchComplete.WaitMSec(20));
540   }
541 
542   if (m_cancelled)
543   { // cancel our script
544     if (scriptId != -1 && CScriptInvocationManager::GetInstance().IsRunning(scriptId))
545     {
546       CLog::Log(LOGDEBUG, "%s- cancelling plugin %s (id=%d)", __FUNCTION__, scriptName.c_str(), scriptId);
547       CScriptInvocationManager::GetInstance().Stop(scriptId);
548     }
549   }
550 
551   return !m_cancelled && m_success;
552 }
553 
SetResolvedUrl(int handle,bool success,const CFileItem * resultItem)554 void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem)
555 {
556   CSingleLock lock(m_handleLock);
557   CPluginDirectory *dir = dirFromHandle(handle);
558   if (!dir)
559     return;
560 
561   dir->m_success = success;
562   *dir->m_fileResult = *resultItem;
563 
564   // set the event to mark that we're done
565   dir->m_fetchComplete.Set();
566 }
567 
GetSetting(int handle,const std::string & strID)568 std::string CPluginDirectory::GetSetting(int handle, const std::string &strID)
569 {
570   CSingleLock lock(m_handleLock);
571   CPluginDirectory *dir = dirFromHandle(handle);
572   if(dir && dir->m_addon)
573     return dir->m_addon->GetSetting(strID);
574   else
575     return "";
576 }
577 
SetSetting(int handle,const std::string & strID,const std::string & value)578 void CPluginDirectory::SetSetting(int handle, const std::string &strID, const std::string &value)
579 {
580   CSingleLock lock(m_handleLock);
581   CPluginDirectory *dir = dirFromHandle(handle);
582   if(dir && dir->m_addon)
583     dir->m_addon->UpdateSetting(strID, value);
584 }
585 
SetContent(int handle,const std::string & strContent)586 void CPluginDirectory::SetContent(int handle, const std::string &strContent)
587 {
588   CSingleLock lock(m_handleLock);
589   CPluginDirectory *dir = dirFromHandle(handle);
590   if (dir)
591     dir->m_listItems->SetContent(strContent);
592 }
593 
SetProperty(int handle,const std::string & strProperty,const std::string & strValue)594 void CPluginDirectory::SetProperty(int handle, const std::string &strProperty, const std::string &strValue)
595 {
596   CSingleLock lock(m_handleLock);
597   CPluginDirectory *dir = dirFromHandle(handle);
598   if (!dir)
599     return;
600   if (strProperty == "fanart_image")
601     dir->m_listItems->SetArt("fanart", strValue);
602   else
603     dir->m_listItems->SetProperty(strProperty, strValue);
604 }
605 
CancelDirectory()606 void CPluginDirectory::CancelDirectory()
607 {
608   m_cancelled = true;
609   m_fetchComplete.Set();
610 }
611 
GetProgress() const612 float CPluginDirectory::GetProgress() const
613 {
614   if (m_totalItems > 0)
615     return (m_listItems->Size() * 100.0f) / m_totalItems;
616   return 0.0f;
617 }
618 
IsMediaLibraryScanningAllowed(const std::string & content,const std::string & strPath)619 bool CPluginDirectory::IsMediaLibraryScanningAllowed(const std::string& content, const std::string& strPath)
620 {
621   if (content.empty())
622     return false;
623 
624   CURL url(strPath);
625   if (url.GetHostName().empty())
626     return false;
627   AddonPtr addon;
628   if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN,
629                                               OnlyEnabled::YES))
630   {
631     CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
632     return false;
633   }
634   CPluginSource* plugin = dynamic_cast<CPluginSource*>(addon.get());
635   if (!plugin)
636     return false;
637 
638   auto& paths = plugin->MediaLibraryScanPaths();
639   if (paths.empty())
640     return false;
641   auto it = paths.find(content);
642   if (it == paths.end())
643     return false;
644   const std::string& path = url.GetFileName();
645   for (const auto& p : it->second)
646     if (p.empty() || p == "/" || URIUtils::PathHasParent(path, p))
647       return true;
648   return false;
649 }
650 
CheckExists(const std::string & content,const std::string & strPath)651 bool CPluginDirectory::CheckExists(const std::string& content, const std::string& strPath)
652 {
653   if (!IsMediaLibraryScanningAllowed(content, strPath))
654     return false;
655   // call the plugin at specified path with option "kodi_action=check_exists"
656   // url exists if the plugin returns any fileitem with setResolvedUrl
657   CURL url(strPath);
658   url.SetOption("kodi_action", "check_exists");
659   CFileItem item;
660   return CPluginDirectory::GetPluginResult(url.Get(), item, false);
661 }
662