adds versioning support for dav-sync push

Sun, 04 Nov 2018 16:35:44 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 04 Nov 2018 16:35:44 +0100
changeset 491
fdc2fb090cc7
parent 490
d94c4fd35c21
child 492
7bde663719dc

adds versioning support for dav-sync push

dav/scfg.c file | annotate | diff | comparison | revisions
dav/scfg.h file | annotate | diff | comparison | revisions
dav/sync.c file | annotate | diff | comparison | revisions
dav/sync.h file | annotate | diff | comparison | revisions
libidav/session.c file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
--- a/dav/scfg.c	Sat Oct 27 15:05:13 2018 +0200
+++ b/dav/scfg.c	Sun Nov 04 16:35:44 2018 +0100
@@ -177,6 +177,9 @@
     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;
@@ -191,10 +194,9 @@
     while(c) {
         if(c->type == XML_ELEMENT_NODE) {
             char *value = util_xml_get_text(c);
-            if(xstreq(c->name, "local-store")) {  
-                if(!value) {
-                    return NULL;
-                } else if(xstreq(value, "xattr")) {
+            if(xstreq(c->name, "local-store")) {
+                CHECK_VALUE(c, value);
+                if(xstreq(value, "xattr")) {
                     conf.store = TAG_STORE_XATTR;
                 } else {
                     return NULL;
@@ -207,9 +209,7 @@
                     xmlFree(format);
                 }
             } else if(xstreq(c->name, "detect-changes")) {
-                if(!value) {
-                    return NULL;
-                }
+                CHECK_VALUE(c, value);
                 conf.detect_changes = util_getboolean(value);
             } else if(xstreq(c->name, "xattr-name")) {
                 if(!value) {
@@ -217,9 +217,7 @@
                 }
                 conf.xattr_name = strdup(value);
             } else if(xstreq(c->name, "on-conflict")) {
-                if(!value) {
-                    return NULL;
-                }
+                CHECK_VALUE(c, value);
                 if(xstreq(value, "no_conflict")) {
                     conf.conflict = TAG_NO_CONFLICT;
                 } else if(xstreq(value, "keep_local")) {
@@ -252,6 +250,43 @@
     return tagconfig;
 }
 
+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;
@@ -260,6 +295,7 @@
     char *repository = NULL;
     char *database = NULL;
     TagConfig *tagconfig = NULL;
+    Versioning *versioning = NULL;
     UcxList *include = NULL;
     UcxList *exclude = NULL;
     UcxList *tagfilter = NULL;
@@ -304,6 +340,8 @@
                 database = value;
             } else if(xstreq(node->name, "tagconfig")) {
                 tagconfig = parse_tagconfig(node);
+            } 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) {
@@ -382,6 +420,7 @@
     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;
--- a/dav/scfg.h	Sat Oct 27 15:05:13 2018 +0200
+++ b/dav/scfg.h	Sun Nov 04 16:35:44 2018 +0100
@@ -49,7 +49,8 @@
 #define DEFAULT_TAG_XATTR "tags"
 #define MACOS_TAG_XATTR "com.apple.metadata:_kMDItemUserTags"
     
-typedef struct TagConfig TagConfig;
+typedef struct TagConfig  TagConfig;
+typedef struct Versioning Versioning;
     
 typedef struct SyncDirectory {
     char *name;
@@ -59,6 +60,7 @@
     char *repository;
     char *database;
     TagConfig *tagconfig;
+    Versioning *versioning;
     UcxList *include;
     UcxList *exclude;
     UcxList *tagfilter;
@@ -103,6 +105,18 @@
     bool detect_changes;
 };
 
+enum VersioningType {
+    VERSIONING_SIMPLE = 0,
+    VERSIONING_DELTAV
+};
+typedef enum VersioningType VersioningType;
+
+struct Versioning {
+    VersioningType type;
+    char *collection;
+    bool always;
+};
+
 int load_sync_config();
 
 UcxMapIterator scfg_directory_iterator();
--- a/dav/sync.c	Sat Oct 27 15:05:13 2018 +0200
+++ b/dav/sync.c	Sun Nov 04 16:35:44 2018 +0100
@@ -1981,6 +1981,135 @@
     return ret;
 }
 
+int gen_random_name(char *buf, size_t len) {
+    char name_prefix[8];
+    memset(name_prefix, 0, 8);
+    dav_rand_bytes(name_prefix, 8);   
+    char *pre = util_hexstr(name_prefix, 8);
+    int64_t ts = (int64_t)time(NULL);
+    int w = snprintf(buf, len, "%s-%"PRId64"\0", pre, ts);
+    free(pre);
+    return w >= len;
+}
+
+#define VBEGIN_ERROR_MKCOL     1
+#define VBEGIN_ERROR_MOVE      2
+#define VBEGIN_ERROR_PROPPATCH 3
+#define VBEGIN_ERROR_CHECKOUT  4
+int versioning_begin(SyncDirectory *dir, DavResource *res, int *exists) {
+    int ret = 0;
+    
+    if(dir->versioning->type == VERSIONING_SIMPLE) {
+        DavResource *history_collection = dav_resource_new(
+                    res->session,
+                    dir->versioning->collection);
+        
+        // get the path to the version history collection for this resource
+        // if propfind fails we just assume that it doesn't exist
+        // better error handling is done later (sync_put_resource)
+        // if there is no history collection for this resource, we create a one
+        
+        DavPropName prop;
+        prop.ns = DAV_NS;
+        prop.name = VERSION_PATH_PROPERTY;
+        *exists = dav_load_prop(res, &prop, 1);
+        
+        char *history_href = NULL;
+        char *vcol_path = dav_get_string_property_ns(res, DAV_NS, VERSION_PATH_PROPERTY);
+        if(!vcol_path) {
+            DavResource *history_res = NULL;
+            
+            // create a new collection for version history
+            // the name is a combination of a random prefix and a timestamp
+            while(!history_res) {
+                char history_res_name[128];
+                gen_random_name(history_res_name, 128);
+
+                history_res = dav_resource_new_child(
+                        res->session,
+                        history_collection,
+                        history_res_name);
+                if(dav_exists(history_res)) {
+                    dav_resource_free(history_res);
+                    history_res = NULL;
+                }
+            }
+            
+            history_res->iscollection = TRUE;
+            if(dav_create(history_res)) {
+                dav_resource_free(history_res);
+                dav_resource_free(history_collection);
+                return VBEGIN_ERROR_MKCOL;
+            }
+            
+            history_href = strdup(history_res->href);
+            
+            dav_resource_free(history_res);
+        } else {
+            history_href = vcol_path;
+        }
+        
+        // find a free url and move 'res' to this location
+        DavResource *version_res = NULL;
+        while(!version_res) {
+            char version_name[128];
+            gen_random_name(version_name, 128);
+
+            char *href = util_concat_path(history_href, version_name);
+            version_res = dav_resource_new_href(res->session, href);
+            free(href);
+
+            char *dest = util_get_url(res->session, version_res->href);
+            int err = dav_moveto(res, dest, FALSE);
+            free(dest);
+
+            if(err) {
+                dav_resource_free(version_res);
+                version_res = NULL;
+                if(res->session->error != DAV_PRECONDITION_FAILED) {
+                    ret = VBEGIN_ERROR_MOVE;
+                    break;
+                }
+            }
+        }
+
+        if(!ret) {
+            dav_set_string_property_ns(version_res, DAV_NS, "origin", res->href);
+            if(dav_store(version_res)) {
+                ret = VBEGIN_ERROR_PROPPATCH;
+            }
+            dav_resource_free(version_res);
+            
+            // we can just set the property here and don't need dav_store
+            // because sync_put_resource will call dav_store(res) later
+            dav_set_string_property_ns(
+                    res,
+                    DAV_NS,
+                    VERSION_PATH_PROPERTY,
+                    history_href);
+        }
+        
+        if(vcol_path != history_href) {
+            free(history_href);
+        }
+        
+        dav_resource_free(history_collection);
+    } else {
+        // Delta V is so much easier :) 
+        if(dav_checkout(res)) {
+            ret = VBEGIN_ERROR_CHECKOUT;
+        }
+    }
+    
+    return ret;
+}
+
+int versioning_end(SyncDirectory *dir, DavResource *res) {
+    if(dir->versioning->type == VERSIONING_DELTAV) {
+        return dav_checkin(res);
+    }
+}
+
 int sync_put_resource(
         SyncDirectory *dir,
         DavResource *res,
@@ -1991,7 +2120,7 @@
     
     SYS_STAT s;
     if(sys_stat(local_path, &s)) {
-        fprintf(stderr, "cannot stat file: %s\n", local_path);
+        fprintf(stderr, "Cannot stat file: %s\n", local_path);
         perror("");
         free(local_path);
         return -1;
@@ -2014,14 +2143,25 @@
             dav_set_property_ns(res, DAV_NS, "tags", prop);
         }
     }
-     
+    
+    int exists = dav_exists(res);
+    if(dir->versioning && dir->versioning->always) {
+        int err = versioning_begin(dir, res, &exists);
+        if(err) {
+            fprintf(stderr, "Cannot store version for resource: %s\n", res->href);
+            free(local_path);
+            return -1;
+        }
+    } else {
+        exists = dav_exists(res);
+    }
+    
     int ret = -1;
-    int created = 0;
     for(int i=0;i<=dir->max_retry;i++) {
-        if(!created && dav_create(res)) {
+        if(!exists && dav_create(res)) {
             continue;
         }
-        created = 1;
+        exists = 1;
         if(dav_store(res)) {
             continue;
         }
@@ -2029,7 +2169,12 @@
         break;
     }
     
-
+    if(dir->versioning && dir->versioning->always) {
+        if(versioning_end(dir, res)) {
+            fprintf(stderr, "Cannot checkin resource\n");
+            ret = 1;
+        }
+    }
 
     if(ret == 0) {
         (*counter)++;
--- a/dav/sync.h	Sat Oct 27 15:05:13 2018 +0200
+++ b/dav/sync.h	Sun Nov 04 16:35:44 2018 +0100
@@ -52,6 +52,8 @@
 #endif
 
 #define STDIN_BUF_SIZE 2048
+    
+#define VERSION_PATH_PROPERTY "version-collection"
 
 typedef struct SyncFile {
     SyncDirectory *dir;
--- a/libidav/session.c	Sat Oct 27 15:05:13 2018 +0200
+++ b/libidav/session.c	Sun Nov 04 16:35:44 2018 +0100
@@ -214,6 +214,7 @@
             case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
             case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
             case 409: sn->error = DAV_CONFLICT; break;
+            case 412: sn->error = DAV_PRECONDITION_FAILED; break;
             case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
             case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
             case 423: sn->error = DAV_LOCKED; break;
--- a/libidav/webdav.h	Sat Oct 27 15:05:13 2018 +0200
+++ b/libidav/webdav.h	Sun Nov 04 16:35:44 2018 +0100
@@ -84,6 +84,7 @@
     DAV_SSL_ERROR,
     DAV_QL_ERROR,
     DAV_CONTENT_VERIFICATION_ERROR,
+    DAV_PRECONDITION_FAILED,
     DAV_REQUEST_ENTITY_TOO_LARGE,
     DAV_REQUEST_URL_TOO_LONG,
     DAV_PROXY_AUTH_REQUIRED,

mercurial