Merge remote branch 'mine/ext-python'
[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 "XBPythonDll.h"
32 #include "filesystem/SpecialProtocol.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "dialogs/GUIDialogKaiToast.h"
35 #include "guilib/LocalizeStrings.h"
36 #include "utils/log.h"
37 #include "threads/SingleLock.h"
38 #include "utils/URIUtils.h"
39 #include "addons/AddonManager.h"
40
41 #include "XBPyThread.h"
42 #include "XBPython.h"
43
44 #include "xbmcmodule/pyutil.h"
45 #include "xbmcmodule/pythreadstate.h"
46
47 #ifndef __GNUC__
48 #pragma code_seg("PY_TEXT")
49 #pragma data_seg("PY_DATA")
50 #pragma bss_seg("PY_BSS")
51 #pragma const_seg("PY_RDATA")
52 #endif
53
54 #ifdef _WIN32
55 extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
56 #else
57 #define fopen_utf8 fopen
58 #endif
59
60 #define PY_PATH_SEP DELIM
61
62 extern "C"
63 {
64   int xbp_chdir(const char *dirname);
65   char* dll_getenv(const char* szKey);
66 }
67
68 XBPyThread::XBPyThread(XBPython *pExecuter, int id)
69 {
70   CLog::Log(LOGDEBUG,"new python thread created. id=%d", id);
71   m_pExecuter   = pExecuter;
72   m_threadState = NULL;
73   m_id          = id;
74   m_stopping    = false;
75   m_argv        = NULL;
76   m_source      = NULL;
77   m_argc        = 0;
78 }
79
80 XBPyThread::~XBPyThread()
81 {
82   stop();
83   g_pythonParser.PulseGlobalEvent();
84   CLog::Log(LOGDEBUG,"waiting for python thread %d to stop", m_id);
85   StopThread();
86   CLog::Log(LOGDEBUG,"python thread %d destructed", m_id);
87   delete [] m_source;
88   if (m_argv)
89   {
90     for (unsigned int i = 0; i < m_argc; i++)
91       delete [] m_argv[i];
92     delete [] m_argv;
93   }
94 }
95
96 int XBPyThread::evalFile(const CStdString &src)
97 {
98   m_type    = 'F';
99   m_source  = new char[src.GetLength()+1];
100   strcpy(m_source, src);
101   Create();
102   return 0;
103 }
104
105 int XBPyThread::evalString(const CStdString &src)
106 {
107   m_type    = 'S';
108   m_source  = new char[src.GetLength()+1];
109   strcpy(m_source, src);
110   Create();
111   return 0;
112 }
113
114 int XBPyThread::setArgv(const std::vector<CStdString> &argv)
115 {
116   m_argc = argv.size();
117   m_argv = new char*[m_argc];
118   for(unsigned int i = 0; i < m_argc; i++)
119   {
120     m_argv[i] = new char[argv[i].GetLength()+1];
121     strcpy(m_argv[i], argv[i].c_str());
122   }
123   return 0;
124 }
125
126 void XBPyThread::OnStartup()
127 {
128   CThread::SetName("Python Thread");
129 }
130
131 void XBPyThread::Process()
132 {
133   CLog::Log(LOGDEBUG,"Python thread: start processing");
134
135   int m_Py_file_input = Py_file_input;
136
137   // get the global lock
138   PyEval_AcquireLock();
139   PyThreadState* state = Py_NewInterpreter();
140   if (!state)
141   {
142     PyEval_ReleaseLock();
143     CLog::Log(LOGERROR,"Python thread: FAILED to get thread state!");
144     return;
145   }
146   // swap in my thread state
147   PyThreadState_Swap(state);
148
149   m_pExecuter->InitializeInterpreter();
150
151   CLog::Log(LOGDEBUG, "%s - The source file to load is %s", __FUNCTION__, m_source);
152
153   // get path from script file name and add python path's
154   // this is used for python so it will search modules from script path first
155   CStdString scriptDir;
156   URIUtils::GetDirectory(_P(m_source), scriptDir);
157   URIUtils::RemoveSlashAtEnd(scriptDir);
158   CStdString path = scriptDir;
159
160   // add on any addon modules the user has installed
161   ADDON::VECADDONS addons;
162   ADDON::CAddonMgr::Get().GetAddons(ADDON::ADDON_SCRIPT_MODULE, addons);
163   for (unsigned int i = 0; i < addons.size(); ++i)
164     path += PY_PATH_SEP + _P(addons[i]->LibPath());
165
166   // and add on whatever our default path is
167   path += PY_PATH_SEP;
168
169 #if (defined USE_EXTERNAL_PYTHON)
170   {
171     // we want to use sys.path so it includes site-packages
172     // if this fails, default to using Py_GetPath
173     PyObject *sysMod(PyImport_ImportModule((char*)"sys")); // must call Py_DECREF when finished
174     PyObject *sysModDict(PyModule_GetDict(sysMod)); // borrowed ref, no need to delete
175     PyObject *pathObj(PyDict_GetItemString(sysModDict, "path")); // borrowed ref, no need to delete
176
177     if( pathObj && PyList_Check(pathObj) )
178     {
179       for( int i = 0; i < PyList_Size(pathObj); i++ )
180       {
181         PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
182         if( e && PyString_Check(e) )
183         {
184             path += PyString_AsString(e); // returns internal data, don't delete or modify
185             path += PY_PATH_SEP;
186         }
187       }
188     }
189     else
190     {
191       path += Py_GetPath();
192     }
193     Py_DECREF(sysMod); // release ref to sysMod
194   }
195 #else
196   path += Py_GetPath();
197 #endif
198
199   // set current directory and python's path.
200   if (m_argv != NULL)
201     PySys_SetArgv(m_argc, m_argv);
202
203   CLog::Log(LOGDEBUG, "%s - Setting the Python path to %s", __FUNCTION__, path.c_str());
204
205   PySys_SetPath((char *)path.c_str());
206
207   CLog::Log(LOGDEBUG, "%s - Entering source directory %s", __FUNCTION__, scriptDir.c_str());
208
209   PyObject* module = PyImport_AddModule((char*)"__main__");
210   PyObject* moduleDict = PyModule_GetDict(module);
211
212   // when we are done initing we store thread state so we can be aborted
213   PyThreadState_Swap(NULL);
214   PyEval_ReleaseLock();
215
216   // we need to check if we was asked to abort before we had inited
217   bool stopping = false;
218   { CSingleLock lock(m_pExecuter->m_critSection);
219     m_threadState = state;
220     stopping = m_stopping;
221   }
222
223   PyEval_AcquireLock();
224   PyThreadState_Swap(state);
225
226   xbp_chdir(scriptDir.c_str());
227
228   if (!stopping)
229   {
230     if (m_type == 'F')
231     {
232 #ifdef USE_EXTERNAL_PYTHON
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 *) _P(m_source).c_str(), (char*)"r");
238       FILE *fp = PyFile_AsFile(file);
239 #else
240       FILE *fp = fopen_utf8(_P(m_source).c_str(), "r");      
241 #endif
242
243       if (fp)
244       {
245         PyObject *f = PyString_FromString(_P(m_source).c_str());
246         PyDict_SetItemString(moduleDict, "__file__", f);
247         Py_DECREF(f);
248         PyRun_File(fp, _P(m_source).c_str(), m_Py_file_input, moduleDict, moduleDict);
249
250 #ifdef USE_EXTERNAL_PYTHON
251         // Get a reference to the main module
252         // and global dictionary
253         PyObject* main_module = PyImport_AddModule((char*)"__main__");
254         PyObject* global_dict = PyModule_GetDict(main_module);
255
256         // Extract a reference to the function "func_name"
257         // from the global dictionary
258         PyObject* expression = PyDict_GetItemString(global_dict, "xbmcclosefilehack");
259
260         if (!PyObject_CallFunction(expression,(char*)"(O)",file))
261           CLog::Log(LOGERROR,"Failed to close the script file %s",_P(m_source).c_str());
262 #else
263         fclose(fp);
264 #endif
265       }
266       else
267         CLog::Log(LOGERROR, "%s not found!", m_source);
268     }
269     else
270     {
271       //run script
272       PyRun_String(m_source, m_Py_file_input, moduleDict, moduleDict);
273     }
274   }
275
276   if (!PyErr_Occurred())
277     CLog::Log(LOGINFO, "Scriptresult: Success");
278   else if (PyErr_ExceptionMatches(PyExc_SystemExit))
279     CLog::Log(LOGINFO, "Scriptresult: Aborted");
280   else
281   {
282     PyObject* exc_type;
283     PyObject* exc_value;
284     PyObject* exc_traceback;
285     PyObject* pystring;
286     pystring = NULL;
287
288     PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
289     if (exc_type == 0 && exc_value == 0 && exc_traceback == 0)
290     {
291       CLog::Log(LOGINFO, "Strange: No Python exception occured");
292     }
293     else
294     {
295       if (exc_type != NULL && (pystring = PyObject_Str(exc_type)) != NULL && (PyString_Check(pystring)))
296       {
297           PyObject *tracebackModule;
298
299           CLog::Log(LOGINFO, "-->Python script returned the following error<--");
300           CLog::Log(LOGERROR, "Error Type: %s", PyString_AsString(PyObject_Str(exc_type)));
301           if (PyObject_Str(exc_value))
302             CLog::Log(LOGERROR, "Error Contents: %s", PyString_AsString(PyObject_Str(exc_value)));
303
304           tracebackModule = PyImport_ImportModule((char*)"traceback");
305           if (tracebackModule != NULL)
306           {
307             PyObject *tbList, *emptyString, *strRetval;
308
309             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);
310             emptyString = PyString_FromString("");
311             strRetval = PyObject_CallMethod(emptyString, (char*)"join", (char*)"O", tbList);
312
313             CLog::Log(LOGERROR, "%s", PyString_AsString(strRetval));
314
315             Py_DECREF(tbList);
316             Py_DECREF(emptyString);
317             Py_DECREF(strRetval);
318             Py_DECREF(tracebackModule);
319           }
320           CLog::Log(LOGINFO, "-->End of Python script error report<--");
321       }
322       else
323       {
324         pystring = NULL;
325         CLog::Log(LOGINFO, "<unknown exception type>");
326       }
327
328       CGUIDialogKaiToast *pDlgToast = (CGUIDialogKaiToast*)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
329       if (pDlgToast)
330       {
331         CStdString desc;
332         CStdString path;
333         CStdString script;
334         URIUtils::Split(m_source, path, script);
335         if (script.Equals("default.py"))
336         {
337           CStdString path2;
338           URIUtils::RemoveSlashAtEnd(path);
339           URIUtils::Split(path, path2, script);
340         }
341
342         desc.Format(g_localizeStrings.Get(2100), script);
343         pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(257), desc);
344       }
345     }
346
347     Py_XDECREF(exc_type);
348     Py_XDECREF(exc_value); // caller owns all 3
349     Py_XDECREF(exc_traceback); // already NULL'd out
350     Py_XDECREF(pystring);
351   }
352
353   PyObject *m = PyImport_AddModule((char*)"xbmc");
354   if(!m || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
355     CLog::Log(LOGERROR, "Scriptresult: failed to set abortRequested");
356
357   // make sure all sub threads have finished
358   for(PyThreadState* s = state->interp->tstate_head, *old = NULL; s;)
359   {
360     if(s == state)
361     {
362       s = s->next;
363       continue;
364     }
365     if(old != s)
366     {
367       CLog::Log(LOGINFO, "Scriptresult: Waiting on thread %"PRIu64, (uint64_t)s->thread_id);
368       old = s;
369     }
370
371     CPyThreadState pyState;
372     Sleep(100);
373     pyState.Restore();
374
375     s = state->interp->tstate_head;
376   }
377
378   // pending calls must be cleared out
379   PyXBMC_ClearPendingCalls(state);
380
381   PyThreadState_Swap(NULL);
382   PyEval_ReleaseLock();
383
384   { CSingleLock lock(m_pExecuter->m_critSection);
385     m_threadState = NULL;
386   }
387
388   PyEval_AcquireLock();
389   PyThreadState_Swap(state);
390
391   m_pExecuter->DeInitializeInterpreter();
392
393   Py_EndInterpreter(state);
394   PyThreadState_Swap(NULL);
395
396   PyEval_ReleaseLock();
397 }
398
399 void XBPyThread::OnExit()
400 {
401   m_pExecuter->setDone(m_id);
402 }
403
404 void XBPyThread::OnException()
405 {
406   PyThreadState_Swap(NULL);
407   PyEval_ReleaseLock();
408
409   CSingleLock lock(m_pExecuter->m_critSection);
410   m_threadState = NULL;
411   CLog::Log(LOGERROR,"%s, abnormally terminating python thread", __FUNCTION__);
412   m_pExecuter->setDone(m_id);
413 }
414
415 bool XBPyThread::isStopping() {
416   return m_stopping;
417 }
418
419 void XBPyThread::stop()
420 {
421   CSingleLock lock(m_pExecuter->m_critSection);
422   if(m_stopping)
423     return;
424
425   m_stopping = true;
426
427   if (m_threadState)
428   {
429     PyEval_AcquireLock();
430     PyThreadState* old = PyThreadState_Swap((PyThreadState*)m_threadState);
431
432     PyObject *m;
433     m = PyImport_AddModule((char*)"xbmc");
434     if(!m || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
435       CLog::Log(LOGERROR, "XBPyThread::stop - failed to set abortRequested");
436
437     for(PyThreadState* state = ((PyThreadState*)m_threadState)->interp->tstate_head; state; state = state->next)
438     {
439       Py_XDECREF(state->async_exc);
440       state->async_exc = PyExc_SystemExit;
441       Py_XINCREF(state->async_exc);
442     }
443
444     PyThreadState_Swap(old);
445     PyEval_ReleaseLock();
446   }
447 }