# HG changeset patch # User Olaf Wintermann # Date 1541345744 -3600 # Node ID fdc2fb090cc76f34f05f6796b3000dfcfbba4b6c # Parent d94c4fd35c215b045fd31cfce7edb03e8e2a3481 adds versioning support for dav-sync push diff -r d94c4fd35c21 -r fdc2fb090cc7 dav/scfg.c --- 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; diff -r d94c4fd35c21 -r fdc2fb090cc7 dav/scfg.h --- 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(); diff -r d94c4fd35c21 -r fdc2fb090cc7 dav/sync.c --- 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)++; diff -r d94c4fd35c21 -r fdc2fb090cc7 dav/sync.h --- 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; diff -r d94c4fd35c21 -r fdc2fb090cc7 libidav/session.c --- 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; diff -r d94c4fd35c21 -r fdc2fb090cc7 libidav/webdav.h --- 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,