fixed: Some minor label setting changes, and added a check for scripts that terminate...
[xbmc:xbmc-antiquated.git] / xbmc / FileSystem / PluginDirectory.cpp
1 /*\r
2  *      Copyright (C) 2005-2007 Team XboxMediaCenter\r
3  *      http://www.xboxmediacenter.com\r
4  *\r
5  *  This Program is free software; you can redistribute it and/or modify\r
6  *  it under the terms of the GNU General Public License as published by\r
7  *  the Free Software Foundation; either version 2, or (at your option)\r
8  *  any later version.\r
9  *\r
10  *  This Program is distributed in the hope that it will be useful,\r
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
13  *  GNU General Public License for more details.\r
14  *\r
15  *  You should have received a copy of the GNU General Public License\r
16  *  along with GNU Make; see the file COPYING.  If not, write to\r
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\r
18  *  http://www.gnu.org/copyleft/gpl.html\r
19  *\r
20  */\r
21 #include "stdafx.h"\r
22 #include "PluginDirectory.h"\r
23 #include "util.h"\r
24 #include "lib/libPython/XBPython.h"\r
25  \r
26 using namespace DIRECTORY;\r
27 \r
28 vector<CPluginDirectory *> CPluginDirectory::globalHandles;\r
29 \r
30 CPluginDirectory::CPluginDirectory(void)\r
31 {\r
32   m_directoryFetched = CreateEvent(NULL, false, false, NULL);\r
33 }\r
34 \r
35 CPluginDirectory::~CPluginDirectory(void)\r
36 {\r
37   CloseHandle(m_directoryFetched);\r
38 }\r
39 \r
40 int CPluginDirectory::getNewHandle(CPluginDirectory *cp)\r
41 {\r
42   int handle = (int)globalHandles.size();\r
43   globalHandles.push_back(cp);\r
44   return handle;\r
45 }\r
46 \r
47 void CPluginDirectory::removeHandle(int handle)\r
48 {\r
49   if (handle > 0 && handle < (int)globalHandles.size())\r
50     globalHandles.erase(globalHandles.begin() + handle);\r
51 }\r
52 \r
53 bool CPluginDirectory::AddItem(int handle, const CFileItem *item)\r
54 {\r
55   if (handle < 0 || handle >= (int)globalHandles.size())\r
56   {\r
57     CLog::Log(LOGERROR, __FUNCTION__" called with an invalid handle.");\r
58     return false;\r
59   }\r
60   CPluginDirectory *dir = globalHandles[handle];\r
61   CFileItem *pItem = new CFileItem(*item);\r
62   dir->m_listItems.Add(pItem);\r
63 \r
64   return !dir->m_cancelled;\r
65 }\r
66 \r
67 void CPluginDirectory::EndOfDirectory(int handle, bool success)\r
68 {\r
69   if (handle < 0 || handle >= (int)globalHandles.size())\r
70   {\r
71     CLog::Log(LOGERROR, __FUNCTION__" called with an invalid handle.");\r
72     return;\r
73   }\r
74   CPluginDirectory *dir = globalHandles[handle];\r
75   dir->m_success = success;\r
76 \r
77   // set the event to mark that we're done\r
78   SetEvent(dir->m_directoryFetched);\r
79 }\r
80 \r
81 void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod)\r
82 {\r
83   if (handle < 0 || handle >= (int)globalHandles.size())\r
84   {\r
85     CLog::Log(LOGERROR, __FUNCTION__" called with an invalid handle.");\r
86     return;\r
87   }\r
88 \r
89   CPluginDirectory *dir = globalHandles[handle];\r
90 \r
91   // TODO: Add all sort methods and fix which labels go on the right or left\r
92   switch(sortMethod)\r
93   {\r
94     case SORT_METHOD_LABEL:\r
95     case SORT_METHOD_LABEL_IGNORE_THE:\r
96       {\r
97         if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))\r
98           dir->m_listItems.AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551, LABEL_MASKS("%T", "%D"));\r
99         else\r
100           dir->m_listItems.AddSortMethod(SORT_METHOD_LABEL, 551, LABEL_MASKS("%T", "%D"));\r
101         break;\r
102       }\r
103     case SORT_METHOD_TITLE:\r
104     case SORT_METHOD_TITLE_IGNORE_THE:\r
105       {\r
106         if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))\r
107           dir->m_listItems.AddSortMethod(SORT_METHOD_TITLE_IGNORE_THE, 556, LABEL_MASKS("%T", "%D"));\r
108         else\r
109           dir->m_listItems.AddSortMethod(SORT_METHOD_TITLE, 556, LABEL_MASKS("%T", "%D"));\r
110         break;\r
111       }\r
112     case SORT_METHOD_ARTIST:\r
113     case SORT_METHOD_ARTIST_IGNORE_THE:\r
114       {\r
115         if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))\r
116           dir->m_listItems.AddSortMethod(SORT_METHOD_ARTIST_IGNORE_THE, 557, LABEL_MASKS("%T", "%A"));\r
117         else\r
118           dir->m_listItems.AddSortMethod(SORT_METHOD_ARTIST, 557, LABEL_MASKS("%T", "%A"));\r
119         break;\r
120       }\r
121     case SORT_METHOD_ALBUM:\r
122     case SORT_METHOD_ALBUM_IGNORE_THE:\r
123       {\r
124         if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))\r
125           dir->m_listItems.AddSortMethod(SORT_METHOD_ALBUM_IGNORE_THE, 558, LABEL_MASKS("%T", "%B"));\r
126         else\r
127           dir->m_listItems.AddSortMethod(SORT_METHOD_ALBUM, 558, LABEL_MASKS("%T", "%B"));\r
128         break;\r
129       }\r
130     case SORT_METHOD_DATE:\r
131       {\r
132         dir->m_listItems.AddSortMethod(SORT_METHOD_DATE, 552, LABEL_MASKS("%T", "%J"));\r
133         break;\r
134       }\r
135     case SORT_METHOD_SIZE:\r
136       {\r
137         dir->m_listItems.AddSortMethod(SORT_METHOD_SIZE, 553, LABEL_MASKS("%T", "%I"));\r
138         break;\r
139       }\r
140     case SORT_METHOD_FILE:\r
141       {\r
142         dir->m_listItems.AddSortMethod(SORT_METHOD_FILE, 561, LABEL_MASKS("%T", "%D"));\r
143         break;\r
144       }\r
145     case SORT_METHOD_TRACKNUM:\r
146       {\r
147         dir->m_listItems.AddSortMethod(SORT_METHOD_TRACKNUM, 554, LABEL_MASKS("[%N. ]%T", "%D"));\r
148         break;\r
149       }\r
150     case SORT_METHOD_DURATION:\r
151       {\r
152         dir->m_listItems.AddSortMethod(SORT_METHOD_DURATION, 555, LABEL_MASKS("%T", "%D"));\r
153         break;\r
154       }\r
155     case SORT_METHOD_VIDEO_RATING:\r
156       {\r
157         dir->m_listItems.AddSortMethod(SORT_METHOD_VIDEO_RATING, 563, LABEL_MASKS("%T", "%R"));\r
158         break;\r
159       }\r
160     case SORT_METHOD_VIDEO_YEAR:\r
161       {\r
162         dir->m_listItems.AddSortMethod(SORT_METHOD_VIDEO_YEAR, 345, LABEL_MASKS("%T", "%Y"));\r
163         break;\r
164       }\r
165     case SORT_METHOD_SONG_RATING:\r
166       {\r
167         dir->m_listItems.AddSortMethod(SORT_METHOD_SONG_RATING, 563, LABEL_MASKS("%T", "%R"));\r
168         break;\r
169       }\r
170     case SORT_METHOD_GENRE:\r
171       {\r
172         dir->m_listItems.AddSortMethod(SORT_METHOD_GENRE, 515, LABEL_MASKS("%T", "%G"));\r
173         break;\r
174       }\r
175     case SORT_METHOD_VIDEO_TITLE:\r
176       {\r
177         dir->m_listItems.AddSortMethod(SORT_METHOD_VIDEO_TITLE, 369, LABEL_MASKS("%T", "%D"));\r
178         break;\r
179       }\r
180     case SORT_METHOD_MPAA_RATING:\r
181       {\r
182         dir->m_listItems.AddSortMethod(SORT_METHOD_MPAA_RATING, 563, LABEL_MASKS("%T", "%O"));\r
183         break;\r
184       }\r
185   }\r
186 }\r
187 \r
188 bool CPluginDirectory::GetDirectory(const CStdString& strPath, CFileItemList& items)\r
189 {\r
190   CURL url(strPath);\r
191   if (url.GetFileName().IsEmpty())\r
192   { // called with no script - should never happen\r
193     return GetPluginsDirectory(url.GetHostName(), items);\r
194   }\r
195 \r
196   CStdString fileName;\r
197   CUtil::AddFileToFolder(url.GetFileName(), "default.py", fileName);\r
198 \r
199   // path is Q:\plugins\<path from here>\r
200   CStdString pathToScript = "Q:\\plugins\\";\r
201   CUtil::AddFileToFolder(pathToScript, url.GetHostName(), pathToScript);\r
202   CUtil::AddFileToFolder(pathToScript, fileName, pathToScript);\r
203   pathToScript.Replace("/", "\\");\r
204 \r
205   // base path\r
206   CStdString basePath = "plugin://";\r
207   CUtil::AddFileToFolder(basePath, url.GetHostName(), basePath);\r
208   CUtil::AddFileToFolder(basePath, url.GetFileName(), basePath);\r
209 \r
210   // options\r
211   CStdString options = url.GetOptions();\r
212   CUtil::RemoveSlashAtEnd(options); // This MAY kill some scripts (eg though with a URL ending with a slash), but\r
213                                     // is needed for all others, as XBMC adds slashes to "folders"\r
214 \r
215   // reset our wait event, and grab a new handle\r
216   ResetEvent(m_directoryFetched);\r
217   int handle = getNewHandle(this);\r
218 \r
219   // clear out our status variables\r
220   m_listItems.Clear();\r
221   m_listItems.m_strPath = strPath;\r
222   m_cancelled = false;\r
223   m_success = false;\r
224 \r
225   // setup our parameters to send the script\r
226   CStdString strHandle;\r
227   strHandle.Format("%i", handle);\r
228   const char *argv[3];\r
229   argv[0] = basePath.c_str();\r
230   argv[1] = strHandle.c_str();\r
231   argv[2] = options.c_str();\r
232 \r
233   // run the script\r
234   CLog::Log(LOGDEBUG, __FUNCTION__" - calling plugin %s('%s','%s','%s')", pathToScript.c_str(), argv[0], argv[1], argv[2]);\r
235   bool success = false;\r
236   if (g_pythonParser.evalFile(pathToScript.c_str(), 3, (const char**)argv) >= 0)\r
237   { // wait for our script to finish\r
238     CStdString scriptName = url.GetFileName();\r
239     CUtil::RemoveSlashAtEnd(scriptName);\r
240     success = WaitOnScriptResult(pathToScript, scriptName);\r
241   }\r
242   else\r
243     CLog::Log(LOGERROR, "Unable to run plugin %s", pathToScript.c_str());\r
244 \r
245   // free our handle\r
246   removeHandle(handle);\r
247 \r
248   // append the items to the list, and return true\r
249   items.AssignPointer(m_listItems);\r
250   m_listItems.ClearKeepPointer();\r
251   return success;\r
252 }\r
253 \r
254 bool CPluginDirectory::HasPlugins(const CStdString &type)\r
255 {\r
256   CStdString path = "Q:\\plugins\\";\r
257   CUtil::AddFileToFolder(path, type, path);\r
258   CFileItemList items;\r
259   if (CDirectory::GetDirectory(path, items, "/", false))\r
260   {\r
261     for (int i = 0; i < items.Size(); i++)\r
262     {\r
263       CFileItem *item = items[i];\r
264       if (item->m_bIsFolder && !item->IsParentFolder() && !item->m_bIsShareOrDrive)\r
265       {\r
266         CStdString defaultPY;\r
267         CUtil::AddFileToFolder(item->m_strPath, "default.py", defaultPY);\r
268         if (XFILE::CFile::Exists(defaultPY))\r
269           return true;\r
270       }\r
271     }\r
272   }\r
273   return false;\r
274 }\r
275 \r
276 bool CPluginDirectory::GetPluginsDirectory(const CStdString &type, CFileItemList &items)\r
277 {\r
278   // retrieve our folder\r
279   CStdString pluginsFolder = "Q:\\plugins";\r
280   CUtil::AddFileToFolder(pluginsFolder, type, pluginsFolder);\r
281 \r
282   if (!CDirectory::GetDirectory(pluginsFolder, items, "*.py", false))\r
283     return false;\r
284 \r
285   // flatten any folders - TODO: Assigning of thumbs\r
286   for (int i = 0; i < items.Size(); i++)\r
287   {\r
288     CFileItem *item = items[i];\r
289     item->m_strPath.Replace("Q:\\plugins\\", "plugin://");\r
290     item->m_strPath.Replace("\\", "/");\r
291   }\r
292   return true;\r
293 }\r
294 \r
295 bool CPluginDirectory::WaitOnScriptResult(const CStdString &scriptPath, const CStdString &scriptName)\r
296 {\r
297   const unsigned int timeBeforeProgressBar = 1500;\r
298   const unsigned int timeToKillScript = 1000;\r
299 \r
300   DWORD startTime = timeGetTime();\r
301   CGUIDialogProgress *progressBar = NULL;\r
302 \r
303   CLog::Log(LOGDEBUG, __FUNCTION__" - waiting on the %s plugin...", scriptName.c_str());\r
304   while (true)\r
305   {\r
306     // check if the python script is finished\r
307     if (WaitForSingleObject(m_directoryFetched, 20) == WAIT_OBJECT_0)\r
308     { // python has returned\r
309       CLog::Log(LOGDEBUG, __FUNCTION__" plugin returned %s", m_success ? "successfully" : "failure");\r
310       break;\r
311     }\r
312     // check our script is still running\r
313     int id = g_pythonParser.getScriptId(scriptPath.c_str());\r
314     if (id == -1)\r
315     { // nope - bail\r
316       CLog::Log(LOGDEBUG, __FUNCTION__" plugin exited prematurely - terminating");\r
317       m_success = false;\r
318       break;\r
319     }\r
320 \r
321     // check whether we should pop up the progress dialog\r
322     if (!progressBar && timeGetTime() - startTime > timeBeforeProgressBar)\r
323     { // loading takes more then 1.5 secs, show a progress dialog\r
324       progressBar = (CGUIDialogProgress *)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);\r
325       if (progressBar)\r
326       {\r
327         progressBar->SetHeading(scriptName);\r
328         progressBar->SetLine(0, 1040);\r
329         progressBar->SetLine(1, "");\r
330         progressBar->SetLine(2, "");\r
331         progressBar->StartModal();\r
332       }\r
333     }\r
334 \r
335     if (progressBar)\r
336     { // update the progress bar and check for user cancel\r
337       CStdString label;\r
338       label.Format(g_localizeStrings.Get(1041).c_str(), m_listItems.Size());\r
339       progressBar->SetLine(2, label);\r
340       progressBar->Progress();\r
341       if (progressBar->IsCanceled())\r
342       { // user has cancelled our process - cancel our process\r
343         if (!m_cancelled)\r
344         {\r
345           m_cancelled = true;\r
346           startTime = timeGetTime();\r
347         }\r
348         if (m_cancelled && timeGetTime() - startTime > timeToKillScript)\r
349         { // cancel our script\r
350           int id = g_pythonParser.getScriptId(scriptPath.c_str());\r
351           if (id != -1 && g_pythonParser.isRunning(id))\r
352           {\r
353             CLog::Log(LOGDEBUG, __FUNCTION__" cancelling plugin %s", scriptName.c_str());\r
354             g_pythonParser.stopScript(id);\r
355             break;\r
356           }\r
357         }\r
358       }\r
359     }\r
360   }\r
361   if (progressBar)\r
362     progressBar->Close();\r
363 \r
364   return !m_cancelled && m_success;\r
365 }\r