| 109 | | static PyObject *pm_make_member(PyObject *osync_module, OSyncMember *member, OSyncError **error) |
| 110 | | { |
| 111 | | PyObject *pymember_cobject = PyCObject_FromVoidPtr(member, NULL); |
| 112 | | if (!pymember_cobject) { |
| 113 | | osync_error_set(error, OSYNC_ERROR_GENERIC, "Couldnt make pymember cobject"); |
| 114 | | PyErr_Print(); |
| 115 | | return NULL; |
| 116 | | } |
| 117 | | |
| 118 | | PyObject *pymember = PyObject_CallMethod(osync_module, "OSyncMember", "O", pymember_cobject); |
| 119 | | if (!pymember) { |
| 120 | | osync_error_set(error, OSYNC_ERROR_GENERIC, "Cannot create Python OSyncMember"); |
| 121 | | PyErr_Print(); |
| 122 | | Py_XDECREF(pymember_cobject); |
| 123 | | return NULL; |
| 124 | | } |
| 125 | | return pymember; |
| 126 | | } |
| | 93 | static PyObject *pm_make_info(PyObject *osync_module, OSyncPluginInfo *info, OSyncError **error) |
| | 94 | { |
| | 95 | PyObject *pyinfo_cobject = PyCObject_FromVoidPtr(info, NULL); |
| | 96 | if (!pyinfo_cobject) { |
| | 97 | osync_error_set(error, OSYNC_ERROR_GENERIC, "Couldnt make pyinfo cobject"); |
| | 98 | PYERR_CLEAR(); |
| | 99 | return NULL; |
| | 100 | } |
| | 101 | |
| | 102 | PyObject *pyinfo = PyObject_CallMethod(osync_module, "PluginInfo", "O", pyinfo_cobject); |
| | 103 | Py_DECREF(pyinfo_cobject); |
| | 104 | if (!pyinfo) { |
| | 105 | osync_error_set(error, OSYNC_ERROR_GENERIC, "Cannot create Python OSyncPluginInfo"); |
| | 106 | PYERR_CLEAR(); |
| | 107 | return NULL; |
| | 108 | } |
| | 109 | return pyinfo; |
| | 110 | } |
| | 111 | |
| | 112 | /* convert a python exception to an OSyncError containing the traceback of the exception */ |
| | 113 | static void pm_pyexcept_to_oserror(PyObject *pytype, PyObject *pyvalue, PyObject *pytraceback, OSyncError **error) |
| | 114 | { |
| | 115 | const char *errmsg = NULL; |
| | 116 | PyObject *tracebackmod = NULL, *stringmod = NULL; |
| | 117 | PyObject *pystrs = NULL, *pystr = NULL; |
| | 118 | |
| | 119 | tracebackmod = PyImport_ImportModule("traceback"); |
| | 120 | if (!tracebackmod) { |
| | 121 | errmsg = "import traceback"; |
| | 122 | goto error; |
| | 123 | } |
| | 124 | |
| | 125 | pystrs = PyObject_CallMethod(tracebackmod, "format_exception", "OOO", pytype, pyvalue, pytraceback); |
| | 126 | if (!pystrs) { |
| | 127 | errmsg = "traceback.format_exception"; |
| | 128 | goto error; |
| | 129 | } |
| | 130 | |
| | 131 | stringmod = PyImport_ImportModule("string"); |
| | 132 | if (!stringmod) { |
| | 133 | errmsg = "import string"; |
| | 134 | goto error; |
| | 135 | } |
| | 136 | |
| | 137 | pystr = PyObject_CallMethod(stringmod, "join", "Os", pystrs, ""); |
| | 138 | if (!pystr) { |
| | 139 | errmsg = "string.join"; |
| | 140 | goto error; |
| | 141 | } |
| | 142 | |
| | 143 | osync_error_set(error, OSYNC_ERROR_GENERIC, "%s", PyString_AsString(pystr)); |
| | 144 | |
| | 145 | error: |
| | 146 | Py_XDECREF(tracebackmod); |
| | 147 | Py_XDECREF(stringmod); |
| | 148 | Py_XDECREF(pystrs); |
| | 149 | Py_XDECREF(pystr); |
| | 150 | |
| | 151 | if (errmsg) { |
| | 152 | PYERR_CLEAR(); |
| | 153 | osync_error_set(error, OSYNC_ERROR_GENERIC, "pm_pyexcept_to_oserror: failed to report error: exception in %s", errmsg); |
| | 154 | } |
| | 155 | } |
| | 156 | |
| | 157 | /** Call a python method, report any exception it raises as an error, if no exception was raised report success |
| | 158 | * |
| | 159 | * Methods called using this function can |
| | 160 | * have one of these formats: |
| | 161 | * |
| | 162 | * - function(info, context) |
| | 163 | * - function(info, context, change) |
| | 164 | */ |
| | 165 | static osync_bool pm_call_module_method(MemberData *data, char *name, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *chg) |
| | 166 | { |
| | 167 | osync_trace(TRACE_ENTRY, "%s(%s, %p, %p, %p)", __func__, name, info, ctx, chg); |
| | 168 | PyObject *ret = NULL; |
| | 169 | OSyncError *error = NULL; |
| | 170 | osync_bool report_error = TRUE; |
| | 171 | |
| | 172 | PyEval_AcquireThread(data->interp_thread); |
| | 173 | |
| | 174 | PyObject *pyinfo = pm_make_info(data->osync_module, info, &error); |
| | 175 | if (!pyinfo) |
| | 176 | goto error; |
| | 177 | |
| | 178 | PyObject *pycontext = pm_make_context(data->osync_module, ctx, &error); |
| | 179 | if (!pycontext) { |
| | 180 | Py_DECREF(pyinfo); |
| | 181 | goto error; |
| | 182 | } |
| | 183 | |
| | 184 | OSyncObjTypeSink *sink = osync_plugin_info_get_sink(info); |
| | 185 | PyObject *sink_pyobject = osync_objtype_sink_get_userdata(sink); |
| | 186 | |
| | 187 | if (chg) { |
| | 188 | PyObject *pychange = pm_make_change(data->osync_module, chg, &error); |
| | 189 | if (!pychange) { |
| | 190 | Py_DECREF(pyinfo); |
| | 191 | Py_DECREF(pycontext); |
| | 192 | goto error; |
| | 193 | } |
| | 194 | |
| | 195 | ret = PyObject_CallMethod(sink_pyobject, name, "OOO", pyinfo, pycontext, pychange); |
| | 196 | |
| | 197 | Py_DECREF(pychange); |
| | 198 | } else { |
| | 199 | ret = PyObject_CallMethod(sink_pyobject, name, "OO", pyinfo, pycontext); |
| | 200 | } |
| | 201 | |
| | 202 | Py_DECREF(pyinfo); |
| | 203 | |
| | 204 | if (ret) { |
| | 205 | Py_DECREF(pycontext); |
| | 206 | Py_DECREF(ret); |
| | 207 | PyEval_ReleaseThread(data->interp_thread); |
| | 208 | osync_context_report_success(ctx); |
| | 209 | osync_trace(TRACE_EXIT, "%s", __func__); |
| | 210 | return TRUE; |
| | 211 | } |
| | 212 | |
| | 213 | /* an exception occurred. get the python exception data */ |
| | 214 | PyObject *pytype, *pyvalue, *pytraceback; |
| | 215 | PyErr_Fetch(&pytype, &pyvalue, &pytraceback); |
| | 216 | |
| | 217 | PyObject *osyncerror = NULL; |
| | 218 | osyncerror = PyObject_GetAttrString(data->osync_module, "Error"); |
| | 219 | if (!osyncerror) { |
| | 220 | PYERR_CLEAR(); |
| | 221 | osync_error_set(&error, OSYNC_ERROR_GENERIC, "Failed to get OSyncError class object"); |
| | 222 | goto out; |
| | 223 | } |
| | 224 | |
| | 225 | if (PyErr_GivenExceptionMatches(pytype, osyncerror)) { |
| | 226 | /* if it's an OSyncError, just report that up on the context object */ |
| | 227 | PyObject *obj = PyObject_CallMethod(pyvalue, "report", "OO", pyvalue, pycontext); |
| | 228 | if (!obj) { |
| | 229 | PYERR_CLEAR(); |
| | 230 | osync_error_set(&error, OSYNC_ERROR_GENERIC, "Failed reporting OSyncError"); |
| | 231 | goto out; |
| | 232 | } |
| | 233 | |
| | 234 | Py_DECREF(obj); |
| | 235 | osync_error_set(&error, OSYNC_ERROR_GENERIC, "Reported OSyncError"); |
| | 236 | report_error = FALSE; |
| | 237 | } else if (PyErr_GivenExceptionMatches(pytype, PyExc_IOError) |
| | 238 | || PyErr_GivenExceptionMatches(pytype, PyExc_OSError)) { |
| | 239 | /* for IOError or OSError, we just report the &error message */ |
| | 240 | PyObject *pystr = PyObject_Str(pyvalue); |
| | 241 | if (!pystr) { |
| | 242 | PYERR_CLEAR(); |
| | 243 | osync_error_set(&error, OSYNC_ERROR_GENERIC, "Failed reporting IOError/OSError"); |
| | 244 | goto out; |
| | 245 | } |
| | 246 | |
| | 247 | osync_error_set(&error, OSYNC_ERROR_IO_ERROR, "%s", PyString_AsString(pystr)); |
| | 248 | Py_DECREF(pystr); |
| | 249 | } else { |
| | 250 | /* for other exceptions, we report a full traceback */ |
| | 251 | pm_pyexcept_to_oserror(pytype, pyvalue, pytraceback, &error); |
| | 252 | } |
| | 253 | |
| | 254 | out: |
| | 255 | Py_DECREF(pycontext); |
| | 256 | Py_XDECREF(pytype); |
| | 257 | Py_XDECREF(pyvalue); |
| | 258 | Py_XDECREF(pytraceback); |
| | 259 | Py_XDECREF(osyncerror); |
| | 260 | |
| | 261 | error: |
| | 262 | PyEval_ReleaseThread(data->interp_thread); |
| | 263 | if (report_error) |
| | 264 | osync_context_report_osyncerror(ctx, error); |
| | 265 | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(&error)); |
| | 266 | return FALSE; |
| | 267 | } |
| | 268 | |
| | 269 | static void pm_connect(void *data, OSyncPluginInfo *info, OSyncContext *ctx) |
| | 270 | { |
| | 271 | pm_call_module_method(data, "connect", info, ctx, NULL); |
| | 272 | } |
| | 273 | |
| | 274 | static void pm_disconnect(void *data, OSyncPluginInfo *info, OSyncContext *ctx) |
| | 275 | { |
| | 276 | pm_call_module_method(data, "disconnect", info, ctx, NULL); |
| | 277 | } |
| | 278 | |
| | 279 | static void pm_get_changes(void *data, OSyncPluginInfo *info, OSyncContext *ctx) |
| | 280 | { |
| | 281 | pm_call_module_method(data, "get_changes", info, ctx, NULL); |
| | 282 | } |
| | 283 | |
| | 284 | static void pm_commit(void *data, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *change) |
| | 285 | { |
| | 286 | pm_call_module_method(data, "commit", info, ctx, change); |
| | 287 | } |
| | 288 | |
| | 289 | static void pm_committed_all(void *data, OSyncPluginInfo *info, OSyncContext *ctx) |
| | 290 | { |
| | 291 | pm_call_module_method(data, "committed_all", info, ctx, NULL); |
| | 292 | } |
| | 293 | |
| | 294 | static osync_bool pm_write(void *data, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *change) |
| | 295 | { |
| | 296 | return pm_call_module_method(data, "write", info, ctx, change); |
| | 297 | } |
| | 298 | |
| | 299 | static osync_bool pm_read(void *data, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *change) |
| | 300 | { |
| | 301 | return pm_call_module_method(data, "read", info, ctx, change); |
| | 302 | } |
| | 303 | |
| | 304 | static void pm_sync_done(void *data, OSyncPluginInfo *info, OSyncContext *ctx) |
| | 305 | { |
| | 306 | pm_call_module_method(data, "sync_done", info, ctx, NULL); |
| | 307 | } |
| | 308 | |
| | 309 | static OSyncObjTypeSinkFunctions pm_sink_functions = { |
| | 310 | .connect = pm_connect, |
| | 311 | .disconnect = pm_disconnect, |
| | 312 | .get_changes = pm_get_changes, |
| | 313 | .commit = pm_commit, |
| | 314 | .write = pm_write, |
| | 315 | .committed_all = pm_committed_all, |
| | 316 | .read = pm_read, |
| | 317 | .batch_commit = NULL, /* not (yet) supported for python plugins */ |
| | 318 | .sync_done = pm_sync_done |
| | 319 | }; |
| 197 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 198 | | } |
| 199 | | |
| 200 | | /** Call a python method |
| 201 | | * |
| 202 | | * Methods called using this function can |
| 203 | | * have one of these formats: |
| 204 | | * |
| 205 | | * - function(context) |
| 206 | | * - function(context, change) |
| 207 | | */ |
| 208 | | static osync_bool pm_call_module_method(OSyncContext *ctx, OSyncChange *chg, char *name, OSyncError **error) |
| 209 | | { |
| 210 | | osync_trace(TRACE_ENTRY, "%s(%p, %p, %s, %p)", __func__, ctx, chg, name, error); |
| 211 | | PyObject *pycontext = NULL; |
| 212 | | PyObject *ret = NULL; |
| 213 | | |
| 214 | | MemberData *data = osync_context_get_plugin_data(ctx); |
| 215 | | PyEval_AcquireThread(data->interp_thread); |
| 216 | | |
| 217 | | pycontext = pm_make_context(data->osync_module, ctx, error); |
| 218 | | if (!pycontext) { |
| 219 | | PyEval_ReleaseThread(data->interp_thread); |
| 220 | | osync_context_report_osyncerror(ctx, error); |
| 221 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
| 222 | | return FALSE; |
| 223 | | } |
| 224 | | |
| 225 | | if (chg) { |
| 226 | | PyObject *pychange = pm_make_change(data->osync_module, chg, error); |
| 227 | | if (!pychange) { |
| 228 | | PyEval_ReleaseThread(data->interp_thread); |
| 229 | | osync_context_report_osyncerror(ctx, error); |
| 230 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
| 231 | | return FALSE; |
| 232 | | } |
| 233 | | |
| 234 | | ret = PyObject_CallMethod(data->object, name, "OO", pycontext, pychange); |
| 235 | | |
| 236 | | Py_XDECREF(pychange); |
| 237 | | } else { |
| 238 | | ret = PyObject_CallMethod(data->object, name, "O", pycontext); |
| 239 | | } |
| 240 | | |
| 241 | | if (!ret) { |
| 242 | | osync_error_set(error, OSYNC_ERROR_GENERIC, "Error during %s() method", name); |
| 243 | | PyErr_Print(); |
| 244 | | PyEval_ReleaseThread(data->interp_thread); |
| 245 | | osync_context_report_osyncerror(ctx, error); |
| 246 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
| 247 | | return FALSE; |
| 248 | | } |
| 249 | | |
| 250 | | Py_XDECREF(ret); |
| 251 | | PyEval_ReleaseThread(data->interp_thread); |
| 252 | | |
| 253 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 254 | | return TRUE; |
| 255 | | } |
| 256 | | |
| 257 | | static void pm_connect(OSyncContext *ctx) |
| 258 | | { |
| 259 | | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, ctx); |
| 260 | | OSyncError *error = NULL; |
| 261 | | pm_call_module_method(ctx, NULL, "connect", &error); |
| 262 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 263 | | } |
| 264 | | |
| 265 | | |
| 266 | | static void pm_get_changeinfo(OSyncContext *ctx) |
| 267 | | { |
| 268 | | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, ctx); |
| 269 | | OSyncError *error = NULL; |
| 270 | | pm_call_module_method(ctx, NULL, "get_changeinfo", &error); |
| 271 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 272 | | } |
| 273 | | |
| 274 | | static osync_bool pm_access(OSyncContext *ctx, OSyncChange *change) |
| 275 | | { |
| 276 | | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, ctx, change); |
| 277 | | OSyncError *error = NULL; |
| 278 | | pm_call_module_method(ctx, change, "access", &error); |
| 279 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 280 | | return TRUE; |
| 281 | | } |
| 282 | | |
| 283 | | static osync_bool pm_commit_change(OSyncContext *ctx, OSyncChange *change) |
| 284 | | { |
| 285 | | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, ctx, change); |
| 286 | | OSyncError *error = NULL; |
| 287 | | pm_call_module_method(ctx, change, "commit_change", &error); |
| 288 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 289 | | return TRUE; |
| 290 | | } |
| 291 | | |
| 292 | | static void pm_sync_done(OSyncContext *ctx) |
| 293 | | { |
| 294 | | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, ctx); |
| 295 | | OSyncError *error = NULL; |
| 296 | | pm_call_module_method(ctx, NULL, "sync_done", &error); |
| 297 | | osync_trace(TRACE_EXIT, "%s", __func__); |
| 298 | | } |
| 299 | | |
| 300 | | static void pm_disconnect(OSyncContext *ctx) |
| 301 | | { |
| 302 | | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, ctx); |
| 303 | | OSyncError *error = NULL; |
| 304 | | pm_call_module_method(ctx, NULL, "disconnect", &error); |