1 /* librepo - A library providing (libcURL like) API to downloading repository
2 * Copyright (C) 2016 Martin Hatina
3 *
4 * Licensed under the GNU Lesser General Public License Version 2.1
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <err.h>
22
23 #include "librepo/librepo.h"
24 #include "librepo/downloader_internal.h"
25 #include "metadatatarget-py.h"
26 #include "handle-py.h"
27 #include "typeconversion.h"
28 #include "exception-py.h"
29 #include "downloader-py.h"
30
31 typedef struct {
32 PyObject_HEAD
33 LrMetadataTarget *target;
34 /* Handle */
35 PyObject *handle;
36 /* Callback */
37 PyObject *cb_data;
38 PyObject *progress_cb;
39 PyObject *mirrorfailure_cb;
40 PyObject *end_cb;
41 /* GIL Stuff */
42 PyThreadState **state;
43 } _MetadataTargetObject;
44
45 LrMetadataTarget *
MetadataTarget_FromPyObject(PyObject * o)46 MetadataTarget_FromPyObject(PyObject *o)
47 {
48 if (!MetadataTargetObject_Check(o)) {
49 PyErr_SetString(PyExc_TypeError, "Expected a librepo.MetadataTarget object.");
50 return NULL;
51 }
52 return ((_MetadataTargetObject *)o)->target;
53 }
54
55 void
MetadataTarget_SetThreadState(PyObject * o,PyThreadState ** state)56 MetadataTarget_SetThreadState(PyObject *o, PyThreadState **state)
57 {
58 _MetadataTargetObject *self = (_MetadataTargetObject *) o;
59 if (!self) return;
60 self->state = state;
61
62 if (self->handle) {
63 Handle_SetThreadState(self->handle, state);
64 }
65 }
66
67 static int
check_MetadataTargetStatus(const _MetadataTargetObject * self)68 check_MetadataTargetStatus(const _MetadataTargetObject *self)
69 {
70 assert(self != NULL);
71 assert(MetadataTargetObject_Check(self));
72 if (self->target == NULL) {
73 PyErr_SetString(LrErr_Exception, "No librepo target");
74 return -1;
75 }
76 return 0;
77 }
78
79 static int
metadatatarget_progress_callback(void * data,double total_to_download,double now_downloaded)80 metadatatarget_progress_callback(void *data, double total_to_download, double now_downloaded)
81 {
82 int ret = LR_CB_OK;
83 _MetadataTargetObject *self;
84 PyObject *user_data, *result;
85 LrCallbackData *callback_data = (LrCallbackData *) data;
86 CbData *cbdata = (CbData *) callback_data->userdata;
87
88 self = (_MetadataTargetObject *)cbdata->cbdata;
89 if (!self || !self->progress_cb)
90 return ret;
91
92 if (self->cb_data)
93 user_data = self->cb_data;
94 else
95 user_data = Py_None;
96
97 EndAllowThreads(self->state);
98 result = PyObject_CallFunction(self->progress_cb,
99 "(Odd)", user_data, total_to_download, now_downloaded);
100
101 if (!result) {
102 ret = LR_CB_ERROR;
103 } else {
104 if (result == Py_None) {
105 ret = LR_CB_OK;
106 } else if (PyLong_Check(result)) {
107 ret = (int) PyLong_AsLong(result);
108 } else {
109 // It's an error if result is None neither int
110 PyErr_SetString(PyExc_TypeError, "Progress callback must return integer number");
111 ret = LR_CB_ERROR;
112 }
113 }
114
115 Py_XDECREF(result);
116 BeginAllowThreads(self->state);
117
118 return ret;
119 }
120
121 static int
metadatatarget_mirrorfailure_callback(void * data,const char * msg,const char * url)122 metadatatarget_mirrorfailure_callback(void *data,
123 const char *msg,
124 const char *url)
125 {
126 int ret = LR_CB_OK; // Assume everything will be ok
127 _MetadataTargetObject *self;
128 PyObject *user_data, *result, *py_msg, *py_url;
129 LrCallbackData *callback_data = (LrCallbackData *) data;
130 CbData *cbdata = (CbData *) callback_data->userdata;
131
132 self = (_MetadataTargetObject *)cbdata->cbdata;
133 if (!self->mirrorfailure_cb)
134 return ret;
135
136 if (self->cb_data)
137 user_data = self->cb_data;
138 else
139 user_data = Py_None;
140
141 EndAllowThreads(self->state);
142
143 py_msg = PyStringOrNone_FromString(msg);
144 py_url = PyStringOrNone_FromString(url);
145
146 result = PyObject_CallFunction(self->mirrorfailure_cb,
147 "(OOO)", user_data, py_msg, py_url);
148
149 Py_DECREF(py_msg);
150 Py_DECREF(py_url);
151
152 if (!result) {
153 // Exception raised in callback leads to the abortion
154 // of whole downloading (it is considered fatal)
155 ret = LR_CB_ERROR;
156 } else {
157 if (result == Py_None) {
158 // Assume that None means that everything is ok
159 ret = LR_CB_OK;
160 } else if (PyLong_Check(result)) {
161 ret = (int) PyLong_AsLong(result);
162 } else {
163 // It's an error if result is None neither int
164 PyErr_SetString(PyExc_TypeError, "Mirror failure callback must return integer number");
165 ret = LR_CB_ERROR;
166 }
167 }
168
169 Py_XDECREF(result);
170 BeginAllowThreads(self->state);
171
172 return ret;
173 }
174
175 static int
metadatatarget_end_callback(void * data,LrTransferStatus status,const char * msg)176 metadatatarget_end_callback(void *data,
177 LrTransferStatus status,
178 const char *msg)
179 {
180 int ret = LR_CB_OK; // Assume everything will be ok
181 _MetadataTargetObject *self;
182 PyObject *user_data, *result, *py_msg;
183 LrCallbackData *callback_data = (LrCallbackData *) data;
184 CbData *cbdata = (CbData *) callback_data->userdata;
185
186 self = (_MetadataTargetObject *)cbdata->cbdata;
187
188 LrMetadataTarget *target = self->target;
189 target->repomd_records_downloaded++;
190
191 if (target->repomd_records_to_download != target->repomd_records_downloaded)
192 return ret;
193 else if (!self->end_cb)
194 return ret;
195
196 if (self->cb_data)
197 user_data = self->cb_data;
198 else
199 user_data = Py_None;
200
201 EndAllowThreads(self->state);
202
203 py_msg = PyStringOrNone_FromString(msg);
204
205 result = PyObject_CallFunction(self->end_cb,
206 "(OiO)", user_data, status, py_msg);
207 Py_DECREF(py_msg);
208 if (!result) {
209 // Exception raised in callback leads to the abortion
210 // of whole downloading (it is considered fatal)
211 ret = LR_CB_ERROR;
212 } else {
213 if (result == Py_None) {
214 // Assume that None means that everything is ok
215 ret = LR_CB_OK;
216 } else if (PyLong_Check(result)) {
217 ret = (int) PyLong_AsLong(result);
218 } else {
219 // It's an error if result is None neither int
220 PyErr_SetString(PyExc_TypeError, "End callback must return integer number");
221 ret = LR_CB_ERROR;
222 }
223 }
224
225 Py_XDECREF(result);
226 BeginAllowThreads(self->state);
227
228 return ret;
229 }
230
231 static PyObject *
metadatatarget_new(PyTypeObject * type,G_GNUC_UNUSED PyObject * args,G_GNUC_UNUSED PyObject * kwds)232 metadatatarget_new(PyTypeObject *type,
233 G_GNUC_UNUSED PyObject *args,
234 G_GNUC_UNUSED PyObject *kwds)
235 {
236 _MetadataTargetObject *self = (_MetadataTargetObject *)type->tp_alloc(type, 0);
237
238 if (self) {
239 self->target = NULL;
240 self->handle = NULL;
241 self->cb_data = NULL;
242 self->progress_cb = NULL;
243 self->mirrorfailure_cb = NULL;
244 self->end_cb = NULL;
245 self->state = NULL;
246 }
247 return (PyObject *)self;
248 }
249
250 static int
metadatatarget_init(_MetadataTargetObject * self,PyObject * args,PyObject * kwds G_GNUC_UNUSED)251 metadatatarget_init(_MetadataTargetObject *self,
252 PyObject *args,
253 PyObject *kwds G_GNUC_UNUSED)
254 {
255 const char *gnupghomedir;
256 PyObject *pyhandle, *py_cbdata;
257 PyObject *py_endcb, *py_progresscb;
258 PyObject *py_mirrorfailurecb;
259 LrEndCb endcb = NULL;
260 LrHandle *handle = NULL;
261 LrProgressCb progresscb = NULL;
262 LrMirrorFailureCb mirrorfailurecb = NULL;
263 GError *tmp_err = NULL;
264
265 if (!PyArg_ParseTuple(args, "OOOOOs:metadatatarget_init",
266 &pyhandle, &py_cbdata, &py_progresscb,
267 &py_mirrorfailurecb, &py_endcb, &gnupghomedir))
268 return -1;
269
270 if (pyhandle != Py_None) {
271 handle = Handle_FromPyObject(pyhandle);
272 if (!handle)
273 return -1;
274 self->handle = pyhandle;
275 Py_INCREF(self->handle);
276 }
277
278 if (!PyCallable_Check(py_progresscb) && py_progresscb != Py_None) {
279 PyErr_SetString(PyExc_TypeError, "progresscb must be callable or None");
280 return -1;
281 }
282
283 if (!PyCallable_Check(py_mirrorfailurecb) && py_mirrorfailurecb != Py_None) {
284 PyErr_SetString(PyExc_TypeError, "mirrorfailurecb must be callable or None");
285 return -1;
286 }
287
288 if (!PyCallable_Check(py_endcb) && py_endcb != Py_None) {
289 PyErr_SetString(PyExc_TypeError, "endcb must be callable or None");
290 return -1;
291 }
292
293 if (py_cbdata) {
294 self->cb_data = py_cbdata;
295 Py_XINCREF(self->cb_data);
296 }
297
298 if (py_progresscb != Py_None) {
299 progresscb = metadatatarget_progress_callback;
300 self->progress_cb = py_progresscb;
301 Py_XINCREF(self->progress_cb);
302 }
303
304 if (py_mirrorfailurecb != Py_None) {
305 mirrorfailurecb = metadatatarget_mirrorfailure_callback;
306 self->mirrorfailure_cb = py_mirrorfailurecb;
307 Py_XINCREF(self->mirrorfailure_cb);
308 }
309
310 if (py_endcb != Py_None) {
311 endcb = metadatatarget_end_callback;
312 self->end_cb = py_endcb;
313 Py_XINCREF(self->end_cb);
314 }
315
316 self->target = lr_metadatatarget_new2(handle, self, progresscb, mirrorfailurecb, endcb, gnupghomedir, &tmp_err);
317
318 if (self->target == NULL) {
319 PyErr_Format(LrErr_Exception,
320 "MetadataTarget initialization failed: %s",
321 tmp_err->message);
322 g_error_free(tmp_err);
323 return -1;
324 }
325 return 0;
326 }
327
328 static void
metadatatarget_dealloc(_MetadataTargetObject * o)329 metadatatarget_dealloc(_MetadataTargetObject *o)
330 {
331 if (o->target)
332 lr_metadatatarget_free(o->target);
333 Py_XDECREF(o->progress_cb);
334 Py_XDECREF(o->mirrorfailure_cb);
335 Py_XDECREF(o->cb_data);
336 Py_XDECREF(o->handle);
337 Py_TYPE(o)->tp_free(o);
338 }
339
340 // Get/setters
341 #define OFFSET(member) (void *) offsetof(LrMetadataTarget, member)
342
343 static PyObject *
get_pythonobj(_MetadataTargetObject * self,void * member_offset)344 get_pythonobj(_MetadataTargetObject *self, void *member_offset)
345 {
346 if (check_MetadataTargetStatus(self))
347 return NULL;
348
349 if (member_offset == OFFSET(handle)) {
350 if (!self->handle)
351 Py_RETURN_NONE;
352 Py_XINCREF(self->handle);
353 return self->handle;
354 }
355
356 if (member_offset == OFFSET(cbdata)) {
357 if (!self->cb_data)
358 Py_RETURN_NONE;
359 Py_XINCREF(self->cb_data);
360 return self->cb_data;
361 }
362
363 if (member_offset == OFFSET(progresscb)) {
364 if (!self->progress_cb)
365 Py_RETURN_NONE;
366 Py_XINCREF(self->progress_cb);
367 return self->progress_cb;
368 }
369
370 if (member_offset == OFFSET(endcb)) {
371 if (!self->end_cb)
372 Py_RETURN_NONE;
373 Py_XINCREF(self->end_cb);
374 return self->end_cb;
375 }
376
377 if (member_offset == OFFSET(mirrorfailurecb)) {
378 if (!self->mirrorfailure_cb)
379 Py_RETURN_NONE;
380 Py_XINCREF(self->mirrorfailure_cb);
381 return self->mirrorfailure_cb;
382 }
383
384 if (member_offset == OFFSET(err)) {
385 LrMetadataTarget *target = self->target;
386
387 if (!target->err)
388 Py_RETURN_NONE;
389
390 int i = 0;
391 PyObject *pylist = PyTuple_New(g_list_length(target->err));
392 for (GList *elem = target->err; elem; i++, elem = g_list_next(elem)) {
393 gchar *error_message = (gchar *) elem->data;
394 PyObject *str = PyStringOrNone_FromString(error_message);
395 PyTuple_SetItem(pylist, i, str);
396 }
397
398 Py_XINCREF(target->err);
399 return pylist;
400 }
401
402 Py_RETURN_NONE;
403 }
404
405 static PyGetSetDef metadatatarget_getsetters[] = {
406 {"handle", (getter)get_pythonobj, NULL, NULL, OFFSET(handle)},
407 {"cbdata", (getter)get_pythonobj, NULL, NULL, OFFSET(cbdata)},
408 {"progresscb", (getter)get_pythonobj, NULL, NULL, OFFSET(progresscb)},
409 {"mirrorfailurecb",(getter)get_pythonobj,NULL, NULL, OFFSET(mirrorfailurecb)},
410 {"endcb", (getter)get_pythonobj, NULL, NULL, OFFSET(endcb)},
411 {"err", (getter)get_pythonobj, NULL, NULL, OFFSET(err)},
412 {NULL, NULL, NULL, NULL, NULL} /* sentinel */
413 };
414
415 static struct
416 PyMethodDef metadatatarget_methods[] = {
417 { NULL }
418 };
419
420 PyTypeObject MetadataTarget_Type = {
421 PyVarObject_HEAD_INIT(NULL, 0)
422 "_librepo.MetadataTarget", /* tp_name */
423 sizeof(_MetadataTargetObject), /* tp_basicsize */
424 0, /* tp_itemsize */
425 (destructor) metadatatarget_dealloc,/* tp_dealloc */
426 0, /* tp_print */
427 0, /* tp_getattr */
428 0, /* tp_setattr */
429 0, /* tp_compare */
430 0, /* tp_repr */
431 0, /* tp_as_number */
432 0, /* tp_as_sequence */
433 0, /* tp_as_mapping */
434 0, /* tp_hash */
435 0, /* tp_call */
436 0, /* tp_str */
437 0, /* tp_getattro */
438 0, /* tp_setattro */
439 0, /* tp_as_buffer */
440 Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,/* tp_flags */
441 "MetadataTarget object", /* tp_doc */
442 0, /* tp_traverse */
443 0, /* tp_clear */
444 0, /* tp_richcompare */
445 0, /* tp_weaklistoffset */
446 PyObject_SelfIter, /* tp_iter */
447 0, /* tp_iternext */
448 metadatatarget_methods, /* tp_methods */
449 0, /* tp_members */
450 metadatatarget_getsetters, /* tp_getset */
451 0, /* tp_base */
452 0, /* tp_dict */
453 0, /* tp_descr_get */
454 0, /* tp_descr_set */
455 0, /* tp_dictoffset */
456 (initproc) metadatatarget_init, /* tp_init */
457 0, /* tp_alloc */
458 metadatatarget_new, /* tp_new */
459 0, /* tp_free */
460 0, /* tp_is_gc */
461 };
462