adds metadata support to dav-sync pull

Sun, 17 Mar 2019 18:11:31 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 17 Mar 2019 18:11:31 +0100
changeset 526
e3c0440bd599
parent 525
26a1d5b9d9d2
child 527
d0e37224eba1

adds metadata support to dav-sync pull

dav/finfo.c file | annotate | diff | comparison | revisions
dav/finfo.h file | annotate | diff | comparison | revisions
dav/main.c file | annotate | diff | comparison | revisions
dav/scfg.c file | annotate | diff | comparison | revisions
dav/sync.c file | annotate | diff | comparison | revisions
dav/sync.h file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
libidav/xml.c file | annotate | diff | comparison | revisions
--- a/dav/finfo.c	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/finfo.c	Sun Mar 17 18:11:31 2019 +0100
@@ -32,15 +32,17 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <errno.h>
 #include <sys/stat.h>
 
 #include <ucx/string.h>
+#include <ucx/list.h>
 #include <libidav/crypto.h>
 #include <libidav/utils.h>
 
 #include "libxattr.h"
 
-uint32_t parse_finfo(const char *str, char **error) {
+uint32_t parse_finfo_settings(const char *str, char **error) {
     scstr_t s = scstr(str);
     
     if(!sstrcmp(s, SC("*")) || !sstrcmp(s, SC("a")) || !sstrcmp(s, SC("all"))) {
@@ -127,6 +129,65 @@
     return 0;
 }
 
+XAttributes* xml_get_attributes(DavXmlNode *xml) {
+    UcxList *names = NULL;
+    UcxList *values = NULL;
+    
+    size_t count = 0;
+    
+    char *hash = NULL;
+    
+    DavXmlNode *node = xml;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            if(!strcmp(node->name, "hash")) {
+                hash = dav_xml_getstring(node->children);
+            } else if(!strcmp(node->name, "xattr")) {
+                char *xattr_name = dav_xml_get_attr(node, "name");
+                if(xattr_name) {
+                    names = ucx_list_append(names, strdup(xattr_name));
+                    
+                    char *text = dav_xml_getstring(node->children);
+                    if(!text) {
+                        text = "";
+                    }
+                    
+                    int len = 0;
+                    char *val = util_base64decode_len(text, &len);
+                    
+                    sstr_t *value = malloc(sizeof(sstr_t));
+                    value->ptr = val;
+                    value->length = len;
+                    
+                    values = ucx_list_append(values, value);
+                    
+                    count++;
+                }
+            }
+        }
+        
+        node = node->next;
+    }
+    
+    XAttributes *attributes = NULL;
+    if(count > 0) {
+        attributes = calloc(1, sizeof(XAttributes));
+        attributes->hash = hash ? strdup(hash) : NULL;
+        attributes->nattr = count;
+        int i=0;
+        UCX_FOREACH(elm, names) {
+            attributes->names[i] = elm->data;
+            i++;
+        }
+        i=0;
+        UCX_FOREACH(elm, values) {
+            attributes->values[i] = *(sstr_t*)elm->data;
+            i++;
+        }
+    }
+    return attributes;
+}
+
 XAttributes* file_get_attributes(const char *path) {
     ssize_t nelm = 0;
     char **attributes = xattr_list(path, &nelm);
@@ -212,3 +273,44 @@
     }
     free(xattr);
 }
+
+char* get_xattr_hash(DavXmlNode *finfo) {
+    DavXmlNode *node = finfo;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT && !strcmp(node->name, "hash")) {
+            return dav_xml_getstring(node->children);
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+void finfo_get_values(DavXmlNode *xml, FileInfo *outval) {
+    memset(outval, 0, sizeof(FileInfo));
+    DavXmlNode *node = xml;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            if(!strcmp(node->name, "mtime")) {
+                char *mtime = dav_xml_getstring(node->children);
+                if(mtime) {
+                    outval->last_modified = util_parse_lastmodified(mtime);
+                    outval->date_set = TRUE;
+                }
+            } else if(!strcmp(node->name, "mode")) {
+                char *mode_str = dav_xml_getstring(node->children);
+                    if(mode_str) {
+                    char *end;
+                    errno = 0;
+                    long int mode = strtol(mode_str, &end, 8);
+                    if(errno == 0) {
+                        mode &= 07777;
+                        outval->mode = mode;
+                        outval->mode_set = TRUE;
+                    }
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+}
--- a/dav/finfo.h	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/finfo.h	Sun Mar 17 18:11:31 2019 +0100
@@ -49,15 +49,31 @@
     char   *hash;
 } XAttributes;
 
-uint32_t parse_finfo(const char *str, char **unknown);
+typedef struct FileInfo {
+    time_t  last_modified;
+    mode_t  mode;
+    uid_t   uid;
+    gid_t   gid;
+    DavBool date_set;
+    DavBool mode_set;
+    DavBool uid_set;
+    DavBool gid_set;
+} FileInfo;
+
+uint32_t parse_finfo_settings(const char *str, char **unknown);
     
 int resource_set_finfo(const char *path, DavResource *res, uint32_t finfo);
 int resource_set_finfo_s(struct stat *s, DavResource *res, uint32_t finfo);
 
+XAttributes* xml_get_attributes(DavXmlNode *xml);
 XAttributes* file_get_attributes(const char *path);
 int resource_set_xattr(DavResource *res, XAttributes *xattr);
 void xattributes_free(XAttributes *xattr);
 
+char* get_xattr_hash(DavXmlNode *finfo);
+
+void finfo_get_values(DavXmlNode *xml, FileInfo *outval);
+
 #ifdef __cplusplus
 }
 #endif
--- a/dav/main.c	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/main.c	Sun Mar 17 18:11:31 2019 +0100
@@ -1259,7 +1259,7 @@
     char *finfo_str = cmd_getoption(a, "finfo");
     uint32_t finfo = 0;
     if(finfo_str) {
-        finfo = parse_finfo(finfo_str, NULL);
+        finfo = parse_finfo_settings(finfo_str, NULL);
     }
     
     int ret;
--- a/dav/scfg.c	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/scfg.c	Sun Mar 17 18:11:31 2019 +0100
@@ -343,7 +343,7 @@
                 tagconfig = parse_tagconfig(node);
             } else if(xstreq(node->name, "metadata")) {
                 char *error = NULL;
-                metadata = parse_finfo(value, &error);
+                metadata = parse_finfo_settings(value, &error);
                 if(error) {
                     print_error(node->line, "unknown metadata: %s\n", error);
                     free(error);
--- a/dav/sync.c	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/sync.c	Sun Mar 17 18:11:31 2019 +0100
@@ -33,6 +33,7 @@
 #include <unistd.h>
 #include <signal.h>
 #include <time.h>
+#include <utime.h>
 #include <libxml/xmlerror.h>
 #include <sys/types.h>
 #include <ucx/string.h>
@@ -499,7 +500,7 @@
     }
     
     int ret = 0;
-    DavResource *ls = dav_query(sn, "select D:getetag,idav:status,idav:tags from / with depth = infinity");
+    DavResource *ls = dav_query(sn, "select D:getetag,idav:status,idav:tags,idav:finfo,idav:xattributes from / with depth = infinity");
     if(!ls) {
         print_resource_error(sn, "/");
         if(locked) {
@@ -549,7 +550,7 @@
     UcxList *res_broken = NULL;
     UcxList *lres_removed = NULL; // list of LocalResource*
     
-    UcxMap *svrres = ucx_map_new(db->resources->count);
+    //UcxMap *svrres = ucx_map_new(db->resources->count);
     UcxMap *dbres = ucx_map_clone(db->resources, NULL, NULL);
     
     UcxList *statls = NULL;
@@ -695,9 +696,18 @@
         
         LocalResource *local = ucx_map_cstr_get(db->resources, res->path);
         if(local) {
+            printf("update: %s\n", res->path);
             char *local_path = util_concat_path(dir->path, res->path);
-            if(sync_store_tags(dir, local_path, local, res)) {
-                fprintf(stderr, "Tag update failed: %s\n", res->path);
+            if(sync_store_metadata(dir, local_path, local, res)) {
+                fprintf(stderr, "Metadata update failed: %s\n", res->path);
+                sync_error++;
+            } else {
+                struct stat s;
+                if(stat(local_path, &s)) {
+                    fprintf(stderr, "Cannot stat file after update: %s\n", strerror(errno));
+                }
+                sync_set_metadata_from_stat(local, &s);
+                sync_success++;
             }
             free(local_path);
         } else {
@@ -819,9 +829,6 @@
             }
             if(!strcmp(e.ptr, local->etag)) {
                 // resource is already up-to-date on the client
-                
-                // TODO: detect metadata update
-                //sync_store_tags(dir, local_path, local, res);
                 nochange = TRUE;
             }
         }
@@ -838,18 +845,64 @@
         ret = REMOTE_CHANGE_NEW;
     }
     
-    if(ret == REMOTE_NO_CHANGE) {
+    while(ret == REMOTE_NO_CHANGE && local) {
         // check if tags have changed
         if(dir->tagconfig) {
+            DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_NS, "tags");
+            UcxList *remote_tags = NULL;
+            if(tagsprop) {
+                remote_tags = parse_dav_xml_taglist(tagsprop);
+            }
+            char *remote_hash = create_tags_hash(remote_tags);
+            if(nullstrcmp(remote_hash, local->remote_tags_hash)) {
+                ret = REMOTE_CHANGE_METADATA;
+            }
+            if(remote_hash) {
+                free(remote_hash);
+            }
+            free_taglist(remote_tags);
             
+            if(ret == REMOTE_CHANGE_METADATA) {
+                break;
+            }
         }
         
+        // check if extended attributes have changed
+        if(dir->metadata & FINFO_XATTR == FINFO_XATTR) {
+            DavXmlNode *xattr = dav_get_property_ns(res, DAV_NS, "xattributes");
+            char *xattr_hash = get_xattr_hash(xattr);
+            if(nullstrcmp(xattr_hash, local->xattr_hash)) {
+                ret = REMOTE_CHANGE_METADATA;
+                break;
+            }
+        } 
+        
+        // check if finfo has changed
+        DavXmlNode *finfo = dav_get_property_ns(res, DAV_NS, "finfo");
+        if(dir->metadata & FINFO_MODE == FINFO_MODE) {
+            FileInfo f;
+            finfo_get_values(finfo, &f);
+            if(f.mode_set && f.mode != local->mode) {
+                ret = REMOTE_CHANGE_METADATA;
+                break;
+            }
+        }
+        
+        break;
     }
     
     free(local_path);
     return ret;
 }
 
+void sync_set_metadata_from_stat(LocalResource *local, struct stat *s) {
+    local->last_modified = s->st_mtime;
+    local->mode = s->st_mode & 07777;
+    local->uid = s->st_uid;
+    local->gid = s->st_gid;
+    local->size = s->st_size;
+}
+
 int sync_get_resource(
         CmdArgs *a,
         SyncDirectory *dir,
@@ -887,6 +940,10 @@
 
     if(ret == 0) {
         (*counter)++;
+        
+        if(sync_store_metadata(dir, tmp_path, local, res)) {
+            fprintf(stderr, "Cannot store metadata: %s\n", res->path);
+        }
 
         if(dir->trash && dir->backuppull) {
             move_to_trash(dir, local_path);
@@ -918,13 +975,11 @@
         if(local->etag) {
             free(local->etag);
         }
+        
         // set metadata from stat
         local->etag = strdup(etag);
-        local->last_modified = s.st_mtime;
-        local->size = s.st_size;
+        sync_set_metadata_from_stat(local, &s);
         local->skipped = FALSE;
-        
-        sync_store_tags(dir, tmp_path, local, res);
     } else {
         if(sys_unlink(tmp_path)) {
             fprintf(stderr, "Cannot remove tmp file: %s\n", tmp_path);
@@ -1849,6 +1904,11 @@
         if(db_res->last_modified == res->last_modified && db_res->size == res->size) {
             return 0;
         }
+    } else {
+        res->tags_updated = 1;
+        res->finfo_updated = 1;
+        res->xattr_updated = 1;
+        res->metadata_updated = 1;
     }
     return 1;
 }
@@ -1954,6 +2014,66 @@
     return equal;
 }
 
+int sync_store_metadata(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res) {
+    int ret = 0;
+    
+    DavXmlNode *fileinfo = dav_get_property_ns(res, DAV_NS, "finfo");
+    if(fileinfo) {
+        FileInfo f;
+        finfo_get_values(fileinfo, &f);
+        if(dir->metadata & FINFO_DATE == FINFO_DATE && f.date_set) {
+            // set mtime
+            struct utimbuf t;
+            t.actime = f.last_modified;
+            t.modtime = f.last_modified;
+            if(utime(path, &t)) {
+                fprintf(stderr, "utime failed for file: %s : %s\n", path, strerror(errno));
+                ret = 1;
+            }
+        }
+        if(dir->metadata & FINFO_MODE == FINFO_MODE && f.mode_set) {
+            // set mode
+            if(chmod(path, f.mode)) {
+                fprintf(stderr, "chmod failed for file: %s : %s\n", path, strerror(errno));
+                ret = 1;
+            }
+        }
+    }
+    
+    DavXmlNode *xattr_prop = dav_get_property_ns(res, DAV_NS, "xattributes");
+    if(xattr_prop) {
+        XAttributes *xattr = xml_get_attributes(xattr_prop);
+        if(xattr) {
+            if(!sync_store_xattr(dir, path, xattr)) {
+                if(local->xattr_hash) {
+                    free(local->xattr_hash);
+                }
+                local->xattr_hash = xattr->hash;
+            }
+        }
+    }
+    
+    if(sync_store_tags(dir, path, local, res)) {
+        ret = 1;
+    }
+    
+    return ret;
+}
+
+int sync_store_xattr(SyncDirectory *dir, const char *path, XAttributes *xattr) {
+    for(int i=0;i<xattr->nattr;i++) {
+        sstr_t value = xattr->values[i];
+        if(xattr_set(path, xattr->names[i], value.ptr, value.length)) {
+            fprintf(
+                    stderr,
+                    "Cannot store xattr '%s' for file: %s\n",
+                    xattr->names[i],
+                    path);
+        }
+    }
+    return 0;
+}
+
 int sync_store_tags(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res) {
     if(!dir->tagconfig) {
         return 0;
@@ -1973,7 +2093,10 @@
     UcxList *local_tags = sync_get_file_tags(dir, local, &tags_changed, NULL);
     if(tags_changed) {
         switch(dir->tagconfig->conflict) {
-            case TAG_NO_CONFLICT:
+            case TAG_NO_CONFLICT: {
+                store_tags = TRUE;
+                break;
+            }
             case TAG_KEEP_LOCAL: {
                 store_tags = FALSE;
                 break;
@@ -2036,7 +2159,7 @@
                 int update = 1;
                 if(local) {
                     if(!local->tags_hash || strcmp(data_hash, local->tags_hash)) {
-                        printf("update: %s\n", local->path);
+                        //printf("update: %s\n", local->path);
                     } else {
                         update = 0;
                     }
@@ -2058,7 +2181,7 @@
             }
         } else {
             if(local) {
-                printf("update: %s\n", local->path);
+                //printf("update: %s\n", local->path);
             }
             ret = xattr_remove(path, dir->tagconfig->xattr_name);
         }
--- a/dav/sync.h	Sun Mar 17 15:00:48 2019 +0100
+++ b/dav/sync.h	Sun Mar 17 18:11:31 2019 +0100
@@ -99,6 +99,7 @@
         SyncDirectory *dir,
         SyncDatabase *db);
 
+void sync_set_metadata_from_stat(LocalResource *local, struct stat *s);
 int sync_get_resource(
         CmdArgs *a,
         SyncDirectory *dir,
@@ -135,6 +136,8 @@
 UcxBuffer* sync_get_file_tag_data(SyncDirectory *dir, LocalResource *res);
 UcxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res, DavBool *changed, char **newhash);
 int sync_tags_equal(UcxList *tags1, UcxList *tags2);
+int sync_store_metadata(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res);
+int sync_store_xattr(SyncDirectory *dir, const char *path, XAttributes *xattr);
 int sync_store_tags(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res);
 int sync_store_tags_local(SyncDirectory *dir, LocalResource *local, const char *path, UcxList *tags);
 int sync_put_resource(
--- a/libidav/webdav.h	Sun Mar 17 15:00:48 2019 +0100
+++ b/libidav/webdav.h	Sun Mar 17 18:11:31 2019 +0100
@@ -342,6 +342,7 @@
 DavXmlNode* dav_xml_createtextnode(const char *text);
 void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child);
 void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value);
+char* dav_xml_get_attr(DavXmlNode *node, const char *name);
 
 DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len);
 
--- a/libidav/xml.c	Sun Mar 17 15:00:48 2019 +0100
+++ b/libidav/xml.c	Sun Mar 17 18:11:31 2019 +0100
@@ -345,6 +345,18 @@
     }
 }
 
+char* dav_xml_get_attr(DavXmlNode *node, const char *name) {
+    DavXmlAttr *attr = node->attributes;
+    while(attr) {
+        if(!strcmp(attr->name, name)) {
+            return attr->value;
+        }
+        
+        attr = attr->next;
+    }
+    return NULL;
+}
+
 DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len) {
     xmlDoc *doc = xmlReadMemory(str, len, NULL, NULL, 0);
     if(!doc) {

mercurial