diff -r a985a587787f -r 90c6bfa94fa2 dav/tags.c --- a/dav/tags.c Fri Jun 01 08:52:05 2018 +0200 +++ b/dav/tags.c Fri Jun 01 09:01:00 2018 +0200 @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include @@ -324,3 +326,281 @@ } #endif + +/* ----------- ----------- tag filter ---------------------- */ + +// TODO: use scstr_t after update to UCX 2.0 +static size_t rtrimskip(sstr_t str, size_t skip) { + while (skip < str.length && isspace(str.ptr[skip])) skip++; + return skip; +} + +static size_t parse_tagfilter_taglist(sstr_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(sstr_t fs, SyncTagFilter* tagfilter); + +static size_t parse_tagfilter_filter(sstr_t fs, SyncTagFilter* tagfilter) { + + size_t consumed = rtrimskip(fs, 0); + fs = sstrsubs(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 = sstrsubs(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(sstr_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 = sstrsubs(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 = sstrsubs(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) { + SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter)); + if (!filterstring) { + return tagfilter; + } + + // TODO: use scstr_t after update to UCX 2.0 + sstr_t fs = sstr((char*) 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(); + } + } +} +