2 * Copyright (C) 2005-2009 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, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #if (defined HAVE_CONFIG_H) && (!defined WIN32)
26 // python.h should always be included first before any other includes
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"
41 #include "XBPyThread.h"
44 #include "xbmcmodule/pyutil.h"
45 #include "xbmcmodule/pythreadstate.h"
46 #include "utils/CharsetConverter.h"
50 extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
52 #define fopen_utf8 fopen
55 #define PY_PATH_SEP DELIM
59 int xbp_chdir(const char *dirname);
60 char* dll_getenv(const char* szKey);
63 XBPyThread::XBPyThread(XBPython *pExecuter, int id) : CThread("XBPyThread")
65 CLog::Log(LOGDEBUG,"new python thread created. id=%d", id);
66 m_pExecuter = pExecuter;
75 XBPyThread::~XBPyThread()
78 g_pythonParser.PulseGlobalEvent();
79 CLog::Log(LOGDEBUG,"waiting for python thread %d to stop", m_id);
81 CLog::Log(LOGDEBUG,"python thread %d destructed", m_id);
85 for (unsigned int i = 0; i < m_argc; i++)
91 void XBPyThread::setSource(const CStdString &src)
94 CStdString strsrc = src;
95 g_charsetConverter.utf8ToSystem(strsrc);
96 m_source = new char[strsrc.GetLength()+1];
97 strcpy(m_source, strsrc);
99 m_source = new char[src.GetLength()+1];
100 strcpy(m_source, src);
104 int XBPyThread::evalFile(const CStdString &src)
112 int XBPyThread::evalString(const CStdString &src)
120 int XBPyThread::setArgv(const std::vector<CStdString> &argv)
122 m_argc = argv.size();
123 m_argv = new char*[m_argc];
124 for(unsigned int i = 0; i < m_argc; i++)
126 m_argv[i] = new char[argv[i].GetLength()+1];
127 strcpy(m_argv[i], argv[i].c_str());
132 void XBPyThread::Process()
134 CLog::Log(LOGDEBUG,"Python thread: start processing");
136 int m_Py_file_input = Py_file_input;
138 // get the global lock
139 PyEval_AcquireLock();
140 PyThreadState* state = Py_NewInterpreter();
143 PyEval_ReleaseLock();
144 CLog::Log(LOGERROR,"Python thread: FAILED to get thread state!");
147 // swap in my thread state
148 PyThreadState_Swap(state);
150 m_pExecuter->InitializeInterpreter(addon);
152 CLog::Log(LOGDEBUG, "%s - The source file to load is %s", __FUNCTION__, m_source);
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;
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
167 CStdString strTmp(CSpecialProtocol::TranslatePath(addons[i]->LibPath()));
168 g_charsetConverter.utf8ToSystem(strTmp);
169 path += PY_PATH_SEP + strTmp;
172 path += PY_PATH_SEP + CSpecialProtocol::TranslatePath(addons[i]->LibPath());
175 // and add on whatever our default path is
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
184 if( pathObj && PyList_Check(pathObj) )
186 for( int i = 0; i < PyList_Size(pathObj); i++ )
188 PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
189 if( e && PyString_Check(e) )
191 path += PyString_AsString(e); // returns internal data, don't delete or modify
198 path += Py_GetPath();
200 Py_DECREF(sysMod); // release ref to sysMod
202 // set current directory and python's path.
204 PySys_SetArgv(m_argc, m_argv);
206 CLog::Log(LOGDEBUG, "%s - Setting the Python path to %s", __FUNCTION__, path.c_str());
208 PySys_SetPath((char *)path.c_str());
210 CLog::Log(LOGDEBUG, "%s - Entering source directory %s", __FUNCTION__, scriptDir.c_str());
212 PyObject* module = PyImport_AddModule((char*)"__main__");
213 PyObject* moduleDict = PyModule_GetDict(module);
215 // when we are done initing we store thread state so we can be aborted
216 PyThreadState_Swap(NULL);
217 PyEval_ReleaseLock();
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;
226 PyEval_AcquireLock();
227 PyThreadState_Swap(state);
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);
242 PyObject *f = PyString_FromString(CSpecialProtocol::TranslatePath(m_source).c_str());
243 PyDict_SetItemString(moduleDict, "__file__", f);
244 if (addon.get() != NULL)
246 PyObject *pyaddonid = PyString_FromString(addon->ID().c_str());
247 PyDict_SetItemString(moduleDict, "__xbmcaddonid__", pyaddonid);
249 CStdString version = ADDON::GetXbmcApiVersionDependency(addon);
250 PyObject *pyxbmcapiversion = PyString_FromString(version.c_str());
251 PyDict_SetItemString(moduleDict, "__xbmcapiversion__", pyxbmcapiversion);
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());
256 PyRun_FileExFlags(fp, CSpecialProtocol::TranslatePath(m_source).c_str(), m_Py_file_input, moduleDict, moduleDict,1,NULL);
259 CLog::Log(LOGERROR, "%s not found!", m_source);
264 PyRun_String(m_source, m_Py_file_input, moduleDict, moduleDict);
268 if (!PyErr_Occurred())
269 CLog::Log(LOGINFO, "Scriptresult: Success");
270 else if (PyErr_ExceptionMatches(PyExc_SystemExit))
271 CLog::Log(LOGINFO, "Scriptresult: Aborted");
276 PyObject* exc_traceback;
280 PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
281 if (exc_type == 0 && exc_value == 0 && exc_traceback == 0)
283 CLog::Log(LOGINFO, "Strange: No Python exception occured");
287 if (exc_type != NULL && (pystring = PyObject_Str(exc_type)) != NULL && (PyString_Check(pystring)))
289 PyObject *tracebackModule;
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)));
296 tracebackModule = PyImport_ImportModule((char*)"traceback");
297 if (tracebackModule != NULL)
299 PyObject *tbList, *emptyString, *strRetval;
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);
305 CLog::Log(LOGERROR, "%s", PyString_AsString(strRetval));
308 Py_DECREF(emptyString);
309 Py_DECREF(strRetval);
310 Py_DECREF(tracebackModule);
312 CLog::Log(LOGINFO, "-->End of Python script error report<--");
317 CLog::Log(LOGINFO, "<unknown exception type>");
320 PYXBMC::PyXBMCGUILock();
321 CGUIDialogKaiToast *pDlgToast = (CGUIDialogKaiToast*)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
327 URIUtils::Split(m_source, path, script);
328 if (script.Equals("default.py"))
331 URIUtils::RemoveSlashAtEnd(path);
332 URIUtils::Split(path, path2, script);
335 desc.Format(g_localizeStrings.Get(2100), script);
336 pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(257), desc);
338 PYXBMC::PyXBMCGUIUnlock();
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);
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");
351 // make sure all sub threads have finished
352 for(PyThreadState* s = state->interp->tstate_head, *old = NULL; s;)
361 CLog::Log(LOGINFO, "Scriptresult: Waiting on thread %"PRIu64, (uint64_t)s->thread_id);
365 CPyThreadState pyState;
369 s = state->interp->tstate_head;
372 // pending calls must be cleared out
373 PyXBMC_ClearPendingCalls(state);
375 PyThreadState_Swap(NULL);
376 PyEval_ReleaseLock();
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
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!)
385 { CSingleLock lock(m_pExecuter->m_critSection);
386 m_threadState = NULL;
389 PyEval_AcquireLock();
390 PyThreadState_Swap(state);
392 m_pExecuter->DeInitializeInterpreter();
394 Py_EndInterpreter(state);
395 PyThreadState_Swap(NULL);
397 PyEval_ReleaseLock();
400 void XBPyThread::OnExit()
402 m_pExecuter->setDone(m_id);
405 void XBPyThread::OnException()
407 PyThreadState_Swap(NULL);
408 PyEval_ReleaseLock();
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);
416 bool XBPyThread::isStopping() {
420 void XBPyThread::stop()
422 CSingleLock lock(m_pExecuter->m_critSection);
430 PyEval_AcquireLock();
431 PyThreadState* old = PyThreadState_Swap((PyThreadState*)m_threadState);
433 //tell xbmc.Monitor to call onAbortRequested()
434 g_pythonParser.OnAbortRequested(addon->ID());
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");
441 PyThreadState_Swap(old);
442 PyEval_ReleaseLock();
444 if(!stoppedEvent.WaitMSec(5000))//let the script 5 secs for shut stuff down
446 CLog::Log(LOGERROR, "XBPyThread::stop - script didn't stop in proper time - lets kill it");
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)
454 Py_XDECREF(state->async_exc);
455 state->async_exc = PyExc_SystemExit;
456 Py_XINCREF(state->async_exc);
459 PyThreadState_Swap(old);
460 PyEval_ReleaseLock();