~ubuntu-branches/ubuntu/saucy/clementine/saucy

« back to all changes in this revision

Viewing changes to src/scripting/python/pythonengine.cpp

  • Committer: Package Import Robot
  • Author(s): Thomas PIERSON
  • Date: 2012-01-01 20:43:39 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20120101204339-lsb6nndwhfy05sde
Tags: 1.0.1+dfsg-1
New upstream release. (Closes: #653926, #651611, #657391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* This file is part of Clementine.
2
 
   Copyright 2010, David Sansome <me@davidsansome.com>
3
 
 
4
 
   Clementine is free software: you can redistribute it and/or modify
5
 
   it under the terms of the GNU General Public License as published by
6
 
   the Free Software Foundation, either version 3 of the License, or
7
 
   (at your option) any later version.
8
 
 
9
 
   Clementine is distributed in the hope that it will be useful,
10
 
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
   GNU General Public License for more details.
13
 
 
14
 
   You should have received a copy of the GNU General Public License
15
 
   along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
16
 
*/
17
 
 
18
 
#include <Python.h>
19
 
#include <frameobject.h>
20
 
#include <sip.h>
21
 
 
22
 
#include "pythonengine.h"
23
 
#include "pythonscript.h"
24
 
#include "sipAPIclementine.h"
25
 
#include "library/library.h"
26
 
 
27
 
#include <QFile>
28
 
#include <QtDebug>
29
 
 
30
 
const char* PythonEngine::kModulePrefix = "clementinescripts";
31
 
PythonEngine* PythonEngine::sInstance = NULL;
32
 
 
33
 
extern "C" {
34
 
  void initclementine();
35
 
 
36
 
#ifdef Q_OS_WIN32
37
 
  void initsip();
38
 
  void initQt();
39
 
  void initQtCore();
40
 
  void initQtGui();
41
 
  void initQtNetwork();
42
 
#endif
43
 
}
44
 
 
45
 
PythonEngine::PythonEngine(ScriptManager* manager)
46
 
  : LanguageEngine(manager),
47
 
    initialised_(false)
48
 
{
49
 
  Q_ASSERT(sInstance == NULL);
50
 
  sInstance = this;
51
 
  #ifdef Q_OS_DARWIN
52
 
    setenv("PYTHONPATH", (QCoreApplication::applicationDirPath() + "/../PlugIns").toLocal8Bit().constData(), 1);
53
 
  #endif
54
 
}
55
 
 
56
 
PythonEngine::~PythonEngine() {
57
 
  sInstance = NULL;
58
 
 
59
 
  if (initialised_) {
60
 
    Py_Finalize();
61
 
  }
62
 
}
63
 
 
64
 
const sipAPIDef* PythonEngine::GetSIPApi() {
65
 
#if defined(SIP_USE_PYCAPSULE)
66
 
  return (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0);
67
 
#else
68
 
  PyObject *sip_module;
69
 
  PyObject *sip_module_dict;
70
 
  PyObject *c_api;
71
 
 
72
 
  /* Import the SIP module. */
73
 
  sip_module = PyImport_ImportModule("sip");
74
 
 
75
 
  if (sip_module == NULL)
76
 
      return NULL;
77
 
 
78
 
  /* Get the module's dictionary. */
79
 
  sip_module_dict = PyModule_GetDict(sip_module);
80
 
 
81
 
  /* Get the "_C_API" attribute. */
82
 
  c_api = PyDict_GetItemString(sip_module_dict, "_C_API");
83
 
 
84
 
  if (c_api == NULL)
85
 
      return NULL;
86
 
 
87
 
  /* Sanity check that it is the right type. */
88
 
  if (!PyCObject_Check(c_api))
89
 
      return NULL;
90
 
 
91
 
  /* Get the actual pointer from the object. */
92
 
  return (const sipAPIDef *)PyCObject_AsVoidPtr(c_api);
93
 
#endif
94
 
}
95
 
 
96
 
bool PythonEngine::EnsureInitialised() {
97
 
  if (initialised_)
98
 
    return true;
99
 
 
100
 
  AddLogLine("Initialising python...", false);
101
 
 
102
 
#ifdef Q_OS_WIN32
103
 
  // On Windows we statically link against SIP and PyQt, so add those modules
104
 
  // to Python's inittab here.
105
 
  PyImport_AppendInittab(const_cast<char*>("sip"), initsip);
106
 
  PyImport_AppendInittab(const_cast<char*>("PyQt4.Qt"), initQt);
107
 
  PyImport_AppendInittab(const_cast<char*>("PyQt4.QtCore"), initQtCore);
108
 
  PyImport_AppendInittab(const_cast<char*>("PyQt4.QtGui"), initQtGui);
109
 
  PyImport_AppendInittab(const_cast<char*>("PyQt4.QtNetwork"), initQtNetwork);
110
 
#endif
111
 
 
112
 
  // Add the Clementine builtin module
113
 
  PyImport_AppendInittab(const_cast<char*>("clementine"), initclementine);
114
 
 
115
 
  // Initialise python
116
 
  Py_SetProgramName(const_cast<char*>("clementine"));
117
 
  PyEval_InitThreads();
118
 
  Py_InitializeEx(0);
119
 
 
120
 
  // Get the clementine module so we can put stuff in it
121
 
  clementine_module_ = PyImport_ImportModule("clementine");
122
 
  if (!clementine_module_) {
123
 
    AddLogLine("Failed to import the clementine module", true);
124
 
    if (PyErr_Occurred()) {
125
 
      PyErr_Print();
126
 
    }
127
 
    Py_Finalize();
128
 
    return false;
129
 
  }
130
 
  sip_api_ = GetSIPApi();
131
 
 
132
 
  // Add objects to the module
133
 
  if (manager()->data().valid_) {
134
 
    AddObject(manager()->data().library_->backend(), sipType_LibraryBackend, "library");
135
 
    AddObject(manager()->data().library_view_, sipType_LibraryView, "library_view");
136
 
    AddObject(manager()->data().player_, sipType_PlayerInterface, "player");
137
 
    AddObject(manager()->data().playlists_, sipType_PlaylistManagerInterface, "playlists");
138
 
    AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model");
139
 
    AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog");
140
 
    AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager");
141
 
  }
142
 
 
143
 
  AddObject(manager()->ui(), sipType_UIInterface, "ui");
144
 
  AddObject(this, sipType_PythonEngine, "pythonengine");
145
 
 
146
 
  // Create a module for scripts
147
 
  PyImport_AddModule(kModulePrefix);
148
 
 
149
 
  // Run the startup script - this redirects sys.stdout and sys.stderr to our
150
 
  // log handler.
151
 
  QFile python_startup(":pythonstartup.py");
152
 
  python_startup.open(QIODevice::ReadOnly);
153
 
  QByteArray python_startup_script = python_startup.readAll();
154
 
 
155
 
  if (PyRun_SimpleString(python_startup_script.constData()) != 0) {
156
 
    AddLogLine("Could not execute startup code", true);
157
 
    Py_Finalize();
158
 
    return false;
159
 
  }
160
 
 
161
 
  PyEval_ReleaseLock();
162
 
 
163
 
  initialised_ = true;
164
 
  return true;
165
 
}
166
 
 
167
 
Script* PythonEngine::CreateScript(const ScriptInfo& info) {
168
 
  // Initialise Python if it hasn't been done yet
169
 
  if (!EnsureInitialised()) {
170
 
    return NULL;
171
 
  }
172
 
 
173
 
  Script* ret = new PythonScript(this, info);
174
 
  loaded_scripts_[ret->info().id()] = ret; // Used by RegisterNativeObject during startup
175
 
  if (ret->Init()) {
176
 
    return ret;
177
 
  }
178
 
 
179
 
  DestroyScript(ret);
180
 
  return NULL;
181
 
}
182
 
 
183
 
void PythonEngine::DestroyScript(Script* script) {
184
 
  script->Unload();
185
 
  loaded_scripts_.remove(script->info().id());
186
 
  delete script;
187
 
}
188
 
 
189
 
void PythonEngine::AddObject(void* object, const _sipTypeDef* type,
190
 
                             const char * name) const {
191
 
  PyObject* python_object = sip_api_->api_convert_from_type(object, type, NULL);
192
 
  PyModule_AddObject(clementine_module_, name, python_object);
193
 
}
194
 
 
195
 
void PythonEngine::AddLogLine(const QString& message, bool error) {
196
 
  manager()->AddLogLine("Python", message, error);
197
 
}
198
 
 
199
 
Script* PythonEngine::FindScriptMatchingId(const QString& id) const {
200
 
  foreach (const QString& script_id, loaded_scripts_.keys()) {
201
 
    if (script_id == id || id.startsWith(script_id + ".")) {
202
 
      return loaded_scripts_[script_id];
203
 
    }
204
 
  }
205
 
  return NULL;
206
 
}
207
 
 
208
 
void PythonEngine::RegisterNativeObject(QObject* object) {
209
 
  // This function is called from Python, we need to figure out which script
210
 
  // called it, so we look at the __package__ variable in the bottom stack
211
 
  // frame.
212
 
 
213
 
  PyFrameObject* frame = PyEval_GetFrame();
214
 
  if (!frame) {
215
 
    qWarning() << __PRETTY_FUNCTION__ << "unable to get stack frame";
216
 
    return;
217
 
  }
218
 
  while (frame->f_back) {
219
 
    frame = frame->f_back;
220
 
  }
221
 
 
222
 
  PyObject* __package__ = PyMapping_GetItemString(
223
 
      frame->f_globals, const_cast<char*>("__package__"));
224
 
  if (!__package__) {
225
 
    qWarning() << __PRETTY_FUNCTION__ << "unable to get __package__";
226
 
    return;
227
 
  }
228
 
 
229
 
  QString package = PyString_AsString(__package__);
230
 
  Py_DECREF(__package__);
231
 
  package.remove(QString(kModulePrefix) + ".");
232
 
 
233
 
  Script* script = FindScriptMatchingId(package);
234
 
  if (!script) {
235
 
    qWarning() << __PRETTY_FUNCTION__ << "unable to find script for package" << package;
236
 
    return;
237
 
  }
238
 
 
239
 
  // Finally got the script - tell it about this object so it will get destroyed
240
 
  // when the script is unloaded.
241
 
  script->AddNativeObject(object);
242
 
 
243
 
  // Save the script as a property on the object so we can remove it later
244
 
  object->setProperty("owning_python_script", QVariant::fromValue(script));
245
 
  connect(object, SIGNAL(destroyed(QObject*)), SLOT(NativeObjectDestroyed(QObject*)));
246
 
}
247
 
 
248
 
void PythonEngine::NativeObjectDestroyed(QObject* object) {
249
 
  Script* script = object->property("owning_python_script").value<Script*>();
250
 
  if (!script || !loaded_scripts_.values().contains(script))
251
 
    return;
252
 
 
253
 
  script->RemoveNativeObject(object);
254
 
}