Add modifyrepo_c
authorTomas Mlcoch <tmlcoch@redhat.com>
Thu, 19 Sep 2013 14:20:23 +0000 (16:20 +0200)
committerTomas Mlcoch <tmlcoch@redhat.com>
Thu, 19 Sep 2013 14:20:23 +0000 (16:20 +0200)
createrepo_c.bash
src/CMakeLists.txt
src/error.c
src/error.h
src/modifyrepo_c.c [new file with mode: 0644]
src/modifyrepo_shared.c [new file with mode: 0644]
src/modifyrepo_shared.h [new file with mode: 0644]

index 2ad76d6..ed3d48a 100644 (file)
@@ -5,6 +5,11 @@ _cr_compress_type()
     COMPREPLY=( $( compgen -W "bz2 gz xz" -- "$2" ) )
 }
 
+_cr_checksum_type()
+{
+    COMPREPLY=( $( compgen -W "md5 sha sha1 sha224 sha256 sha384 sha512" -- "$2" ) )
+}
+
 _cr_createrepo()
 {
     COMPREPLY=()
@@ -24,7 +29,7 @@ _cr_createrepo()
             return 0
             ;;
         -s|--checksum)
-            COMPREPLY=( $( compgen -W 'md5 sha sha1 sha224 sha256 sha384 sha512' -- "$2" ) )
+            _cr_checksum_type "$1" "$2"
             return 0
             ;;
         -i|--pkglist|--read-pkgs-list)
@@ -104,6 +109,35 @@ _cr_mergerepo()
 } &&
 complete -F _cr_mergerepo -o filenames mergerepo_c
 
+_cr_modifyrepo()
+{
+    COMPREPLY=()
+
+    case $3 in
+        --version|-h|--help|-a|--archlist|--unique-md-filenames|--simple-md-filenames|--verbose)
+            return 0
+            ;;
+        -f|--batchfile)
+            COMPREPLY=( $( compgen -f -o plusdirs -- "$2" ) )
+            return 0
+            ;;
+        --compress-type)
+            _cr_compress_type "" "$2"
+            return 0
+            ;;
+        -s|--checksum)
+            _cr_checksum_type "$1" "$2"
+            return 0
+            ;;
+    esac
+
+    COMPREPLY=( $( compgen -W '--version --help --mdtype --remove
+        --compress --no-compress --compress-type --checksum
+        --unique-md-filenames --simple-md-filenames
+        --verbose --batchfile --new-name' -- "$2" ) )
+} &&
+complete -F _cr_modifyrepo -o filenames modifyrepo_c
+
 # Local variables:
 # mode: shell-script
 # sh-basic-offset: 4
index 6eaafcb..2c07997 100644 (file)
@@ -5,6 +5,7 @@ SET (createrepo_c_SRCS
      load_metadata.c
      locate_metadata.c
      misc.c
+     modifyrepo_shared.c
      package.c
      parsehdr.c
      parsepkg.c
@@ -32,6 +33,7 @@ SET(headers
     load_metadata.h
     locate_metadata.h
     misc.h
+    modifyrepo_shared.h
     package.h
     parsehdr.h
     parsepkg.h
@@ -74,6 +76,12 @@ TARGET_LINK_LIBRARIES(mergerepo_c
                         ${GLIB2_LIBRARIES}
                         ${GTHREAD2_LIBRARIES})
 
+ADD_EXECUTABLE(modifyrepo_c modifyrepo_c.c)
+TARGET_LINK_LIBRARIES(modifyrepo_c
+                        libcreaterepo_c
+                        ${GLIB2_LIBRARIES}
+                        ${GTHREAD2_LIBRARIES})
+
 CONFIGURE_FILE("createrepo_c.pc.cmake" "${CMAKE_SOURCE_DIR}/src/createrepo_c.pc" @ONLY)
 CONFIGURE_FILE("version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/version.h" @ONLY)
 
@@ -87,5 +95,6 @@ INSTALL(FILES "createrepo_c.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig")
 INSTALL(TARGETS libcreaterepo_c LIBRARY DESTINATION ${LIB_INSTALL_DIR})
 INSTALL(TARGETS createrepo_c DESTINATION bin/)
 INSTALL(TARGETS mergerepo_c DESTINATION bin/)
+INSTALL(TARGETS modifyrepo_c DESTINATION bin/)
 
 ADD_SUBDIRECTORY(python)
index f681b77..1015428 100644 (file)
@@ -128,6 +128,11 @@ cr_misc_error_quark(void)
     return g_quark_from_static_string("cr_misc_error");
 }
 
+GQuark cr_modifyrepo_error_quark(void)
+{
+    return g_quark_from_static_string("cr_modifyrepo_error");
+}
+
 GQuark
 cr_repomd_error_quark(void)
 {
index 7417016..a59f398 100644 (file)
@@ -89,13 +89,14 @@ typedef enum {
 const char *cr_strerror(cr_Error rc);
 
 /* Error domains */
-#define CR_CMD_ERROR                    cr_cmd_error_quark()
 #define CR_CHECKSUM_ERROR               cr_checksum_error_quark()
+#define CR_CMD_ERROR                    cr_cmd_error_quark()
 #define CR_COMPRESSION_WRAPPER_ERROR    cr_compression_wrapper_error_quark()
 #define CR_DB_ERROR                     cr_db_error_quark()
 #define CR_LOAD_METADATA_ERROR          cr_load_metadata_error_quark()
 #define CR_LOCATE_METADATA_ERROR        cr_locate_metadata_error_quark()
 #define CR_MISC_ERROR                   cr_misc_error_quark()
+#define CR_MODIFYREPO_ERROR             cr_modifyrepo_error_quark()
 #define CR_PARSEPKG_ERROR               cr_parsepkg_error_quark()
 #define CR_REPOMD_ERROR                 cr_repomd_error_quark()
 #define CR_REPOMD_RECORD_ERROR          cr_repomd_record_error_quark()
@@ -111,13 +112,14 @@ const char *cr_strerror(cr_Error rc);
 #define CR_XML_PARSER_PRI_ERROR         cr_xml_parser_pri_error_quark()
 #define CR_XML_PARSER_REPOMD_ERROR      cr_xml_parser_repomd_error_quark()
 
-GQuark cr_cmd_error_quark(void);
 GQuark cr_checksum_error_quark(void);
+GQuark cr_cmd_error_quark(void);
 GQuark cr_compression_wrapper_error_quark(void);
 GQuark cr_db_error_quark(void);
 GQuark cr_load_metadata_error_quark(void);
 GQuark cr_locate_metadata_error_quark(void);
 GQuark cr_misc_error_quark(void);
+GQuark cr_modifyrepo_error_quark(void);
 GQuark cr_parsepkg_error_quark(void);
 GQuark cr_repomd_error_quark(void);
 GQuark cr_repomd_record_error_quark(void);
diff --git a/src/modifyrepo_c.c b/src/modifyrepo_c.c
new file mode 100644 (file)
index 0000000..436c6c4
--- /dev/null
@@ -0,0 +1,330 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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.
+ */
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include "error.h"
+#include "version.h"
+#include "compression_wrapper.h"
+#include "misc.h"
+#include "locate_metadata.h"
+#include "load_metadata.h"
+#include "package.h"
+#include "repomd.h"
+#include "sqlite.h"
+#include "xml_file.h"
+#include "modifyrepo_shared.h"
+
+#define G_LOG_DOMAIN            ((gchar*) 0)
+
+typedef struct {
+
+    gboolean version;
+    gchar *mdtype;
+    gchar *remove;
+    gboolean compress;
+    gboolean no_compress;
+    gchar *compress_type;
+    gchar *checksum;
+    gboolean unique_md_filenames;
+    gboolean simple_md_filenames;
+    gboolean verbose;
+    gchar *batchfile;
+    gchar *new_name;
+
+} RawCmdOptions;
+
+static gboolean
+parse_arguments(int *argc, char ***argv, RawCmdOptions *options, GError **err)
+{
+    const GOptionEntry cmd_entries[] = {
+
+        { "version", 0, 0, G_OPTION_ARG_NONE, &(options->version),
+          "Show program's version number and exit.", NULL },
+        { "mdtype", 0, 0, G_OPTION_ARG_STRING, &(options->mdtype),
+          "Specific datatype of the metadata, will be derived from "
+          "the filename if not specified.", "MDTYPE" },
+        { "remove", 0, 0, G_OPTION_ARG_STRING, &(options->remove),
+          "Remove specified file from repodata.", NULL },
+        { "compress", 0, 0, G_OPTION_ARG_NONE, &(options->compress),
+          "Compress the new repodata before adding it to the repo. "
+          "(default)", NULL },
+        { "no-compress", 0, 0, G_OPTION_ARG_NONE, &(options->no_compress),
+          "Do not compress the new repodata before adding it to the repo.",
+          NULL },
+        { "compress-type", 0, 0, G_OPTION_ARG_STRING, &(options->compress_type),
+          "Compression format to use.", NULL },
+        { "checksum", 's', 0, G_OPTION_ARG_STRING, &(options->checksum),
+          "Specify the checksum type to use. (default: sha256)", "SUMTYPE" },
+        { "unique-md-filenames", 0, 0, G_OPTION_ARG_NONE,
+          &(options->unique_md_filenames),
+          "Include the file's checksum in the filename, helps with proxies. "
+          "(default)", NULL },
+        { "simple-md-filenames", 0, 0, G_OPTION_ARG_NONE,
+          &(options->simple_md_filenames),
+          "Do not include the file's checksum in the filename.", NULL },
+        { "verbose", 0, 0, G_OPTION_ARG_NONE, &(options->verbose),
+          "Verbose output.", NULL},
+        { "batchfile", 'f', 0, G_OPTION_ARG_STRING, &(options->batchfile),
+          "Batch file.", "BATCHFILE" },
+        { "new-name", 0, 0, G_OPTION_ARG_STRING, &(options->new_name),
+          "New filename for the file", "NEWFILENAME"},
+        { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL },
+
+    };
+
+    // Frstly, set default values
+    options->version = FALSE;
+    options->mdtype = NULL;
+    options->remove = NULL;
+    options->compress = TRUE;
+    options->no_compress = FALSE;
+    options->compress_type = NULL;
+    options->checksum = NULL;
+    options->unique_md_filenames = TRUE;
+    options->simple_md_filenames = FALSE;
+    options->verbose = FALSE;
+    options->batchfile = NULL;
+    options->new_name = NULL;
+
+    GOptionContext *context;
+    context = g_option_context_new(": Modify a repository's repomd.xml");
+    g_option_context_add_main_entries(context, cmd_entries, NULL);
+    gboolean ret = g_option_context_parse(context, argc, argv, err);
+    g_option_context_free(context);
+    return ret;
+}
+
+static gboolean
+check_arguments(RawCmdOptions *options, GError **err)
+{
+    // --no-compress
+    if (options->no_compress) {
+        options->compress = FALSE;
+        if (options->compress_type) {
+            g_warning("Use --compress-type simultaneously with --no-compress "
+                      "doesn't make a sense");
+        }
+    }
+
+    // --compress-type
+    if (options->compress_type
+        && cr_compression_type(options->compress_type) == \
+           CR_CW_UNKNOWN_COMPRESSION)
+    {
+        g_set_error(err, CR_MODIFYREPO_ERROR, CRE_ERROR,
+                    "Unknown compression type \"%s\"", options->compress_type);
+        return FALSE;
+    }
+
+    // -s/--checksum
+    if (options->checksum
+        && cr_checksum_type(options->checksum) == CR_CHECKSUM_UNKNOWN)
+    {
+        g_set_error(err, CR_MODIFYREPO_ERROR, CRE_ERROR,
+                    "Unknown checksum type \"%s\"", options->checksum);
+        return FALSE;
+    }
+
+    // --unique_md_filenames && --simple_md_filenames
+    if (options->simple_md_filenames) {
+        options->unique_md_filenames = FALSE;
+    }
+
+    // -f/--batchfile
+    if (options->batchfile
+        && !g_file_test(options->batchfile, G_FILE_TEST_IS_REGULAR)) {
+        g_set_error(err, CR_MODIFYREPO_ERROR, CRE_ERROR,
+                    "File \"%s\" doesn't exist", options->batchfile);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void
+print_usage(void)
+{
+    g_printerr(
+        "Usage: modifyrepo_c [options] <input metadata> <output repodata>\n"
+        "Usage: modifyrepo_c --remove <metadata type> <output repodata>\n"
+        "Usage: modifyrepo_c [options] --batchfile "
+        "<batch file> <output repodata>\n");
+}
+
+static gboolean
+cmd_options_to_task(GSList **modifyrepotasks,
+                    RawCmdOptions *options,
+                    gchar *metadatapath,
+                    GError **err)
+{
+    assert(modifyrepotasks);
+    assert(!err || *err == NULL);
+
+    if (!options)
+        return TRUE;
+
+    //assert(metadatapath || options->remove);
+
+    if (options->remove)
+        g_debug("Preparing remove-task for: %s", options->remove);
+    else
+        g_debug("Preparing task for: %s", metadatapath);
+
+    if (metadatapath && !g_file_test(metadatapath, G_FILE_TEST_IS_REGULAR)) {
+        g_set_error(err, CR_MODIFYREPO_ERROR, CRE_ERROR,
+                    "File \"%s\" is not regular file or doesn't exists",
+                    metadatapath);
+        return FALSE;
+    }
+
+    if (options->remove)
+        metadatapath = options->remove;
+
+    cr_ModifyRepoTask *task = cr_modifyrepotask_new();
+    task->path = cr_safe_string_chunk_insert_null(task->chunk, metadatapath);
+    task->type = cr_safe_string_chunk_insert_null(task->chunk, options->mdtype);
+    task->remove = (options->remove) ? TRUE : FALSE;
+    task->compress = options->compress;
+    task->compress_type = cr_compression_type(options->compress_type);
+    task->unique_md_filenames = options->unique_md_filenames;
+    task->checksum_type = cr_checksum_type(options->checksum);
+    task->new_name = cr_safe_string_chunk_insert_null(task->chunk,
+                                                      options->new_name);
+
+    *modifyrepotasks = g_slist_prepend(*modifyrepotasks, task);
+
+    g_debug("Task: [path: %s, type: %s, remove: %d, compress: %d, "
+            "compress_type: %d (%s), unique_md_filenames: %d, "
+            "checksum_type: %d (%s), new_name: %s]",
+            task->path, task->type, task->remove, task->compress,
+            task->compress_type, cr_compression_suffix(task->compress_type),
+            task->unique_md_filenames, task->checksum_type,
+            cr_checksum_name_str(task->checksum_type), task->new_name);
+
+    return TRUE;
+}
+
+int
+main(int argc, char **argv)
+{
+    gboolean ret = TRUE;
+    RawCmdOptions options;
+    GError *err = NULL;
+
+    // Parse arguments
+
+    parse_arguments(&argc, &argv, &options, &err);
+    if (err) {
+        g_printerr("%s\n", err->message);
+        print_usage();
+        g_error_free(err);
+        exit(EXIT_FAILURE);
+    }
+
+    // Set logging
+
+    g_log_set_default_handler(cr_log_fn, NULL);
+    if (options.verbose) {
+        // Verbose mode
+        GLogLevelFlags levels = G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_WARNING;
+        g_log_set_handler(NULL, levels, cr_log_fn, NULL);
+        g_log_set_handler("C_CREATEREPOLIB", levels, cr_log_fn, NULL);
+    } else {
+        // Standard mode
+        GLogLevelFlags levels = G_LOG_LEVEL_DEBUG;
+        g_log_set_handler(NULL, levels, cr_null_log_fn, NULL);
+        g_log_set_handler("C_CREATEREPOLIB", levels, cr_null_log_fn, NULL);
+    }
+
+    // Print version if required
+
+    if (options.version) {
+        printf("Version: %d.%d.%d\n", CR_VERSION_MAJOR,
+                                      CR_VERSION_MINOR,
+                                      CR_VERSION_PATCH);
+        exit(EXIT_SUCCESS);
+    }
+
+    // Check arguments
+
+    check_arguments(&options, &err);
+    if (err) {
+        g_printerr("%s\n", err->message);
+        print_usage();
+        g_error_free(err);
+        exit(EXIT_FAILURE);
+    }
+
+    // Prepare list of tasks to do
+
+    gchar *repodatadir = NULL;
+    GSList *modifyrepotasks = NULL;
+
+    if (!options.batchfile && !options.remove && argc == 3) {
+        // three arguments (prog, metadata, repodata_dir)
+        repodatadir = argv[2];
+        ret = cmd_options_to_task(&modifyrepotasks,
+                                  &options,
+                                  argv[1],
+                                  &err);
+    } else if (options.batchfile && argc == 2) {
+        // two arguments (prog, repodata_dir)
+        repodatadir = argv[1];
+        ret = cr_modifyrepo_parse_batchfile(options.batchfile,
+                                            &modifyrepotasks,
+                                            &err);
+    } else if (!options.batchfile && options.remove && argc == 2) {
+        // two arguments (prog, repodata_dir)
+        repodatadir = argv[1];
+        ret = cmd_options_to_task(&modifyrepotasks,
+                                  &options,
+                                  NULL,
+                                  &err);
+    } else {
+        // Bad arguments
+        print_usage();
+        exit(EXIT_FAILURE);
+    }
+
+    if (!ret) {
+        g_printerr("%s\n", err->message);
+        g_error_free(err);
+        exit(EXIT_FAILURE);
+    }
+
+    // Process the tasks
+
+    ret = cr_modifyrepo(modifyrepotasks, repodatadir, &err);
+    g_slist_free_full(modifyrepotasks, (GDestroyNotify)cr_modifyrepotask_free);
+
+    if (!ret) {
+        g_printerr("%s\n", err->message);
+        g_error_free(err);
+        exit(EXIT_FAILURE);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/src/modifyrepo_shared.c b/src/modifyrepo_shared.c
new file mode 100644 (file)
index 0000000..a29810d
--- /dev/null
@@ -0,0 +1,431 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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.
+ */
+
+#include <glib.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include "error.h"
+#include "logging.h"
+#include "misc.h"
+#include "checksum.h"
+#include "modifyrepo_shared.h"
+#include "compression_wrapper.h"
+#include "threads.h"
+#include "xml_dump.h"
+
+#define DEFAULT_COMPRESSION     CR_CW_GZ_COMPRESSION
+#define DEFAULT_CHECKSUM        CR_CHECKSUM_SHA256
+
+cr_ModifyRepoTask *
+cr_modifyrepotask_new(void)
+{
+    cr_ModifyRepoTask *task = g_new0(cr_ModifyRepoTask, 1);
+    task->chunk = g_string_chunk_new(16);
+    return task;
+}
+
+void
+cr_modifyrepotask_free(cr_ModifyRepoTask *task)
+{
+    if (!task) return;
+    g_string_chunk_free(task->chunk);
+    g_free(task);
+}
+
+gboolean
+cr_modifyrepo(GSList *modifyrepotasks, gchar *repopath, GError **err)
+{
+    assert(!err || *err == NULL);
+
+    if (!modifyrepotasks) {
+        g_debug("%s: No tasks to process", __func__);
+        return TRUE;
+    }
+
+    // Parse repomd.xml
+
+    gchar *repomd_path = g_build_filename(repopath, "repomd.xml", NULL);
+    if (!g_file_test(repomd_path, G_FILE_TEST_IS_REGULAR)) {
+        g_set_error(err, CR_MODIFYREPO_ERROR, CRE_IO,
+                    "Regular file \"%s\" doesn't exists", repomd_path);
+        g_free(repomd_path);
+        return FALSE;
+    }
+
+    cr_Repomd *repomd = cr_repomd_new();
+    int rc = cr_xml_parse_repomd(repomd_path, repomd, cr_warning_cb,
+                                  "Repomd XML parser", err);
+    if (rc != CRE_OK) {
+        g_debug("%s: Error while parsing repomd.xml", __func__);
+        cr_repomd_free(repomd);
+        g_free(repomd_path);
+        return FALSE;
+    }
+
+    // TODO:
+    // (?) Autodetect used checksum_type
+    // (?) Autodetect if unique_md_filenames are used
+
+    // Prepare tasks
+
+    for (GSList *elem = modifyrepotasks; elem; elem = g_slist_next(elem)) {
+        cr_ModifyRepoTask *task = elem->data;
+
+        if (!task->type) {
+            // If type is not specified, derive it from path or new name
+            gchar *basename;
+
+            if (task->new_name)
+                basename = g_path_get_basename(task->new_name);
+            else
+                basename = g_path_get_basename(task->path);
+
+            // Split at first '.' in filename and use only first part
+            for (gchar *tmp=basename; *tmp; tmp++) {
+                if (*tmp == '.') {
+                    *tmp = '\0';
+                    break;
+                }
+            }
+
+            task->type = cr_safe_string_chunk_insert_null(task->chunk,
+                                                          basename);
+            g_debug("%s: Use derived type \"%s\" (%s)",
+                    __func__, task->type, basename);
+            g_free(basename);
+        }
+
+        if (task->remove)
+            continue;
+
+        if (task->compress && task->compress_type == CR_CW_UNKNOWN_COMPRESSION)
+            // If compression enabled but type not specified, use default
+            task->compress_type = DEFAULT_COMPRESSION;
+
+        if (task->checksum_type == CR_CHECKSUM_UNKNOWN)
+            // If no checksum type specified, use default
+            task->checksum_type = DEFAULT_CHECKSUM;
+    }
+
+    // Check tasks
+
+    for (GSList *elem = modifyrepotasks; elem; elem = g_slist_next(elem)) {
+        cr_ModifyRepoTask *task = elem->data;
+
+        if (task->remove) {
+
+            // Check if metadata of a type that should be removed
+            // exists in repomd
+            if (!cr_repomd_get_record(repomd, task->type))
+                g_warning("Record of type \"%s\", which should be removed, "
+                          "doesn't exist in repomd.xml", task->path);
+
+            if (task->new_name)
+                g_warning("Use remove with new_name doesn't make a sense");
+
+        } else {
+
+            // Check if file exists
+            if (!g_file_test(task->path, G_FILE_TEST_IS_REGULAR)) {
+                g_debug("%s: Regular file \"%s\" doesn't exist",
+                        __func__, task->path);
+                cr_repomd_free(repomd);
+                g_free(repomd_path);
+                return FALSE;
+            }
+
+            // Check if new_name is not empty string
+            if (task->new_name) {
+                if (!g_strcmp0(task->new_name, "")) {
+                    g_debug("%s: New name cannot be empty", __func__);
+                    cr_repomd_free(repomd);
+                    g_free(repomd_path);
+                    return FALSE;
+                }
+            }
+
+            // Check if record with this name doesn't exists yet
+            if (cr_repomd_get_record(repomd, task->type))
+                g_warning("Record with type \"%s\" already exists "
+                          "in repomd.xml", task->type);
+
+        }
+    }
+
+    //
+    // Modifications of the target repository starts here
+    //
+
+    // Add (copy) new metadata to repodata/ directory
+    for (GSList *elem = modifyrepotasks; elem; elem = g_slist_next(elem)) {
+        cr_ModifyRepoTask *task = elem->data;
+
+        if (task->remove)
+            // Skip removing task
+            continue;
+
+        gchar *src_fn = task->path;
+        gchar *dst_fn = NULL;
+        const gchar *suffix = NULL;
+        cr_CompressionType compress_type = CR_CW_NO_COMPRESSION;
+
+        if (task->compress) {
+            compress_type = task->compress_type;
+            suffix = cr_compression_suffix(compress_type);
+        }
+
+        // Prepare dst filename
+        gchar *filename = NULL;
+        if (task->new_name)
+            filename = g_path_get_basename(task->new_name);
+        else
+            filename = g_path_get_basename(src_fn);
+
+        if (suffix) {
+            gchar *tmp_fn = g_strconcat(filename, suffix, NULL);
+            g_free(filename);
+            filename = tmp_fn;
+        }
+
+        dst_fn = g_build_filename(repopath, filename, NULL);
+        g_free(filename);
+
+        if (g_file_test(dst_fn, G_FILE_TEST_EXISTS)) {
+            g_warning("Destination file \"%s\" already exists and will be "
+                      "overwritten", dst_fn);
+        }
+
+        // Do the copy
+        g_debug("%s: Copy & compress operation %s -> %s",
+                 __func__, src_fn, dst_fn);
+
+        if (cr_compress_file(src_fn, dst_fn, compress_type, err) != CRE_OK) {
+            g_debug("%s: Copy & compress operation failed", __func__);
+            cr_repomd_free(repomd);
+            g_free(repomd_path);
+            g_free(dst_fn);
+            return FALSE;
+        }
+
+        task->repopath = cr_safe_string_chunk_insert_null(task->chunk, dst_fn);
+        g_free(dst_fn);
+    }
+
+    // Prepare new repomd records
+    GSList *repomdrecords = NULL;
+    GSList *repomdrecords_uniquefn = NULL;
+    GSList *repomdrecordfilltasks = NULL;
+
+    GThreadPool *fill_pool = g_thread_pool_new(cr_repomd_record_fill_thread,
+                                               NULL, 5, FALSE, NULL);
+
+    for (GSList *elem = modifyrepotasks; elem; elem = g_slist_next(elem)) {
+        cr_ModifyRepoTask *task = elem->data;
+
+        if (task->remove)
+            continue;
+
+        cr_RepomdRecord *rec = cr_repomd_record_new(task->type,
+                                                    task->repopath);
+        cr_RepomdRecordFillTask *filltask = cr_repomdrecordfilltask_new(rec,
+                                            task->checksum_type, NULL);
+        g_thread_pool_push(fill_pool, filltask, NULL);
+
+        repomdrecords = g_slist_prepend(repomdrecords, rec);
+        if (task->unique_md_filenames)
+            repomdrecords_uniquefn = g_slist_prepend(repomdrecords_uniquefn, rec);
+        repomdrecordfilltasks = g_slist_prepend(repomdrecordfilltasks,
+                                                filltask);
+    }
+
+    g_thread_pool_free(fill_pool, FALSE, TRUE); // Wait
+
+    for (GSList *elem = repomdrecordfilltasks; elem; elem = g_slist_next(elem)) {
+        // Clean up tasks
+        cr_RepomdRecordFillTask *filltask = elem->data;
+        cr_repomdrecordfilltask_free(filltask, NULL);
+    }
+    g_slist_free(repomdrecordfilltasks);
+
+    // Detach records from repomd
+    GSList *recordstoremove = NULL;
+    for (GSList *elem = modifyrepotasks; elem; elem = g_slist_next(elem)) {
+        cr_ModifyRepoTask *task = elem->data;
+
+        // Remove both, records that will be removed but also
+        // records with types that will be added.
+        cr_RepomdRecord *rec = cr_repomd_get_record(repomd, task->type);
+        if (rec) {
+            g_debug("%s: Removing record \"%s\" from repomd.xml",
+                    __func__, task->type);
+            recordstoremove = g_slist_prepend(recordstoremove, rec);
+            cr_repomd_detach_record(repomd, rec);
+        }
+    }
+
+    // Prepend checksum
+    for (GSList *elem = repomdrecords_uniquefn;
+         elem;
+         elem = g_slist_next(elem))
+    {
+        cr_RepomdRecord *rec = elem->data;
+        cr_repomd_record_rename_file(rec, NULL);
+    }
+    g_slist_free(repomdrecords_uniquefn);
+
+    // Add records into repomd
+    for (GSList *elem = repomdrecords; elem; elem = g_slist_next(elem)) {
+        cr_RepomdRecord *rec = elem->data;
+        cr_repomd_set_record(repomd, rec);
+    }
+    g_slist_free(repomdrecords);
+
+    // Write repomd.xml
+    gchar *repomd_xml = cr_xml_dump_repomd(repomd, NULL);
+    printf("REPOMD.XML:\n%s", repomd_xml);
+
+    g_debug("%s: Writing modified %s", __func__, repomd_path);
+    gboolean ret = cr_write_to_file(err, repomd_path, "%s", repomd_xml);
+
+    g_free(repomd_xml);
+    g_free(repomd_path);
+
+    if (!ret) {
+        assert(!err || *err);
+        cr_repomd_free(repomd);
+        return FALSE;
+    }
+
+    // Delete files of removed records
+    for (GSList *elem = recordstoremove; elem; elem = g_slist_next(elem)) {
+        cr_RepomdRecord *rec = elem->data;
+
+        if (rec->location_base)
+            // Do not even try to remove records with base location
+            continue;
+
+        // Firstly check if the file that should be deleted isn't
+        // really used by other record anymore.
+        // It could happend if user add a file, that already exists,
+        // in repodata. Then we don't want to remove this file.
+        gboolean remove_this = TRUE;
+        for (GSList *e = repomd->records; e; e = g_slist_next(e)) {
+            cr_RepomdRecord *lrec = e->data;
+
+            if (!g_strcmp0(rec->location_href, lrec->location_href)) {
+                remove_this = FALSE;
+                break;
+            }
+        }
+
+        if (!remove_this)
+            break;
+
+        gchar *realpath = g_build_filename(repopath,
+                                           "../",
+                                           rec->location_href,
+                                           NULL);
+
+        g_debug("%s: Removing \"%s\"", __func__, realpath);
+
+        if (remove(realpath) == -1)
+            g_warning("Cannot remove \"%s\": %s", realpath, strerror(errno));
+        g_free(realpath);
+    }
+    g_slist_free_full(recordstoremove, (GDestroyNotify)cr_repomd_record_free);
+
+    cr_repomd_free(repomd);
+
+    return TRUE;
+}
+
+gboolean
+cr_modifyrepo_parse_batchfile(const gchar *path,
+                              GSList **modifyrepotasks,
+                              GError **err)
+{
+    assert(!err || *err == NULL);
+
+    if (!path)
+        return TRUE;
+
+    GKeyFile *keyfile = g_key_file_new();
+
+    gboolean ret = TRUE;
+    ret = g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, err);
+    if (!ret) {
+        g_debug("%s: Parsing of modifyrepo batchfile failed", __func__);
+        return FALSE;
+    }
+
+    gsize length;
+    gchar **groups = g_key_file_get_groups(keyfile, &length);
+    GSList *tasks = NULL;
+    gboolean success = TRUE;
+    for (gsize x = 0; x < length; x++) {
+        gchar *group = groups[x];
+        assert(group);
+
+        g_debug("%s: Group: %s", __func__, group);
+
+        cr_ModifyRepoTask *task = cr_modifyrepotask_new();
+        tasks = g_slist_append(tasks, task);
+
+        gchar *tmp_str;
+
+        task->path = cr_safe_string_chunk_insert(task->chunk, group);
+        task->type = cr_safe_string_chunk_insert_and_free(task->chunk,
+                    g_key_file_get_string(keyfile, group, "type", NULL));
+        task->remove = cr_key_file_get_boolean_default(keyfile, group,
+                   "remove", FALSE, NULL);
+        task->compress = cr_key_file_get_boolean_default(keyfile, group,
+                   "compress", TRUE, NULL);
+        tmp_str = g_key_file_get_string(keyfile, group, "compress-type", NULL);
+        task->compress_type = cr_compression_type(tmp_str);
+        g_free(tmp_str);
+        task->unique_md_filenames = cr_key_file_get_boolean_default(keyfile,
+                    group, "unique-md-filenames", TRUE, NULL);
+        tmp_str = g_key_file_get_string(keyfile, group, "checksum", NULL);
+        task->checksum_type = cr_checksum_type(tmp_str);
+        g_free(tmp_str);
+        task->new_name = cr_safe_string_chunk_insert_and_free(task->chunk,
+                    g_key_file_get_string(keyfile, group, "new-name", NULL));
+
+        g_debug("Task: [path: %s, type: %s, remove: %d, compress: %d, "
+                "compress_type: %d (%s), unique_md_filenames: %d, "
+                "checksum_type: %d (%s), new_name: %s]",
+                task->path, task->type, task->remove, task->compress,
+                task->compress_type, cr_compression_suffix(task->compress_type),
+                task->unique_md_filenames, task->checksum_type,
+                cr_checksum_name_str(task->checksum_type), task->new_name);
+
+ }
+
+    g_strfreev(groups);
+
+    if (success) {
+        *modifyrepotasks = g_slist_concat(*modifyrepotasks, tasks);
+    } else {
+        g_slist_free_full(tasks, (GDestroyNotify)cr_modifyrepotask_free);
+    }
+
+    g_key_file_free(keyfile);
+    return success;
+}
diff --git a/src/modifyrepo_shared.h b/src/modifyrepo_shared.h
new file mode 100644 (file)
index 0000000..121625e
--- /dev/null
@@ -0,0 +1,76 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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.
+ */
+
+#ifndef __C_CREATEREPOLIB_MODIFYREPO_SHARED_H__
+#define __C_CREATEREPOLIB_MODIFYREPO_SHARED_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "checksum.h"
+#include "compression_wrapper.h"
+#include "package.h"
+
+/** \defgroup   modifyrepo_shared   Modifyrepo API.
+ *
+ * Module with modifyrepo API
+ *
+ *  \addtogroup modifyrepo_shared
+ *  @{
+ */
+
+typedef struct {
+
+    gchar *path;
+    gchar *type;
+    gboolean remove;
+    gboolean compress;
+    cr_CompressionType compress_type;
+    gboolean unique_md_filenames;
+    cr_ChecksumType checksum_type;
+    gchar *new_name;
+
+    // Internal use
+    gchar *repopath;
+    GStringChunk *chunk;
+
+} cr_ModifyRepoTask;
+
+cr_ModifyRepoTask *
+cr_modifyrepotask_new(void);
+
+void
+cr_modifyrepotask_free(cr_ModifyRepoTask *task);
+
+gboolean
+cr_modifyrepo(GSList *modifyrepotasks, gchar *repopath, GError **err);
+
+gboolean
+cr_modifyrepo_parse_batchfile(const gchar *path,
+                              GSList **modifyrepotasks,
+                              GError **err);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __C_CREATEREPOLIB_MODIFYREPO_SHARED__ */