strip added smb:// shares of their user/pass when adding, and instead store that...
[vuplus_xbmc] / xbmc / interfaces / python / XBPyThread.cpp
1 /*
2  *      Copyright (C) 2005-2009 Team XBMC
3  *      http://www.xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #if (defined HAVE_CONFIG_H) && (!defined WIN32)
23   #include "config.h"
24 #endif
25
26 // python.h should always be included first before any other includes
27 #include <Python.h>
28 #include <osdefs.h>
29
30 #include "system.h"
31 #include "filesystem/SpecialProtocol.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "dialogs/GUIDialogKaiToast.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "utils/log.h"
36 #include "threads/SingleLock.h"
37 #include "utils/URIUtils.h"
38 #include "addons/AddonManager.h"
39 #include "addons/Addon.h"
40
41 #include "XBPyThread.h"
42 #include "XBPython.h"
43
44 #include "xbmcmodule/pyutil.h"
45 #include "xbmcmodule/pythreadstate.h"
46 #include "utils/CharsetConverter.h"
47
48
49 #ifdef _WIN32
50 extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
51 #else
52 #define fopen_utf8 fopen
53 #endif
54
55 #define PY_PATH_SEP DELIM
56
57 extern "C"
58 {
59   int xbp_chdir(const char *dirname);
60   char* dll_getenv(const char* szKey);
61 }
62
63 XBPyThread::XBPyThread(XBPython *pExecuter, int id) : CThread("XBPyThread")
64 {
65   CLog::Log(LOGDEBUG,"new python thread created. id=%d", id);
66   m_pExecuter   = pExecuter;
67   m_threadState = NULL;
68   m_id          = id;
69   m_stopping    = false;
70   m_argv        = NULL;
71   m_source      = NULL;
72   m_argc        = 0;
73 }
74
75 XBPyThread::~XBPyThread()
76 {
77   stop();
78   g_pythonParser.PulseGlobalEvent();
79   CLog::Log(LOGDEBUG,"waiting for python thread %d to stop", m_id);
80   StopThread();
81   CLog::Log(LOGDEBUG,"python thread %d destructed", m_id);
82   delete [] m_source;
83   if (m_argv)
84   {
85     for (unsigned int i = 0; i < m_argc; i++)
86       delete [] m_argv[i];
87     delete [] m_argv;
88   }
89 }
90
91 void XBPyThread::setSource(const CStdString &src)
92 {
93 #ifdef TARGET_WINDOWS
94   CStdString strsrc = src;
95   g_charsetConverter.utf8ToSystem(strsrc);
96   m_source  = new char[strsrc.GetLength()+1];
97   strcpy(m_source, strsrc);
98 #else
99   m_source  = new char[src.GetLength()+1];
100   strcpy(m_source, src);
101 #endif
102 }
103
104 int XBPyThread::evalFile(const CStdString &src)
105 {
106   m_type    = 'F';
107   setSource(src);
108   Create();
109   return 0;
110 }
111
112 int XBPyThread::evalString(const CStdString &src)
113 {
114   m_type    = 'S';
115   setSource(src);
116   Create();
117   return 0;
118 }
119
120 int XBPyThread::setArgv(const std::vector<CStdString> &argv)
121 {
122   m_argc = argv.size();
123   m_argv = new char*[m_argc];
124   for(unsigned int i = 0; i < m_argc; i++)
125   {
126     m_argv[i] = new char[argv[i].GetLength()+1];
127     strcpy(m_argv[i], argv[i].c_str());
128   }
129   return 0;
130 }
131
132 void XBPyThread::Process()
133 {
134   CLog::Log(LOGDEBUG,"Python thread: start processing");
135
136   int m_Py_file_input = Py_file_input;
137
138   // get the global lock
139   PyEval_AcquireLock();
140   PyThreadState* state = Py_NewInterpreter();
141   if (!state)
142   {
143     PyEval_ReleaseLock();
144     CLog::Log(LOGERROR,"Python thread: FAILED to get thread state!");
145     return;
146   }
147   // swap in my thread state
148   PyThreadState_Swap(state);
149
150   m_pExecuter->InitializeInterpreter(addon);
151
152   CLog::Log(LOGDEBUG, "%s - The source file to load is %s", __FUNCTION__, m_source);
153
154   // get path from script file name and add python path's
155   // this is used for python so it will search modules from script path first
156   CStdString scriptDir;
157   URIUtils::GetDirectory(CSpecialProtocol::TranslatePath(m_source), scriptDir);
158   URIUtils::RemoveSlashAtEnd(scriptDir);
159   CStdString path = scriptDir;
160
161   // add on any addon modules the user has installed
162   ADDON::VECADDONS addons;
163   ADDON::CAddonMgr::Get().GetAddons(ADDON::ADDON_SCRIPT_MODULE, addons);
164   for (unsigned int i = 0; i < addons.size(); ++i)
165 #ifdef TARGET_WINDOWS
166   {
167     CStdString strTmp(CSpecialProtocol::TranslatePath(addons[i]->LibPath()));
168     g_charsetConverter.utf8ToSystem(strTmp);
169     path += PY_PATH_SEP + strTmp;
170   }
171 #else
172     path += PY_PATH_SEP + CSpecialProtocol::TranslatePath(addons[i]->LibPath());
173 #endif
174
175   // and add on whatever our default path is
176   path += PY_PATH_SEP;
177
178   // we want to use sys.path so it includes site-packages
179   // if this fails, default to using Py_GetPath
180   PyObject *sysMod(PyImport_ImportModule((char*)"sys")); // must call Py_DECREF when finished
181   PyObject *sysModDict(PyModule_GetDict(sysMod)); // borrowed ref, no need to delete
182   PyObject *pathObj(PyDict_GetItemString(sysModDict, "path")); // borrowed ref, no need to delete
183
184   if( pathObj && PyList_Check(pathObj) )
185   {
186     for( int i = 0; i < PyList_Size(pathObj); i++ )
187     {
188       PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
189       if( e && PyString_Check(e) )
190       {
191           path += PyString_AsString(e); // returns internal data, don't delete or modify
192           path += PY_PATH_SEP;
193       }
194     }
195   }
196   else
197   {
198     path += Py_GetPath();
199   }
200   Py_DECREF(sysMod); // release ref to sysMod
201
202   // set current directory and python's path.
203   if (m_argv != NULL)
204     PySys_SetArgv(m_argc, m_argv);
205
206   CLog::Log(LOGDEBUG, "%s - Setting the Python path to %s", __FUNCTION__, path.c_str());
207
208   PySys_SetPath((char *)path.c_str());
209
210   CLog::Log(LOGDEBUG, "%s - Entering source directory %s", __FUNCTION__, scriptDir.c_str());
211
212   PyObject* module = PyImport_AddModule((char*)"__main__");
213   PyObject* moduleDict = PyModule_GetDict(module);
214
215   // when we are done initing we store thread state so we can be aborted
216   PyThreadState_Swap(NULL);
217   PyEval_ReleaseLock();
218
219   // we need to check if we was asked to abort before we had inited
220   bool stopping = false;
221   { CSingleLock lock(m_pExecuter->m_critSection);
222     m_threadState = state;
223     stopping = m_stopping;
224   }
225
226   PyEval_AcquireLock();
227   PyThreadState_Swap(state);
228
229   if (!stopping)
230   {
231     if (m_type == 'F')
232     {
233       // run script from file
234       // We need to have python open the file because on Windows the DLL that python
235       //  is linked against may not be the DLL that xbmc is linked against so
236       //  passing a FILE* to python from an fopen has the potential to crash.
237       PyObject* file = PyFile_FromString((char *) CSpecialProtocol::TranslatePath(m_source).c_str(), (char*)"r");
238       FILE *fp = PyFile_AsFile(file);
239
240       if (fp)
241       {
242         PyObject *f = PyString_FromString(CSpecialProtocol::TranslatePath(m_source).c_str());
243         PyDict_SetItemString(moduleDict, "__file__", f);
244         if (addon.get() != NULL)
245         {
246           PyObject *pyaddonid = PyString_FromString(addon->ID().c_str());
247           PyDict_SetItemString(moduleDict, "__xbmcaddonid__", pyaddonid);
248
249           CStdString version = ADDON::GetXbmcApiVersionDependency(addon);
250           PyObject *pyxbmcapiversion = PyString_FromString(version.c_str());
251           PyDict_SetItemString(moduleDict, "__xbmcapiversion__", pyxbmcapiversion);
252
253           CLog::Log(LOGDEBUG,"Instantiating addon using automatically obtained id of \"%s\" dependent on version %s of the xbmc.python api",addon->ID().c_str(),version.c_str());
254         }
255         Py_DECREF(f);
256         PyRun_FileExFlags(fp, CSpecialProtocol::TranslatePath(m_source).c_str(), m_Py_file_input, moduleDict, moduleDict,1,NULL);
257       }
258       else
259         CLog::Log(LOGERROR, "%s not found!", m_source);
260     }
261     else
262     {
263       //run script
264       PyRun_String(m_source, m_Py_file_input, moduleDict, moduleDict);
265     }
266   }
267
268   if (!PyErr_Occurred())
269     CLog::Log(LOGINFO, "Scriptresult: Success");
270   else if (PyErr_ExceptionMatches(PyExc_SystemExit))
271     CLog::Log(LOGINFO, "Scriptresult: Aborted");
272   else
273   {
274     PyObject* exc_type;
275     PyObject* exc_value;
276     PyObject* exc_traceback;
277     PyObject* pystring;
278     pystring = NULL;
279
280     PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
281     if (exc_type == 0 && exc_value == 0 && exc_traceback == 0)
282     {
283       CLog::Log(LOGINFO, "Strange: No Python exception occured");
284     }
285     else
286     {
287       if (exc_type != NULL && (pystring = PyObject_Str(exc_type)) != NULL && (PyString_Check(pystring)))
288       {
289           PyObject *tracebackModule;
290
291           CLog::Log(LOGINFO, "-->Python script returned the following error<--");
292           CLog::Log(LOGERROR, "Error Type: %s", PyString_AsString(PyObject_Str(exc_type)));
293           if (PyObject_Str(exc_value))
294             CLog::Log(LOGERROR, "Error Contents: %s", PyString_AsString(PyObject_Str(exc_value)));
295
296           tracebackModule = PyImport_ImportModule((char*)"traceback");
297           if (tracebackModule != NULL)
298           {
299             PyObject *tbList, *emptyString, *strRetval;
300
301             tbList = PyObject_CallMethod(tracebackModule, (char*)"format_exception", (char*)"OOO", exc_type, exc_value == NULL ? Py_None : exc_value, exc_traceback == NULL ? Py_None : exc_traceback);
302             emptyString = PyString_FromString("");
303             strRetval = PyObject_CallMethod(emptyString, (char*)"join", (char*)"O", tbList);
304
305             CLog::Log(LOGERROR, "%s", PyString_AsString(strRetval));
306
307             Py_DECREF(tbList);
308             Py_DECREF(emptyString);
309             Py_DECREF(strRetval);
310             Py_DECREF(tracebackModule);
311           }
312           CLog::Log(LOGINFO, "-->End of Python script error report<--");
313       }
314       else
315       {
316         pystring = NULL;
317         CLog::Log(LOGINFO, "<unknown exception type>");
318       }
319
320       PYXBMC::PyXBMCGUILock();
321       CGUIDialogKaiToast *pDlgToast = (CGUIDialogKaiToast*)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
322       if (pDlgToast)
323       {
324         CStdString desc;
325         CStdString path;
326         CStdString script;
327         URIUtils::Split(m_source, path, script);
328         if (script.Equals("default.py"))
329         {
330           CStdString path2;
331           URIUtils::RemoveSlashAtEnd(path);
332           URIUtils::Split(path, path2, script);
333         }
334
335         desc.Format(g_localizeStrings.Get(2100), script);
336         pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(257), desc);
337       }
338       PYXBMC::PyXBMCGUIUnlock();
339     }
340
341     Py_XDECREF(exc_type);
342     Py_XDECREF(exc_value); // caller owns all 3
343     Py_XDECREF(exc_traceback); // already NULL'd out
344     Py_XDECREF(pystring);
345   }
346   
347   PyObject *m = PyImport_AddModule((char*)"xbmc");
348   if(!m || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
349     CLog::Log(LOGERROR, "Scriptresult: failed to set abortRequested");
350
351   // make sure all sub threads have finished
352   for(PyThreadState* s = state->interp->tstate_head, *old = NULL; s;)
353   {
354     if(s == state)
355     {
356       s = s->next;
357       continue;
358     }
359     if(old != s)
360     {
361       CLog::Log(LOGINFO, "Scriptresult: Waiting on thread %"PRIu64, (uint64_t)s->thread_id);
362       old = s;
363     }
364
365     CPyThreadState pyState;
366     Sleep(100);
367     pyState.Restore();
368
369     s = state->interp->tstate_head;
370   }
371
372   // pending calls must be cleared out
373   PyXBMC_ClearPendingCalls(state);
374
375   PyThreadState_Swap(NULL);
376   PyEval_ReleaseLock();
377
378   //set stopped event - this allows ::stop to run and kill remaining threads
379   //this event has to be fired without holding m_pExecuter->m_critSection
380   //before
381   //Also the GIL (PyEval_AcquireLock) must not be held
382   //if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!)
383   stoppedEvent.Set();
384
385   { CSingleLock lock(m_pExecuter->m_critSection);
386     m_threadState = NULL;
387   }
388
389   PyEval_AcquireLock();
390   PyThreadState_Swap(state);
391
392   m_pExecuter->DeInitializeInterpreter();
393
394   Py_EndInterpreter(state);
395   PyThreadState_Swap(NULL);
396
397   PyEval_ReleaseLock();
398 }
399
400 void XBPyThread::OnExit()
401 {
402   m_pExecuter->setDone(m_id);
403 }
404
405 void XBPyThread::OnException()
406 {
407   PyThreadState_Swap(NULL);
408   PyEval_ReleaseLock();
409
410   CSingleLock lock(m_pExecuter->m_critSection);
411   m_threadState = NULL;
412   CLog::Log(LOGERROR,"%s, abnormally terminating python thread", __FUNCTION__);
413   m_pExecuter->setDone(m_id);
414 }
415
416 bool XBPyThread::isStopping() {
417   return m_stopping;
418 }
419
420 void XBPyThread::stop()
421 {
422   CSingleLock lock(m_pExecuter->m_critSection);
423   if(m_stopping)
424     return;
425
426   m_stopping = true;
427
428   if (m_threadState)
429   {
430     PyEval_AcquireLock();
431     PyThreadState* old = PyThreadState_Swap((PyThreadState*)m_threadState);
432
433     //tell xbmc.Monitor to call onAbortRequested()
434     g_pythonParser.OnAbortRequested(addon->ID());
435
436     PyObject *m;
437     m = PyImport_AddModule((char*)"xbmc");
438     if(!m || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
439       CLog::Log(LOGERROR, "XBPyThread::stop - failed to set abortRequested");
440
441     PyThreadState_Swap(old);
442     PyEval_ReleaseLock();
443
444     if(!stoppedEvent.WaitMSec(5000))//let the script 5 secs for shut stuff down
445     {
446       CLog::Log(LOGERROR, "XBPyThread::stop - script didn't stop in proper time - lets kill it");
447     }
448     
449     //everything which didn't exit by now gets killed
450     PyEval_AcquireLock();
451     old = PyThreadState_Swap((PyThreadState*)m_threadState);    
452     for(PyThreadState* state = ((PyThreadState*)m_threadState)->interp->tstate_head; state; state = state->next)
453     {
454       Py_XDECREF(state->async_exc);
455       state->async_exc = PyExc_SystemExit;
456       Py_XINCREF(state->async_exc);
457     }
458
459     PyThreadState_Swap(old);
460     PyEval_ReleaseLock();
461   }
462 }