Merge pull request #2948 from ace20022/blu_lang_fix
[vuplus_xbmc] / xbmc / interfaces / python / PythonInvoker.cpp
1 /*
2  *      Copyright (C) 2013 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, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS)
22   #include "config.h"
23 #endif
24
25 // python.h should always be included first before any other includes
26 #include <Python.h>
27 #include <osdefs.h>
28
29 #include "system.h"
30 #include "PythonInvoker.h"
31 #include "Application.h"
32 #include "ApplicationMessenger.h"
33 #include "addons/AddonManager.h"
34 #include "dialogs/GUIDialogKaiToast.h"
35 #include "filesystem/File.h"
36 #include "filesystem/SpecialProtocol.h"
37 #include "guilib/GraphicContext.h"
38 #include "guilib/GUIWindowManager.h"
39 #include "interfaces/legacy/Addon.h"
40 #include "interfaces/python/LanguageHook.h"
41 #include "interfaces/python/PyContext.h"
42 #include "interfaces/python/pythreadstate.h"
43 #include "interfaces/python/swig.h"
44 #include "interfaces/python/XBPython.h"
45 #include "threads/SingleLock.h"
46 #if defined(TARGET_WINDOWS)
47 #include "utils/CharsetConverter.h"
48 #endif // defined(TARGET_WINDOWS)
49 #include "utils/log.h"
50 #include "utils/URIUtils.h"
51
52 #ifdef TARGET_WINDOWS
53 extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
54 #else
55 #define fopen_utf8 fopen
56 #endif
57
58 #define GC_SCRIPT \
59   "import gc\n" \
60   "gc.collect(2)\n"
61
62 #define PY_PATH_SEP DELIM
63
64 // Time before ill-behaved scripts are terminated
65 #define PYTHON_SCRIPT_TIMEOUT 5000 // ms
66
67 using namespace std;
68 using namespace XFILE;
69
70 extern "C"
71 {
72   int xbp_chdir(const char *dirname);
73   char* dll_getenv(const char* szKey);
74 }
75
76 static const CStdString getListOfAddonClassesAsString(XBMCAddon::AddonClass::Ref<XBMCAddon::Python::LanguageHook>& languageHook)
77 {
78   CStdString message;
79   XBMCAddon::AddonClass::Synchronize l(*(languageHook.get()));
80   std::set<XBMCAddon::AddonClass*>& acs = languageHook->GetRegisteredAddonClasses();
81   bool firstTime = true;
82   for (std::set<XBMCAddon::AddonClass*>::iterator iter = acs.begin(); iter != acs.end(); ++iter)
83   {
84     if (!firstTime)
85       message += ",";
86     else
87       firstTime = false;
88     message += (*iter)->GetClassname().c_str();
89   }
90
91   return message;
92 }
93
94 CPythonInvoker::CPythonInvoker(ILanguageInvocationHandler *invocationHandler)
95   : ILanguageInvoker(invocationHandler),
96     m_source(NULL), m_argc(0), m_argv(NULL),
97     m_threadState(NULL), m_stop(false)
98 { }
99
100 CPythonInvoker::~CPythonInvoker()
101 {
102   // nothing to do for the default invoker used for registration with the
103   // CScriptInvocationManager
104   if (GetId() < 0)
105     return;
106
107   if (GetState() < InvokerStateDone)
108     CLog::Log(LOGDEBUG, "CPythonInvoker(%d): waiting for python thread \"%s\" to stop",
109       GetId(), (m_source != NULL ? m_source : "unknown script"));
110   Stop(true);
111   g_pythonParser.PulseGlobalEvent();
112
113   delete [] m_source;
114   if (m_argv != NULL)
115   {
116     for (unsigned int i = 0; i < m_argc; i++)
117       delete [] m_argv[i];
118     delete [] m_argv;
119   }
120   g_pythonParser.FinalizeScript();
121 }
122
123 bool CPythonInvoker::Execute(const std::string &script, const std::vector<std::string> &arguments /* = std::vector<std::string>() */)
124 {
125   if (script.empty())
126     return false;
127
128   if (!CFile::Exists(script))
129   {
130     CLog::Log(LOGERROR, "CPythonInvoker(%d): python script \"%s\" does not exist", GetId(), CSpecialProtocol::TranslatePath(script).c_str());
131     return false;
132   }
133
134   if (!g_pythonParser.InitializeEngine())
135     return false;
136
137   return ILanguageInvoker::Execute(script, arguments);
138 }
139
140 bool CPythonInvoker::execute(const std::string &script, const std::vector<std::string> &arguments)
141 {
142   // copy the code/script into a local string buffer
143 #ifdef TARGET_WINDOWS
144   CStdString strsrc = script;
145   g_charsetConverter.utf8ToSystem(strsrc);
146   m_source = new char[strsrc.length() + 1];
147   strcpy(m_source, strsrc);
148 #else
149   m_source = new char[script.length() + 1];
150   strcpy(m_source, script.c_str());
151 #endif
152
153   // copy the arguments into a local buffer
154   m_argc = arguments.size();
155   m_argv = new char*[m_argc];
156   for (unsigned int i = 0; i < m_argc; i++)
157   {
158     m_argv[i] = new char[arguments.at(i).length() + 1];
159     strcpy(m_argv[i], arguments.at(i).c_str());
160   }
161
162   CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): start processing", GetId(), m_source);
163   int m_Py_file_input = Py_file_input;
164
165   // get the global lock
166   PyEval_AcquireLock();
167   PyThreadState* state = Py_NewInterpreter();
168   if (state == NULL)
169   {
170     PyEval_ReleaseLock();
171     CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): FAILED to get thread state!", GetId(), m_source);
172     return false;
173   }
174   // swap in my thread state
175   PyThreadState_Swap(state);
176
177   XBMCAddon::AddonClass::Ref<XBMCAddon::Python::LanguageHook> languageHook(new XBMCAddon::Python::LanguageHook(state->interp));
178   languageHook->RegisterMe();
179
180   g_pythonParser.InitializeInterpreter(m_addon);
181   setState(InvokerStateInitialized);
182
183   CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): the source file to load is %s", GetId(), m_source, m_source);
184
185   // get path from script file name and add python path's
186   // this is used for python so it will search modules from script path first
187   CStdString scriptDir;
188   URIUtils::GetDirectory(CSpecialProtocol::TranslatePath(m_source), scriptDir);
189   URIUtils::RemoveSlashAtEnd(scriptDir);
190   addPath(scriptDir);
191
192   // add on any addon modules the user has installed
193   ADDON::VECADDONS addons;
194   ADDON::CAddonMgr::Get().GetAddons(ADDON::ADDON_SCRIPT_MODULE, addons);
195   for (unsigned int i = 0; i < addons.size(); ++i)
196     addPath(CSpecialProtocol::TranslatePath(addons[i]->LibPath()));
197
198   // we want to use sys.path so it includes site-packages
199   // if this fails, default to using Py_GetPath
200   PyObject *sysMod(PyImport_ImportModule((char*)"sys")); // must call Py_DECREF when finished
201   PyObject *sysModDict(PyModule_GetDict(sysMod)); // borrowed ref, no need to delete
202   PyObject *pathObj(PyDict_GetItemString(sysModDict, "path")); // borrowed ref, no need to delete
203
204   if (pathObj != NULL && PyList_Check(pathObj))
205   {
206     for (int i = 0; i < PyList_Size(pathObj); i++)
207     {
208       PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
209       if (e != NULL && PyString_Check(e))
210         addPath(PyString_AsString(e)); // returns internal data, don't delete or modify
211     }
212   }
213   else
214     addPath(Py_GetPath());
215
216   Py_DECREF(sysMod); // release ref to sysMod
217
218   // set current directory and python's path.
219   if (m_argv != NULL)
220     PySys_SetArgv(m_argc, m_argv);
221
222   CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): setting the Python path to %s", GetId(), m_source, m_pythonPath.c_str());
223   PySys_SetPath((char *)m_pythonPath.c_str());
224
225   CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): entering source directory %s", GetId(), m_source, scriptDir.c_str());
226   PyObject* module = PyImport_AddModule((char*)"__main__");
227   PyObject* moduleDict = PyModule_GetDict(module);
228
229   // when we are done initing we store thread state so we can be aborted
230   PyThreadState_Swap(NULL);
231   PyEval_ReleaseLock();
232
233   // we need to check if we was asked to abort before we had inited
234   bool stopping = false;
235   { CSingleLock lock(m_critical);
236     m_threadState = state;
237     stopping = m_stop;
238   }
239
240   PyEval_AcquireLock();
241   PyThreadState_Swap(state);
242
243   bool failed = false;
244   if (!stopping)
245   {
246     try
247     {
248       // run script from file
249       // We need to have python open the file because on Windows the DLL that python
250       //  is linked against may not be the DLL that xbmc is linked against so
251       //  passing a FILE* to python from an fopen has the potential to crash.
252       PyObject* file = PyFile_FromString((char *) CSpecialProtocol::TranslatePath(m_source).c_str(), (char*)"r");
253       FILE *fp = PyFile_AsFile(file);
254
255       if (fp != NULL)
256       {
257         PyObject *f = PyString_FromString(CSpecialProtocol::TranslatePath(m_source).c_str());
258         PyDict_SetItemString(moduleDict, "__file__", f);
259
260         if (m_addon.get() != NULL)
261         {
262           PyObject *pyaddonid = PyString_FromString(m_addon->ID().c_str());
263           PyDict_SetItemString(moduleDict, "__xbmcaddonid__", pyaddonid);
264
265           CStdString version = ADDON::GetXbmcApiVersionDependency(m_addon);
266           PyObject *pyxbmcapiversion = PyString_FromString(version.c_str());
267           PyDict_SetItemString(moduleDict, "__xbmcapiversion__", pyxbmcapiversion);
268
269           CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): instantiating addon using automatically obtained id of \"%s\" dependent on version %s of the xbmc.python api",
270             GetId(), m_source, m_addon->ID().c_str(), version.c_str());
271         }
272
273         Py_DECREF(f);
274         setState(InvokerStateRunning);
275         XBMCAddon::Python::PyContext pycontext; // this is a guard class that marks this callstack as being in a python context
276         PyRun_FileExFlags(fp, CSpecialProtocol::TranslatePath(m_source).c_str(), m_Py_file_input, moduleDict, moduleDict,1,NULL);
277       }
278       else
279         CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): %s not found!", GetId(), m_source, m_source);
280     }
281     catch (const XbmcCommons::Exception& e)
282     {
283       setState(InvokerStateFailed);
284       e.LogThrowMessage();
285       failed = true;
286     }
287     catch (...)
288     {
289       setState(InvokerStateFailed);
290       CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failure in script", GetId(), m_source);
291       failed = true;
292     }
293   }
294
295   bool systemExitThrown = false;
296   if (!failed && !PyErr_Occurred())
297   {
298     CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): script successfully run", GetId(), m_source);
299     setState(InvokerStateDone);
300   }
301   else if (PyErr_ExceptionMatches(PyExc_SystemExit))
302   {
303     systemExitThrown = true;
304     CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): script aborted", GetId(), m_source);
305     setState(InvokerStateFailed);
306   }
307   else
308   {
309     setState(InvokerStateFailed);
310
311     // if it failed with an exception we already logged the details
312     if (!failed)
313     {
314       PythonBindings::PythonToCppException e;
315       e.LogThrowMessage();
316     }
317
318     {
319       CPyThreadState releaseGil;
320       CSingleLock gc(g_graphicsContext);
321
322       CGUIDialogKaiToast *pDlgToast = (CGUIDialogKaiToast*)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
323       if (pDlgToast != NULL)
324       {
325         CStdString desc;
326         CStdString script;
327         if (m_addon.get() != NULL)
328           script = m_addon->Name();
329         else
330         {
331           CStdString path;
332           URIUtils::Split(m_source, path, script);
333           if (script.Equals("default.py"))
334           {
335             CStdString path2;
336             URIUtils::RemoveSlashAtEnd(path);
337             URIUtils::Split(path, path2, script);
338           }
339         }
340
341         desc.Format(g_localizeStrings.Get(2100), script);
342         pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(257), desc);
343       }
344     }
345   }
346
347   // no need to do anything else because the script has already stopped
348   if (failed)
349     return true;
350
351   PyObject *m = PyImport_AddModule((char*)"xbmc");
352   if (m == NULL || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
353     CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_source);
354
355   // make sure all sub threads have finished
356   for (PyThreadState* s = state->interp->tstate_head, *old = NULL; s;)
357   {
358     if (s == state)
359     {
360       s = s->next;
361       continue;
362     }
363     if (old != s)
364     {
365       CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): waiting on thread %"PRIu64, GetId(), m_source, (uint64_t)s->thread_id);
366       old = s;
367     }
368
369     CPyThreadState pyState;
370     Sleep(100);
371     pyState.Restore();
372
373     s = state->interp->tstate_head;
374   }
375
376   // pending calls must be cleared out
377   XBMCAddon::RetardedAsynchCallbackHandler::clearPendingCalls(state);
378
379   PyThreadState_Swap(NULL);
380   PyEval_ReleaseLock();
381
382   // set stopped event - this allows ::stop to run and kill remaining threads
383   // this event has to be fired without holding m_critical
384   // also the GIL (PyEval_AcquireLock) must not be held
385   // if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!)
386   m_stoppedEvent.Set();
387
388   { CSingleLock lock(m_critical);
389     m_threadState = NULL;
390   }
391
392   PyEval_AcquireLock();
393   PyThreadState_Swap(state);
394
395   g_pythonParser.DeInitializeInterpreter();
396
397   // run the gc before finishing
398   //
399   // if the script exited by throwing a SystemExit excepton then going back
400   // into the interpreter causes this python bug to get hit:
401   //    http://bugs.python.org/issue10582
402   // and that causes major failures. So we are not going to go back in
403   // to run the GC if that's the case.
404   if (!m_stop && languageHook->HasRegisteredAddonClasses() && !systemExitThrown &&
405       PyRun_SimpleString(GC_SCRIPT) == -1)
406     CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to run the gc to clean up after running prior to shutting down the Interpreter", GetId(), m_source);
407
408   Py_EndInterpreter(state);
409
410   // If we still have objects left around, produce an error message detailing what's been left behind
411   if (languageHook->HasRegisteredAddonClasses())
412     CLog::Log(LOGWARNING, "CPythonInvoker(%d, %s): the python script \"%s\" has left several "
413       "classes in memory that we couldn't clean up. The classes include: %s",
414       GetId(), m_source, m_source, getListOfAddonClassesAsString(languageHook).c_str());
415
416   // unregister the language hook
417   languageHook->UnregisterMe();
418
419   PyEval_ReleaseLock();
420
421   return true;
422 }
423
424 bool CPythonInvoker::stop(bool abort)
425 {
426   CSingleLock lock(m_critical);
427   m_stop = true;
428
429   if (!IsRunning())
430     return false;
431
432   setState(InvokerStateStopping);
433
434   if (m_threadState != NULL)
435   {
436     PyEval_AcquireLock();
437     PyThreadState* old = PyThreadState_Swap((PyThreadState*)m_threadState);
438
439     //tell xbmc.Monitor to call onAbortRequested()
440     if (m_addon != NULL)
441       g_pythonParser.OnAbortRequested(m_addon->ID());
442
443     PyObject *m;
444     m = PyImport_AddModule((char*)"xbmc");
445     if (m == NULL || PyObject_SetAttrString(m, (char*)"abortRequested", PyBool_FromLong(1)))
446       CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_source);
447
448     PyThreadState_Swap(old);
449     old = NULL;
450     PyEval_ReleaseLock();
451
452     XbmcThreads::EndTime timeout(PYTHON_SCRIPT_TIMEOUT);
453     while (!m_stoppedEvent.WaitMSec(15))
454     {
455       if (timeout.IsTimePast())
456       {
457         CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): script didn't stop in %d seconds - let's kill it", GetId(), m_source, PYTHON_SCRIPT_TIMEOUT / 1000);
458         break;
459       }
460
461       // We can't empty-spin in the main thread and expect scripts to be able to
462       // dismantle themselves. Python dialogs aren't normal XBMC dialogs, they rely
463       // on TMSG_GUI_PYTHON_DIALOG messages, so pump the message loop.
464       if (g_application.IsCurrentThread())
465       {
466         CSingleExit ex(g_graphicsContext);
467         CApplicationMessenger::Get().ProcessMessages();
468       }
469     }
470
471     // Useful for add-on performance metrics
472     if (!timeout.IsTimePast())
473       CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): script termination took %dms", GetId(), m_source, PYTHON_SCRIPT_TIMEOUT - timeout.MillisLeft());
474
475     // everything which didn't exit by now gets killed
476     {
477       // grabbing the PyLock while holding the m_critical is asking for a deadlock
478       CSingleExit ex2(m_critical);
479       PyEval_AcquireLock();
480     }
481
482     // Since we released the m_critical it's possible that the state is cleaned up
483     // so we need to recheck for m_threadState == NULL
484     if (m_threadState != NULL)
485     {
486       old = PyThreadState_Swap((PyThreadState*)m_threadState);
487       for (PyThreadState* state = ((PyThreadState*)m_threadState)->interp->tstate_head; state; state = state->next)
488       {
489         // Raise a SystemExit exception in python threads
490         Py_XDECREF(state->async_exc);
491         state->async_exc = PyExc_SystemExit;
492         Py_XINCREF(state->async_exc);
493       }
494
495       // If a dialog entered its doModal(), we need to wake it to see the exception
496       g_pythonParser.PulseGlobalEvent();
497     }
498
499     if (old != NULL)
500       PyThreadState_Swap(old);
501
502     lock.Leave();
503     PyEval_ReleaseLock();
504   }
505
506   return true;
507 }
508
509 void CPythonInvoker::onError()
510 {
511   PyThreadState_Swap(NULL);
512   PyEval_ReleaseLock();
513
514   setState(InvokerStateFailed);
515   CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): abnormally terminating python thread", GetId(), m_source);
516
517   CSingleLock lock(m_critical);
518   m_threadState = NULL;
519
520   ILanguageInvoker::onError();
521 }
522
523 void CPythonInvoker::addPath(const std::string path)
524 {
525   if (path.empty())
526     return;
527
528   if (!m_pythonPath.empty())
529     m_pythonPath += PY_PATH_SEP;
530
531 #if defined(TARGET_WINDOWS)
532   CStdString tmp(path);
533   g_charsetConverter.utf8ToSystem(tmp);
534   m_pythonPath += tmp;
535 #else
536   m_pythonPath += path;
537 #endif // defined(TARGET_WINDOWS)
538 }