2 * Copyright (C) 2005-2012 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"
30 #include "interfaces/python/XBPython.h"
32 #include "threads/SingleLock.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "dialogs/GUIDialogProgress.h"
35 #include "settings/GUISettings.h"
37 #include "video/VideoInfoTag.h"
38 #include "guilib/LocalizeStrings.h"
39 #include "utils/log.h"
40 #include "utils/TimeUtils.h"
41 #include "ApplicationMessenger.h"
42 #include "Application.h"
45 using namespace XFILE;
47 using namespace ADDON;
49 map<int, CPluginDirectory *> CPluginDirectory::globalHandles;
50 int CPluginDirectory::handleCounter = 0;
51 CCriticalSection CPluginDirectory::m_handleLock;
53 CPluginDirectory::CPluginDirectory()
55 m_listItems = new CFileItemList;
56 m_fileResult = new CFileItem;
59 CPluginDirectory::~CPluginDirectory(void)
65 int CPluginDirectory::getNewHandle(CPluginDirectory *cp)
67 CSingleLock lock(m_handleLock);
68 int handle = ++handleCounter;
69 globalHandles[handle] = cp;
73 void CPluginDirectory::removeHandle(int handle)
75 CSingleLock lock(m_handleLock);
76 if (!globalHandles.erase(handle))
77 CLog::Log(LOGWARNING, "Attempt to erase invalid handle %i", handle);
80 CPluginDirectory *CPluginDirectory::dirFromHandle(int handle)
82 CSingleLock lock(m_handleLock);
83 map<int, CPluginDirectory *>::iterator i = globalHandles.find(handle);
84 if (i != globalHandles.end())
86 CLog::Log(LOGWARNING, "Attempt to use invalid handle %i", handle);
90 bool CPluginDirectory::StartScript(const CStdString& strPath, bool retrievingDir)
94 if (!CAddonMgr::Get().GetAddon(url.GetHostName(), m_addon, ADDON_PLUGIN) && !CAddonInstaller::Get().PromptForInstall(url.GetHostName(), m_addon))
96 CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
101 CStdString options = url.GetOptions();
102 URIUtils::RemoveSlashAtEnd(options); // This MAY kill some scripts (eg though with a URL ending with a slash), but
103 // is needed for all others, as XBMC adds slashes to "folders"
104 url.SetOptions(""); // do this because we can then use the url to generate the basepath
105 // which is passed to the plugin (and represents the share)
107 CStdString basePath(url.Get());
108 // reset our wait event, and grab a new handle
109 m_fetchComplete.Reset();
110 int handle = getNewHandle(this);
112 // clear out our status variables
113 m_fileResult->Reset();
114 m_listItems->Clear();
115 m_listItems->SetPath(strPath);
116 m_listItems->SetLabel(m_addon->Name());
121 // setup our parameters to send the script
122 CStdString strHandle;
123 strHandle.Format("%i", handle);
124 vector<CStdString> argv;
125 argv.push_back(basePath);
126 argv.push_back(strHandle);
127 argv.push_back(options);
130 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());
131 bool success = false;
133 CStdString file = m_addon->LibPath();
134 int id = g_pythonParser.evalFile(file, argv,m_addon);
136 { // wait for our script to finish
137 CStdString scriptName = m_addon->Name();
138 success = WaitOnScriptResult(file, id, scriptName, retrievingDir);
142 CLog::Log(LOGERROR, "Unable to run plugin %s", m_addon->Name().c_str());
145 removeHandle(handle);
150 bool CPluginDirectory::GetPluginResult(const CStdString& strPath, CFileItem &resultItem)
153 CPluginDirectory* newDir = new CPluginDirectory();
155 bool success = newDir->StartScript(strPath, false);
158 { // update the play path and metadata, saving the old one as needed
159 if (!resultItem.HasProperty("original_listitem_url"))
160 resultItem.SetProperty("original_listitem_url", resultItem.GetPath());
161 resultItem.SetPath(newDir->m_fileResult->GetPath());
162 resultItem.SetMimeType(newDir->m_fileResult->GetMimeType(false));
163 resultItem.UpdateInfo(*newDir->m_fileResult);
164 if (newDir->m_fileResult->HasVideoInfoTag() && newDir->m_fileResult->GetVideoInfoTag()->m_resumePoint.IsSet())
165 resultItem.m_lStartOffset = STARTOFFSET_RESUME; // resume point set in the resume item, so force resume
172 bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems)
174 CSingleLock lock(m_handleLock);
175 CPluginDirectory *dir = dirFromHandle(handle);
179 CFileItemPtr pItem(new CFileItem(*item));
180 dir->m_listItems->Add(pItem);
181 dir->m_totalItems = totalItems;
183 return !dir->m_cancelled;
186 bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems)
188 CSingleLock lock(m_handleLock);
189 CPluginDirectory *dir = dirFromHandle(handle);
193 CFileItemList pItemList;
194 pItemList.Copy(*items);
195 dir->m_listItems->Append(pItemList);
196 dir->m_totalItems = totalItems;
198 return !dir->m_cancelled;
201 void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc)
203 CSingleLock lock(m_handleLock);
204 CPluginDirectory *dir = dirFromHandle(handle);
209 dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER);
211 dir->m_success = success;
212 dir->m_listItems->SetReplaceListing(replaceListing);
214 if (!dir->m_listItems->HasSortDetails())
215 dir->m_listItems->AddSortMethod(SORT_METHOD_NONE, 552, LABEL_MASKS("%L", "%D"));
217 // set the event to mark that we're done
218 dir->m_fetchComplete.Set();
221 void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const CStdString &label2Mask)
223 CSingleLock lock(m_handleLock);
224 CPluginDirectory *dir = dirFromHandle(handle);
228 // TODO: Add all sort methods and fix which labels go on the right or left
231 case SORT_METHOD_LABEL:
232 case SORT_METHOD_LABEL_IGNORE_THE:
234 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
235 dir->m_listItems->AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551, LABEL_MASKS("%T", label2Mask));
237 dir->m_listItems->AddSortMethod(SORT_METHOD_LABEL, 551, LABEL_MASKS("%T", label2Mask));
240 case SORT_METHOD_TITLE:
241 case SORT_METHOD_TITLE_IGNORE_THE:
243 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
244 dir->m_listItems->AddSortMethod(SORT_METHOD_TITLE_IGNORE_THE, 556, LABEL_MASKS("%T", label2Mask));
246 dir->m_listItems->AddSortMethod(SORT_METHOD_TITLE, 556, LABEL_MASKS("%T", label2Mask));
249 case SORT_METHOD_ARTIST:
250 case SORT_METHOD_ARTIST_IGNORE_THE:
252 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
253 dir->m_listItems->AddSortMethod(SORT_METHOD_ARTIST_IGNORE_THE, 557, LABEL_MASKS("%T", "%A"));
255 dir->m_listItems->AddSortMethod(SORT_METHOD_ARTIST, 557, LABEL_MASKS("%T", "%A"));
258 case SORT_METHOD_ALBUM:
259 case SORT_METHOD_ALBUM_IGNORE_THE:
261 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
262 dir->m_listItems->AddSortMethod(SORT_METHOD_ALBUM_IGNORE_THE, 558, LABEL_MASKS("%T", "%B"));
264 dir->m_listItems->AddSortMethod(SORT_METHOD_ALBUM, 558, LABEL_MASKS("%T", "%B"));
267 case SORT_METHOD_DATE:
269 dir->m_listItems->AddSortMethod(SORT_METHOD_DATE, 552, LABEL_MASKS("%T", "%J"));
272 case SORT_METHOD_BITRATE:
274 dir->m_listItems->AddSortMethod(SORT_METHOD_BITRATE, 623, LABEL_MASKS("%T", "%X"));
277 case SORT_METHOD_SIZE:
279 dir->m_listItems->AddSortMethod(SORT_METHOD_SIZE, 553, LABEL_MASKS("%T", "%I"));
282 case SORT_METHOD_FILE:
284 dir->m_listItems->AddSortMethod(SORT_METHOD_FILE, 561, LABEL_MASKS("%T", label2Mask));
287 case SORT_METHOD_TRACKNUM:
289 dir->m_listItems->AddSortMethod(SORT_METHOD_TRACKNUM, 554, LABEL_MASKS("[%N. ]%T", label2Mask));
292 case SORT_METHOD_DURATION:
294 dir->m_listItems->AddSortMethod(SORT_METHOD_DURATION, 180, LABEL_MASKS("%T", "%D"));
297 case SORT_METHOD_VIDEO_RATING:
299 dir->m_listItems->AddSortMethod(SORT_METHOD_VIDEO_RATING, 563, LABEL_MASKS("%T", "%R"));
302 case SORT_METHOD_YEAR:
304 dir->m_listItems->AddSortMethod(SORT_METHOD_YEAR, 562, LABEL_MASKS("%T", "%Y"));
307 case SORT_METHOD_SONG_RATING:
309 dir->m_listItems->AddSortMethod(SORT_METHOD_SONG_RATING, 563, LABEL_MASKS("%T", "%R"));
312 case SORT_METHOD_GENRE:
314 dir->m_listItems->AddSortMethod(SORT_METHOD_GENRE, 515, LABEL_MASKS("%T", "%G"));
317 case SORT_METHOD_COUNTRY:
319 dir->m_listItems->AddSortMethod(SORT_METHOD_COUNTRY, 574, LABEL_MASKS("%T", "%G"));
322 case SORT_METHOD_VIDEO_TITLE:
324 dir->m_listItems->AddSortMethod(SORT_METHOD_VIDEO_TITLE, 369, LABEL_MASKS("%T", label2Mask));
327 case SORT_METHOD_VIDEO_SORT_TITLE:
328 case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE:
330 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
331 dir->m_listItems->AddSortMethod(SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE, 556, LABEL_MASKS("%T", label2Mask));
333 dir->m_listItems->AddSortMethod(SORT_METHOD_VIDEO_SORT_TITLE, 556, LABEL_MASKS("%T", label2Mask));
336 case SORT_METHOD_MPAA_RATING:
338 dir->m_listItems->AddSortMethod(SORT_METHOD_MPAA_RATING, 20074, LABEL_MASKS("%T", "%O"));
341 case SORT_METHOD_VIDEO_RUNTIME:
343 dir->m_listItems->AddSortMethod(SORT_METHOD_VIDEO_RUNTIME, 180, LABEL_MASKS("%T", "%D"));
346 case SORT_METHOD_STUDIO:
347 case SORT_METHOD_STUDIO_IGNORE_THE:
349 if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
350 dir->m_listItems->AddSortMethod(SORT_METHOD_STUDIO_IGNORE_THE, 572, LABEL_MASKS("%T", "%U"));
352 dir->m_listItems->AddSortMethod(SORT_METHOD_STUDIO, 572, LABEL_MASKS("%T", "%U"));
355 case SORT_METHOD_PROGRAM_COUNT:
357 dir->m_listItems->AddSortMethod(SORT_METHOD_PROGRAM_COUNT, 567, LABEL_MASKS("%T", "%C"));
360 case SORT_METHOD_UNSORTED:
362 dir->m_listItems->AddSortMethod(SORT_METHOD_UNSORTED, 571, LABEL_MASKS("%T", label2Mask));
365 case SORT_METHOD_NONE:
367 dir->m_listItems->AddSortMethod(SORT_METHOD_NONE, 552, LABEL_MASKS("%T", label2Mask));
370 case SORT_METHOD_DRIVE_TYPE:
372 dir->m_listItems->AddSortMethod(SORT_METHOD_DRIVE_TYPE, 564, LABEL_MASKS()); // Preformatted
375 case SORT_METHOD_PLAYLIST_ORDER:
377 CStdString strTrackLeft=g_guiSettings.GetString("musicfiles.trackformat");
378 CStdString strTrackRight=g_guiSettings.GetString("musicfiles.trackformatright");
380 dir->m_listItems->AddSortMethod(SORT_METHOD_PLAYLIST_ORDER, 559, LABEL_MASKS(strTrackLeft, strTrackRight));
383 case SORT_METHOD_EPISODE:
385 dir->m_listItems->AddSortMethod(SORT_METHOD_EPISODE,20359,LABEL_MASKS("%E. %T","%R"));
388 case SORT_METHOD_PRODUCTIONCODE:
390 //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
391 dir->m_listItems->AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%H. %T","%P", "%H. %T","%P"));
394 case SORT_METHOD_LISTENERS:
396 dir->m_listItems->AddSortMethod(SORT_METHOD_LISTENERS,20455,LABEL_MASKS("%T","%W"));
405 bool CPluginDirectory::GetDirectory(const CStdString& strPath, CFileItemList& items)
409 bool success = StartScript(strPath, true);
411 // append the items to the list
412 items.Assign(*m_listItems, true); // true to keep the current items
413 m_listItems->Clear();
417 bool CPluginDirectory::RunScriptWithParams(const CStdString& strPath)
420 if (url.GetHostName().IsEmpty()) // called with no script - should never happen
424 if (!CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_PLUGIN) && !CAddonInstaller::Get().PromptForInstall(url.GetHostName(), addon))
426 CLog::Log(LOGERROR, "Unable to find plugin %s", url.GetHostName().c_str());
431 CStdString options = url.GetOptions();
432 URIUtils::RemoveSlashAtEnd(options); // This MAY kill some scripts (eg though with a URL ending with a slash), but
433 // is needed for all others, as XBMC adds slashes to "folders"
434 url.SetOptions(""); // do this because we can then use the url to generate the basepath
435 // which is passed to the plugin (and represents the share)
437 CStdString basePath(url.Get());
439 // setup our parameters to send the script
440 CStdString strHandle;
441 strHandle.Format("%i", -1);
442 vector<CStdString> argv;
443 argv.push_back(basePath);
444 argv.push_back(strHandle);
445 argv.push_back(options);
449 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());
450 if (g_pythonParser.evalFile(addon->LibPath(), argv,addon) >= 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
483 if (!g_pythonParser.isRunning(scriptId))
485 { // check whether we exited normally
486 if (!m_fetchComplete.WaitMSec(0))
487 { // python didn't return correctly
488 CLog::Log(LOGDEBUG, " %s - plugin exited prematurely - terminating", __FUNCTION__);
494 // check whether we should pop up the progress dialog
495 if (!retrievingDir && !progressBar && XbmcThreads::SystemClockMillis() - startTime > timeBeforeProgressBar)
496 { // loading takes more then 1.5 secs, show a progress dialog
497 progressBar = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
499 // if script has shown progressbar don't override it
500 if (progressBar && progressBar->IsActive())
502 startTime = XbmcThreads::SystemClockMillis();
508 progressBar->SetHeading(scriptName);
509 progressBar->SetLine(0, retrievingDir ? 1040 : 10214);
510 progressBar->SetLine(1, "");
511 progressBar->SetLine(2, "");
512 progressBar->ShowProgressBar(retrievingDir);
513 progressBar->StartModal();
518 { // update the progress bar and check for user cancel
519 progressBar->Progress();
520 if (progressBar->IsCanceled())
521 { // user has cancelled our process - cancel our process
525 else // if the progressBar exists and we call StartModal or Progress we get the
526 // ProcessRenderLoop call anyway.
528 g_windowManager.ProcessRenderLoop();
530 if (!cancelled && m_cancelled)
533 startTime = XbmcThreads::SystemClockMillis();
535 if (cancelled && XbmcThreads::SystemClockMillis() - startTime > timeToKillScript)
536 { // cancel our script
538 if (scriptId != -1 && g_pythonParser.isRunning(scriptId))
540 CLog::Log(LOGDEBUG, "%s- cancelling plugin %s (id=%d)", __FUNCTION__, scriptName.c_str(), scriptId);
541 g_pythonParser.stopScript(scriptId);
549 CApplicationMessenger::Get().Close(progressBar, false, false);
551 return !cancelled && m_success;
554 void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem)
556 CSingleLock lock(m_handleLock);
557 CPluginDirectory *dir = dirFromHandle(handle);
561 dir->m_success = success;
562 *dir->m_fileResult = *resultItem;
564 // set the event to mark that we're done
565 dir->m_fetchComplete.Set();
568 CStdString CPluginDirectory::GetSetting(int handle, const CStdString &strID)
570 CSingleLock lock(m_handleLock);
571 CPluginDirectory *dir = dirFromHandle(handle);
572 if(dir && dir->m_addon)
573 return dir->m_addon->GetSetting(strID);
578 void CPluginDirectory::SetSetting(int handle, const CStdString &strID, const CStdString &value)
580 CSingleLock lock(m_handleLock);
581 CPluginDirectory *dir = dirFromHandle(handle);
582 if(dir && dir->m_addon)
583 dir->m_addon->UpdateSetting(strID, value);
586 void CPluginDirectory::SetContent(int handle, const CStdString &strContent)
588 CSingleLock lock(m_handleLock);
589 CPluginDirectory *dir = dirFromHandle(handle);
591 dir->m_listItems->SetContent(strContent);
594 void CPluginDirectory::SetProperty(int handle, const CStdString &strProperty, const CStdString &strValue)
596 CSingleLock lock(m_handleLock);
597 CPluginDirectory *dir = dirFromHandle(handle);
600 if (strProperty == "fanart_image")
601 dir->m_listItems->SetArt("fanart", strValue);
603 dir->m_listItems->SetProperty(strProperty, strValue);
606 void CPluginDirectory::CancelDirectory()
611 float CPluginDirectory::GetProgress() const
613 if (m_totalItems > 0)
614 return (m_listItems->Size() * 100.0f) / m_totalItems;