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