dav/tags.c

Sun, 12 May 2019 13:49:36 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 12 May 2019 13:49:36 +0200
changeset 599
508cbc4d30ea
parent 525
26a1d5b9d9d2
child 667
6cdcd3e4e368
permissions
-rw-r--r--

fix res_link initialization

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2018 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <ucx/string.h>
#include <ucx/utils.h>

#include <libidav/crypto.h>

#include "libxattr.h"

#include "tags.h"

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif

void free_dav_tag(DavTag* tag) {
    free(tag->name);
    if(tag->color) {
        free(tag->color);
    }
    free(tag);
}

void free_taglist(UcxList *list) {
    ucx_list_free_content(list, (ucx_destructor)free_dav_tag);
    ucx_list_free(list);
}

int compare_tagname(DavTag* left, DavTag* right, void* ignorecase) {
    sstr_t leftname = sstr(left->name);
    sstr_t rightname = sstr(right->name);
    if (ignorecase && *((int*) ignorecase)) {
        return sstrcasecmp(leftname, rightname);
    } else {
        return sstrcmp(leftname, rightname);
    }
}

UcxList* parse_text_taglist(const char *buf, size_t length) {
    UcxList *tags = NULL;
    
    int line_start = 0;
    for(int i=0;i<length;i++) {
        if(buf[i] == '\n' || i == length-1) {
            sstr_t line = sstrtrim(sstrn((char*)buf + line_start, i - line_start));
            if(line.length > 0) {
                DavTag *tag = calloc(1, sizeof(DavTag));
                sstr_t color = sstrchr(line, '#');
                if(color.length>0) {
                    sstr_t name = line;
                    name.length = (int)(color.ptr-line.ptr);
                    if(name.length != 0) {
                        tag->name = sstrdup(name).ptr;
                        color.ptr++;
                        color.length--;
                        if(color.length > 0) {
                            tag->color = sstrdup(color).ptr;
                        }
                    } else {
                        free(tag);
                    }
                } else {
                    tag->name = sstrdup(line).ptr;
                    tag->color = NULL;
                }
                
                tags = ucx_list_append(tags, tag);
            }
            line_start = i+1;
        }
    }
    
    return tags;
}

UcxBuffer* create_text_taglist(UcxList *tags) {
    if(!tags) {
        return NULL;
    }
    
    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
    UCX_FOREACH(elm, tags) {
        DavTag *tag = elm->data;
        if(tag->color) {
            ucx_bprintf(buf, "%s#%s\n", tag->name, tag->color);
        } else {
            ucx_bprintf(buf, "%s\n", tag->name);
        }
    }
    return buf;
}


UcxList* parse_csv_taglist(const char *buf, size_t length) {
    UcxList *taglist = NULL;
    
    sstr_t str = sstrn((char*)buf, length);
    ssize_t count = 0;
    sstr_t *tags = sstrsplit(str, S(","), &count);
    for(int i=0;i<count;i++) {
        sstr_t trimmed_tag = sstrtrim(tags[i]);
        if (trimmed_tag.length > 0) {
            DavTag *tag = malloc(sizeof(DavTag));
            tag->name = sstrdup(trimmed_tag).ptr;
            tag->color = NULL;
            taglist = ucx_list_append(taglist, tag);
        }
        free(tags[i].ptr);
    }
    if(tags) {
        free(tags);
    }
    return taglist;
}

UcxBuffer* create_csv_taglist(UcxList *tags) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
    int insertsep = 0;
    UCX_FOREACH(elm, tags) {
        DavTag *tag = elm->data;
        if(insertsep) {
            ucx_buffer_putc(buf, ',');
        }
        ucx_buffer_puts(buf, tag->name);
        insertsep = 1;
    }
    return buf;
}


static DavTag* parse_xml_dav_tag(DavXmlNode *node) {
    char *name = NULL;
    char *color = NULL;
    
    DavXmlNode *c = node->children;
    while(c) {
        if(c->type == DAV_XML_ELEMENT) {
            char *value = dav_xml_getstring(c->children);
            if(value) {
                if(!strcmp(c->namespace, DAV_NS)) {
                    if(!strcmp(c->name, "name")) {
                        char *value = dav_xml_getstring(c->children);
                        if(value) {
                            name = value;
                        }
                    }
                    if(!strcmp(c->name, "color")) {
                        char *value = dav_xml_getstring(c->children);
                        if(value) {
                            color = value;
                        }
                    }
                }
            }
        }
        c = c->next;
    }
    
    DavTag *tag = NULL;
    if(name) {
        tag = malloc(sizeof(DavTag));
        tag->name = strdup(name);
        tag->color = color ? strdup(color) : NULL;
    }
    return tag;
} 

UcxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) {
    UcxList *tags = NULL;
    
    DavXmlNode *node = taglistnode;
    while(node) {
        if(node->type == DAV_XML_ELEMENT) {
            if(!strcmp(node->namespace, DAV_NS) && !strcmp(node->name, "tag")) {
                DavTag *tag = parse_xml_dav_tag(node);
                if(tag) {
                    tags = ucx_list_append(tags, tag);
                }
            }
        } 
        node = node->next;
    }
    
    return tags;
}

DavXmlNode* create_xml_taglist(UcxList *tags) {
    DavXmlNode *tag1 = NULL;
    DavXmlNode *lasttag = NULL;
    UCX_FOREACH(elm, tags) {
        DavTag *tag = elm->data;
        
        DavXmlNode *tagelm = dav_xml_createnode(DAV_NS, "tag");
        DavXmlNode *tagname = dav_xml_createnode_with_text(DAV_NS, "name", tag->name);
        tagelm->children = tagname;
        if(tag->color) {
            DavXmlNode *tagcolor = dav_xml_createnode_with_text(DAV_NS, "color", tag->color);
            tagname->next = tagcolor;
        }
        
        if(lasttag) {
            lasttag->next = tagelm;
            tagelm->prev = lasttag;
        } else {
            tag1 = tagelm;
        }
        lasttag = tagelm;
    }
    return tag1;
}


#ifdef __APPLE__
static DavTag* tagstr2davtag(const char *str) {
    const char *name = str;
    const char *color = NULL;
    size_t len = strlen(str);
    size_t namelen = len;
    
    if(len == 0) {
        return NULL;
    }
    
    // start with 1 because the first char should not be a linebreak
    for(int i=1;i<len;i++) {
        if(str[i] == '\n') {
            if(!color) {
                color = str + i + 1;
                namelen = i;
            }
        }
    }
    int colorlen = len - namelen - 1;
    
    DavTag *tag = malloc(sizeof(DavTag));
    tag->name = malloc(namelen + 1);
    memcpy(tag->name, name, namelen);
    tag->name[namelen] = 0;
    if(colorlen > 0) {
        tag->color = malloc(colorlen + 1);
        memcpy(tag->color, color, colorlen);
        tag->color[colorlen] = 0;
    } else {
        tag->color = NULL;
    }
    
    return tag;
}

UcxList* parse_macos_taglist(const char *buf, size_t length) {
    UcxList *taglist = NULL;
    
    CFDataRef data = CFDataCreateWithBytesNoCopy(
            kCFAllocatorDefault,
            (const UInt8*)buf,
            length,
            kCFAllocatorNull);
    CFPropertyListRef propertylist = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL);
    CFArrayRef array = propertylist;
    int count = CFArrayGetCount(array);
    for(int i=0;i<count;i++) {
        CFStringRef str = CFArrayGetValueAtIndex(array, i);
        int slen = CFStringGetLength(str);
        size_t cstrbuflen = slen * 4 + 4;
        char *cstr = malloc(cstrbuflen);
        if(CFStringGetCString(str, cstr, cstrbuflen, kCFStringEncodingUTF8)) {
            DavTag *tag = tagstr2davtag(cstr);
            if(tag) {
                taglist = ucx_list_append(taglist, tag);
            }
        }
        free(cstr);
    }
    
    CFRelease(propertylist);
    CFRelease(data);
    
    return taglist;
}

UcxBuffer* create_macos_taglist(UcxList *tags) {
    size_t count = ucx_list_size(tags);
    if(count == 0) {
        return NULL;
    }
    
    CFStringRef *strings = calloc(sizeof(CFStringRef), count);
    int i = 0;
    UCX_FOREACH(elm, tags) {
        DavTag *tag = elm->data;
        CFStringRef str = NULL;
        if(tag->color) {
            sstr_t s = sstrcat(3, sstr(tag->name), S("\n"), sstr(tag->color));
            str = CFStringCreateWithCString(kCFAllocatorDefault, s.ptr, kCFStringEncodingUTF8);
            free(s.ptr);
        } else {
            str = CFStringCreateWithCString(kCFAllocatorDefault, tag->name, kCFStringEncodingUTF8);
        }
        strings[i] = str;
        i++; 
    }
    
    CFPropertyListRef array = CFArrayCreate(kCFAllocatorDefault, (const void**)strings, count, &kCFTypeArrayCallBacks);
    CFDataRef data = CFPropertyListCreateData(kCFAllocatorDefault, array, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
    
    UcxBuffer *buf = NULL;
    if(data) {
        int datalen = CFDataGetLength(data);
        CFRange range;
        range.location = 0;
        range.length = datalen;
        buf = ucx_buffer_new(NULL, datalen, 0);
        CFDataGetBytes(data, range, (UInt8*)buf->space);
        buf->size = datalen;
        CFRelease(data);
    }
    
    for(int i=0;i<count;i++) {
        CFRelease(strings[i]);
    }
    CFRelease(array);
    
    return buf;
}

#else
UcxList* parse_macos_taglist(const char *buf, size_t length) {
    fprintf(stderr, "Error: macos tags not supported on this platform.\n");
    return NULL;
}
UcxBuffer* create_macos_taglist(UcxList *tags) {
    fprintf(stderr, "Error: macos tags not supported on this platform.\n");
    return NULL;
}
#endif


int compare_taglists(UcxList *tags1, UcxList *tags2) {
    if(!tags1) {
        return tags2 ? 0 : 1;
    }
    if(!tags2) {
        return tags1 ? 0 : 1;
    }
    
    UcxMap *map1 = ucx_map_new(32);
    UCX_FOREACH(elm, tags1) {
        DavTag *t = elm->data;
        ucx_map_cstr_put(map1, t->name, t);
    }
    
    int equal = 1;
    int i = 0;
    UCX_FOREACH(elm, tags2) {
        DavTag *t = elm->data;
        if(!ucx_map_cstr_get(map1, t->name)) {
            equal = 0;
            break;
        }
        i++;
    }
    
    if(i != map1->count) {
        equal = 0;
    }
    ucx_map_free(map1);
    return equal;
}

char* create_tags_hash(UcxList *tags) {
    if(!tags) {
        return NULL;
    }
    UcxBuffer *buf = create_text_taglist(tags);
    char *hash = dav_create_hash(buf->space, buf->size);
    ucx_buffer_free(buf);
    return hash;
}

UcxList* 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;
}

void add_tag_colors(UcxList *taglist, UcxList *colored) {
    UcxMap *tagmap = ucx_map_new(32);
    UCX_FOREACH(elm, taglist) {
        DavTag *tag = elm->data;
        ucx_map_cstr_put(tagmap, tag->name, tag);
    }
    
    UCX_FOREACH(elm, colored) {
        DavTag *colored_tag = elm->data;
        if(colored_tag->color) {
            DavTag *tag = ucx_map_cstr_get(tagmap, colored_tag->name);
            if(tag && !tag->color) {
                tag->color = strdup(colored_tag->color);
            }
        }
    }
    
    ucx_map_free(tagmap);
}

/* ----------- ----------- tag filter  ---------------------- */

static size_t rtrimskip(scstr_t str, size_t skip) {
    while (skip < str.length && isspace(str.ptr[skip])) skip++;
    return skip;
}

static size_t parse_tagfilter_taglist(scstr_t fs, SyncTagFilter* tagfilter) {
    size_t csvlen;
    for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) {
        if (fs.ptr[csvlen] == ')') break;
    }
    fs.length = csvlen;
        
    tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length);
        
    return csvlen;
}

static size_t parse_tagfilter_subfilters(scstr_t fs, SyncTagFilter* tagfilter);

static size_t parse_tagfilter_filter(scstr_t fs, SyncTagFilter* tagfilter) {
    
    size_t consumed = rtrimskip(fs, 0);
    fs = scstrsubs(fs, consumed);
    
    if (fs.length == 0) {
        return consumed;
    } else {

        // optional operator
        int hasop = 0;
        if (fs.ptr[0] == '&') {
            tagfilter->mode = DAV_SYNC_TAGFILTER_AND;
            hasop = 1;
        } else if (fs.ptr[0] == '|') {
            tagfilter->mode = DAV_SYNC_TAGFILTER_OR;
            hasop = 1;
        } else if (fs.ptr[0] == '0') {
            tagfilter->mode = DAV_SYNC_TAGFILTER_NONE;
            hasop = 1;
        } else if (fs.ptr[0] == '1') {
            tagfilter->mode = DAV_SYNC_TAGFILTER_ONE;
            hasop = 1;
        } else {
            // default operator is AND
            tagfilter->mode = DAV_SYNC_TAGFILTER_AND;
        }
        
        if (hasop) {
            size_t skip = rtrimskip(fs, 1);
            consumed += skip;
            fs = scstrsubs(fs, skip);
        }

        if (fs.length > 0 && fs.ptr[0] == '(') {
            size_t c = parse_tagfilter_subfilters(fs, tagfilter);
            if (c) {
                return consumed + c;
            } else {
                return 0;
            }
        } else {
            tagfilter->subfilter_count = 0;
            tagfilter->subfilters = NULL;
            return consumed + parse_tagfilter_taglist(fs, tagfilter);
        }
    }
}

/*
 * Parses:  ( "(" , filter , ")" )+
 */
static size_t parse_tagfilter_subfilters(scstr_t fs, SyncTagFilter* f) {
    
    // strategy: allocate much and give back later (instead of reallocs in loop)
    size_t subfilter_cap = 8;
    f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*));
    f->subfilter_count = 0;
    
    size_t total_consumed = 0;
    size_t c;
    do {
        // skip leading parenthesis (and white spaces)
        c = rtrimskip(fs, 1);
        fs = scstrsubs(fs, c);
        total_consumed += c;
    
        // increase array capacity, if necessary
        if (f->subfilter_count >= subfilter_cap) {
            subfilter_cap *= 2;
            SyncTagFilter** newarr = realloc(f->subfilters,
                    subfilter_cap * sizeof(SyncTagFilter*));
            if (newarr) {
                f->subfilters = newarr;
            } else {
                abort(); // no error handling reachable, so we are fucked
            }
        }
        
        // allocate space for a new filter
        SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter));
        
        // parse that filter
        c = parse_tagfilter_filter(fs, subf);
        
        // sanity check: we must end with a closing parenthesis
        if (c > 0 && fs.ptr[c] == ')') {
            f->subfilters[f->subfilter_count++] = subf;
            
            // consume ')' and find the next parenthesis or the end-of-string
            c = rtrimskip(fs, 1+c);
            fs = scstrsubs(fs, c);
            total_consumed += c;

            if (fs.length == 0 || fs.ptr[0] == ')') {
                // our job is done
                break;
            } else if (fs.ptr[0] != '(') {
                // anything else than a parenthesis or end-of-string is an error
                return 0;
            }
        } else {
            free(subf);
            break;
        }
        
    } while(1);
    
    // try to shrink the array
    if (f->subfilter_count > 0) {
        SyncTagFilter** shrinked_array = realloc(f->subfilters,
                f->subfilter_count * sizeof(SyncTagFilter*));
        if (shrinked_array) {
            f->subfilters = shrinked_array;
        }
    } else {
        free(f->subfilters);
        f->subfilters = NULL;
    }
        
    return total_consumed;
}

SyncTagFilter* parse_tagfilter_string(const char* filterstring, int scope) {
    SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter));
    tagfilter->scope = scope;
    if (!filterstring) {
        return tagfilter;
    }
    
    scstr_t fs = scstr(filterstring);
    size_t consumed = parse_tagfilter_filter(fs, tagfilter);
    if (!consumed) {
        free_tagfilter(tagfilter);
        return NULL;
    }
    
    // consume trailing white spaces
    consumed = rtrimskip(fs, consumed);
    
    // sanity check: have we consumed the whole string?
    if (consumed != fs.length) {
        free_tagfilter(tagfilter);
        return NULL;
    }
        
    return tagfilter;
}

void free_tagfilter(SyncTagFilter* filter) {
    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
        free_tagfilter(filter->subfilters[i]);
    }
    free(filter->subfilters);
    free(filter);
}


static int matches_tags_and(UcxList *dav_tags, UcxList *tags, int ignorecase) {
    UCX_FOREACH(e, tags) {
        if (!ucx_list_contains(dav_tags, e->data,
                (cmp_func) compare_tagname, &ignorecase)) {
            return 0;
        }
    }
    return 1;
}

static int matches_tags_or(UcxList *dav_tags, UcxList *tags, int ignorecase) {
    UCX_FOREACH(e, tags) {
        if (ucx_list_contains(dav_tags, e->data,
                (cmp_func) compare_tagname, &ignorecase)) {
            return 1;
        }
    }
    return 0;
}

static int matches_tags_one(UcxList *dav_tags, UcxList *tags, int ignorecase) {
    int matches_exactly_one = 0;
    UCX_FOREACH(e, tags) {
        if (ucx_list_contains(dav_tags, e->data,
                (cmp_func) compare_tagname, &ignorecase)) {
            if (matches_exactly_one) {
                return 0;
            } else {
                matches_exactly_one = 1;
            }
        }
    }
    return matches_exactly_one;
}

static int matches_subfilters_and(UcxList *dav_tags, SyncTagFilter *filter) {
    int ret = 1;
    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
        ret &= matches_tagfilter(dav_tags, filter->subfilters[i]);
    }
    return ret;
}

static int matches_subfilters_or(UcxList *dav_tags, SyncTagFilter *filter) {
    int ret = 0;
    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
        ret |= matches_tagfilter(dav_tags, filter->subfilters[i]);
    }
    return ret;
}

static int matches_subfilters_one(UcxList *dav_tags, SyncTagFilter *filter) {
    int one = 0;
    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
        if (matches_tagfilter(dav_tags, filter->subfilters[i])) {
            if (one) {
                return 0;
            } else {
                one = 1;
            }
        }
    }
    return one;
}

int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter) {

    if (tagfilter->subfilter_count > 0) {
        switch (tagfilter->mode) {
        case DAV_SYNC_TAGFILTER_OR:
            return matches_subfilters_or(dav_tags, tagfilter);
        case DAV_SYNC_TAGFILTER_AND:
            return matches_subfilters_and(dav_tags, tagfilter);
        case DAV_SYNC_TAGFILTER_NONE:
            return !matches_subfilters_or(dav_tags, tagfilter);
        case DAV_SYNC_TAGFILTER_ONE:
            return matches_subfilters_one(dav_tags, tagfilter);
        default:
            abort();
        }
    } else {
        int ignorecase = 0; // TODO: maybe add support later
        switch (tagfilter->mode) {
        case DAV_SYNC_TAGFILTER_OR:
            return matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
        case DAV_SYNC_TAGFILTER_AND:
            return matches_tags_and(dav_tags, tagfilter->tags, ignorecase);
        case DAV_SYNC_TAGFILTER_NONE:
            return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
        case DAV_SYNC_TAGFILTER_ONE:
            return matches_tags_one(dav_tags, tagfilter->tags, ignorecase);
        default:
            abort();
        }
    }
}

mercurial