1 /* Python module for OpenSync
2 * Copyright (C) 2005 Eduardo Pereira Habkost <ehabkost@conectiva.com.br>
3 * Copyright (C) 2007 Andrew Baumann <andrewb@cse.unsw.edu.au>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library 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 GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 * @author Eduardo Pereira Habkost <ehabkost@conectiva.com.br>
20 * @author Andrew Baumann <andrewb@cse.unsw.edu.au>
22 * Additional changes by Armin Bauer <armin.bauer@desscon.com>
26 #include <opensync/opensync.h>
27 #include <opensync/opensync-plugin.h>
28 #include <opensync/opensync-client.h>
32 /* change this define for python exception output on stderr */
33 //#define PYERR_CLEAR() PyErr_Clear()
34 #define PYERR_CLEAR() PyErr_Print()
36 typedef struct MemberData
{
37 PyObject
*osync_module
;
39 PyObject
*init_return
;
42 static PyObject
*pm_load_opensync(OSyncError
**error
)
44 PyObject
*osync_module
= PyImport_ImportModule("opensync1");
46 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't load OpenSync1 module");
53 static PyObject
*pm_make_change(PyObject
*osync_module
, OSyncChange
*change
, OSyncError
**error
)
55 PyObject
*pychg_cobject
= PyCObject_FromVoidPtr(change
, NULL
);
57 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldnt make pychg cobject");
62 PyObject
*pychg
= PyObject_CallMethod(osync_module
, "Change", "O", pychg_cobject
);
63 Py_DECREF(pychg_cobject
);
65 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Cannot create Python OSyncChange");
72 static PyObject
*pm_make_context(PyObject
*osync_module
, OSyncContext
*ctx
, OSyncError
**error
)
74 PyObject
*pyctx_cobject
= PyCObject_FromVoidPtr(ctx
, NULL
);
76 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldnt make pyctx cobject");
81 PyObject
*pyctx
= PyObject_CallMethod(osync_module
, "Context", "O", pyctx_cobject
);
82 Py_DECREF(pyctx_cobject
);
84 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Cannot create Python OSyncContext");
91 static PyObject
*pm_make_info(PyObject
*osync_module
, OSyncPluginInfo
*info
, OSyncError
**error
)
93 PyObject
*pyinfo_cobject
= PyCObject_FromVoidPtr(info
, NULL
);
94 if (!pyinfo_cobject
) {
95 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldnt make pyinfo cobject");
100 PyObject
*pyinfo
= PyObject_CallMethod(osync_module
, "PluginInfo", "O", pyinfo_cobject
);
101 Py_DECREF(pyinfo_cobject
);
103 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Cannot create Python OSyncPluginInfo");
110 /* convert a python exception to an OSyncError containing the traceback of the exception */
111 static void pm_pyexcept_to_oserror(PyObject
*pytype
, PyObject
*pyvalue
, PyObject
*pytraceback
, OSyncError
**error
)
113 const char *errmsg
= NULL
;
114 PyObject
*tracebackmod
= NULL
, *stringmod
= NULL
;
115 PyObject
*pystrs
= NULL
, *pystr
= NULL
;
117 tracebackmod
= PyImport_ImportModule("traceback");
119 errmsg
= "import traceback";
123 if (pytraceback
== NULL
) {
124 pystrs
= PyObject_CallMethod(tracebackmod
, "format_exception_only", "OO", pytype
, pyvalue
);
126 errmsg
= "traceback.format_exception_only";
130 pystrs
= PyObject_CallMethod(tracebackmod
, "format_exception", "OOO", pytype
, pyvalue
, pytraceback
);
132 errmsg
= "traceback.format_exception";
137 stringmod
= PyImport_ImportModule("string");
139 errmsg
= "import string";
143 pystr
= PyObject_CallMethod(stringmod
, "join", "Os", pystrs
, "");
145 errmsg
= "string.join";
149 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "%s", PyString_AsString(pystr
));
152 Py_XDECREF(tracebackmod
);
153 Py_XDECREF(stringmod
);
159 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "pm_pyexcept_to_oserror: failed to report error: exception in %s", errmsg
);
163 /** Call a python method, report any exception it raises as an error, if no exception was raised report success
165 * Methods called using this function can
166 * have one of these formats:
168 * - function(info, context)
169 * - function(info, context, change)
171 static osync_bool
pm_call_module_method(OSyncObjTypeSink
*sink
,
172 OSyncPluginInfo
*info
, OSyncContext
*ctx
, osync_bool
*pslow_sync
,
173 void *userdata
, char *name
, OSyncChange
*chg
)
175 osync_trace(TRACE_ENTRY
, "%s(%s, %p, %p, %p)", __func__
, name
, info
, ctx
, chg
);
176 PyObject
*ret
= NULL
;
177 OSyncError
*error
= NULL
;
178 osync_bool report_error
= TRUE
;
179 PyObject
*osync_module
= NULL
;
181 PyGILState_STATE pystate
= PyGILState_Ensure();
183 PyObject
*sink_pyobject
= userdata
;
184 if (!sink_pyobject
) {
185 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "%s: %s: sink has no callback object", __func__
, name
);
189 if (!(osync_module
= pm_load_opensync(&error
)))
192 PyObject
*pyinfo
= pm_make_info(osync_module
, info
, &error
);
196 PyObject
*pycontext
= pm_make_context(osync_module
, ctx
, &error
);
203 PyObject
*pychange
= pm_make_change(osync_module
, chg
, &error
);
205 Py_DECREF(pycontext
);
210 ret
= PyObject_CallMethod(sink_pyobject
, name
, "OOO", pyinfo
, pycontext
, pychange
);
214 else if (pslow_sync
) {
215 // this is get_changes or connect_done, which passes a slow_sync flag
216 ret
= PyObject_CallMethod(sink_pyobject
, name
, "OOO",
217 pyinfo
, pycontext
, (*pslow_sync
? Py_True
: Py_False
));
220 ret
= PyObject_CallMethod(sink_pyobject
, name
, "OO", pyinfo
, pycontext
);
226 Py_DECREF(pycontext
);
228 Py_XDECREF(osync_module
);
229 PyGILState_Release(pystate
);
230 osync_context_report_success(ctx
);
231 osync_trace(TRACE_EXIT
, "%s", __func__
);
235 /* an exception occurred. get the python exception data */
236 PyObject
*pytype
, *pyvalue
, *pytraceback
;
237 PyErr_Fetch(&pytype
, &pyvalue
, &pytraceback
);
239 PyObject
*osyncerror
= NULL
;
240 osyncerror
= PyObject_GetAttrString(osync_module
, "Error");
243 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "Failed to get OSyncError class object");
247 if (PyErr_GivenExceptionMatches(pytype
, osyncerror
)) {
248 /* if it's an OSyncError, just report that up on the context object */
249 PyObject
*obj
= PyObject_CallMethod(pyvalue
, "report", "O", pycontext
);
252 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "Failed reporting OSyncError");
257 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "Reported OSyncError");
258 report_error
= FALSE
;
259 } else if (PyErr_GivenExceptionMatches(pytype
, PyExc_IOError
)
260 || PyErr_GivenExceptionMatches(pytype
, PyExc_OSError
)) {
261 /* for IOError or OSError, we just report the &error message */
262 PyObject
*pystr
= PyObject_Str(pyvalue
);
265 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "Failed reporting IOError/OSError");
269 osync_error_set(&error
, OSYNC_ERROR_IO_ERROR
, "%s", PyString_AsString(pystr
));
272 /* for other exceptions, we report a full traceback */
273 pm_pyexcept_to_oserror(pytype
, pyvalue
, pytraceback
, &error
);
277 Py_DECREF(pycontext
);
280 Py_XDECREF(pytraceback
);
281 Py_XDECREF(osyncerror
);
284 Py_XDECREF(osync_module
);
285 PyGILState_Release(pystate
);
287 osync_context_report_osyncerror(ctx
, error
);
288 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(&error
));
292 static void pm_connect(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
293 OSyncContext
*ctx
, void *userdata
)
295 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "connect", NULL
);
298 static void pm_disconnect(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
299 OSyncContext
*ctx
, void *userdata
)
301 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "disconnect", NULL
);
304 static void pm_get_changes(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
305 OSyncContext
*ctx
, osync_bool slow_sync
, void *userdata
)
307 pm_call_module_method(sink
, info
, ctx
, &slow_sync
, userdata
,
308 "get_changes", NULL
);
311 static void pm_commit(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
312 OSyncContext
*ctx
, OSyncChange
*change
, void *userdata
)
314 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "commit", change
);
317 static void pm_committed_all(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
318 OSyncContext
*ctx
, void *userdata
)
320 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "committed_all", NULL
);
323 static void pm_read(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
324 OSyncContext
*ctx
, OSyncChange
*change
, void *userdata
)
326 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "read", change
);
329 static void pm_sync_done(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
330 OSyncContext
*ctx
, void *userdata
)
332 pm_call_module_method(sink
, info
, ctx
, NULL
, userdata
, "sync_done", NULL
);
335 static void pm_connect_done(OSyncObjTypeSink
*sink
, OSyncPluginInfo
*info
,
336 OSyncContext
*ctx
, osync_bool slow_sync
, void *userdata
)
338 pm_call_module_method(sink
, info
, ctx
, &slow_sync
, userdata
, "connect_done", NULL
);
342 /** Calls the method initialize function
344 * The python initialize() function register one or more sink objects
345 * that have the other plugin methods (get_changeinfo, commit, etc.)
347 static void *pm_initialize(OSyncPlugin
*plugin
, OSyncPluginInfo
*info
, OSyncError
**error
)
349 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, plugin
, info
, error
);
350 MemberData
*data
= g_malloc0(sizeof(MemberData
));
351 char *modulename
= NULL
;
352 OSyncList
*s
, *sinks
= NULL
;
353 OSyncObjTypeSink
*sink
= NULL
;
355 PyGILState_STATE pystate
= PyGILState_Ensure();
358 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Failed to allocate module data");
362 // The modulename is set in the xml file, so we can use it
363 // here, but we only need it for initialize, so free it below
364 // once we're done with it. We set the plugin data to NULL here
366 if (!(modulename
= osync_plugin_get_data(plugin
))) {
367 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Failed to retrieve module name");
370 osync_plugin_set_data(plugin
, NULL
);
372 if (!(data
->osync_module
= pm_load_opensync(error
)))
375 if (!(data
->module
= PyImport_ImportModule(modulename
))) {
377 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't load module %s", modulename
);
383 PyObject
*pyinfo
= pm_make_info(data
->osync_module
, info
, error
);
387 PyObject
*ret
= PyObject_CallMethod(data
->module
, "initialize", "O", pyinfo
);
390 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't initialize module");
395 /* loop through all objtype sinks, set up function pointers */
396 sinks
= osync_plugin_info_get_objtype_sinks(info
);
397 for (s
= sinks
; s
; s
= s
->next
) {
398 sink
= (OSyncObjTypeSink
*)s
->data
;
400 osync_objtype_sink_set_connect_func(sink
, pm_connect
);
401 osync_objtype_sink_set_disconnect_func(sink
, pm_disconnect
);
402 osync_objtype_sink_set_get_changes_func(sink
, pm_get_changes
);
403 osync_objtype_sink_set_commit_func(sink
, pm_commit
);
404 osync_objtype_sink_set_committed_all_func(sink
, pm_committed_all
);
405 osync_objtype_sink_set_read_func(sink
, pm_read
);
406 osync_objtype_sink_set_sync_done_func(sink
, pm_sync_done
);
407 osync_objtype_sink_set_connect_done_func(sink
, pm_connect_done
);
409 osync_list_free(sinks
);
411 /* set functions for the main sink, if one is provided */
412 sink
= osync_plugin_info_get_main_sink(info
);
414 osync_objtype_sink_set_connect_func(sink
, pm_connect
);
415 osync_objtype_sink_set_disconnect_func(sink
, pm_disconnect
);
416 osync_objtype_sink_set_get_changes_func(sink
, pm_get_changes
);
417 osync_objtype_sink_set_commit_func(sink
, pm_commit
);
418 osync_objtype_sink_set_committed_all_func(sink
, pm_committed_all
);
419 osync_objtype_sink_set_read_func(sink
, pm_read
);
420 osync_objtype_sink_set_sync_done_func(sink
, pm_sync_done
);
421 osync_objtype_sink_set_connect_done_func(sink
, pm_connect_done
);
424 /* return value can be module data, if it's not None, store it */
425 if (ret
== Py_None
) {
426 Py_DECREF(ret
); // decrement for the CallMethod above
429 data
->init_return
= ret
; /* if it's an object, this takes our ref to it */
431 PyGILState_Release(pystate
);
432 osync_trace(TRACE_EXIT
, "%s", __func__
);
436 Py_XDECREF(data
->module
);
437 Py_XDECREF(data
->osync_module
);
438 PyGILState_Release(pystate
);
439 if (data
) free(data
);
440 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
444 static osync_bool
pm_discover(OSyncPluginInfo
*info
, void *data_in
, OSyncError
**error
)
446 osync_trace(TRACE_ENTRY
, "%s(%p, %p, %p)", __func__
, data_in
, info
, error
);
448 MemberData
*data
= data_in
;
450 PyGILState_STATE pystate
= PyGILState_Ensure();
452 PyObject
*pyinfo
= pm_make_info(data
->osync_module
, info
, error
);
456 PyObject
*ret
= NULL
;
457 if (data
->init_return
) {
458 ret
= PyObject_CallMethod(data
->module
, "discover", "OO", pyinfo
, data
->init_return
);
461 ret
= PyObject_CallMethod(data
->module
, "discover", "OO", pyinfo
, Py_None
);
470 PyGILState_Release(pystate
);
471 osync_trace(TRACE_EXIT
, "%s", __func__
);
475 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't call discover method");
477 PyGILState_Release(pystate
);
478 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
482 static void pm_finalize(void *data
)
484 osync_trace(TRACE_ENTRY
, "%s(%p)", __func__
, data
);
485 MemberData
*mydata
= data
;
486 OSyncError
**error
= NULL
;
487 PyGILState_STATE pystate
= PyGILState_Ensure();
488 PyObject
*ret
= NULL
;
489 int has_finalize
= 0;
491 /* we make finalize optional in the plugin */
492 if ((has_finalize
= PyObject_HasAttrString(mydata
->module
, "finalize")) == 1) {
493 if (mydata
->init_return
) {
494 ret
= PyObject_CallMethod(mydata
->module
, "finalize", "O", mydata
->init_return
);
497 ret
= PyObject_CallMethod(mydata
->module
, "finalize", "O", Py_None
);
502 if (mydata
->init_return
) Py_DECREF(mydata
->init_return
);
503 Py_DECREF(mydata
->module
);
504 Py_DECREF(mydata
->osync_module
);
507 if ((has_finalize
== 1) && (!ret
))
511 PyGILState_Release(pystate
);
512 osync_trace(TRACE_EXIT
, "%s", __func__
);
516 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't call finalize method");
518 PyGILState_Release(pystate
);
519 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(error
));
523 /* set python search path to look in our module directory first */
524 static osync_bool
set_search_path(OSyncError
**error
)
526 PyObject
*sys_module
= PyImport_ImportModule("sys");
528 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Couldn't import sys module");
533 PyObject
*path
= PyObject_GetAttrString(sys_module
, "path");
535 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "sys module has no path attribute?");
537 Py_DECREF(sys_module
);
541 if (!PyList_Check(path
)) {
542 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "sys.path is not a list?");
544 Py_DECREF(sys_module
);
548 PyObject
*plugindir
= Py_BuildValue("s", OPENSYNC_PYTHONPLG_DIR
);
550 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Error constructing plugindir string for sys.path");
553 Py_DECREF(sys_module
);
557 int r
= PySequence_Contains(path
, plugindir
);
559 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Error checking for 'plugindir in sys.path'");
561 Py_DECREF(plugindir
);
563 Py_DECREF(sys_module
);
567 if (r
== 0 && PyList_Insert(path
, 0, plugindir
) != 0) {
568 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Error inserting plugin directory into sys.path");
570 Py_DECREF(plugindir
);
572 Py_DECREF(sys_module
);
576 Py_DECREF(plugindir
);
578 Py_DECREF(sys_module
);
583 int main(int argc
, char *argv
[])
585 osync_trace(TRACE_ENTRY
, "%s(%d, %p)", __func__
, argc
, argv
);
587 const char *modulename
= NULL
;
588 const char *pipe_path
= NULL
;
590 OSyncError
*error
= NULL
;
591 OSyncClient
*client
= NULL
;
592 OSyncPlugin
*plugin
= NULL
;
598 // check for plugin modulename
599 if (argc
>= (arg
+1)) {
600 modulename
= argv
[arg
];
604 fprintf(stderr
, "module name is missing!\n");
608 // check for pipe path
609 if (argc
>= (arg
+1)) {
610 pipe_path
= argv
[arg
];
614 fprintf(stderr
, "pipe path is missing!\n");
620 // load python library
623 /* Because OpenSync likes to call this function multiple times in
624 * different threads, and because we may be sharing the python
625 * interpreter with other code, we have to:
626 * * init python only once
627 * * acquire the Python lock before making any API calls
630 if (!Py_IsInitialized()) {
631 /* We're the first user of python in this process. Initialise
632 * it, enable threading, and release the lock that will be
633 * re-acquired by the PyGILState_Ensure() call below. */
635 PyEval_InitThreads();
636 PyThreadState
*pts
= PyGILState_GetThisThreadState();
637 PyEval_ReleaseThread(pts
);
638 } else if (!PyEval_ThreadsInitialized()) {
639 /* Python has been initialised, but threads are not. */
640 osync_error_set(&error
, OSYNC_ERROR_GENERIC
, "The Python interpreter in this process has been initialised without threading support.");
641 osync_trace(TRACE_EXIT_ERROR
, "%s: %s", __func__
, osync_error_print(&error
));
645 PyGILState_STATE pystate
= PyGILState_Ensure();
647 if (!set_search_path(&error
))
650 // import opensync module
651 PyObject
*osync_module
= pm_load_opensync(&error
);
655 PyGILState_Release(pystate
);
661 plugin
= osync_plugin_new(&error
);
665 osync_plugin_set_initialize_func(plugin
, pm_initialize
);
666 osync_plugin_set_finalize_func(plugin
, pm_finalize
);
667 osync_plugin_set_discover_func(plugin
, pm_discover
);
668 osync_plugin_set_data(plugin
, g_strdup(modulename
));
674 client
= osync_client_new(&error
);
678 osync_client_set_pipe_path(client
, pipe_path
);
679 osync_client_set_plugin(client
, plugin
);
680 osync_plugin_unref(plugin
);
683 printf("[python_module %s]: %s OSyncPlugin:%p OSyncClient:%p\n",
684 modulename
, __func__
, plugin
, client
);
685 printf("[python_module %s]: Starting (blocking) OSyncClient ...\n",
688 if (!osync_client_run_and_block(client
, &error
))
691 printf("[python_module %s]: OSyncClient completed.\n", modulename
);
693 osync_client_unref(client
);
695 osync_trace(TRACE_EXIT
, "%s", __func__
);
699 PyGILState_Release(pystate
);
702 fprintf(stderr
, "[python_module %s] Error: %s\n",
703 modulename
, osync_error_print(&error
));
704 osync_error_unref(&error
);
705 osync_trace(TRACE_EXIT_ERROR
, "%s", __func__
);