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