dav/db.c

Fri, 02 Feb 2018 18:57:21 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 02 Feb 2018 18:57:21 +0100
changeset 367
4a6a59f89f9f
parent 366
5228b912c925
child 521
c5bbae4b3cca
permissions
-rw-r--r--

adds dav-sync config for autodetecting tag changes

/*
 * 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 "db.h"

#include <libidav/utils.h>

#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>


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

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

SyncDatabase* load_db(char *name) {
    char *dav_dir = util_concat_path(ENV_HOME, ".dav");
    char *db_file = util_concat_path(dav_dir, name);
    free(dav_dir);
    
    SyncDatabase *db = malloc(sizeof(SyncDatabase));
    db->resources = ucx_map_new(2048);
    db->conflict = ucx_map_new(16);
    
    xmlTextReaderPtr reader = xmlReaderForFile(db_file, NULL, 0);
    if(!reader) {
        xmlDoc *doc =  doc = xmlNewDoc(BAD_CAST "1.0");
        xmlNode *root = xmlNewNode(NULL, BAD_CAST "directory");
        xmlDocSetRootElement(doc, root);
        if(xmlSaveFormatFileEnc(db_file, doc, "UTF-8", 1) == -1) {
            destroy_db(db);
            db = NULL;
        }
        xmlFreeDoc(doc);
        free(db_file);
        return db;
    }
    free(db_file);
    
    int error = 0;
    while(xmlTextReaderRead(reader)) {
        int type = xmlTextReaderNodeType(reader);
        const xmlChar *name = xmlTextReaderConstName(reader);
        
        if(type == XML_READER_TYPE_ELEMENT) {
            if(xstreq(name, "resource")) {
                LocalResource *res = process_resource(reader);
                if(res) {
                    ucx_map_cstr_put(db->resources, res->path, res);
                } else {
                    error = 1;
                    break;
                }
            } else if(xstreq(name, "conflict")) {
                LocalResource *res = process_conflict(reader);
                if(res) {
                    ucx_map_cstr_put(db->conflict, res->path, res);
                } else {
                    error = 1;
                    break;
                }
            }
        }
    }
    
    xmlFreeTextReader(reader);
    if(error) {
        destroy_db(db);
        return NULL;
    } else {
        return db;
    }
}

LocalResource* process_resource(xmlTextReaderPtr reader) {
    LocalResource *res = calloc(1, sizeof(LocalResource));
    
    int field = -1;
    while(xmlTextReaderRead(reader)) {
        int type = xmlTextReaderNodeType(reader);
        const xmlChar *name = xmlTextReaderConstName(reader);
        
        if(type == XML_READER_TYPE_ELEMENT) {
            if(xstreq(name, "path")) {
                field = 0;
            } else if(xstreq(name, "etag")) {
                field = 1;
            } else if(xstreq(name, "lastmodified")) {
                field = 2;
            } else if(xstreq(name, "size")) {
                field = 3;
            } else if(xstreq(name, "tags-hash")) {
                field = 4;
            } else if(xstreq(name, "skipped")) {
                res->skipped = TRUE;
            } else if(xstreq(name, "tags-updated")) {
                res->tags_updated = TRUE;
            }
        } else if(type == XML_READER_TYPE_TEXT) {
            const xmlChar *value = xmlTextReaderConstValue(reader);
            //int b = 0;
            switch(field) {
                case 0: {
                    res->path = strdup((char*)value);
                    break;
                }
                case 1: {
                    res->etag = strdup((char*)value);
                    break;
                }
                case 2: {
                    //res->last_modified = util_parse_lastmodified((char*)value);
                    //res->last_modified = atoi((char*)value);
                    char *endptr = (char*)value;
                    time_t t = strtoll((char*)value, &endptr, 10);
                    if(endptr == (char*)value) {
                        fprintf(
                            stderr,
                            "lastmodified does not contain a number: %s\n", value);
                    } else {
                        res->last_modified = t;
                    }
                    break;
                }
                case 3: {
                    res->size = 0;
                    int64_t filelen = 0;
                    if(util_strtoint((char*)value, &filelen)) {
                        if(filelen > 0) {
                            res->size = (size_t)filelen;
                        }
                    }
                    break;
                }
                case 4: {
                    res->tags_hash = strdup((char*)value);
                    break;
                }
            }
        } else if(XML_READER_TYPE_END_ELEMENT) {
            if(xstreq(name, "resource")) {
                break;
            } else {
                field = -1;
            }
        }
    }
    
    if(!res->path) {
        // TODO: free res
        return NULL;
    } else {
        return res;
    }
}

LocalResource* process_conflict(xmlTextReaderPtr reader) {
    LocalResource *res = calloc(1, sizeof(LocalResource));
    
    int path = 0;
    while(xmlTextReaderRead(reader)) {
        int type = xmlTextReaderNodeType(reader);
        const xmlChar *name = xmlTextReaderConstName(reader);
        
        if(type == XML_READER_TYPE_ELEMENT) {
            if(xstreq(name, "path")) {
                path = 1;
            }
        } else if(type == XML_READER_TYPE_TEXT && path) {
            const xmlChar *value = xmlTextReaderConstValue(reader);
            res->path = strdup((char*)value);
        } else if(XML_READER_TYPE_END_ELEMENT) {
            if(xstreq(name, "conflict")) {
                break;
            } else {
                path = 0;
            }
        }
    }
    
    if(!res->path) {
        // TODO: free res
        return NULL;
    } else {
        return res;
    }
}

int store_db(SyncDatabase *db, char *name) {
    // open writer
    char *dav_dir = util_concat_path(ENV_HOME, ".dav");
    char *db_file = util_concat_path(dav_dir, name);
    free(dav_dir);
    xmlTextWriterPtr writer = xmlNewTextWriterFilename(db_file, 0);
    if(!writer) {
        fprintf(stderr, "Cannot write db file: %s\n", db_file);
        free(db_file);
        return -1;
    }
    free(db_file);
    
    // start document
    int r = 0;
    r = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
    if(r < 0) {
        xmlFreeTextWriter(writer);
        return -1;
    }
    xmlTextWriterStartElement(writer, BAD_CAST "directory");
    
    // write all resource entries
    UcxMapIterator i = ucx_map_iterator(db->resources);
    LocalResource *res;
    UCX_MAP_FOREACH(key, res, i) {
        // <resource>
        xmlTextWriterStartElement(writer, BAD_CAST "resource");
        
        r = xmlTextWriterWriteElement(
                writer,
                BAD_CAST "path",
                BAD_CAST res->path);
        if(r < 0) {
            fprintf(stderr, "Cannot write path: %s\n", res->path);
            xmlFreeTextWriter(writer);
            return -1;
        }
        
        if(res->etag) {
            r = xmlTextWriterWriteElement(
                    writer,
                    BAD_CAST "etag",
                    BAD_CAST res->etag);
            if(r < 0) {
                fprintf(stderr, "Cannot write etag: %s\n", res->etag);
                xmlFreeTextWriter(writer);
                return -1;
            }
        }

        r = xmlTextWriterWriteFormatElement(
                writer,
                BAD_CAST "lastmodified",
                "%" PRId64,
                (int64_t)res->last_modified);
        if(r < 0) {
            fprintf(stderr, "Cannot write lastmodified\n");
            xmlFreeTextWriter(writer);
            return -1;
        }
        
        r = xmlTextWriterWriteFormatElement(
                writer,
                BAD_CAST "size",
                "%" PRId64,
                (int64_t)res->size);
        if(r < 0) {
            fprintf(stderr, "Cannot write size\n");
            xmlFreeTextWriter(writer);
            return -1;
        }
        
        if(res->tags_hash) {
            r = xmlTextWriterWriteElement(
                    writer,
                    BAD_CAST "tags-hash",
                    BAD_CAST res->tags_hash);
            if(r < 0) {
                fprintf(stderr, "Cannot write tags-hash: %s\n", res->etag);
                xmlFreeTextWriter(writer);
                return -1;
            }
        }
        
        if(res->skipped) {
            r = xmlTextWriterStartElement(writer, BAD_CAST "skipped");
            r += xmlTextWriterEndElement(writer);
            if(r < 0) {
                fprintf(stderr, "Cannot write skipped\n");
                xmlFreeTextWriter(writer);
                return -1;
            }
        }
        
        if(res->tags_updated) {
            r = xmlTextWriterStartElement(writer, BAD_CAST "tags-updated");
            r += xmlTextWriterEndElement(writer);
            if(r < 0) {
                fprintf(stderr, "Cannot write tags-updated\n");
                xmlFreeTextWriter(writer);
                return -1;
            }
        }
        
        // </resource>
        xmlTextWriterEndElement(writer);
    }
    
    // write all remove entries
/*
    i = ucx_map_iterator(db->remove);
    UCX_MAP_FOREACH(key, res, i) {
        // <remove>
        xmlTextWriterStartElement(writer, BAD_CAST "remove");
        
        xmlTextWriterWriteElement(
                    writer,
                    BAD_CAST "path",
                    BAD_CAST res->path);
        
        // </remove>
        xmlTextWriterEndElement(writer);
    }
*/
    
    // write all conflict entries
    i = ucx_map_iterator(db->conflict);
    UCX_MAP_FOREACH(key, res, i) {
        // <conflict>
        xmlTextWriterStartElement(writer, BAD_CAST "conflict");
        
        xmlTextWriterWriteElement(
                    writer,
                    BAD_CAST "path",
                    BAD_CAST res->path);
        
        // </conflict>
        xmlTextWriterEndElement(writer);
    }
    
    // end
    xmlTextWriterEndElement(writer);
    r = xmlTextWriterEndDocument(writer);
    if(r < 0) {
        xmlFreeTextWriter(writer);
        return -1;
    }
    xmlFreeTextWriter(writer);
    return 0;
}

void destroy_db(SyncDatabase *db) {
    ucx_map_free_content(db->resources, (ucx_destructor)local_resource_free);
    ucx_map_free_content(db->conflict, (ucx_destructor)local_resource_free);
    ucx_map_free(db->resources);
    ucx_map_free(db->conflict);
    free(db);
}

void local_resource_free(LocalResource *res) {
    if(!res) {
        return;
    }
    if(res->name) {
        free(res->name);
    }
    if(res->path) {
        free(res->path);
    }
    if(res->etag) {
        free(res->etag);
    }
    if(res->cached_tags) {
        ucx_buffer_free(res->cached_tags);
    }
    if(res->tags_hash) {
        free(res->tags_hash);
    }
    free(res);
}

mercurial