diff -Nru librepo-1.19.0/CMakeLists.txt librepo-1.20.0/CMakeLists.txt --- librepo-1.19.0/CMakeLists.txt 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/CMakeLists.txt 2025-06-20 05:48:22.000000000 +0100 @@ -39,7 +39,11 @@ FIND_PACKAGE(CURL 7.52.0 REQUIRED) IF (USE_GPGME) - FIND_PACKAGE(Gpgme REQUIRED) + PKG_SEARCH_MODULE(GPGME gpgme) + IF (NOT GPGME_FOUND) + FIND_PACKAGE(Gpgme REQUIRED) + SET (GPGME_LIBRARIES "${GPGME_VANILLA_LIBRARIES}") + ENDIF(NOT GPGME_FOUND) IF (ENABLE_SELINUX) PKG_CHECK_MODULES(SELINUX REQUIRED libselinux) ENDIF(ENABLE_SELINUX) diff -Nru librepo-1.19.0/debian/changelog librepo-1.20.0/debian/changelog --- librepo-1.19.0/debian/changelog 2024-11-07 11:45:24.000000000 +0000 +++ librepo-1.20.0/debian/changelog 2025-08-12 23:23:26.000000000 +0100 @@ -1,3 +1,20 @@ +librepo (1.20.0-1~deb13u1) trixie; urgency=medium + + * Upload to trixie + + -- Luca Boccassi Tue, 12 Aug 2025 23:23:26 +0100 + +librepo (1.20.0-1) unstable; urgency=medium + + * Improve handling of SELinux in the Debian packaging + * Update upstream source from tag 'upstream/1.20.0' + * Drop patches merged upstream + * d/control: bump Standards-Version to 4.7.2, no changes + * d/copyright: use GPL URL instead of old FSF postal address + * Add new symbols to librepo0.symbols + + -- Luca Boccassi Mon, 21 Jul 2025 16:54:10 +0100 + librepo (1.19.0-1) unstable; urgency=medium * Update upstream source from tag 'upstream/1.19.0' diff -Nru librepo-1.19.0/debian/control librepo-1.20.0/debian/control --- librepo-1.19.0/debian/control 2024-04-15 00:23:58.000000000 +0100 +++ librepo-1.20.0/debian/control 2025-07-21 16:36:40.000000000 +0100 @@ -23,8 +23,9 @@ python3-flask, python3-pyxattr, python3-gpg, + libselinux1-dev [linux-any], Rules-Requires-Root: no -Standards-Version: 4.7.0 +Standards-Version: 4.7.2 Homepage: https://github.com/rpm-software-management/librepo Vcs-Browser: https://salsa.debian.org/pkg-rpm-team/librepo Vcs-Git: https://salsa.debian.org/pkg-rpm-team/librepo.git diff -Nru librepo-1.19.0/debian/copyright librepo-1.20.0/debian/copyright --- librepo-1.19.0/debian/copyright 2024-01-03 18:08:49.000000000 +0000 +++ librepo-1.20.0/debian/copyright 2025-07-21 16:36:40.000000000 +0100 @@ -48,8 +48,7 @@ GNU General Public License for more details. . You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + with this program; if not, see . . On Debian systems, the complete text of the GNU General Public License v2 can be found in `/usr/share/common-licenses/GPL-2'. diff -Nru librepo-1.19.0/debian/gbp.conf librepo-1.20.0/debian/gbp.conf --- librepo-1.19.0/debian/gbp.conf 2024-04-14 22:21:36.000000000 +0100 +++ librepo-1.20.0/debian/gbp.conf 2025-08-12 23:23:26.000000000 +0100 @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = debian/sid +debian-branch = debian/trixie upstream-branch = upstream pristine-tar = True sign-tags = True diff -Nru librepo-1.19.0/debian/librepo0.symbols librepo-1.20.0/debian/librepo0.symbols --- librepo-1.19.0/debian/librepo0.symbols 2024-04-15 00:23:58.000000000 +0100 +++ librepo-1.20.0/debian/librepo0.symbols 2025-07-21 16:36:40.000000000 +0100 @@ -10,6 +10,7 @@ fillInvalidationValues@Base 1.10.5 handle_failure@Base 1.10.5 hmfcb@Base 1.10.5 + join_glist_strings@Base 1.20.0 lr_best_checksum@Base 1.10.5 lr_char_handler@Base 1.10.5 lr_check_packages@Base 1.10.5 @@ -94,6 +95,7 @@ lr_lrmirrorlist_nth_url@Base 1.10.5 lr_malloc0@Base 1.10.5 lr_malloc@Base 1.10.5 + lr_metadata_target_end_func@Base 1.20.0 lr_metadatatarget_append_error@Base 1.10.5 lr_metadatatarget_free@Base 1.10.5 lr_metadatatarget_new2@Base 1.10.5 diff -Nru librepo-1.19.0/debian/patches/0002-doc_c_Doxyfile.in.in_help-reproducible-builds.patch librepo-1.20.0/debian/patches/0002-doc_c_Doxyfile.in.in_help-reproducible-builds.patch --- librepo-1.19.0/debian/patches/0002-doc_c_Doxyfile.in.in_help-reproducible-builds.patch 2024-01-03 18:08:49.000000000 +0000 +++ librepo-1.20.0/debian/patches/0002-doc_c_Doxyfile.in.in_help-reproducible-builds.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,29 +0,0 @@ -From 64599dbbde0b4f8fc4d756e44c2eddc94453582d Mon Sep 17 00:00:00 2001 -From: Mihai Moldovan -Date: Tue, 24 Nov 2020 11:26:00 +0100 -Subject: [PATCH] Make documentation builds reproducible - -Doxygen includes the full path to sources in its documentation by default, -which makes builds non-reproducible. - -Instruct it to use the shortest possible path that makes file names unique -instead. ---- - doc/c/Doxyfile.in.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/doc/c/Doxyfile.in.in b/doc/c/Doxyfile.in.in -index 6f784f2..420d71b 100644 ---- a/doc/c/Doxyfile.in.in -+++ b/doc/c/Doxyfile.in.in -@@ -158,7 +158,7 @@ INLINE_INHERITED_MEMB = NO - # shortest path that makes the file name unique will be used - # The default value is: YES. - --FULL_PATH_NAMES = YES -+FULL_PATH_NAMES = NO - - # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. - # Stripping is only done if one of the specified strings matches the left-hand --- -2.31.0 diff -Nru librepo-1.19.0/debian/patches/0004-find-gpgme-pkg-conf.patch librepo-1.20.0/debian/patches/0004-find-gpgme-pkg-conf.patch --- librepo-1.19.0/debian/patches/0004-find-gpgme-pkg-conf.patch 2024-04-14 22:27:52.000000000 +0100 +++ librepo-1.20.0/debian/patches/0004-find-gpgme-pkg-conf.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,28 +0,0 @@ -Description: fix FTBFS against libgpgme-dev >= 1.18.0-2 -Author: Andreas Metzler -Bug: https://bugs.debian.org/1024576 -Last-Update: 2023-01-05 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -37,7 +37,7 @@ - FIND_PACKAGE(CURL 7.52.0 REQUIRED) - - IF (USE_GPGME) -- FIND_PACKAGE(Gpgme REQUIRED) -+ PKG_SEARCH_MODULE(GPGME REQUIRED gpgme) - IF (ENABLE_SELINUX) - PKG_CHECK_MODULES(SELINUX REQUIRED libselinux) - ENDIF(ENABLE_SELINUX) ---- a/librepo/CMakeLists.txt -+++ b/librepo/CMakeLists.txt -@@ -70,7 +70,7 @@ - ${GLIB2_LIBRARIES} - ) - IF (USE_GPGME) -- TARGET_LINK_LIBRARIES(librepo ${GPGME_VANILLA_LIBRARIES}) -+ TARGET_LINK_LIBRARIES(librepo ${GPGME_LIBRARIES}) - IF (ENABLE_SELINUX) - TARGET_LINK_LIBRARIES(librepo ${SELINUX_LIBRARIES}) - ENDIF(ENABLE_SELINUX) diff -Nru librepo-1.19.0/debian/patches/0005-test_yum_package_downloading.py-Don-t-hardcode-a-val.patch librepo-1.20.0/debian/patches/0005-test_yum_package_downloading.py-Don-t-hardcode-a-val.patch --- librepo-1.19.0/debian/patches/0005-test_yum_package_downloading.py-Don-t-hardcode-a-val.patch 2024-11-07 11:43:32.000000000 +0000 +++ librepo-1.20.0/debian/patches/0005-test_yum_package_downloading.py-Don-t-hardcode-a-val.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -From abed2856f5a35c86e03851a94a9e4d92c95c1386 Mon Sep 17 00:00:00 2001 -From: Adrian Bunk -Date: Mon, 9 Jan 2023 20:06:46 +0200 -Subject: test_yum_package_downloading.py: Don't hardcode a value for - EOPNOTSUPP - -For historical reasons, errno numbers are not the same on all -architectures (compatibility with other operating systems already -available on the same hardware was considered more important than -having the same numbers on all Linux architectures). - -Use errno.EOPNOTSUPP instead of hardcoding a number. ---- - tests/python/tests/test_yum_package_downloading.py | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - ---- a/tests/python/tests/test_yum_package_downloading.py -+++ b/tests/python/tests/test_yum_package_downloading.py -@@ -6,6 +6,7 @@ - import unittest - import tempfile - import xattr -+import errno - - import tests.servermock.yum_mock.config as config - -@@ -758,7 +759,7 @@ - "user.librepo.downloadinprogress".encode("utf-8"), - "".encode("utf-8")) - except IOError as err: -- if err.errno == 95: -+ if err.errno == errno.EOPNOTSUPP: - self.skipTest('extended attributes are not supported') - raise - diff -Nru librepo-1.19.0/debian/patches/series librepo-1.20.0/debian/patches/series --- librepo-1.19.0/debian/patches/series 2024-01-03 18:09:34.000000000 +0000 +++ librepo-1.20.0/debian/patches/series 2025-07-21 16:36:40.000000000 +0100 @@ -1,4 +1 @@ -0002-doc_c_Doxyfile.in.in_help-reproducible-builds.patch 0003-Set-CMAKE_SKIP_RPATH-to-TRUE.patch -0004-find-gpgme-pkg-conf.patch -0005-test_yum_package_downloading.py-Don-t-hardcode-a-val.patch diff -Nru librepo-1.19.0/debian/rules librepo-1.20.0/debian/rules --- librepo-1.19.0/debian/rules 2024-04-15 00:23:58.000000000 +0100 +++ librepo-1.20.0/debian/rules 2025-07-21 16:36:39.000000000 +0100 @@ -8,12 +8,15 @@ include /usr/share/dpkg/default.mk include /usr/share/debhelper/dh_package_notes/package-notes.mk +ifneq (linux,$(DEB_HOST_ARCH_OS)) + EXTRA_CMAKE_ARGS += -DENABLE_SELINUX:BOOL=OFF +endif %: dh "${@}" --buildsystem=cmake override_dh_auto_configure: dh_auto_configure --builddirectory=build -- \ - -DENABLE_DOCS:BOOL=ON -DENABLE_TESTS:BOOL=ON -DWITH_ZCHUNK:BOOL=OFF + -DENABLE_DOCS:BOOL=ON -DENABLE_TESTS:BOOL=ON -DWITH_ZCHUNK:BOOL=OFF $(EXTRA_CMAKE_ARGS) override_dh_auto_build: dh_auto_build --builddirectory=build -- all doc diff -Nru librepo-1.19.0/debian/salsa-ci.yml librepo-1.20.0/debian/salsa-ci.yml --- librepo-1.19.0/debian/salsa-ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ librepo-1.20.0/debian/salsa-ci.yml 2025-08-12 23:23:26.000000000 +0100 @@ -0,0 +1,14 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml + +variables: + RELEASE: 'trixie' + SALSA_CI_IMAGES_LINTIAN: ${SALSA_CI_IMAGES}/lintian:stable + SALSA_CI_LINTIAN_SUPPRESS_TAGS: "bad-distribution-in-changes-file" + # The following jobs all run on unstable, and cannot work because they expect + # overrides, compiler flags, dependencies, etc to work as in unstable, but this + # is a stable branch + SALSA_CI_DISABLE_BLHC: 1 + SALSA_CI_DISABLE_REPROTEST: 1 + SALSA_CI_DISABLE_PIUPARTS: 1 diff -Nru librepo-1.19.0/doc/c/CMakeLists.txt librepo-1.20.0/doc/c/CMakeLists.txt --- librepo-1.19.0/doc/c/CMakeLists.txt 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/doc/c/CMakeLists.txt 2025-06-20 05:48:22.000000000 +0100 @@ -1,7 +1,5 @@ -find_package(Doxygen) -if(DOXYGEN_FOUND) - CONFIGURE_FILE("Doxyfile.in.in" "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" @ONLY) - add_custom_target(doc-c - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in - COMMENT "Building C API documentation with Doxygen" VERBATIM) -endif(DOXYGEN_FOUND) +find_package(Doxygen REQUIRED) +CONFIGURE_FILE("Doxyfile.in.in" "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" @ONLY) +add_custom_target(doc-c + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in + COMMENT "Building C API documentation with Doxygen" VERBATIM) diff -Nru librepo-1.19.0/doc/c/Doxyfile.in.in librepo-1.20.0/doc/c/Doxyfile.in.in --- librepo-1.19.0/doc/c/Doxyfile.in.in 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/doc/c/Doxyfile.in.in 2025-06-20 05:48:22.000000000 +0100 @@ -158,7 +158,7 @@ # shortest path that makes the file name unique will be used # The default value is: YES. -FULL_PATH_NAMES = YES +FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand diff -Nru librepo-1.19.0/examples/c/download_repos_parallel.c librepo-1.20.0/examples/c/download_repos_parallel.c --- librepo-1.19.0/examples/c/download_repos_parallel.c 1970-01-01 01:00:00.000000000 +0100 +++ librepo-1.20.0/examples/c/download_repos_parallel.c 2025-06-20 05:48:22.000000000 +0100 @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include + +#define DESTDIR "downloaded_metadata" +#define PROGRESSBAR_LEN 50 + +static int progress_callback(void *data, double total_to_download, + double downloaded) { + if (total_to_download <= 0) { + return 0; + } + int completed = downloaded / (total_to_download / PROGRESSBAR_LEN); + + printf("[%.*s%.*s] %.f/%.f (%s)\n", completed, + "##################################################", // '#' repeated + PROGRESSBAR_LEN - completed, + "--------------------------------------------------", // '-' repeated + downloaded, total_to_download, (const char *)data); + + fflush(stdout); + return 0; +} + +static int end_callback(void *data, LrTransferStatus status, const char *msg) { + printf("End status %u: %s (%s)\n", status, msg, (const char *)data); + return 0; +} + +static int hmf_callback(void *data, const char *msg, const char *url) { + printf("%s: %s (%s)\n", msg, url, (const char *)data); + return 0; +} + +LrHandle *create_handle(const char *repo) { + // Handle represents a download configuration + LrHandle *h = lr_handle_init(); + + // --- Mandatory arguments ------------------------------------------- + gchar *metalink = + g_strconcat("https://mirrors.fedoraproject.org/metalink?repo=", repo, + "&arch=x86_64", NULL); + lr_handle_setopt(h, NULL, LRO_METALINKURL, metalink); + g_free(metalink); + // Type of repository + lr_handle_setopt(h, NULL, LRO_REPOTYPE, LR_YUMREPO); + + // --- Optional arguments -------------------------------------------- + // Make download interruptible + lr_handle_setopt(h, NULL, LRO_INTERRUPTIBLE, 1); + // Destination directory for metadata + gchar *repo_destdir = g_strconcat(DESTDIR, "/", repo, NULL); + lr_handle_setopt(h, NULL, LRO_DESTDIR, repo_destdir); + g_free(repo_destdir); + // Check checksum of all files (if checksum is available in repomd.xml) + lr_handle_setopt(h, NULL, LRO_CHECKSUM, 1); + // Download only primary.xml, comps.xml and updateinfo + // Note: repomd.xml is downloaded implicitly! + // Note: If LRO_YUMDLIST is None -> all files are downloaded + char *download_list[] = {"primary", "filelists", "group", "updateinfo", NULL}; + lr_handle_setopt(h, NULL, LRO_YUMDLIST, download_list); + + return h; +} + +int main(void) { + int rc = EXIT_SUCCESS; + GError *tmp_err = NULL; + // Prepare destination directory + struct stat buffer; + if (stat(DESTDIR, &buffer) == 0) { + system("rm -rf " DESTDIR); + } + mkdir(DESTDIR, 0777); + + const char *cbdata1 = "Callback data from target 1"; + LrHandle *h1 = create_handle("fedora-41"); + LrMetadataTarget *m1 = + lr_metadatatarget_new2(h1, (void *)cbdata1, progress_callback, + hmf_callback, end_callback, NULL, &tmp_err); + const char *cbdata2 = "Callback data from target 2"; + LrHandle *h2 = create_handle("updates-released-f41"); + LrMetadataTarget *m2 = + lr_metadatatarget_new2(h2, (void *)cbdata2, progress_callback, + hmf_callback, end_callback, NULL, &tmp_err); + + GSList *metadata_targets = NULL; + metadata_targets = g_slist_append(metadata_targets, m1); + metadata_targets = g_slist_append(metadata_targets, m2); + + if (!lr_download_metadata(metadata_targets, &tmp_err)) { + if (tmp_err != NULL) { + fprintf(stderr, "Error encountered: %s\n", tmp_err->message); + g_error_free(tmp_err); + } else { + fprintf(stderr, "Unknown error encountered\n"); + } + rc = EXIT_FAILURE; + } + + if (m1->err) { + for (GList *elem = m1->err; elem; elem = g_list_next(elem)) { + const char *msg = elem->data; + fprintf(stderr, "Error encountered: %s\n", msg); + } + rc = EXIT_FAILURE; + } + if (m2->err) { + for (GList *elem = m2->err; elem; elem = g_list_next(elem)) { + const char *msg = elem->data; + fprintf(stderr, "Error encountered: %s\n", msg); + } + rc = EXIT_FAILURE; + } + + lr_handle_free(h1); + lr_handle_free(h2); + g_slist_free_full(metadata_targets, (GDestroyNotify)lr_metadatatarget_free); + + return rc; +} diff -Nru librepo-1.19.0/examples/c/Makefile librepo-1.20.0/examples/c/Makefile --- librepo-1.19.0/examples/c/Makefile 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/examples/c/Makefile 2025-06-20 05:48:22.000000000 +0100 @@ -2,7 +2,7 @@ # export LD_LIBRARY_PATH="../../build/librepo/" CC=gcc -CFLAGS= -Wall -Wextra -g -std=c99 -O3 -I../../ `pkg-config --cflags glib-2.0` +CFLAGS= -Wall -Wextra -g -std=c99 -O3 -I../../build/librepo `pkg-config --cflags glib-2.0` LINKFLAGS= -L../../build/librepo/ -lrepo `pkg-config --libs glib-2.0` all: \ @@ -11,7 +11,8 @@ download_packages \ download_repo_with_callback \ fastestmirror \ - fastestmirror_with_callback + fastestmirror_with_callback \ + download_repos_parallel download_repo: $(CC) $(CFLAGS) download_repo.c $(LINKFLAGS) -o download_repo @@ -25,6 +26,9 @@ download_repo_with_callback: $(CC) $(CFLAGS) download_repo_with_callback.c $(LINKFLAGS) -o download_repo_with_callback +download_repos_parallel: + $(CC) $(CFLAGS) download_repos_parallel.c $(LINKFLAGS) -o download_repos_parallel + fastestmirror: $(CC) $(CFLAGS) fastestmirror.c $(LINKFLAGS) -o fastestmirror @@ -38,7 +42,8 @@ download_packages \ download_repo_with_callback \ fastestmirror \ - fastestmirror_with_callback + fastestmirror_with_callback \ + download_repos_parallel run: LD_LIBRARY_PATH="../../build/librepo/" ./download_repo diff -Nru librepo-1.19.0/examples/python/download_repos_parallel.py librepo-1.20.0/examples/python/download_repos_parallel.py --- librepo-1.19.0/examples/python/download_repos_parallel.py 1970-01-01 01:00:00.000000000 +0100 +++ librepo-1.20.0/examples/python/download_repos_parallel.py 2025-06-20 05:48:22.000000000 +0100 @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +""" +librepo - example of usage of download_metadata API +""" + +import os +import sys +import shutil + +import librepo + +DESTDIR = "downloaded_metadata" +PROGRESSBAR_LEN = 50 + +def hmf_callback_handle(data, msg, url, metadata): + """Handle mirror failure callback""" + print("%s: %s: %s (%s)" % (metadata, msg, url, data)) + sys.stdout.flush() + +def hmf_callback(data, msg, url): + """Handle mirror failure callback""" + print("%s: %s (%s)" % (msg, url, data)) + sys.stdout.flush() + +def end_callback(data, status, msg): + """End callback""" + print("End status: %s: %s (%s)" % (str(status), str(msg), str(data))) + sys.stdout.flush() + +def progress_callback(data, total_to_download, downloaded): + """Progress callback""" + if total_to_download <= 0: + return + completed = int(downloaded / (total_to_download / PROGRESSBAR_LEN)) + print( + "[%s%s] %8s/%8s (%s)\r" % ('#'*completed, '-'*(PROGRESSBAR_LEN-completed), int(downloaded), int(total_to_download), str(data)), + ) + sys.stdout.flush() + +def create_handle(repo): + # Handle represents a download configuration + h = librepo.Handle() + + # --- Mandatory arguments ------------------------------------------- + # URL of metalink + h.setopt(librepo.LRO_METALINKURL, "https://mirrors.fedoraproject.org/metalink?repo=" + repo + "&arch=x86_64") + # Type of repository + h.setopt(librepo.LRO_REPOTYPE, librepo.LR_YUMREPO) + + # --- Optional arguments -------------------------------------------- + # Make download interruptible + h.setopt(librepo.LRO_INTERRUPTIBLE, True) + # Destination directory for metadata + h.setopt(librepo.LRO_DESTDIR, DESTDIR + "/" + repo) + # Check checksum of all files (if checksum is available in repomd.xml) + h.setopt(librepo.LRO_CHECKSUM, True) + # Download only primary.xml, comps.xml and updateinfo + # Note: repomd.xml is downloaded implicitly! + # Note: If LRO_YUMDLIST is None -> all files are downloaded + h.setopt(librepo.LRO_YUMDLIST, ["primary", "group", "updateinfo"]) + + # Callback to display progress of downloading + h.setopt(librepo.LRO_PROGRESSCB, progress_callback) + # Callback to call handle mirror failure + h.setopt(librepo.LRO_HMFCB, hmf_callback_handle) + # Set user data for the callback + h.setopt(librepo.LRO_PROGRESSDATA, "Callback data set in handle") + + return h + + +if __name__ == "__main__": + # Prepare destination directory + if os.path.exists(DESTDIR): + shutil.rmtree(DESTDIR) + os.mkdir(DESTDIR) + + cbdata = "Callback data set in metadata target" + m1 = librepo.MetadataTarget(create_handle("fedora-41"), cbdata, progress_callback, hmf_callback, end_callback) + m2 = librepo.MetadataTarget(create_handle("updates-released-f41"), cbdata, progress_callback, hmf_callback, end_callback) + + ret = librepo.download_metadata([m1, m2]) + if (ret): + print(ret, file=sys.stderr) + + if (m1.err): + print(m1.err, file=sys.stderr) + if (m2.err): + print(m2.err, file=sys.stderr) diff -Nru librepo-1.19.0/.github/CODEOWNERS librepo-1.20.0/.github/CODEOWNERS --- librepo-1.19.0/.github/CODEOWNERS 1970-01-01 01:00:00.000000000 +0100 +++ librepo-1.20.0/.github/CODEOWNERS 2025-06-20 05:48:22.000000000 +0100 @@ -0,0 +1,2 @@ +# Assign everything by default to the dnf-team +* @rpm-software-management/dnf-team diff -Nru librepo-1.19.0/.github/workflows/ci.yml librepo-1.20.0/.github/workflows/ci.yml --- librepo-1.19.0/.github/workflows/ci.yml 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/.github/workflows/ci.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,130 +0,0 @@ ---- -name: DNF CI -on: pull_request_target - -jobs: - copr-build-dnf4: - name: DNF4 Copr Build - runs-on: ubuntu-latest - container: - image: ghcr.io/rpm-software-management/dnf-ci-host - outputs: - package-urls: ${{steps.copr-build.outputs.package-urls}} - steps: - - name: Check out ci-dnf-stack - uses: actions/checkout@v2 - with: - repository: rpm-software-management/ci-dnf-stack - - - name: Setup CI - id: setup-ci - uses: ./.github/actions/setup-ci - with: - copr-user: ${{secrets.COPR_USER}} - copr-api-token: ${{secrets.COPR_API_TOKEN}} - - - name: Check out sources - uses: actions/checkout@v2 - with: - path: gits/${{github.event.repository.name}} - ref: ${{github.event.pull_request.head.sha}} # check out the PR HEAD - fetch-depth: 0 - - - name: Run Copr Build - id: copr-build - uses: ./.github/actions/copr-build - with: - copr-user: ${{steps.setup-ci.outputs.copr-user}} - - copr-build-dnf5: - name: DNF5 Copr Build - runs-on: ubuntu-latest - container: - image: ghcr.io/rpm-software-management/dnf-ci-host - outputs: - package-urls: ${{steps.copr-build.outputs.package-urls}} - steps: - - name: Check out ci-dnf-stack - uses: actions/checkout@v2 - with: - repository: rpm-software-management/ci-dnf-stack - - - name: Setup CI - id: setup-ci - uses: ./.github/actions/setup-ci - with: - copr-user: ${{secrets.COPR_USER}} - copr-api-token: ${{secrets.COPR_API_TOKEN}} - - - name: Check out sources - uses: actions/checkout@v2 - with: - path: gits/${{github.event.repository.name}} - ref: ${{github.event.pull_request.head.sha}} # check out the PR HEAD - fetch-depth: 0 - - - name: Run Copr Build - id: copr-build - uses: ./.github/actions/copr-build - with: - copr-user: ${{steps.setup-ci.outputs.copr-user}} - overlay: dnf5-ci - - integration-tests-dnf4: - name: DNF4 Integration Tests - needs: copr-build-dnf4 - runs-on: ubuntu-latest - container: - image: ghcr.io/rpm-software-management/dnf-ci-host - options: --privileged - steps: - - name: Check out ci-dnf-stack - uses: actions/checkout@v2 - with: - repository: rpm-software-management/ci-dnf-stack - ref: dnf-4-stack - - - name: Run Integration Tests - uses: ./.github/actions/integration-tests - with: - package-urls: ${{needs.copr-build.outputs.package-urls}} - - integration-tests-dnf5: - name: DNF5 Integration Tests - needs: copr-build-dnf5 - runs-on: ubuntu-latest - container: - image: ghcr.io/rpm-software-management/dnf-ci-host - options: --privileged - strategy: - matrix: - extra-run-args: [--tags dnf5 --command dnf5, --tags dnf5daemon --command dnf5daemon-client] - steps: - - name: Check out ci-dnf-stack - uses: actions/checkout@v2 - with: - repository: rpm-software-management/ci-dnf-stack - - - name: Run Integration Tests - uses: ./.github/actions/integration-tests - with: - package-urls: ${{needs.copr-build.outputs.package-urls}} - extra-run-args: ${{matrix.extra-run-args}} - - ansible-tests: - name: Ansible Tests - needs: copr-build-dnf4 - runs-on: ubuntu-latest - container: - image: ghcr.io/rpm-software-management/dnf-ci-host - options: --privileged - steps: - - name: Check out ci-dnf-stack - uses: actions/checkout@v2 - with: - repository: rpm-software-management/ci-dnf-stack - - - name: Run Ansible Tests - uses: ./.github/actions/ansible-tests - with: - package-urls: ${{needs.copr-build.outputs.package-urls}} diff -Nru librepo-1.19.0/librepo/CMakeLists.txt librepo-1.20.0/librepo/CMakeLists.txt --- librepo-1.19.0/librepo/CMakeLists.txt 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/CMakeLists.txt 2025-06-20 05:48:22.000000000 +0100 @@ -70,7 +70,7 @@ ${GLIB2_LIBRARIES} ) IF (USE_GPGME) - TARGET_LINK_LIBRARIES(librepo ${GPGME_VANILLA_LIBRARIES}) + TARGET_LINK_LIBRARIES(librepo ${GPGME_LIBRARIES}) IF (ENABLE_SELINUX) TARGET_LINK_LIBRARIES(librepo ${SELINUX_LIBRARIES}) ENDIF(ENABLE_SELINUX) diff -Nru librepo-1.19.0/librepo/downloader.c librepo-1.20.0/librepo/downloader.c --- librepo-1.19.0/librepo/downloader.c 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/downloader.c 2025-06-20 05:48:22.000000000 +0100 @@ -1535,6 +1535,17 @@ if(target->zck_state == LR_ZCK_DL_FINISHED) { g_debug("%s: Target already fully downloaded: %s", __func__, target->target->path); target->state = LR_DS_FINISHED; + LrEndCb end_cb = target->target->endcb; + if (end_cb) { + int rc = end_cb(target->target->cbdata, + LR_TRANSFER_SUCCESSFUL, + "Already downloaded"); + if (rc == LR_CB_ERROR) { + g_set_error(err, LR_DOWNLOADER_ERROR, LRE_CBINTERRUPTED, + "Interrupted by LR_CB_ERROR from end callback"); + goto fail; + } + } curl_easy_cleanup(target->curl_handle); target->curl_handle = NULL; g_free(target->headercb_interrupt_reason); @@ -2910,6 +2921,26 @@ return shared_cbdata->mfcb(cbdata->userdata, msg, url); } +int +lr_metadata_target_end_func(void *ptr, LrTransferStatus status, const char *msg) +{ + int ret = LR_CB_OK; // Assume everything will be ok + LrCallbackData *cbdata = ptr; + LrSharedCallbackData *shared_cbdata = cbdata->sharedcbdata; + + LrMetadataTarget *target = shared_cbdata->target; + target->repomd_records_downloaded++; + + // We want to call the endcb only once per repo but this callback is called + // whenever a target (file) from the repo is downloaded. Call endcb only once + // all files have been downloaded. + if (target->repomd_records_to_download != target->repomd_records_downloaded) { + return ret; + } + + return shared_cbdata->endcb(cbdata->userdata, status, msg); +} + gboolean lr_download_single_cb(GSList *targets, gboolean failfast, diff -Nru librepo-1.19.0/librepo/downloader_internal.h librepo-1.20.0/librepo/downloader_internal.h --- librepo-1.19.0/librepo/downloader_internal.h 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/downloader_internal.h 2025-06-20 05:48:22.000000000 +0100 @@ -21,6 +21,7 @@ #ifndef LIBREPO_DOWNLOADER_INTERNAL_H #define LIBREPO_DOWNLOADER_INTERNAL_H +#include "librepo/metadata_downloader.h" #include G_BEGIN_DECLS @@ -32,9 +33,16 @@ LrMirrorFailureCb mfcb; /*!< Mirror failure callback */ + LrEndCb endcb; /*!< + End callback */ + GSList *singlecbdata; /*!< List of LrCallbackData */ + LrMetadataTarget * target; /*!< + Metadata target for which are these callback data shared. + It is currently only used in lr_multi_end_func to determine + if endcb should be called. */ } LrSharedCallbackData; typedef struct { @@ -44,6 +52,17 @@ LrSharedCallbackData *sharedcbdata; /*!< Shared cb data */ } LrCallbackData; +int +lr_multi_progress_func(void* ptr, + double total_to_download, + double now_downloaded); + +int +lr_multi_mf_func(void *ptr, const char *msg, const char *url); + +int +lr_metadata_target_end_func(void *ptr, LrTransferStatus status, const char *msg); + G_END_DECLS #endif //LIBREPO_DOWNLOADER_INTERNAL_H diff -Nru librepo-1.19.0/librepo/handle.c librepo-1.20.0/librepo/handle.c --- librepo-1.19.0/librepo/handle.c 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/handle.c 2025-06-20 05:48:22.000000000 +0100 @@ -1009,7 +1009,6 @@ static gboolean lr_handle_prepare_mirrorlist(LrHandle *handle, gchar *localpath, GError **err) { - assert(handle->mirrorlist_fd == -1); assert(!handle->mirrorlist_mirrors); int fd = -1; @@ -1019,6 +1018,9 @@ if (!localpath && !handle->mirrorlisturl) { // Nothing to do return TRUE; + } else if (handle->mirrorlist_fd >= 0) { + // The mirrorlist is already provided + fd = handle->mirrorlist_fd; } else if (localpath && !handle->mirrorlisturl) { // Just try to use mirrorlist of the local repository _cleanup_free_ gchar *path = lr_pathconcat(localpath, "mirrorlist", NULL); @@ -1123,7 +1125,6 @@ static gboolean lr_handle_prepare_metalink(LrHandle *handle, gchar *localpath, GError **err) { - assert(handle->metalink_fd == -1); assert(!handle->metalink_mirrors); assert(!handle->metalink); @@ -1134,6 +1135,9 @@ if (!localpath && !handle->metalinkurl) { // Nothing to do return TRUE; + } else if (handle->metalink_fd >= 0) { + // The metalink is already provided + fd = handle->metalink_fd; } else if (localpath && !handle->metalinkurl) { // Just try to use metalink of the local repository _cleanup_free_ gchar *path = lr_pathconcat(localpath, "metalink.xml", NULL); diff -Nru librepo-1.19.0/librepo/metadata_downloader.c librepo-1.20.0/librepo/metadata_downloader.c --- librepo-1.19.0/librepo/metadata_downloader.c 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/metadata_downloader.c 2025-06-20 05:48:22.000000000 +0100 @@ -26,6 +26,7 @@ #include #include #include +#include "cleanup.h" #include "librepo/librepo.h" @@ -78,7 +79,9 @@ target->progresscb = progresscb; target->mirrorfailurecb = mirrorfailure_cb; target->endcb = endcb; - target->gnupghomedir = g_string_chunk_insert(target->chunk, gnupghomedir); + if (gnupghomedir) { + target->gnupghomedir = g_string_chunk_insert(target->chunk, gnupghomedir); + } return target; } @@ -90,6 +93,8 @@ return; g_string_chunk_free(target->chunk); g_list_free_full(target->err, g_free); + lr_yum_repo_free(target->repo); + lr_yum_repomd_free(target->repomd); g_free(target); } @@ -232,6 +237,11 @@ continue; } + if (handle->fetchmirrors) { + fillInvalidationValues(fd_list, paths); + continue; + } + if (mkdir(handle->destdir, S_IRWXU) == -1 && errno != EEXIST) { lr_metadatatarget_append_error(target, "Cannot create tmpdir: %s %s", handle->destdir, g_strerror(errno)); fillInvalidationValues(fd_list, paths); @@ -259,42 +269,43 @@ handle_failure(target, fd_list, paths, err); continue; } - } - if (handle->metalink && (handle->checks & LR_CHECK_CHECKSUM)) { - lr_get_best_checksum(handle->metalink, &checksums); - } - - CbData *cbdata = lr_get_metadata_failure_callback(handle); - - download_target = lr_downloadtarget_new(target->handle, - "repodata/repomd.xml", - NULL, - fd, - NULL, - checksums, - 0, - 0, - NULL, - cbdata, - NULL, - (cbdata) ? hmfcb : NULL, - target, - 0, - 0, - NULL, - TRUE, - FALSE); + if (handle->metalink && (handle->checks & LR_CHECK_CHECKSUM)) { + lr_get_best_checksum(handle->metalink, &checksums); + } - target->download_target = download_target; - (*download_targets) = g_slist_append((*download_targets), download_target); + download_target = lr_downloadtarget_new(target->handle, + "repodata/repomd.xml", + NULL, + fd, + NULL, + checksums, + 0, + 0, + target->progresscb, + target->cbdata, + NULL, + target->mirrorfailurecb, + target, + 0, + 0, + NULL, + TRUE, + FALSE); + + target->download_target = download_target; + (*download_targets) = g_slist_append((*download_targets), download_target); + (*fd_list) = appendFdValue((*fd_list), fd); + (*paths) = appendPath((*paths), path); + } else { + fillInvalidationValues(fd_list, paths); + } - (*fd_list) = appendFdValue((*fd_list), fd); - (*paths) = appendPath((*paths), path); lr_free(path); } } +// If it returns FALSE then err is set void process_repomd_xml(GSList *targets, GSList *fd_list, @@ -306,7 +317,18 @@ elem = g_slist_next(elem), fd = g_slist_next(fd), path = g_slist_next(path)) { LrMetadataTarget *target = elem->data; - LrHandle *handle; + LrHandle *handle = target->handle; + + // No repomd should be present + if (handle->fetchmirrors) { + continue; + } + + // For an update we use repomd from the previous (first) run + if (handle->update) { + continue; + } + gboolean ret; int fd_value = *((int *) fd->data); @@ -314,7 +336,6 @@ goto fail; } - handle = target->handle; handle->used_mirror = g_strdup(target->download_target->usedmirror); handle->gnupghomedir = g_strdup(target->gnupghomedir); @@ -325,7 +346,7 @@ if (!lr_check_repomd_xml_asc_availability(handle, target->repo, fd_value, path->data, &error)) { lr_metadatatarget_append_error(target, error->message); - g_error_free(error); + g_clear_error(&error); goto fail; } @@ -334,7 +355,7 @@ "Repomd xml parser", &error); if (!ret) { lr_metadatatarget_append_error(target, "Parsing unsuccessful: %s", error->message); - g_error_free(error); + g_clear_error(&error); goto fail; } @@ -369,6 +390,7 @@ lr_downloadtarget_free(download_target); } + g_slist_free(download_targets); return ret; } @@ -388,6 +410,147 @@ return *err == NULL; } +// metadata is unused here because the LrHandle hmfcb callback is different to the LrMetadataTarget callback +static int +hmfcb_metadata_target_wrapper(void * clientp, const char *msg, const char *url, G_GNUC_UNUSED const char *metadata) { + LrMetadataTarget *target = clientp; + if (target->mirrorfailurecb) { + return target->mirrorfailurecb(target->cbdata, msg, url); + } + + return LR_CB_OK; +} + +static int +usercb_metadata_target_wrapper(void * clientp, double total_to_download, double now_downloaded) { + LrMetadataTarget *target = clientp; + if (target->progresscb) { + return target->progresscb(target->cbdata, total_to_download, now_downloaded); + } + + return LR_CB_OK; +} + +typedef struct { + LrProgressCb user_cb; + void *user_data; + LrHandleMirrorFailureCb hmfcb; +} HandleCallbacksBackup; + +static void +restore_handle_callbacks(GSList *targets, GSList *handle_callbacks_backups) +{ + assert(g_slist_length(targets) == g_slist_length(handle_callbacks_backups)); + + GSList *elem = targets; + GSList *backup_handle_elem = handle_callbacks_backups; + + for (; elem; elem = g_slist_next(elem), backup_handle_elem = g_slist_next(backup_handle_elem)) { + LrMetadataTarget *metadata_target = elem->data; + + // Restore original handle callbacks + HandleCallbacksBackup *backup = backup_handle_elem->data; + LrHandle *handle = metadata_target->handle; + if (handle) { + handle->user_cb = backup->user_cb; + handle->user_data = backup->user_data; + handle->hmfcb = backup->hmfcb; + } + lr_free(backup); + + } + g_slist_free(handle_callbacks_backups); +} + +static void +append_url_target(const char *url, LrMetadataTarget *target, GSList *download_targets) { + int fd = lr_gettmpfile(); + if (fd < 0) { + lr_metadatatarget_append_error(target, "Cannot create a temporary file for: %s", url); + return; + } + target->handle->onetimeflag_apply = TRUE; + LrDownloadTarget *download_target = lr_downloadtarget_new(target->handle, + url, + NULL, + fd, + NULL, + NULL, + 0, + 0, + target->progresscb, + target->cbdata, + NULL, + target->mirrorfailurecb, + target, + 0, + 0, + NULL, + TRUE, + FALSE); + + download_targets = g_slist_append(download_targets, download_target); +} + +static void +create_metalink_and_mirrorlist_download_targets(GSList *targets, GSList *download_targets) +{ + for (GSList *elem = targets; elem; elem = g_slist_next(elem)) { + LrMetadataTarget *target = elem->data; + LrHandle *handle; + // handle is required + assert(target->handle); + handle = target->handle; + + if (handle->offline || handle->local) { + // Handle is configured not to download + continue; + } + + // If metalink is configured but mirrors are not populated yet + // we need to download it. + if (handle->metalinkurl && !handle->metalink_mirrors) { + _cleanup_free_ gchar *url = lr_prepend_url_protocol(handle->metalinkurl); + append_url_target(url, target, download_targets); + } + // If mirrorlist is configured but mirrors are not populated yet + // we need to download it. + if (handle->mirrorlisturl && !handle->mirrorlist_mirrors) { + _cleanup_free_ gchar *url = lr_prepend_url_protocol(handle->mirrorlisturl); + append_url_target(url, target, download_targets); + } + } +} + +static gboolean +propagate_metalink_or_mirrorlist_download_targets(GSList *download_targets, GError **err) +{ + for (GSList *elem = download_targets; elem; elem = g_slist_next(elem)) { + LrDownloadTarget *download_target = elem->data; + LrMetadataTarget *target = download_target->userdata; + + if (target->handle->metalinkurl) { + target->handle->metalink_fd = download_target->fd; + } else if (target->handle->mirrorlisturl) { + target->handle->mirrorlist_fd = download_target->fd; + } else { + // The targets should download only metalinks or mirrorlists + assert(0); + } + + if (lseek(download_target->fd, 0, SEEK_SET) != 0) { + g_debug("%s: Seek error: %s", __func__, g_strerror(errno)); + g_set_error(err, LR_HANDLE_ERROR, LRE_IO, + "lseek(%d, 0, SEEK_SET) error: %s", + download_target->fd, g_strerror(errno)); + close(download_target->fd); + return FALSE; + } + } + + return TRUE; +} + gboolean lr_download_metadata(GSList *targets, GError **err) @@ -406,9 +569,60 @@ return FALSE; } + // Override handle callbacks with callbacks set from LrMetadataTargets. + // We want to consistently use LrMetadataTarget callbacks for everything. + // Store them so we can put them back once finished. + GSList *handle_callbacks_backups = NULL; + for (GSList *elem = targets; elem; elem = g_slist_next(elem)) { + LrMetadataTarget *target = elem->data; + LrHandle *handle = target->handle; + if (handle) { + HandleCallbacksBackup * backup = lr_malloc0(sizeof(HandleCallbacksBackup)); + backup->user_cb = handle->user_cb; + backup->user_data = handle->user_data; + backup->hmfcb = handle->hmfcb; + + // Use indirect callback wrappers because handle->hmfcb and target->mirrorfailurecb have different types + handle->user_data = target; + handle->user_cb = usercb_metadata_target_wrapper; + handle->hmfcb = hmfcb_metadata_target_wrapper; + + handle_callbacks_backups = g_slist_append(handle_callbacks_backups, backup); + } else { + // In case there is no handle add NULL to ensure both targets and handle_callbacks_backups have + // the same number of elements. + handle_callbacks_backups = g_slist_append(handle_callbacks_backups, NULL); + } + } + + create_metalink_and_mirrorlist_download_targets(targets, download_targets); + + // To match commit 12d0b4 (retry the metalink/mirrorlist download 3x times) + // multiply allowed_mirror_failures by 3. Since each metalink/mirrorlist has + // exactly one url (mirror) it has the same effect. + // Modify the first handle becasue that is where lr_download takes the config from. + LrHandle *first_lr_handle = ((LrDownloadTarget *) targets->data)->handle; + first_lr_handle->allowed_mirror_failures *= 3; + + if (!lr_download(download_targets, FALSE, err)) { + first_lr_handle->allowed_mirror_failures /= 3; + restore_handle_callbacks(targets, handle_callbacks_backups); + return cleanup(download_targets, err); + } + // Restore previous value. + first_lr_handle->allowed_mirror_failures /= 3; + + if (!propagate_metalink_or_mirrorlist_download_targets(download_targets, err)) { + restore_handle_callbacks(targets, handle_callbacks_backups); + return cleanup(download_targets, err); + } + lr_metadata_download_cleanup(download_targets); + download_targets = NULL; + create_repomd_xml_download_targets(targets, &download_targets, &fd_list, &paths); if (!lr_download(download_targets, FALSE, err)) { + restore_handle_callbacks(targets, handle_callbacks_backups); return cleanup(download_targets, err); } @@ -419,6 +633,7 @@ lr_yum_download_repos(targets, err); + restore_handle_callbacks(targets, handle_callbacks_backups); return cleanup(download_targets, err); } diff -Nru librepo-1.19.0/librepo/metadata_downloader.h librepo-1.20.0/librepo/metadata_downloader.h --- librepo-1.19.0/librepo/metadata_downloader.h 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/metadata_downloader.h 2025-06-20 05:48:22.000000000 +0100 @@ -75,8 +75,8 @@ /** * Create new LrMetadataTarget object. * @param handle Handle related to this download or NULL. - * @param repo Related repo. - * @param repomd Related repomd. + * @param repo Related repo (transfers ownership to the LrMetadataTarget). + * @param repomd Related repomd (transfers ownership to the LrMetadataTarget). * @param cbdata User data for the callback * @param err GError ** * @return Newly allocated LrMetadataTarget or NULL on error @@ -89,9 +89,9 @@ * Almost same as lr_metadatatarget_new() except this function * could set more callbacks. * @param handle Handle related to this download or NULL. - * @param cbdata User data for the callback - * @param progresscb Progress callback for this transfer. - * @param mirror_failure_cb Called when download from a mirror failed. + * @param cbdata User data for the callback (overrides handle user_data). + * @param progresscb Progress callback for this transfer (overrides handle user_cb). + * @param mirror_failure_cb Called when download from a mirror failed (overrides handle hmfcb). * @param endcb Callback called when target transfer is done. * @param err GError ** * @return Newly allocated LrMetadataTarget or NULL on error diff -Nru librepo-1.19.0/librepo/python/metadatatarget-py.c librepo-1.20.0/librepo/python/metadatatarget-py.c --- librepo-1.19.0/librepo/python/metadatatarget-py.c 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/python/metadatatarget-py.c 2025-06-20 05:48:22.000000000 +0100 @@ -80,12 +80,9 @@ metadatatarget_progress_callback(void *data, double total_to_download, double now_downloaded) { int ret = LR_CB_OK; - _MetadataTargetObject *self; + _MetadataTargetObject *self = (_MetadataTargetObject *)data; PyObject *user_data, *result; - LrCallbackData *callback_data = (LrCallbackData *) data; - CbData *cbdata = (CbData *) callback_data->userdata; - self = (_MetadataTargetObject *)cbdata->cbdata; if (!self || !self->progress_cb) return ret; @@ -124,13 +121,10 @@ const char *url) { int ret = LR_CB_OK; // Assume everything will be ok - _MetadataTargetObject *self; + _MetadataTargetObject *self = (_MetadataTargetObject *)data; PyObject *user_data, *result, *py_msg, *py_url; - LrCallbackData *callback_data = (LrCallbackData *) data; - CbData *cbdata = (CbData *) callback_data->userdata; - self = (_MetadataTargetObject *)cbdata->cbdata; - if (!self->mirrorfailure_cb) + if (!self || !self->mirrorfailure_cb) return ret; if (self->cb_data) @@ -178,19 +172,10 @@ const char *msg) { int ret = LR_CB_OK; // Assume everything will be ok - _MetadataTargetObject *self; + _MetadataTargetObject *self = (_MetadataTargetObject *)data; PyObject *user_data, *result, *py_msg; - LrCallbackData *callback_data = (LrCallbackData *) data; - CbData *cbdata = (CbData *) callback_data->userdata; - self = (_MetadataTargetObject *)cbdata->cbdata; - - LrMetadataTarget *target = self->target; - target->repomd_records_downloaded++; - - if (target->repomd_records_to_download != target->repomd_records_downloaded) - return ret; - else if (!self->end_cb) + if (!self || !self->end_cb) return ret; if (self->cb_data) @@ -262,7 +247,7 @@ LrMirrorFailureCb mirrorfailurecb = NULL; GError *tmp_err = NULL; - if (!PyArg_ParseTuple(args, "OOOOOs:metadatatarget_init", + if (!PyArg_ParseTuple(args, "OOOOOz:metadatatarget_init", &pyhandle, &py_cbdata, &py_progresscb, &py_mirrorfailurecb, &py_endcb, &gnupghomedir)) return -1; diff -Nru librepo-1.19.0/librepo/yum.c librepo-1.20.0/librepo/yum.c --- librepo-1.19.0/librepo/yum.c 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo/yum.c 2025-06-20 05:48:22.000000000 +0100 @@ -43,6 +43,7 @@ #include "handle_internal.h" #include "result_internal.h" #include "yum_internal.h" +#include "downloader_internal.h" #include "gpg.h" #include "cleanup.h" #include "librepo.h" @@ -904,6 +905,20 @@ return TRUE; } +gchar* join_glist_strings(GList *list, const gchar *separator) { + GString *result = g_string_new(NULL); + + for (GList *l = list; l != NULL; l = l->next) { + gchar *str = (gchar *)l->data; + g_string_append(result, str); + if (l->next != NULL) { + g_string_append(result, separator); + } + } + + return g_string_free(result, FALSE); // FALSE = return the string, not free it +} + gboolean lr_yum_download_repos(GSList *targets, GError **err) @@ -911,37 +926,112 @@ gboolean ret; GSList *download_targets = NULL; GSList *cbdata_list = NULL; + GSList *shared_cbdata_list = NULL; GError *download_error = NULL; for (GSList *elem = targets; elem; elem = g_slist_next(elem)) { - LrMetadataTarget *target = elem->data; + LrMetadataTarget *repo_target = elem->data; + GSList *repo_download_targets = NULL; - if (!target->handle) { + if (!repo_target->handle) { continue; } - prepare_repo_download_targets(target->handle, - target->repo, - target->repomd, - target, - &download_targets, + prepare_repo_download_targets(repo_target->handle, + repo_target->repo, + repo_target->repomd, + repo_target, + &repo_download_targets, &cbdata_list, &download_error); + + + // Shared data for all targets from a single repository + LrSharedCallbackData *shared_cbdata = lr_malloc0(sizeof(*shared_cbdata)); + shared_cbdata->cb = repo_target->progresscb; + shared_cbdata->mfcb = repo_target->mirrorfailurecb; + shared_cbdata->endcb = repo_target->endcb; + shared_cbdata->singlecbdata = NULL; + shared_cbdata->target = repo_target; + shared_cbdata_list = g_slist_append(shared_cbdata_list, shared_cbdata); + + for (GSList *elem = repo_download_targets; elem; elem = g_slist_next(elem)) { + LrDownloadTarget *download_target = elem->data; + LrCallbackData *lrcbdata = lr_malloc0(sizeof(*lrcbdata)); + lrcbdata->downloaded = 0.0; + lrcbdata->total = 0.0; + lrcbdata->userdata = repo_target->cbdata; + lrcbdata->sharedcbdata = shared_cbdata; + + download_target->progresscb = (repo_target->progresscb) ? lr_multi_progress_func : NULL; + download_target->mirrorfailurecb = (repo_target->mirrorfailurecb) ? lr_multi_mf_func : NULL; + download_target->endcb = (repo_target->endcb) ? lr_metadata_target_end_func : NULL; + download_target->cbdata = lrcbdata; + + shared_cbdata->singlecbdata = g_slist_append(shared_cbdata->singlecbdata, + lrcbdata); + } + + if ((g_slist_length(repo_download_targets) == 0) && (repo_target->endcb)) { + LrTransferStatus status; + const char *msg; + if (g_list_length(repo_target->err) == 0) { + // If there is nothing to download for this repo_target and + // there were no erors it is finished. + // This can happen when downloading just metalink/repomd.xml + status = LR_TRANSFER_SUCCESSFUL; + msg = "Successfully downloaded"; + } else { + // If there were errors (we failed to download/verify/parse repomd) + // for this repo_target it cannot continue and is finished. + status = LR_TRANSFER_ERROR; + const char * err_msg = join_glist_strings(repo_target->err, ","); + msg = err_msg ? err_msg : "Unknown error."; + } + int ret = repo_target->endcb(repo_target->cbdata, status, msg); + if (ret == LR_CB_ERROR) { + g_debug("%s: Downloading was aborted by LR_CB_ERROR from end callback", __func__); + g_set_error(err, LR_DOWNLOADER_ERROR, + LRE_CBINTERRUPTED, + "Interrupted by LR_CB_ERROR from end callback"); + return FALSE; + } + } else { + download_targets = g_slist_concat(download_targets, repo_download_targets); + } } if (!download_targets) { - g_propagate_error(err, download_error); + if (download_error) { + g_propagate_error(err, download_error); + } return TRUE; } - ret = lr_download_single_cb(download_targets, - FALSE, - (cbdata_list) ? progresscb : NULL, - (cbdata_list) ? hmfcb : NULL, - &download_error); + ret = lr_download(download_targets, + FALSE, + &download_error); + + if (!ret && download_error) { + g_propagate_error(err, download_error); + } - error_handling(download_targets, err, download_error); + // Propagate download target error to its metadata target + for (GSList *elem = download_targets; elem; elem = g_slist_next(elem)) { + LrDownloadTarget *target = elem->data; + if (target->err) { + LrCallbackData *lrcbdata = target->cbdata; + LrSharedCallbackData *shared_cbdata = lrcbdata->sharedcbdata; + LrMetadataTarget *metadata_target = shared_cbdata->target; + metadata_target->err = g_list_append(metadata_target->err, g_strdup(target->err)); + } + } + for (GSList *elem = shared_cbdata_list; elem; elem = g_slist_next(elem)) { + LrSharedCallbackData *shared_cbdata = elem->data; + g_slist_free_full(shared_cbdata->singlecbdata, (GDestroyNotify)lr_free); + } + g_slist_free_full(shared_cbdata_list, (GDestroyNotify)lr_free); g_slist_free_full(cbdata_list, (GDestroyNotify)cbdata_free); g_slist_free_full(download_targets, (GDestroyNotify)lr_downloadtarget_free); @@ -961,7 +1051,12 @@ assert(!err || *err == NULL); - prepare_repo_download_targets(handle, repo, repomd, NULL, &targets, &cbdata_list, err); + ret = prepare_repo_download_targets(handle, repo, repomd, NULL, &targets, &cbdata_list, err); + if (!ret) { + assert(!err || *err != NULL); + return ret; + } + assert(!err || *err == NULL); if (!targets) return TRUE; diff -Nru librepo-1.19.0/librepo.spec librepo-1.20.0/librepo.spec --- librepo-1.19.0/librepo.spec 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/librepo.spec 2025-06-20 05:48:22.000000000 +0100 @@ -30,7 +30,7 @@ %global dnf_conflict 2.8.8 Name: librepo -Version: 1.19.0 +Version: 1.20.0 Release: 1%{?dist} Summary: Repodata downloading library diff -Nru librepo-1.19.0/tests/python/tests/test_yum_package_downloading.py librepo-1.20.0/tests/python/tests/test_yum_package_downloading.py --- librepo-1.19.0/tests/python/tests/test_yum_package_downloading.py 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/tests/python/tests/test_yum_package_downloading.py 2025-06-20 05:48:22.000000000 +0100 @@ -6,6 +6,7 @@ import unittest import tempfile import xattr +import errno import tests.servermock.yum_mock.config as config @@ -758,7 +759,7 @@ "user.librepo.downloadinprogress".encode("utf-8"), "".encode("utf-8")) except IOError as err: - if err.errno == 95: + if err.errno == errno.EOPNOTSUPP: self.skipTest('extended attributes are not supported') raise diff -Nru librepo-1.19.0/VERSION.cmake librepo-1.20.0/VERSION.cmake --- librepo-1.19.0/VERSION.cmake 2024-11-04 16:54:08.000000000 +0000 +++ librepo-1.20.0/VERSION.cmake 2025-06-20 05:48:22.000000000 +0100 @@ -1,3 +1,3 @@ SET(LIBREPO_MAJOR "1") -SET(LIBREPO_MINOR "19") +SET(LIBREPO_MINOR "20") SET(LIBREPO_PATCH "0")