dav/scfg.c

Sat, 06 Apr 2019 14:25:07 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 06 Apr 2019 14:25:07 +0200
changeset 549
af91d3c96994
parent 539
8deb52292c99
child 576
62cc92445234
permissions
-rw-r--r--

add hashing config

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2018 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libidav/utils.h>
#include <ucx/map.h>
#include <ucx/utils.h>

#include "scfg.h"
#include "config.h"

#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)


#define print_error(lineno, ...) \
    do {\
        fprintf(stderr, "Error (sync.xml line %u): ", lineno); \
        fprintf(stderr, __VA_ARGS__); \
        fprintf(stderr, "Abort.\n"); \
    } while(0);
#define print_warning(lineno, ...) \
    do {\
        fprintf(stderr, "Warning (sync.xml line %u): ", lineno); \
        fprintf(stderr, __VA_ARGS__); \
    } while(0);

#ifdef _WIN32
#define ENV_HOME getenv("USERPROFILE")
#else
#define ENV_HOME getenv("HOME")
#endif /* _WIN32 */

static UcxMap *directories;

UcxMapIterator scfg_directory_iterator() {
    return ucx_map_iterator(directories);
}

static int create_default_sync_config(char *file) {
    FILE *out = fopen(file, "w");
    if(!out) {
        perror("Cannot create config file");
        return -1;
    }
    
    fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", out);
    fputs("<configuration>\n", out);
    fputs("</configuration>\n", out);
    fclose(out);
    
    return 0;
}

static UcxList* add_regex_pattern(UcxList *list, char *value,
        unsigned short xmlline) {
    regex_t *regex = malloc(sizeof(regex_t));
    if (regcomp(regex, value, REG_EXTENDED|REG_NOSUB)) {
        print_warning(xmlline,
                "Invalid regular expression (%s) ... skipped\n", value);
        free(regex);
        return list;
    } else {
        return ucx_list_append(list, regex);
    }
}

static int scfg_load_filter(
        xmlNode *node,
        UcxList **include,
        UcxList **exclude,
        UcxList **tags)
{
    node = node->children;
    
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            char *value = util_xml_get_text(node);
            if(xstreq(node->name, "include")) {
                if(value) {
                    *include = add_regex_pattern(*include, value, node->line);
                }
            } else if(xstreq(node->name, "exclude")) {
                if(value) {
                    *exclude = add_regex_pattern(*exclude, value, node->line);
                }
            } else if(xstreq(node->name, "tags")) {
                if(value) {
                    SyncTagFilter *tagfilter = parse_tagfilter_string(
                            value, DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
                    if(!tagfilter) {
                        print_error(
                                node->line,
                                "malformed tag filter: %s\n",
                                value);
                        return 1;
                    } else {
                        // get scope
                        xmlChar *scope = xmlGetNoNsProp(node, BAD_CAST "scope");
                        if(scope) {
                            if(xstreq(scope, "resource"))
                            {
                                tagfilter->scope = 
                                        DAV_SYNC_TAGFILTER_SCOPE_RESOURCE;
                            } else if(xstreq(scope, "collection")) {
                                tagfilter->scope = 
                                        DAV_SYNC_TAGFILTER_SCOPE_COLLECTION;
                            } else if(xstreq(scope, "all")) {
                                tagfilter->scope = 
                                        DAV_SYNC_TAGFILTER_SCOPE_RESOURCE
                                      | DAV_SYNC_TAGFILTER_SCOPE_COLLECTION;
                            } else {
                                tagfilter->scope =
                                        DAV_SYNC_TAGFILTER_SCOPE_RESOURCE;
                            }
                        }
                        xmlFree(scope);
                        
                        *tags = ucx_list_append(*tags, tagfilter);
                    }
                }
            } else {
                print_error(node->line,
                        "unknown filter config element: %s\n", node->name);
                return 1;
            }
            if(!value) {
                print_error(node->line,
                        "missing value for filter: %s\n", node->name);
                return 1;
            }
        }
        
        node = node->next;
    }
    
    return 0;
}

static TagFormat str2tagformat(const char *str) {
    if(!strcmp(str, "text")) {
        return TAG_FORMAT_TEXT;
    } else if(!strcmp(str, "csv")) {
        return TAG_FORMAT_CSV;
    } else if(!strcmp(str, "xml")) {
        return TAG_FORMAT_XML;
    } else if(!strcmp(str, "macos")) {
        return TAG_FORMAT_MACOS;
    }
    return TAG_FORMAT_UNKNOWN;
}

#define CHECK_VALUE(element, value) if(!(value)) \
        print_error(element->line, "missing value element: %s\n", element->name);

static TagConfig* parse_tagconfig(xmlNode *node) {
    TagConfig conf;
    conf.store = TAG_STORE_XATTR;
    conf.local_format = TAG_FORMAT_TEXT;
    conf.server_format = TAG_FORMAT_XML;
    conf.xattr_name = NULL;
    conf.detect_changes = false;
    conf.conflict = TAG_NO_CONFLICT;
    xmlNode *c = node->children;
    
    // TODO: error handling
    while(c) {
        if(c->type == XML_ELEMENT_NODE) {
            char *value = util_xml_get_text(c);
            if(xstreq(c->name, "local-store")) {
                CHECK_VALUE(c, value);
                if(xstreq(value, "xattr")) {
                    conf.store = TAG_STORE_XATTR;
                } else {
                    return NULL;
                }
                
                xmlAttr *attr = c->properties;
                xmlChar *format = xmlGetNoNsProp(node, BAD_CAST "format");
                if(format) {
                    conf.local_format = str2tagformat((char*)format);
                    xmlFree(format);
                }
            } else if(xstreq(c->name, "detect-changes")) {
                CHECK_VALUE(c, value);
                conf.detect_changes = util_getboolean(value);
            } else if(xstreq(c->name, "xattr-name")) {
                if(!value) {
                    return NULL;
                }
                conf.xattr_name = strdup(value);
            } else if(xstreq(c->name, "on-conflict")) {
                CHECK_VALUE(c, value);
                if(xstreq(value, "no_conflict")) {
                    conf.conflict = TAG_NO_CONFLICT;
                } else if(xstreq(value, "keep_local")) {
                    conf.conflict = TAG_KEEP_LOCAL;
                } else if(xstreq(value, "keep_remote")) {
                    conf.conflict = TAG_KEEP_REMOTE;
                } else if(xstreq(value, "merge")) {
                    conf.conflict = TAG_MERGE;
                } else {
                    fprintf(stderr, "on-conflict: unknown value: %s\n", value);
                    return NULL;
                }
            }
        }
        c = c->next;
    }
    
    if(conf.store == TAG_STORE_XATTR && !conf.xattr_name) {
        switch(conf.local_format) {
            default:
            case TAG_FORMAT_TEXT: 
            case TAG_FORMAT_CSV: 
            case TAG_FORMAT_XML: conf.xattr_name = strdup(DEFAULT_TAG_XATTR); break;
            case TAG_FORMAT_MACOS: conf.xattr_name = strdup(MACOS_TAG_XATTR); break;
        }
    }
    
    TagConfig *tagconfig = malloc(sizeof(TagConfig));
    *tagconfig = conf;
    return tagconfig;
}

static SplitConfig* parse_split(xmlNode *node) {
    char *pattern = NULL;
    char *minsize = NULL;
    char *blocksize = NULL;
    
    xmlNode *c = node->children;
    while(c) {
        char *value = util_xml_get_text(c);
        if(xstreq(c->name, "pattern")) {
            pattern = value;
        } else if(xstreq(c->name, "minsize")) {
            minsize = value;
        } else if(xstreq(c->name, "blocksize")) {
            blocksize = value;
        }
        c = c->next;
    }
    
    uint64_t sz = 0;
    if(!blocksize) {
        fprintf(stderr, "splitconfig: no blocksize specified\n");
        return NULL;
    }
    size_t bsz_len = strlen(blocksize);
    if(bsz_len < 2) {
        fprintf(stderr, "splitconfig: blocksize too small\n");
        return NULL;
    }
    
    if(!util_szstrtouint(blocksize, &sz)) {
        fprintf(stderr, "splitconfig: blocksize is not a number\n");
        return NULL;
    }
    
    if(!pattern && !minsize) {
        fprintf(stderr, "splitconfig: pattern or minsize must be specified\n");
        return NULL;
    }
    
    int64_t minsz = -1;
    if(minsize) {
        uint64_t m;
        if(!util_szstrtouint(minsize, &m)) {
            fprintf(stderr, "splitconfig: minsize is not a number\n");
            return NULL;
        }
        minsz = (int64_t)m;
    }
    
    SplitConfig *sc = calloc(1, sizeof(SplitConfig));
    if(pattern) {
        regex_t *regex = malloc(sizeof(regex_t));
        if (regcomp(regex, pattern, REG_EXTENDED|REG_NOSUB)) {
            fprintf(stderr, "Invalid regular expression (%s)\n", pattern);
        } else {
            sc->pattern = regex;
        }
    }
    sc->minsize = minsz;
    sc->blocksize = (size_t)sz;
    return sc;    
}

static UcxList* parse_splitconfig(xmlNode *node, int *error) {
    UcxList *splitconfig = NULL;
    int err = 0;
    xmlNode *c = node->children;
    while(c) {
        if(c->type == XML_ELEMENT_NODE && xstreq(c->name, "split")) {
            SplitConfig *sc = parse_split(c);
            if(sc) {
                splitconfig = ucx_list_append(splitconfig, sc);
            } else {
                err = 1;
                break;
            }
        }
        c = c->next;
    }
    
    if(error) {
        *error = err;
    }
    return splitconfig;
}

static Versioning* parse_versioning_config(xmlNode *node) {
    Versioning v;
    v.always = FALSE;
    v.type = VERSIONING_SIMPLE;
    v.collection = "/.dav-version-history";
    
    xmlNode *c = node->children;
    while(c) {
        if(c->type == XML_ELEMENT_NODE) {
            char *value = util_xml_get_text(c);
            if(xstreq(c->name, "type")) {
                CHECK_VALUE(c, value);
                if(!strcmp(value, "simple")) {
                    v.type = VERSIONING_SIMPLE;
                } else if(!strcmp(value, "deltav")) {
                    v.type = VERSIONING_DELTAV;
                } else {
                    return NULL;
                }
            } else if(xstreq(c->name, "collection")) {
                CHECK_VALUE(c, value);
                v.collection = value;
            } else if(xstreq(c->name, "always")) {
                CHECK_VALUE(c, value);
                v.always = util_getboolean(value);
            }
        }
        c = c->next;
    }
    
    v.collection = strdup(v.collection); 
    
    Versioning *versioning = malloc(sizeof(Versioning));
    *versioning = v;
    return versioning;
}

static int scfg_load_directory(xmlNode *node) {
    char *name = NULL;
    char *path = NULL;
    char *trash = NULL;
    char *collection = NULL;
    char *repository = NULL;
    char *database = NULL;
    TagConfig *tagconfig = NULL;
    Versioning *versioning = NULL;
    UcxList *include = NULL;
    UcxList *exclude = NULL;
    UcxList *tagfilter = NULL;
    UcxList *splitconfig = NULL;
    int max_retry = 0;
    int allow_cmd = SYNC_CMD_PULL | SYNC_CMD_PUSH
                  | SYNC_CMD_ARCHIVE | SYNC_CMD_RESTORE;
    bool backuppull = false;
    bool lockpull = false;
    bool lockpush = false;
    bool hashing = false;
    bool detect_copy = false;
    time_t lock_timeout = 0;
    uint32_t metadata = 0;
    
    unsigned short parentlineno = node->line;
    node = node->children;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            char *value = util_xml_get_text(node);
            /* every key needs a value */
            if(!value) {
                /* TODO: maybe this should only be reported, if the key is valid
                 * But this makes the code very ugly.
                 */
                print_error(node->line,
                        "missing value for directory element: %s\n",
                        node->name);
                return 1;
            }
            if(xstreq(node->name, "name")) {
                name = value;
            } else if(xstreq(node->name, "path")) {
                path = value;
            } else if(xstreq(node->name, "trash")) {
                trash = value;
            } else if(xstreq(node->name, "collection")) {
                collection = value;
            } else if(xstreq(node->name, "repository")) {
                repository = value;
            } else if(xstreq(node->name, "filter")) {
                if(scfg_load_filter(node, &include, &exclude, &tagfilter)) {
                    return 1;
                }
            } else if(xstreq(node->name, "database")) {
                database = value;
            } else if(xstreq(node->name, "tagconfig")) {
                tagconfig = parse_tagconfig(node);
            } else if(xstreq(node->name, "splitconfig")) {
                int err = 0;
                splitconfig = parse_splitconfig(node, &err);
                if(err) {
                    return 1;
                }
            } else if(xstreq(node->name, "metadata")) {
                char *error = NULL;
                metadata = parse_finfo_settings(value, &error);
                if(error) {
                    print_error(node->line, "unknown metadata: %s\n", error);
                    free(error);
                }
            } else if(xstreq(node->name, "versioning")) {
                versioning = parse_versioning_config(node);
            } else if(xstreq(node->name, "max-retry")) {
                int64_t i;
                if(util_strtoint(value, &i) && i >= 0) {
                    max_retry = (int)i;
                } else {
                    print_warning(node->line, "unsigned integer value "
                            "expected in <max-retry> element\n");
                }
            } else if(xstreq(node->name, "allow-cmd")) {
                int cmds = 0;
                const char *delims = " ,\r\n";
                char *cmdstr = strdup(value);
                char *cmd = strtok(cmdstr, delims);
                while(cmd) {
                    if(!strcmp(cmd, "pull")) {
                        cmds |= SYNC_CMD_PULL;
                    } else if(!strcmp(cmd, "push")) {
                        cmds |= SYNC_CMD_PUSH;
                    } else if(!strcmp(cmd, "archive")) {
                        cmds |= SYNC_CMD_ARCHIVE;
                    } else if(!strcmp(cmd, "restore")) {
                        cmds |= SYNC_CMD_RESTORE;
                    }
                    cmd = strtok(NULL, delims);
                }
                free(cmdstr);
                allow_cmd = cmds;
                
            } else if(xstreq(node->name, "backup-on-pull")) {
                backuppull = util_getboolean(value);
            } else if(xstreq(node->name, "lock-pull")) {
                lockpull = util_getboolean(value);
            } else if(xstreq(node->name, "lock-push")) {
                lockpush = util_getboolean(value);
            } else if(xstreq(node->name, "lock-timeout")) {
                int64_t t = 0;
                if(util_strtoint(value, &t)) {
                    lock_timeout = (time_t)t;
                } else {
                    print_warning(node->line, "integer value "
                            "expected in <lock-timeout> element\n");
                }
            } else if(xstreq(node->name, "hashing")) {
                hashing = util_getboolean(value);
            } else {
                print_error(node->line,
                        "unknown directory config element: %s\n", node->name);
                return 1;
            }
        }
        node = node->next;
    }
    
    if(!name) {
        print_error(parentlineno, "missing name element for directory\n");
        return 1;
    }
    if(!path) {
        print_error(parentlineno,
                "missing path element for directory %s\n", name);
        return 1;
    }
    if(!repository) {
        print_error(parentlineno,
                "missing repository element for directory %s\n", name);
        return 1;
    }
    if(!database) {
        print_error(parentlineno,
                "missing database element for directory %s\n", name);
        return 1;
    }
    
    SyncDirectory *dir = calloc(1, sizeof(SyncDirectory));
    dir->name = strdup(name);
    dir->path = scfg_create_path(path);
    dir->collection = collection ? strdup(collection) : NULL;
    dir->repository = strdup(repository);
    dir->database = strdup(database);
    dir->tagconfig = tagconfig;
    dir->versioning = versioning;
    dir->max_retry = max_retry;
    dir->allow_cmd = allow_cmd;
    dir->backuppull = backuppull;
    dir->lockpull = lockpull;
    dir->lockpush = lockpush;
    dir->hashing = hashing;
    dir->lock_timeout = lock_timeout;
    dir->metadata = metadata;
    dir->splitconfig = splitconfig;
    if((metadata & FINFO_MODE) == FINFO_MODE) {
        dir->db_settings = DB_STORE_MODE;
    }
    if((metadata & FINFO_OWNER) == FINFO_OWNER) {
        dir->db_settings |= DB_STORE_OWNER;
    }
    
    if (include) {
        dir->include = include;
    } else {
        regex_t *matchall = malloc(sizeof(regex_t));
        regcomp(matchall, ".*", REG_NOSUB);
        dir->include = ucx_list_append(NULL, matchall);
    }
    if (exclude) {
        dir->exclude = exclude;
    } else {
        regex_t *matchnothing = malloc(sizeof(regex_t));
        regcomp(matchnothing, "///", REG_NOSUB);
        dir->exclude = ucx_list_append(NULL, matchnothing);
    }
    dir->tagfilter = tagfilter;
    
    if (trash && sstrtrim(sstr(trash)).length > 0) {
        if (trash[0] == '/' || trash[0] == '$') {
            dir->trash = scfg_create_path(trash);
        } else {
            char *t = util_concat_path(dir->path, trash);
            dir->trash = util_concat_path(t, "/");
            free(t);
        }
        
        if(dir->trash[strlen(dir->trash)-1] != '/') {
            char *t = dir->trash;
            dir->trash = util_concat_path(t, "/");
            free(t);
        }
    } else {
        dir->trash = NULL;
    }
    
    ucx_map_cstr_put(directories, name, dir);
    
    return 0;
}

int load_sync_config() {
    directories = ucx_map_new(8);
    
    if(check_config_dir()) {
        fprintf(stderr, "Cannot create .dav directory\n");
        return 1;
    }
    
    char *file = util_concat_path(ENV_HOME, ".dav/sync.xml");
    
    struct stat s;
    if(stat(file, &s)) {
        switch(errno) {
            case ENOENT: {
                if(create_default_sync_config(file)) {
                    return 1;
                }
                break;
            }
            default: {
                perror("Cannot load sync.xml");
            }
        }
        free(file);
        return 0;
    }
    
    xmlDoc *doc = xmlReadFile(file, NULL, 0);
    if(!doc) {
        fprintf(stderr, "Cannot load sync.xml\n");
        free(file);
        return -1;
    }
    
    int ret = 0;
    xmlNode *node = xmlDocGetRootElement(doc)->children;
    while(node && !ret) {
        if(node->type == XML_ELEMENT_NODE) {
            if(xstreq(node->name, "directory")) {
                ret = scfg_load_directory(node);
            } else {
                print_error(node->line,
                        "unknown config element: %s\n", node->name);
                ret = 1;
            }
        }
        node = node->next;
    }
    
    xmlFreeDoc(doc);
    free(file);
    return ret;
}

SyncDirectory* scfg_get_dir(char *name) {
    return ucx_map_cstr_get(directories, name);
}

int scfg_check_dir(SyncDirectory *dir) {
    struct stat s;
    if(stat(dir->path, &s)) {
        int err = errno;
        if(err == ENOENT) {
            fprintf(stderr, "directory %s does not exist.\n", dir->path);
        } else {
            fprintf(stderr, "Cannot stat directory %s.\n", dir->path);
            perror(NULL);
        }
        fprintf(stderr, "Abort.\n");
        return -1;
    }
    
    if(dir->trash) {
        // create trash directory
        mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
        if (util_mkdir(dir->trash, mode)) {
            if (errno != EEXIST) {
                fprintf(stderr, "Cannot create trash directory: %s\nAbort.\n", dir->trash);
                return -1;
            }
        }
    }
    
    return 0;
}

char* scfg_create_path(char *cfg) {
    if(!cfg) {
        return NULL;
    }
    if(cfg[0] != '$') {
        return strdup(cfg);
    }
    
    sstr_t s = sstr(cfg);
    sstr_t path = sstrchr(sstr(cfg), '/');
    char *localpath = NULL;
    if(path.length > 0) {
        // path = $var/path/
        
        sstr_t var = sstrsubsl(s, 1, path.ptr - s.ptr - 1);
        if(var.length > 0) {
            char *env = sstrdup(var).ptr;
            char *envval = getenv(env);
            free(env);
            if(envval) {
                localpath = util_concat_path(envval, path.ptr);
            } else {
                fprintf(
                        stderr,
                        "Environment variable %.*s not set.\nAbort.\n",
                        (int)var.length,
                        var.ptr);
                exit(-1);
            }
        } else {
            localpath = sstrdup(path).ptr;
        }
    } else {
        // path = $var
        char *envval = getenv(cfg + 1);
        if(envval) {
            localpath = strdup(envval);
        } else {
            fprintf(
                    stderr,
                    "Environment variable %s not set.\nAbort.\n",
                    cfg);
            exit(-1);
        }
    }
    return localpath;
}


int add_directory(SyncDirectory *dir) {
    char *file = util_concat_path(ENV_HOME, ".dav/sync.xml");
    xmlDoc *doc = xmlReadFile(file, NULL, 0);
    if(!doc) {
        free(file);
        fprintf(stderr, "Cannot load config.xml\n");
        return 1;
    }
    
    xmlNode *root = xmlDocGetRootElement(doc);
    
    xmlNode *dirNode = xmlNewNode(NULL, BAD_CAST "directory");
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    
    xmlNewTextChild(dirNode, NULL, BAD_CAST "name", BAD_CAST dir->name);
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    
    xmlNewTextChild(dirNode, NULL, BAD_CAST "path", BAD_CAST dir->path);
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    
    xmlNewTextChild(dirNode, NULL, BAD_CAST "repository", BAD_CAST dir->repository);
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    
    xmlNewTextChild(dirNode, NULL, BAD_CAST "collection", BAD_CAST dir->collection);
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    
    if(dir->trash) {
        xmlNewTextChild(dirNode, NULL, BAD_CAST "trash", BAD_CAST dir->trash);
        xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t");
    }
    xmlNewTextChild(dirNode, NULL, BAD_CAST "database", BAD_CAST dir->database);
    xmlNodeAddContent(dirNode, BAD_CAST "\n\t");
    
    xmlNodeAddContent(root, BAD_CAST "\n\t");
    xmlAddChild(root, dirNode);
    xmlNodeAddContent(root, BAD_CAST "\n");
    
    int ret = 0;
    if(xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) {
        ret = 1;
    }
    xmlFreeDoc(doc);
    free(file);
    
    return ret;
}

char* generate_db_name(char *basename) {
    char *dbname = NULL;
    int count = -1;
    while(!dbname) {
        sstr_t name = count < 0 ?
                ucx_sprintf("%s-db.xml", basename) : 
                ucx_sprintf("%s%d-db.xml", basename, count);
        count++;
        
        UcxMapIterator i = ucx_map_iterator(directories);
        SyncDirectory *dir;
        bool unique = true;
        UCX_MAP_FOREACH(key, dir, i) {
            if(!sstrcmp(name, sstr(dir->database))) {
                unique = false;
                break;
            }
        }
        if(unique) {
            dbname = name.ptr;
            break;
        }
    }
    return dbname;
}


void free_sync_config() {
    if(directories) {
        UcxMapIterator i = ucx_map_iterator(directories);
        SyncDirectory *dir;
        UCX_MAP_FOREACH(elm, dir, i) {
            free(dir->name);
            free(dir->path);
            free(dir->repository);
            free(dir->database);

            if(dir->collection) {
                free(dir->collection);
            }
            if(dir->trash) {
                free(dir->trash);
            }

            UCX_FOREACH(elm, dir->include) {
                regfree(elm->data);
                free(elm->data);
            }
            ucx_list_free(dir->include);
            UCX_FOREACH(elm, dir->exclude) {
                regfree(elm->data);
                free(elm->data);
            }
            ucx_list_free(dir->exclude);

            free(dir);
        }

        ucx_map_free(directories);
    }
}

mercurial