dav/sync.c

changeset 525
26a1d5b9d9d2
parent 524
d53fd1006485
child 526
e3c0440bd599
--- a/dav/sync.c	Fri Mar 15 20:30:09 2019 +0100
+++ b/dav/sync.c	Sun Mar 17 15:00:48 2019 +0100
@@ -71,6 +71,22 @@
     va_end(ap);
 }
 
+/*
+ * strcmp version that works with NULL pointers
+ */
+static int nullstrcmp(const char *s1, const char *s2) {
+    if(!s1 && s2) {
+        return -1;
+    }
+    if(s1 && s2) {
+        return 1;
+    }
+    if(!s1 && !s2) {
+        return 0;
+    }
+    return strcmp(s1, s2);
+}
+
 int main(int argc, char **argv) {
     if(argc < 2) {
         fprintf(stderr, "Missing command\n");
@@ -311,7 +327,7 @@
     }
 
     DavBool changed = 0;
-    UcxList *res_tags = sync_get_file_tags(dir, res, &changed);
+    UcxList *res_tags = sync_get_file_tags(dir, res, &changed, NULL);
     
     int ret = matches_tagfilter(res_tags, tagfilter);
     UCX_FOREACH(elm, res_tags) {
@@ -1319,7 +1335,7 @@
                     abort = 1;
                 }
 
-                if(dir->tagconfig && local_res->tags_updated && !abort) {
+                if(local_res->metadata_updated && !abort) {
                     sync_update_metadata(dir, sn, res, local_res);
                 }
             } else if(sn->error != DAV_OK) {
@@ -1330,7 +1346,7 @@
                 error = 1;
             }
         } else {
-            if(cdt && remote_resource_is_changed(sn, dir, db, local_res)) {
+            if(cdt && remote_resource_is_changed(sn, dir, db, res, local_res)) {
                 printf("conflict: %s\n", local_res->path);
                 local_res->last_modified = 0;
                 local_res->skipped = TRUE;
@@ -1357,6 +1373,18 @@
         LocalResource *local_res = elm->data;
         
         DavResource *res = dav_resource_new(sn, local_res->path);
+        if(dir->tagconfig) {
+            DavPropName properties[] = {
+                {DAV_NS,"tags"},
+            };
+            if(dav_load_prop(res, properties, 1)) {
+                sync_error++;
+                print_resource_error(sn, res->path);
+                ret = -1;
+                error = 1;
+                continue;
+            }
+        }
         
         if(local_res->metadata_updated) {
             if(!sync_update_metadata(dir, sn, res, local_res)) {
@@ -1764,6 +1792,12 @@
         if(db_res->tags_hash) {
             res->tags_hash = strdup(db_res->tags_hash);
         }
+        if(db_res->remote_tags_hash) {
+            res->remote_tags_hash = strdup(db_res->remote_tags_hash);
+        }
+        if(db_res->xattr_hash) {
+            res->xattr_hash = strdup(db_res->xattr_hash);
+        }
         
         if(dir->tagconfig && dir->tagconfig->detect_changes && !res->tags_updated) {
             UcxBuffer *tags = sync_get_file_tag_data(dir, res);
@@ -1823,15 +1857,22 @@
         DavSession *sn,
         SyncDirectory *dir,
         SyncDatabase *db,
+        DavResource *remote,
         LocalResource *res)
 {
+    DavPropName properties[] = {
+        {"DAV:","getetag"},
+        {DAV_NS,"tags"},
+        {DAV_NS,VERSION_PATH_PROPERTY}
+    };
+    int err = dav_load_prop(remote, properties, 3);
+    
     if(res->restore) {
         return 0;
     }
     
-    DavResource *remote = dav_get(sn, res->path, "D:getetag");
     int ret = 0;
-    if(remote) {
+    if(err == 0) {
         char *etag = dav_get_string_property(remote, "D:getetag");
         if(!res->etag) {
             // the resource is on the server and the client has no etag
@@ -1848,7 +1889,6 @@
             // something weird is happening, the server must support etags
             fprintf(stderr, "Warning: resource %s has no etag\n", remote->href);
         }
-        dav_resource_free(remote);
     }
     return ret;
 }
@@ -1882,38 +1922,6 @@
     return ret;
 }
 
-UcxList* sync_merge_tags(UcxList *tags1, UcxList *tags2) {
-    // this map is used to check the existence of tags
-    UcxMap *tag_map = ucx_map_new(32);
-    // merged taglist
-    UcxList *new_tags = NULL;
-
-    // add all local tags
-    UCX_FOREACH(elm, tags1) {
-        DavTag *t = elm->data;
-        ucx_map_cstr_put(tag_map, t->name, t);
-        DavTag *newt = calloc(1, sizeof(DavTag));
-        newt->color = t->color ? strdup(t->color) : NULL;
-        newt->name = strdup(t->name);
-        new_tags = ucx_list_append(new_tags, newt);
-    }
-    // check if a remote tag is already in the map
-    // and if not add it to the new taglist
-    UCX_FOREACH(elm, tags2) {
-        DavTag *t = elm->data;
-        if(!ucx_map_cstr_get(tag_map, t->name)) {
-            DavTag *newt = calloc(1, sizeof(DavTag));
-            newt->color = t->color ? strdup(t->color) : NULL;
-            newt->name = strdup(t->name);
-            new_tags = ucx_list_append(new_tags, newt);
-        }
-    }
-
-    ucx_map_free(tag_map);
-
-    return new_tags;
-}
-
 int sync_tags_equal(UcxList *tags1, UcxList *tags2) {
     if(!tags1) {
         return tags2 ? 0 : 1;
@@ -1962,7 +1970,7 @@
     
     DavBool store_tags = FALSE;
     DavBool tags_changed = FALSE;
-    UcxList *local_tags = sync_get_file_tags(dir, local, &tags_changed);
+    UcxList *local_tags = sync_get_file_tags(dir, local, &tags_changed, NULL);
     if(tags_changed) {
         switch(dir->tagconfig->conflict) {
             case TAG_NO_CONFLICT:
@@ -1975,7 +1983,7 @@
                 local->tags_updated = FALSE;
             }
             case TAG_MERGE: {
-                UcxList *new_tags = sync_merge_tags(local_tags, tags);
+                UcxList *new_tags = merge_tags(local_tags, tags);
                 // TODO: free tags and local_tags
                 tags = new_tags;
                 store_tags = TRUE;   
@@ -2085,7 +2093,7 @@
     return buf;
 }
 
-UcxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res, DavBool *changed) {
+UcxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res, DavBool *changed, char **newhash) {
     if(changed) *changed = FALSE;
     
     UcxList *tags = NULL;
@@ -2106,16 +2114,20 @@
                 sync_get_file_tag_data(dir, res);
         
         if(tag_buf) {
-            char *newhash = dav_create_hash(tag_buf->space, tag_buf->size);
+            char *new_hash = dav_create_hash(tag_buf->space, tag_buf->size);
             if(res->tags_hash) {
-                if(changed && strcmp(res->tags_hash, newhash)) {
+                if(changed && strcmp(res->tags_hash, new_hash)) {
                     *changed = TRUE;
                 }
                 free(res->tags_hash);
             } else {
                 if(changed) *changed = TRUE;
             }
-            res->tags_hash = newhash;
+            if(!newhash) {
+                *newhash = new_hash;
+            } else {
+                free(newhash);
+            }
             
             switch(dir->tagconfig->local_format) {
                 default: break;
@@ -2166,10 +2178,10 @@
 #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 versioning_begin(SyncDirectory *dir, DavResource *res) {
     int ret = 0;
     
-    if(dir->versioning->type == VERSIONING_SIMPLE && *exists) {
+    if(dir->versioning->type == VERSIONING_SIMPLE && res->exists) {
         DavResource *history_collection = dav_resource_new(
                     res->session,
                     dir->versioning->collection);
@@ -2179,11 +2191,6 @@
         // better error handling is done later (sync_put_resource)
         // if there is no history collection for this resource, we create 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) {
@@ -2289,6 +2296,27 @@
     return 0;
 }
 
+static void update_metadata_hashes(LocalResource *local, MetadataHashes hashes) {
+    if(hashes.tags) {
+        if(local->tags_hash) {
+            free(local->tags_hash);
+        }
+        local->tags_hash = hashes.tags;
+    }
+    if(hashes.tags_remote) {
+        if(local->remote_tags_hash) {
+            free(local->remote_tags_hash);
+        }
+        local->remote_tags_hash = hashes.tags_remote;
+    }
+    if(hashes.xattr) {
+        if(local->xattr_hash) {
+            free(local->xattr_hash);
+        }
+        local->xattr_hash = hashes.xattr;
+    }
+}
+
 int sync_put_resource(
         SyncDirectory *dir,
         DavResource *res,
@@ -2315,24 +2343,19 @@
     dav_set_content(res, in, (dav_read_func)myread, (dav_seek_func)file_seek);
     dav_set_content_length(res, s.st_size);
     
-    if(dir->tagconfig) {
-        UcxList *tags = sync_get_file_tags(dir, local, NULL);
-        DavXmlNode *prop = create_xml_taglist(tags);
-        if(prop) {
-            dav_set_property_ns(res, DAV_NS, "tags", prop);
-        }
-    }
+    MetadataHashes hashes;
+    hashes = sync_set_metadata_properties(dir, res->session, res, local);
     
-    int exists = dav_exists(res);
+    // before sync_put_resource, remote_resource_is_changed does a propfind
+    // and sets res->exists
+    int exists = res->exists;
     if(dir->versioning && dir->versioning->always) {
-        int err = versioning_begin(dir, res, &exists);
+        int err = versioning_begin(dir, res);
         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;
@@ -2358,6 +2381,8 @@
     if(ret == 0) {
         (*counter)++;
         
+        update_metadata_hashes(local, hashes);
+        
         // check contentlength and get new etag
         DavResource *up_res = dav_get(res->session, res->path, "D:getetag,idav:status,idav:tags");
         
@@ -2483,17 +2508,70 @@
     return ret;
 }
 
-int sync_update_metadata(SyncDirectory *dir, DavSession *sn, DavResource *res, LocalResource *local) {
-    if(dir->tagconfig && local->tags_updated) {
+MetadataHashes sync_set_metadata_properties(
+        SyncDirectory *dir,
+        DavSession *sn,
+        DavResource *res,
+        LocalResource *local)
+{
+    MetadataHashes hashes = {NULL, NULL, NULL};
+    if(dir->tagconfig) {
         // get local tags
-        UcxList *tags = sync_get_file_tags(dir, local, NULL);
-
-        DavXmlNode *prop = create_xml_taglist(tags);
-        if(prop) {
-            dav_set_property_ns(res, DAV_NS, "tags", prop);
+        DavBool changed = 0;
+        char *tags_hash = NULL;
+        UcxList *tags = sync_get_file_tags(dir, local, &changed, &tags_hash);
+        if(changed || local->tags_updated) {
+            hashes.tags = tags_hash;            
+            
+            DavBool store_tags = TRUE;
+            // get remote tags
+            UcxList *remote_tags = NULL;
+            DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_NS, "tags");
+            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)) {
+                // the tags have changed on the server
+                switch(dir->tagconfig->conflict) {
+                    case TAG_NO_CONFLICT: break;
+                    case TAG_KEEP_LOCAL: break;
+                    case TAG_KEEP_REMOTE: {
+                        store_tags = FALSE;
+                        local->tags_updated = FALSE;
+                        break;
+                    }
+                    case TAG_MERGE: {
+                        UcxList *new_tags = merge_tags(tags, remote_tags);
+                        free_taglist(tags);
+                        tags = new_tags;
+                        break;
+                    }
+                }
+            }
+            
+            if(dir->tagconfig->local_format == TAG_FORMAT_CSV) {
+                // csv tag lists don't have colors, so we have to add
+                // the colors from the remote tag list
+                add_tag_colors(tags, remote_tags);
+            }
+            
+            if(store_tags) {
+                if(tags) {
+                    DavXmlNode *tagprop = create_xml_taglist(tags);
+                    dav_set_property_ns(res, DAV_NS, "tags", tagprop);
+                } else {
+                    dav_remove_property_ns(res, DAV_NS, "tags");
+                }
+            }
+            
+            free_taglist(remote_tags);
         } else {
-            dav_remove_property_ns(res, DAV_NS, "tags");
+            if(tags_hash) {
+                free(tags_hash);
+            }
         }
+        free_taglist(tags);
     }
     
     if(local->finfo_updated) {
@@ -2508,10 +2586,21 @@
     if(local->xattr_updated) {
         if(local->xattr) {
             resource_set_xattr(res, local->xattr);
+            hashes.xattr = strdup(local->xattr->hash);
         } else {
             dav_remove_property(res, "idav:xattributes");
         }
     }
+    return hashes;
+}
+
+int sync_update_metadata(
+        SyncDirectory *dir,
+        DavSession *sn,
+        DavResource *res,
+        LocalResource *local)
+{
+    MetadataHashes hashes = sync_set_metadata_properties(dir, sn, res, local);
     
     int err = 0;
     printf("update: %s\n", local->path);
@@ -2519,24 +2608,9 @@
         print_resource_error(sn, local->path);
         err = 1;
     } else {
-        if(dir->tagconfig && local->tags_updated) {
-            UcxBuffer *tag_data = local->cached_tags;
-            if(local->tags_hash) {
-                free(local->tags_hash);
-                local->tags_hash = NULL;
-            }
-            if(tag_data) {
-                char *hash = dav_create_hash(tag_data->space, tag_data->size);
-                local->tags_hash = hash;
-            }
-            local->tags_updated = FALSE;
-        }
-        if(local->xattr) {
-            local->xattr_hash = strdup(local->xattr->hash);
-        }
+        update_metadata_hashes(local, hashes);
     }
-       
-    // TODO: free stuff
+    
     return err;
 }
 
@@ -2895,7 +2969,7 @@
         fprintf(stderr, "Too %s arguments\n", args->argc <= 1 ? "few" : "many");
         return -1;
     }
-    return cmd_tagopt(args, CMD_TAG_ADD);
+    return cmd_tagop(args, CMD_TAG_ADD);
 }
 
 int cmd_remove_tag(CmdArgs *args) {
@@ -2903,7 +2977,7 @@
         fprintf(stderr, "Too %s arguments\n", args->argc <= 1 ? "few" : "many");
         return -1;
     }
-    return cmd_tagopt(args, CMD_TAG_REMOVE);
+    return cmd_tagop(args, CMD_TAG_REMOVE);
 }
 
 int cmd_set_tags(CmdArgs *args) {
@@ -2911,7 +2985,7 @@
         fprintf(stderr, "Too %s arguments\n", args->argc < 1 ? "few" : "many");
         return -1;
     }
-    return cmd_tagopt(args, CMD_TAG_SET);
+    return cmd_tagop(args, CMD_TAG_SET);
 }
 
 int cmd_list_tags(CmdArgs *args) {
@@ -2919,10 +2993,10 @@
         fprintf(stderr, "Too %s arguments\n", args->argc <= 1 ? "few" : "many");
         return -1;
     }
-    return cmd_tagopt(args, CMD_TAG_LIST);
+    return cmd_tagop(args, CMD_TAG_LIST);
 }
 
-int cmd_tagopt(CmdArgs *args, int cmd) {
+int cmd_tagop(CmdArgs *args, int cmd) {
     SyncFile file;
     int ret = 0;
     char *path = args->argv[0];
@@ -2952,7 +3026,7 @@
         char *tag = args->argv[1];
         char *tagcolor = NULL; // TODO: get color
 
-        tags = sync_get_file_tags(file.dir, localres, NULL);
+        tags = sync_get_file_tags(file.dir, localres, NULL, NULL);
         UcxList *x = NULL;
         UCX_FOREACH(elm, tags) {
             DavTag *t = elm->data;

mercurial