1 /*
2 * Copyright (C) 2002-2003 Fhg Fokus
3 *
4 * This file is part of SEMS, a free SIP media server.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "PySemsAudio.h"
22 #include "PySems.h"
23
24 #include "AmConfigReader.h"
25 #include "AmConfig.h"
26 #include "log.h"
27 #include "AmApi.h"
28 #include "AmUtils.h"
29 #include "AmPlugIn.h"
30
31 #include "PySemsDialog.h"
32 #include "PySemsB2BDialog.h"
33 #include "PySemsB2ABDialog.h"
34 #include "PySemsUtils.h"
35
36 #include <sip.h>
37 #include "sip/sipAPIpy_sems_lib.h"
38
39 // earlier than 4.7.6, include headers:
40 #if SIP_VERSION < 0x040706
41 #include "sip/sippy_sems_libPySemsDialog.h"
42 #include "sip/sippy_sems_libPySemsB2BDialog.h"
43 #include "sip/sippy_sems_libPySemsB2ABDialog.h"
44 #endif
45
46 #if SIP_VERSION < 0x040901
47 #define SIP_USE_OLD_CLASS_CONVERSION 1
48 #endif
49
50 #include <unistd.h>
51 #include <pthread.h>
52 #include <regex.h>
53 #include <dirent.h>
54
55 #include <set>
56 using std::set;
57
58
59 #define PYFILE_REGEX "(.+)\\.(py|pyc|pyo)$"
60
61
62 EXPORT_SESSION_FACTORY(PySemsFactory,MOD_NAME);
63
64 PyMODINIT_FUNC initpy_sems_lib();
65
66 /**
67 * \brief gets python global interpreter lock (GIL)
68 *
69 * structure to acquire the python global interpreter lock (GIL)
70 * while the structure is allocated.
71 */
72 struct PythonGIL
73 {
74 PyGILState_STATE gst;
75
PythonGILPythonGIL76 PythonGIL() { gst = PyGILState_Ensure(); }
~PythonGILPythonGIL77 ~PythonGIL(){ PyGILState_Release(gst); }
78 };
79
80
81 // This must be the first declaration of every
82 // function using Python C-API.
83 // But this is not necessary in function which
84 // will get called from Python
85 #define PYLOCK PythonGIL _py_gil
86
87 extern "C" {
88
py_sems_log(PyObject *,PyObject * args)89 static PyObject* py_sems_log(PyObject*, PyObject* args)
90 {
91 int level;
92 char *msg;
93
94 if(!PyArg_ParseTuple(args,"is",&level,&msg))
95 return NULL;
96
97 _LOG(level, "%s", msg);
98
99 Py_INCREF(Py_None);
100 return Py_None;
101 }
102
py_sems_getHeader(PyObject *,PyObject * args)103 static PyObject* py_sems_getHeader(PyObject*, PyObject* args)
104 {
105 char* headers;
106 char* header_name;
107 if(!PyArg_ParseTuple(args,"ss",&headers,&header_name))
108 return NULL;
109
110 string res = getHeader(headers,header_name, true);
111 return PyString_FromString(res.c_str());
112 }
113
114
115 static PyMethodDef py_sems_methods[] = {
116 {"log", (PyCFunction)py_sems_log, METH_VARARGS,"Log a message using Sems' logging system"},
117 {"getHeader", (PyCFunction)py_sems_getHeader, METH_VARARGS,"Python getHeader wrapper"},
118 {NULL} /* Sentinel */
119 };
120 }
121
PySemsFactory(const string & _app_name)122 PySemsFactory::PySemsFactory(const string& _app_name)
123 : AmSessionFactory(_app_name)
124 {
125 }
126
127 // void PySemsFactory::setScriptPath(const string& path)
128 // {
129 // string python_path = script_path = path;
130
131 // if(python_path.length()){
132 // add_env_path("PYTHONPATH", python_path);
133 // }
134
135 // add_env_path("PYTHONPATH",AmConfig::PlugInPath);
136 // }
137
import_object(PyObject * m,char * name,PyTypeObject * type)138 void PySemsFactory::import_object(PyObject* m, char* name, PyTypeObject* type)
139 {
140 if (PyType_Ready(type) < 0){
141 ERROR("PyType_Ready failed !\n");
142 return;
143 }
144 Py_INCREF(type);
145 PyModule_AddObject(m, name, (PyObject *)type);
146 }
147
import_py_sems_builtins()148 void PySemsFactory::import_py_sems_builtins()
149 {
150 // py_sems module - start
151 PyImport_AddModule("py_sems");
152 py_sems_module = Py_InitModule("py_sems",py_sems_methods);
153
154 // PySemsAudioFile
155 import_object(py_sems_module,"PySemsAudioFile",&PySemsAudioFileType);
156
157 PyModule_AddIntConstant(py_sems_module, "AUDIO_READ",AUDIO_READ);
158 PyModule_AddIntConstant(py_sems_module, "AUDIO_WRITE",AUDIO_WRITE);
159 // py_sems module - end
160
161 // add log level for the log module
162 PyModule_AddIntConstant(py_sems_module, "SEMS_LOG_LEVEL",log_level);
163
164 import_module("py_sems_log");
165 initpy_sems_lib();
166 }
167
set_sys_path(const string & script_path)168 void PySemsFactory::set_sys_path(const string& script_path)
169 {
170 PyObject* py_mod = import_module("sys");
171 if(!py_mod) return;
172
173 PyObject* sys_path_str = PyString_FromString("path");
174 PyObject* sys_path = PyObject_GetAttr(py_mod,sys_path_str);
175 Py_DECREF(sys_path_str);
176
177 if(!sys_path){
178 PyErr_Print();
179 Py_DECREF(py_mod);
180 return;
181 }
182
183 if(!PyList_Insert(sys_path,0,PyString_FromString(script_path.c_str()))){
184 PyErr_Print();
185 }
186 }
187
import_module(const char * modname)188 PyObject* PySemsFactory::import_module(const char* modname)
189 {
190 PyObject* py_mod_name = PyString_FromString(modname);
191 PyObject* py_mod = PyImport_Import(py_mod_name);
192 Py_DECREF(py_mod_name);
193
194 if(!py_mod){
195 PyErr_Print();
196 ERROR("PySemsFactory: could not find python module '%s'.\n",modname);
197 ERROR("PySemsFactory: please check your installation.\n");
198 return NULL;
199 }
200
201 return py_mod;
202 }
203
init_python_interpreter(const string & script_path)204 void PySemsFactory::init_python_interpreter(const string& script_path)
205 {
206 if(!Py_IsInitialized()){
207
208 add_env_path("PYTHONPATH",AmConfig::PlugInPath);
209 Py_Initialize();
210 }
211
212 PyEval_InitThreads();
213 set_sys_path(script_path);
214 import_py_sems_builtins();
215 PyEval_ReleaseLock();
216 }
217
newDlg(const string & name)218 AmSession* PySemsFactory::newDlg(const string& name)
219 {
220 PYLOCK;
221
222 map<string,PySemsScriptDesc>::iterator mod_it = mod_reg.find(name);
223 if(mod_it == mod_reg.end()){
224 ERROR("Unknown script name '%s'\n", name.c_str());
225 throw AmSession::Exception(500,"Unknown Application");
226 }
227
228 PySemsScriptDesc& mod_desc = mod_it->second;
229
230 PyObject* dlg_inst = PyObject_Call(mod_desc.dlg_class,PyTuple_New(0),NULL);
231 if(!dlg_inst){
232
233 PyErr_Print();
234 ERROR("PySemsFactory: while loading \"%s\": could not create instance\n",
235 name.c_str());
236 throw AmSession::Exception(500,"Internal error in PY_SEMS plug-in.");
237
238 return NULL;
239 }
240
241 int err=0;
242
243 AmSession* sess = NULL;
244 PySemsDialogBase* dlg_base = NULL;
245
246 switch(mod_desc.dt) {
247 case PySemsScriptDesc::None: {
248 ERROR("wrong script type: None.\n");
249 }; break;
250 case PySemsScriptDesc::Dialog: {
251 PySemsDialog* dlg = (PySemsDialog*)
252 #ifdef SIP_USE_OLD_CLASS_CONVERSION
253 sipForceConvertTo_PySemsDialog(dlg_inst,&err);
254 #else
255 sipForceConvertToType(dlg_inst, sipType_PySemsDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
256 #endif
257 sess = dlg;
258 dlg_base = dlg;
259 }; break;
260
261 case PySemsScriptDesc::B2BDialog: {
262 PySemsB2BDialog* b2b_dlg = (PySemsB2BDialog*)
263 #ifdef SIP_USE_OLD_CLASS_CONVERSION
264 sipForceConvertTo_PySemsB2BDialog(dlg_inst,&err);
265 #else
266 sipForceConvertToType(dlg_inst, sipType_PySemsB2BDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
267 #endif
268 sess = b2b_dlg;
269 dlg_base = b2b_dlg;
270 }; break;
271
272 case PySemsScriptDesc::B2ABDialog: {
273 PySemsB2ABDialog* b2ab_dlg = (PySemsB2ABDialog*)
274 #ifdef SIP_USE_OLD_CLASS_CONVERSION
275 sipForceConvertTo_PySemsB2ABDialog(dlg_inst,&err);
276 #else
277 sipForceConvertToType(dlg_inst, sipType_PySemsB2ABDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
278 #endif
279 sess = b2ab_dlg;
280 dlg_base = b2ab_dlg;
281
282 }; break;
283 }
284
285 if (err || !dlg_base) {
286 // no luck
287 PyErr_Print();
288 ERROR("PySemsFactory: while loading \"%s\": could not retrieve a PySems*Dialog ptr.\n",
289 name.c_str());
290 throw AmSession::Exception(500,"Internal error in PY_SEMS plug-in.");
291 Py_DECREF(dlg_inst);
292 return NULL;
293 }
294
295
296 // take the ownership over dlg
297 sipTransferTo(dlg_inst,dlg_inst);
298 Py_DECREF(dlg_inst);
299 dlg_base->setPyPtrs(NULL,dlg_inst);
300 return sess;
301 }
302
loadScript(const string & path)303 bool PySemsFactory::loadScript(const string& path)
304 {
305 PYLOCK;
306
307 PyObject *modName,*mod,*dict, *dlg_class, *config=NULL;
308 PySemsScriptDesc::DialogType dt = PySemsScriptDesc::None;
309
310
311 modName = PyString_FromString(path.c_str());
312 mod = PyImport_Import(modName);
313
314 AmConfigReader cfg;
315 string cfg_file = add2path(AmConfig::ModConfigPath,1,(path + ".conf").c_str());
316
317 Py_DECREF(modName);
318
319 if(!mod){
320 PyErr_Print();
321 WARN("PySemsFactory: Failed to load \"%s\"\n", path.c_str());
322
323 dict = PyImport_GetModuleDict();
324 Py_INCREF(dict);
325 PyDict_DelItemString(dict,path.c_str());
326 Py_DECREF(dict);
327
328 return false;
329 }
330
331 dict = PyModule_GetDict(mod);
332 dlg_class = PyDict_GetItemString(dict, "PySemsScript");
333
334 if(!dlg_class){
335
336 PyErr_Print();
337 WARN("PySemsFactory: class PySemsDialog not found in \"%s\"\n", path.c_str());
338 goto error1;
339 }
340
341 Py_INCREF(dlg_class);
342
343 if(PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsDialog)) {
344 dt = PySemsScriptDesc::Dialog;
345 DBG("Loaded a Dialog Script.\n");
346 } else if (PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsB2BDialog)) {
347 DBG("Loaded a B2BDialog Script.\n");
348 dt = PySemsScriptDesc::B2BDialog;
349 } else if (PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsB2ABDialog)) {
350 DBG("Loaded a B2ABDialog Script.\n");
351 dt = PySemsScriptDesc::B2ABDialog;
352 } else {
353 WARN("PySemsFactory: in \"%s\": PySemsScript is not a "
354 "subtype of PySemsDialog\n", path.c_str());
355
356 goto error2;
357 }
358
359 if(cfg.loadFile(cfg_file)){
360 ERROR("could not load config file at %s\n",cfg_file.c_str());
361 goto error2;
362 }
363
364 config = PyDict_New();
365 if(!config){
366 ERROR("could not allocate new dict for config\n");
367 goto error2;
368 }
369
370 for(map<string,string>::const_iterator it = cfg.begin();
371 it != cfg.end(); it++){
372
373 PyDict_SetItem(config,
374 PyString_FromString(it->first.c_str()),
375 PyString_FromString(it->second.c_str()));
376 }
377
378 PyObject_SetAttrString(mod,"config",config);
379
380 mod_reg.insert(std::make_pair(path,
381 PySemsScriptDesc(mod,dlg_class, dt)));
382
383 return true;
384
385 error2:
386 Py_DECREF(dlg_class);
387 error1:
388 Py_DECREF(mod);
389
390 return false;
391 }
392
393 /**
394 * Loads python script path and default script file from configuration file
395 */
onLoad()396 int PySemsFactory::onLoad()
397 {
398 AmConfigReader cfg;
399
400 if(cfg.loadFile(add2path(AmConfig::ModConfigPath,1,MOD_NAME ".conf")))
401 return -1;
402
403 // get application specific global parameters
404 configureModule(cfg);
405
406 string script_path = cfg.getParameter("script_path");
407 init_python_interpreter(script_path);
408
409 #ifdef PY_SEMS_WITH_TTS
410 DBG("** PY_SEMS Text-To-Speech enabled\n");
411 #else
412 DBG("** PY_SEMS Text-To-Speech disabled\n");
413 #endif
414
415 DBG("** PY_SEMS script path: \'%s\'\n", script_path.c_str());
416
417 regex_t reg;
418 if(regcomp(®,PYFILE_REGEX,REG_EXTENDED)){
419 ERROR("while compiling regular expression\n");
420 return -1;
421 }
422
423 DIR* dir = opendir(script_path.c_str());
424 if(!dir){
425 regfree(®);
426 ERROR("PySems: script pre-loader (%s): %s\n",
427 script_path.c_str(),strerror(errno));
428 return -1;
429 }
430
431 DBG("directory '%s' opened\n",script_path.c_str());
432
433 set<string> unique_entries;
434 regmatch_t pmatch[2];
435
436 struct dirent* entry=0;
437 while((entry = readdir(dir)) != NULL){
438
439 if(!regexec(®,entry->d_name,2,pmatch,0)){
440
441 string name(entry->d_name + pmatch[1].rm_so,
442 pmatch[1].rm_eo - pmatch[1].rm_so);
443
444 unique_entries.insert(name);
445 }
446 }
447 closedir(dir);
448 regfree(®);
449
450 AmPlugIn* plugin = AmPlugIn::instance();
451 for(set<string>::iterator it = unique_entries.begin();
452 it != unique_entries.end(); it++) {
453
454 if(loadScript(*it)){
455 bool res = plugin->registerFactory4App(*it,this);
456 if(res)
457 INFO("Application script registered: %s.\n",
458 it->c_str());
459 }
460 }
461
462 return 0; // don't stop sems from starting up
463 }
464
465 /**
466 * Load a script using user name from URI.
467 * Note: there is no default script.
468 */
onInvite(const AmSipRequest & req)469 AmSession* PySemsFactory::onInvite(const AmSipRequest& req)
470 {
471 if(req.cmd != MOD_NAME)
472 return newDlg(req.cmd);
473 else
474 return newDlg(req.user);
475 }
476
477 // PySemsDialogBase - base class for all possible PySemsDialog classes
PySemsDialogBase()478 PySemsDialogBase::PySemsDialogBase()
479 : py_mod(NULL),
480 py_dlg(NULL)
481 {
482 }
483
~PySemsDialogBase()484 PySemsDialogBase::~PySemsDialogBase() {
485 PYLOCK;
486 Py_XDECREF(py_dlg);
487 }
488
setPyPtrs(PyObject * mod,PyObject * dlg)489 void PySemsDialogBase::setPyPtrs(PyObject *mod, PyObject *dlg)
490 {
491 PYLOCK;
492 Py_XDECREF(py_dlg);
493 py_dlg = dlg;
494 }
495
callPyEventHandler(char * name,char * fmt,...)496 bool PySemsDialogBase::callPyEventHandler(char* name, char* fmt, ...)
497 {
498 bool ret=false;
499 va_list va;
500
501 PYLOCK;
502
503 va_start(va, fmt);
504 PyObject* o = PyObject_VaCallMethod(py_dlg,name,fmt,va);
505 va_end(va);
506
507 if(!o) {
508
509 if(PyErr_ExceptionMatches(PyExc_AttributeError)){
510
511 DBG("method %s is not implemented, trying default one\n",name);
512 return true;
513 }
514
515 PyErr_Print();
516 }
517 else {
518 if(o && PyBool_Check(o) && (o == Py_True)) {
519
520 ret = true;
521 }
522
523 Py_DECREF(o);
524 }
525
526 return ret;
527 }
528