2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "threads/SystemClock.h"
24 #include "PluginDirectory.h"
25 #include "utils/URIUtils.h"
26 #include "addons/AddonManager.h"
27 #include "addons/AddonInstaller.h"
28 #include "addons/IAddon.h"
29 #include "interfaces/generic/ScriptInvocationManager.h"
30 #include "threads/SingleLock.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "dialogs/GUIDialogProgress.h"
33 #include "settings/Settings.h"
35 #include "video/VideoInfoTag.h"
36 #include "guilib/LocalizeStrings.h"
37 #include "utils/log.h"
38 #include "utils/TimeUtils.h"
39 #include "utils/StringUtils.h"
40 #include "ApplicationMessenger.h"
41 #include "Application.h"
44 using namespace XFILE;
46 using namespace ADDON;
48 map<int, CPluginDirectory *> CPluginDirectory::globalHandles;
49 int CPluginDirectory::handleCounter = 0;
50 CCriticalSection CPluginDirectory::m_handleLock;
52 CPluginDirectory::CPluginDirectory()
54 m_listItems = new CFileItemList;
55 m_fileResult = new CFileItem;
58 CPluginDirectory::~CPluginDirectory(void)
64 int CPluginDirectory::getNewHandle(CPluginDirectory *cp)
66 CSingleLock lock(m_handleLock);
67 int handle = ++handleCounter;
68 globalHandles[handle] = cp;
72 void CPluginDirectory::removeHandle(int handle)
74 CSingleLock lock(m_handleLock);
75 if (!globalHandles.erase(handle))
76 CLog::Log(LOGWARNING, "Attempt to erase invalid handle %i", handle);
79 CPluginDirectory *CPluginDirectory::dirFromHandle(int handle)
81 CSingleLock lock(m_handleLock);
82 map<int, CPluginDirectory *>::iterator i = globalHandles.find(handle);
83 if (i != globalHandles.end())
85 CLog::Log(LOGWARNING, "Attempt to use invalid handle %i", handle);
89 bool CPluginDirectory::StartScript(const CStdString& strPath, bool retrievingDir)
93 // try the plugin type first, and if not found, try an unknown type
94 if (!CAddonMgr::Get().GetAddon(url.GetHostName(), m_addon, ADDON_PLUGIN) &&
95 !CAddonMgr::Get().GetAddon(url.GetHostName(), m_addon, ADDON_UNKNOWN) &&
96 !CAddonInstaller::Get().PromptForInstall(url.GetHostName(), m_addon))
98 CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
103 CStdString options = url.GetOptions();
104 URIUtils::RemoveSlashAtEnd(options); // This MAY kill some scripts (eg though with a URL ending with a slash), but
105 // is needed for all others, as XBMC adds slashes to "folders"
106 url.SetOptions(""); // do this because we can then use the url to generate the basepath
107 // which is passed to the plugin (and represents the share)
109 CStdString basePath(url.Get());
110 // reset our wait event, and grab a new handle
111 m_fetchComplete.Reset();
112 int handle = getNewHandle(this);
114 // clear out our status variables
115 m_fileResult->Reset();
116 m_listItems->Clear();
117 m_listItems->SetPath(strPath);
118 m_listItems->SetLabel(m_addon->Name());
123 // setup our parameters to send the script
124 CStdString strHandle = StringUtils::Format("%i", handle);
126 argv.push_back(basePath);
127 argv.push_back(strHandle);
128 argv.push_back(options);
131 CLog::Log(LOGDEBUG, "%s - calling plugin %s('%s','%s','%s')", __FUNCTION__, m_addon->Name().c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str());
132 bool success = false;
133 CStdString file = m_addon->LibPath();
134 int id = CScriptInvocationManager::Get().Execute(file, m_addon, argv);
136 { // wait for our script to finish
137 CStdString scriptName = m_addon->Name();
138 success = WaitOnScriptResult(file, id, scriptName, retrievingDir);
141 CLog::Log(LOGERROR, "Unable to run plugin %s", m_addon->Name().c_str());
144 removeHandle(handle);
149 bool CPluginDirectory::GetPluginResult(const CStdString& strPath, CFileItem &resultItem)
152 CPluginDirectory* newDir = new CPluginDirectory();
154 bool success = newDir->StartScript(strPath, false);
157 { // update the play path and metadata, saving the old one as needed
158 if (!resultItem.HasProperty("original_listitem_url"))
159 resultItem.SetProperty("original_listitem_url", resultItem.GetPath());
160 resultItem.SetPath(newDir->m_fileResult->GetPath());
161 resultItem.SetMimeType(newDir->m_fileResult->GetMimeType());
162 resultItem.UpdateInfo(*newDir->m_fileResult);
163 if (newDir->m_fileResult->HasVideoInfoTag() && newDir->m_fileResult->GetVideoInfoTag()->m_resumePoint.IsSet())
164 resultItem.m_lStartOffset = STARTOFFSET_RESUME; // resume point set in the resume item, so force resume
171 bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems)
173 CSingleLock lock(m_handleLock);
174 CPluginDirectory *dir = dirFromHandle(handle);
178 CFileItemPtr pItem(new CFileItem(*item));
179 dir->m_listItems->Add(pItem);
180 dir->m_totalItems = totalItems;
182 return !dir->m_cancelled;
185 bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems)
187 CSingleLock lock(m_handleLock);
188 CPluginDirectory *dir = dirFromHandle(handle);
192 CFileItemList pItemList;
193 pItemList.Copy(*items);
194 dir->m_listItems->Append(pItemList);
195 dir->m_totalItems = totalItems;
197 return !dir->m_cancelled;
200 void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc)
202 CSingleLock lock(m_handleLock);
203 CPluginDirectory *dir = dirFromHandle(handle);
208 dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER);
210 dir->m_success = success;
211 dir->m_listItems->SetReplaceListing(replaceListing);
213 if (!dir->m_listItems->HasSortDetails())
214 dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%L", "%D"));
216 // set the event to mark that we're done
217 dir->m_fetchComplete.Set();
220 void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const CStdString &label2Mask)
222 CSingleLock lock(m_handleLock);
223 CPluginDirectory *dir = dirFromHandle(handle);
227 // TODO: Add all sort methods and fix which labels go on the right or left
230 case SORT_METHOD_LABEL:
231 case SORT_METHOD_LABEL_IGNORE_THE:
233 dir->m_listItems->AddSortMethod(SortByLabel, 551, LABEL_MASKS("%T", label2Mask), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
236 case SORT_METHOD_TITLE:
237 case SORT_METHOD_TITLE_IGNORE_THE:
239 dir->m_listItems->AddSortMethod(SortByTitle, 556, LABEL_MASKS("%T", label2Mask), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
242 case SORT_METHOD_ARTIST:
243 case SORT_METHOD_ARTIST_IGNORE_THE:
245 dir->m_listItems->AddSortMethod(SortByArtist, 557, LABEL_MASKS("%T", "%A"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
248 case SORT_METHOD_ALBUM:
249 case SORT_METHOD_ALBUM_IGNORE_THE:
251 dir->m_listItems->AddSortMethod(SortByAlbum, 558, LABEL_MASKS("%T", "%B"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
254 case SORT_METHOD_DATE:
256 dir->m_listItems->AddSortMethod(SortByDate, 552, LABEL_MASKS("%T", "%J"));
259 case SORT_METHOD_BITRATE:
261 dir->m_listItems->AddSortMethod(SortByBitrate, 623, LABEL_MASKS("%T", "%X"));
264 case SORT_METHOD_SIZE:
266 dir->m_listItems->AddSortMethod(SortBySize, 553, LABEL_MASKS("%T", "%I"));
269 case SORT_METHOD_FILE:
271 dir->m_listItems->AddSortMethod(SortByFile, 561, LABEL_MASKS("%T", label2Mask));
274 case SORT_METHOD_TRACKNUM:
276 dir->m_listItems->AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS("[%N. ]%T", label2Mask));
279 case SORT_METHOD_DURATION:
280 case SORT_METHOD_VIDEO_RUNTIME:
282 dir->m_listItems->AddSortMethod(SortByTime, 180, LABEL_MASKS("%T", "%D"));
285 case SORT_METHOD_VIDEO_RATING:
286 case SORT_METHOD_SONG_RATING:
288 dir->m_listItems->AddSortMethod(SortByRating, 563, LABEL_MASKS("%T", "%R"));
291 case SORT_METHOD_YEAR:
293 dir->m_listItems->AddSortMethod(SortByYear, 562, LABEL_MASKS("%T", "%Y"));
296 case SORT_METHOD_GENRE:
298 dir->m_listItems->AddSortMethod(SortByGenre, 515, LABEL_MASKS("%T", "%G"));
301 case SORT_METHOD_COUNTRY:
303 dir->m_listItems->AddSortMethod(SortByCountry, 574, LABEL_MASKS("%T", "%G"));
306 case SORT_METHOD_VIDEO_TITLE:
308 dir->m_listItems->AddSortMethod(SortByTitle, 369, LABEL_MASKS("%T", label2Mask));
311 case SORT_METHOD_VIDEO_SORT_TITLE:
312 case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE:
314 dir->m_listItems->AddSortMethod(SortBySortTitle, 556, LABEL_MASKS("%T", label2Mask), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
317 case SORT_METHOD_MPAA_RATING:
319 dir->m_listItems->AddSortMethod(SortByMPAA, 20074, LABEL_MASKS("%T", "%O"));
322 case SORT_METHOD_STUDIO:
323 case SORT_METHOD_STUDIO_IGNORE_THE:
325 dir->m_listItems->AddSortMethod(SortByStudio, 572, LABEL_MASKS("%T", "%U"), CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone);
328 case SORT_METHOD_PROGRAM_COUNT:
330 dir->m_listItems->AddSortMethod(SortByProgramCount, 567, LABEL_MASKS("%T", "%C"));
333 case SORT_METHOD_UNSORTED:
335 dir->m_listItems->AddSortMethod(SortByNone, 571, LABEL_MASKS("%T", label2Mask));
338 case SORT_METHOD_NONE:
340 dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%T", label2Mask));
343 case SORT_METHOD_DRIVE_TYPE:
345 dir->m_listItems->AddSortMethod(SortByDriveType, 564, LABEL_MASKS()); // Preformatted
348 case SORT_METHOD_PLAYLIST_ORDER:
350 CStdString strTrackLeft=CSettings::Get().GetString("musicfiles.trackformat");
351 CStdString strTrackRight=CSettings::Get().GetString("musicfiles.trackformatright");
353 dir->m_listItems->AddSortMethod(SortByPlaylistOrder, 559, LABEL_MASKS(strTrackLeft, strTrackRight));
356 case SORT_METHOD_EPISODE:
358 dir->m_listItems->AddSortMethod(SortByEpisodeNumber, 20359, LABEL_MASKS("%E. %T","%R"));
361 case SORT_METHOD_PRODUCTIONCODE:
363 //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
364 dir->m_listItems->AddSortMethod(SortByProductionCode, 20368, LABEL_MASKS("%H. %T","%P", "%H. %T","%P"));
367 case SORT_METHOD_LISTENERS:
369 dir->m_listItems->AddSortMethod(SortByListeners, 20455, LABEL_MASKS("%T","%W"));
372 case SORT_METHOD_DATEADDED:
374 dir->m_listItems->AddSortMethod(SortByDateAdded, 570, LABEL_MASKS("%T", "%a"));
377 case SORT_METHOD_FULLPATH:
379 dir->m_listItems->AddSortMethod(SortByPath, 573, LABEL_MASKS("%T", label2Mask));
382 case SORT_METHOD_LABEL_IGNORE_FOLDERS:
384 dir->m_listItems->AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS("%T", label2Mask));
387 case SORT_METHOD_LASTPLAYED:
389 dir->m_listItems->AddSortMethod(SortByLastPlayed, 568, LABEL_MASKS("%T", "%G"));
392 case SORT_METHOD_PLAYCOUNT:
394 dir->m_listItems->AddSortMethod(SortByPlaycount, 567, LABEL_MASKS("%T", "%V"));
397 case SORT_METHOD_CHANNEL:
399 dir->m_listItems->AddSortMethod(SortByChannel, 19029, LABEL_MASKS("%T", label2Mask));
408 bool CPluginDirectory::GetDirectory(const CStdString& strPath, CFileItemList& items)
412 bool success = StartScript(strPath, true);
414 // append the items to the list
415 items.Assign(*m_listItems, true); // true to keep the current items
416 m_listItems->Clear();
420 bool CPluginDirectory::RunScriptWithParams(const CStdString& strPath)
423 if (url.GetHostName().empty()) // called with no script - should never happen
427 if (!CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN) && !CAddonInstaller::Get().PromptForInstall(url.GetHostName(), addon))
429 CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
434 CStdString options = url.GetOptions();
435 URIUtils::RemoveSlashAtEnd(options); // This MAY kill some scripts (eg though with a URL ending with a slash), but
436 // is needed for all others, as XBMC adds slashes to "folders"
437 url.SetOptions(""); // do this because we can then use the url to generate the basepath
438 // which is passed to the plugin (and represents the share)
440 CStdString basePath(url.Get());
442 // setup our parameters to send the script
443 CStdString strHandle = StringUtils::Format("%i", -1);
445 argv.push_back(basePath);
446 argv.push_back(strHandle);
447 argv.push_back(options);
450 CLog::Log(LOGDEBUG, "%s - calling plugin %s('%s','%s','%s')", __FUNCTION__, addon->Name().c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str());
451 if (CScriptInvocationManager::Get().Execute(addon->LibPath(), addon, argv) >= 0)
454 CLog::Log(LOGERROR, "Unable to run plugin %s", addon->Name().c_str());
459 bool CPluginDirectory::WaitOnScriptResult(const CStdString &scriptPath, int scriptId, const CStdString &scriptName, bool retrievingDir)
461 const unsigned int timeBeforeProgressBar = 1500;
462 const unsigned int timeToKillScript = 1000;
464 unsigned int startTime = XbmcThreads::SystemClockMillis();
465 CGUIDialogProgress *progressBar = NULL;
466 bool cancelled = false;
467 bool inMainAppThread = g_application.IsCurrentThread();
469 CLog::Log(LOGDEBUG, "%s - waiting on the %s (id=%d) plugin...", __FUNCTION__, scriptName.c_str(), scriptId);
473 CSingleExit ex(g_graphicsContext);
474 // check if the python script is finished
475 if (m_fetchComplete.WaitMSec(20))
476 { // python has returned
477 CLog::Log(LOGDEBUG, "%s- plugin returned %s", __FUNCTION__, m_success ? "successfully" : "failure");
481 // check our script is still running
482 if (!CScriptInvocationManager::Get().IsRunning(scriptId))
483 { // check whether we exited normally
484 if (!m_fetchComplete.WaitMSec(0))
485 { // python didn't return correctly
486 CLog::Log(LOGDEBUG, " %s - plugin exited prematurely - terminating", __FUNCTION__);
492 // check whether we should pop up the progress dialog
493 if (!retrievingDir && !progressBar && XbmcThreads::SystemClockMillis() - startTime > timeBeforeProgressBar)
494 { // loading takes more then 1.5 secs, show a progress dialog
495 progressBar = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
497 // if script has shown progressbar don't override it
498 if (progressBar && progressBar->IsActive())
500 startTime = XbmcThreads::SystemClockMillis();
506 progressBar->SetHeading(scriptName);
507 progressBar->SetLine(0, retrievingDir ? 1040 : 10214);
508 progressBar->SetLine(1, "");
509 progressBar->SetLine(2, "");
510 progressBar->ShowProgressBar(retrievingDir);
511 progressBar->StartModal();
516 { // update the progress bar and check for user cancel
517 progressBar->Progress();
518 if (progressBar->IsCanceled())
519 { // user has cancelled our process - cancel our process
523 else // if the progressBar exists and we call StartModal or Progress we get the
524 // ProcessRenderLoop call anyway.
526 g_windowManager.ProcessRenderLoop();
528 if (!cancelled && m_cancelled)
531 startTime = XbmcThreads::SystemClockMillis();
533 if ((cancelled && XbmcThreads::SystemClockMillis() - startTime > timeToKillScript) || g_application.m_bStop)
534 { // cancel our script
535 if (scriptId != -1 && CScriptInvocationManager::Get().IsRunning(scriptId))
537 CLog::Log(LOGDEBUG, "%s- cancelling plugin %s (id=%d)", __FUNCTION__, scriptName.c_str(), scriptId);
538 CScriptInvocationManager::Get().Stop(scriptId);
545 CApplicationMessenger::Get().Close(progressBar, false, false);
547 return !cancelled && m_success;
550 void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem)
552 CSingleLock lock(m_handleLock);
553 CPluginDirectory *dir = dirFromHandle(handle);
557 dir->m_success = success;
558 *dir->m_fileResult = *resultItem;
560 // set the event to mark that we're done
561 dir->m_fetchComplete.Set();
564 CStdString CPluginDirectory::GetSetting(int handle, const CStdString &strID)
566 CSingleLock lock(m_handleLock);
567 CPluginDirectory *dir = dirFromHandle(handle);
568 if(dir && dir->m_addon)
569 return dir->m_addon->GetSetting(strID);
574 void CPluginDirectory::SetSetting(int handle, const CStdString &strID, const CStdString &value)
576 CSingleLock lock(m_handleLock);
577 CPluginDirectory *dir = dirFromHandle(handle);
578 if(dir && dir->m_addon)
579 dir->m_addon->UpdateSetting(strID, value);
582 void CPluginDirectory::SetContent(int handle, const CStdString &strContent)
584 CSingleLock lock(m_handleLock);
585 CPluginDirectory *dir = dirFromHandle(handle);
587 dir->m_listItems->SetContent(strContent);
590 void CPluginDirectory::SetProperty(int handle, const CStdString &strProperty, const CStdString &strValue)
592 CSingleLock lock(m_handleLock);
593 CPluginDirectory *dir = dirFromHandle(handle);
596 if (strProperty == "fanart_image")
597 dir->m_listItems->SetArt("fanart", strValue);
599 dir->m_listItems->SetProperty(strProperty, strValue);
602 void CPluginDirectory::CancelDirectory()
607 float CPluginDirectory::GetProgress() const
609 if (m_totalItems > 0)
610 return (m_listItems->Size() * 100.0f) / m_totalItems;