dav/pull.c

Tue, 09 Sep 2025 16:01:30 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 09 Sep 2025 16:01:30 +0200
branch
dav-2
changeset 885
591377a27fa3
parent 881
64989511f6c3
permissions
-rw-r--r--

move resource_get_remote_change to a separate source file

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2025 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 "pull.h"
#include "syncdir.h"

#include <libidav/utils.h>
#include <errno.h>
#include <string.h>

/*
 * 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);
}

static char* nullstrdup(const char *s) {
    return s ? strdup(s) : NULL;
}



RemoteChangeType resource_get_remote_change(
        CmdArgs *a,
        DavResource *res,
        SyncDirectory *dir,
        SyncDatabase *db)
{
    DavBool update_db = FALSE;
    
    char *etag = dav_get_string_property(res, "D:getetag");
    if(!etag) {
        fprintf(stderr, "Error: resource %s has no etag\n", res->path);
        return REMOTE_NO_CHANGE;
    }
    char *hash = sync_get_content_hash(res);
      
    DavBool issplit = dav_get_property(res, "idav:split") ? TRUE : FALSE;
    if(issplit) {
        util_remove_trailing_pathseparator(res->path);
    }
    DavBool iscollection = res->iscollection && !issplit;
    
    RemoteChangeType type = cmd_getoption(a, "conflict") ?
            REMOTE_CHANGE_MODIFIED : REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
    
    LocalResource *local = cxMapGet(db->resources, dav_resource_path_key(res));
    char *local_path = syncdir_create_local_path(dir, res->path);
    
    char *link = SYNC_SYMLINK(dir) ? 
            dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL;
    
    SYS_STAT s;
    DavBool exists = 1;
    if(sys_stat(local_path, &s)) {
        if(errno != ENOENT) {
            fprintf(stderr, "Cannot stat file: %s\n", local_path);
            free(local_path);
            return REMOTE_NO_CHANGE;
        }
        exists = 0;
    }
    
    RemoteChangeType ret = REMOTE_NO_CHANGE;
    if(iscollection) {
        if(!exists) {
            ret = REMOTE_CHANGE_MKDIR;
        } else if(local && S_ISDIR(s.st_mode)) {
            local->isdirectory = 1; // make sure isdirectory is set
        } else {
            // set change to REMOTE_CHANGE_MKDIR, which will fail later
            ret = REMOTE_CHANGE_MKDIR;
        }
    } else if(local) {
        DavBool nochange = FALSE;
        if(SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) {
            ret = REMOTE_CHANGE_LINK;
            nochange = TRUE;
            
            if(local->link_target) {
                LocalResource *local2 = local_resource_new(dir, db, local->path);
                if(type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) {
                    ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
                }            
                local_resource_free(local2);

                if(!nullstrcmp(link, local->link_target)) {
                    ret = REMOTE_NO_CHANGE;
                    update_db = TRUE;
                }
            } 
        } else if(issplit && local->hash && hash) {
            if(!strcmp(local->hash, hash)) {
                // resource is already up-to-date on the client
                nochange = TRUE;
            }
        } else if(local->etag) {
            cxstring e = cx_str(etag);
            if(cx_strprefix(e, CX_STR("W/"))) {
                e = cx_strsubs(e, 2);
            }
            if(!strcmp(e.ptr, local->etag)) {
                // resource is already up-to-date on the client
                nochange = TRUE;
            }
        }
        
        if(!nochange) {
            if(!(exists && s.st_mtime != local->last_modified)) {
                type = REMOTE_CHANGE_MODIFIED;
            }
            ret = type;
        }
    } else if(link) {
        // new file is a link
        ret = REMOTE_CHANGE_LINK;
        
        if(exists && type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) {
            // a file with the same name already exists
            // if it is a link, compare the targets
            LocalResource *local2 = local_resource_new(dir, db, res->path);
            if(local2) {
                if(local2->link_target) {
                    if(strcmp(link, local2->link_target)) {
                        ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
                    }
                } else {
                    ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
                }
                
                local_resource_free(local2);
            }
        }
        
    } else if(exists) {
        ret = type;
    } else {
        ret = REMOTE_CHANGE_NEW;
    }
     
    // if hashing is enabled we can compare the hash of the remote file
    // with the local file to test if a file is really modified
    char *update_hash = NULL;
    if (!iscollection &&
        !link &&
        (ret == REMOTE_CHANGE_MODIFIED || ret == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) &&
        exists &&
        hash &&
        !dir->pull_skip_hashing)
    {
        // because rehashing a file is slow, there is a config element for
        // disabling this (pull-skip-hashing)
        char *local_hash = util_file_hash(local_path);
        if(local_hash) {
            if(!strcmp(hash, local_hash)) {
                ret = REMOTE_NO_CHANGE;
                update_db = TRUE;
                update_hash = local_hash;
                
                // if local already exists, update the hash here
                // because it is possible that there are metadata updates
                // and in this case the db will updated later and needs
                // the current hash
                if(local) {
                    if(local->hash) {
                        free(local->hash);
                    }
                    local->hash = local_hash;
                }
            } else {
                free(local_hash);
            }
        }
    }
    
    // if a file is not modified, check if the metadata has changed
    while(ret == REMOTE_NO_CHANGE && local) {
        // check if tags have changed
        if(dir->tagconfig) {
            DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_PROPS_NS, "tags");
            CxList *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_PROPS_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_PROPS_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;
    }
    
    // if update_db is set, a file was modified on the server, but we already
    // have the file content, but we need to update the db
    if(ret == REMOTE_NO_CHANGE && update_db) {
        if(!local) {
            local = calloc(1, sizeof(LocalResource));
            local->path = strdup(res->path);
            
            cxMapPut(db->resources, cx_hash_key_str(local->path), local);
        }
        
        // update local res
        SYS_STAT statdata;
        if(!sys_stat(local_path, &statdata)) {
            sync_set_metadata_from_stat(local, &statdata);
        } else {
            fprintf(stderr, "stat failed for file: %s : %s", local_path, strerror(errno));
        }
        local_resource_set_etag(local, etag);
        if(!local->hash) {
            local->hash = update_hash;
        } // else: hash already updated
        if(link) {
            free(local->link_target);
            local->link_target = link;
        }
    }
        
    free(local_path);
    return ret;
}

mercurial