From ec56016302cde500ff27f8d2263a06929e0d3871 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 14 Mar 2025 16:04:19 +0000 Subject: [PATCH] python: Add functions to register a progress callback Signed-off-by: Michael Tremer --- src/python/ctx.c | 151 ++++++++++++++++++++++++++++++++++++++++++++ src/python/ctx.h | 1 + tests/python/ctx.py | 14 ++++ 3 files changed, 166 insertions(+) diff --git a/src/python/ctx.c b/src/python/ctx.c index 375f7308..204dc705 100644 --- a/src/python/ctx.c +++ b/src/python/ctx.c @@ -198,6 +198,7 @@ static void Ctx_dealloc(CtxObject* self) { } Py_XDECREF(self->callbacks.confirm); + Py_XDECREF(self->callbacks.progress); Py_XDECREF(self->logger); Py_TYPE(self)->tp_free((PyObject *)self); @@ -263,6 +264,150 @@ static PyObject* Ctx_set_confirm_callback(CtxObject* self, PyObject* args) { Py_RETURN_NONE; } +// Progress Callback + +static int Ctx_progress_start(struct pakfire_ctx* ctx, + struct pakfire_progress* progress, void* data, unsigned long int value) { + PyObject* ret = NULL; + PyObject* p = data; + int r = -1; + + // Fetch the start method + PyObject* start = PyObject_GetAttrString(p, "start"); + if (!start) + goto ERROR; + + // Call it with the value + ret = PyObject_CallFunction(start, "k", value); + if (!ret) + goto ERROR; + + // Success + r = 0; + +ERROR: + Py_XDECREF(start); + Py_XDECREF(ret); + + return r; +} + +static int Ctx_progress_finish(struct pakfire_ctx* ctx, + struct pakfire_progress* progress, void* data) { + PyObject* ret = NULL; + PyObject* p = data; + int r = -1; + + // Fetch the finish method + PyObject* finish = PyObject_GetAttrString(p, "finish"); + if (!finish) + goto ERROR; + + // Call it with any arguments + ret = PyObject_CallNoArgs(finish); + if (!ret) + goto ERROR; + + // Success + r = 0; + +ERROR: + Py_XDECREF(finish); + Py_XDECREF(ret); + + return r; +} + +static int Ctx_progress_update(struct pakfire_ctx* ctx, + struct pakfire_progress* progress, void* data, unsigned long int value) { + PyObject* ret = NULL; + PyObject* p = data; + int r = -1; + + // Fetch the update method + PyObject* update = PyObject_GetAttrString(p, "update"); + if (!update) + goto ERROR; + + // Call it with the value + ret = PyObject_CallFunction(update, "k", value); + if (!ret) + goto ERROR; + + // Success + r = 0; + +ERROR: + Py_XDECREF(update); + Py_XDECREF(ret); + + return r; +} + +static void Ctx_progress_free( + struct pakfire_ctx* ctx, struct pakfire_progress* progress, void* data) { + PyObject* p = data; + + // Free the object + Py_XDECREF(p); +} + +static int Ctx_progress_callback( + struct pakfire_ctx* ctx, void* data, struct pakfire_progress* progress) { + PyObject* callback = data; + PyObject* p = NULL; + + // Fetch the title + const char* title = pakfire_progress_get_title(progress); + + // Call the callback to create a new progress object + p = PyObject_CallFunction(callback, "z", title); + if (!p) + return -1; + + // Set callback data + pakfire_progress_set_callback_data(progress, p); + + // Register the progress callbacks with the progress object + + // Set start callback + pakfire_progress_set_start_callback(progress, Ctx_progress_start); + + // Set finish callback + pakfire_progress_set_finish_callback(progress, Ctx_progress_finish); + + // Set update callback + pakfire_progress_set_update_callback(progress, Ctx_progress_update); + + // Set free callback + pakfire_progress_set_free_callback(progress, Ctx_progress_free); + + return 0; +} + +static PyObject* Ctx_set_progress_callback(CtxObject* self, PyObject* args) { + PyObject* callback = NULL; + + // Parse arguments + if (!PyArg_ParseTuple(args, "O", &callback)) + return NULL; + + // Check if the callback is actually callable + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_ValueError, "Callback is not callable"); + return NULL; + } + + // Store a reference to the callback + self->callbacks.progress = callback; + Py_INCREF(self->callbacks.progress); + + // Set the progress callback of the context + pakfire_ctx_set_progress_callback(self->ctx, Ctx_progress_callback, self->callbacks.progress); + + Py_RETURN_NONE; +} + // Cache Path static PyObject* Ctx_get_cache_path(CtxObject* self) { @@ -306,6 +451,12 @@ static struct PyMethodDef Ctx_methods[] = { METH_VARARGS, NULL, }, + { + "set_progress_callback", + (PyCFunction)Ctx_set_progress_callback, + METH_VARARGS, + NULL, + }, { "set_logger", (PyCFunction)Ctx_set_logger, diff --git a/src/python/ctx.h b/src/python/ctx.h index 81d12ad9..c5a0e3a8 100644 --- a/src/python/ctx.h +++ b/src/python/ctx.h @@ -34,6 +34,7 @@ typedef struct { // Callbacks struct { PyObject* confirm; + PyObject* progress; } callbacks; } CtxObject; diff --git a/tests/python/ctx.py b/tests/python/ctx.py index 80ce2d9e..3c9fc923 100755 --- a/tests/python/ctx.py +++ b/tests/python/ctx.py @@ -112,6 +112,20 @@ class CtxTests(tests.TestCase): # Set the confirm callback ctx.set_confirm_callback(confirm_callback) + # Try setting something that we cannot call + with self.assertRaises(ValueError): + ctx.set_progress_callback(None) + + def progress_callback(title): + """ + This should actually return an object, + but we don't expect this to be called ever. + """ + pass + + # Set the progress callback + ctx.set_progress_callback(progress_callback) + if __name__ == "__main__": tests.main() -- 2.39.5