#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <cx/string.h>
#include <cx/utils.h>
#include <cx/printf.h>
#include <cx/hash_map.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(CxList *list) {
if(!list) {
return;
}
cxListDestroy(list);
}
int compare_tagname(DavTag* left, DavTag* right,
void* ignorecase) {
cxstring leftname = cx_str(left->name);
cxstring rightname = cx_str(right->name);
if (ignorecase && *((
int*) ignorecase)) {
return cx_strcasecmp(leftname, rightname);
}
else {
return cx_strcmp(leftname, rightname);
}
}
CxList* parse_text_taglist(
const char *buf,
size_t length) {
CxList *tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(tags, free_dav_tag);
int line_start =
0;
for(
int i=
0;i<length;i++) {
if(buf[i] ==
'\n' || i == length-
1) {
cxstring line = cx_strtrim(cx_strn((
char*)buf + line_start, i - line_start));
if(line.length >
0) {
DavTag *tag = calloc(
1,
sizeof(DavTag));
cxstring color = cx_strchr(line,
'#');
if(color.length>
0) {
cxstring name = line;
name.length = (
int)(color.ptr-line.ptr);
if(name.length !=
0) {
tag->name = cx_strdup(name).ptr;
color.ptr++;
color.length--;
if(color.length >
0) {
tag->color = cx_strdup(color).ptr;
}
}
else {
free(tag);
}
}
else {
tag->name = cx_strdup(line).ptr;
tag->color =
NULL;
}
cxListAdd(tags, tag);
}
line_start = i+
1;
}
}
return tags;
}
CxMap* taglist2map(CxList *tags) {
if(!tags) {
return cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
8);
}
CxMap *map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, cxListSize(tags) +
8);
CxIterator iter = cxListIterator(tags);
cx_foreach(DavTag*, t, iter) {
cxMapPut(map, cx_hash_key_str(t->name), t);
}
return map;
}
CxBuffer* create_text_taglist(CxList *tags) {
if(!tags) {
return NULL;
}
CxBuffer *buf = cxBufferCreate(
NULL,
128, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag *, tag, i) {
if(tag->color) {
cx_bprintf(buf,
"%s#%s\n", tag->name, tag->color);
}
else {
cx_bprintf(buf,
"%s\n", tag->name);
}
}
return buf;
}
CxList* parse_csv_taglist(
const char *buf,
size_t length) {
CxList *taglist = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(taglist, free_dav_tag);
cxstring str = cx_strn(buf, length);
CxStrtokCtx tags = cx_strtok(str,
CX_STR(
","),
INT_MAX);
cxstring tagstr;
while(cx_strtok_next(&tags, &tagstr)) {
cxstring trimmed_tag = cx_strtrim(tagstr);
if (trimmed_tag.length >
0) {
DavTag *tag = malloc(
sizeof(DavTag));
tag->name = cx_strdup(trimmed_tag).ptr;
tag->color =
NULL;
cxListAdd(taglist, tag);
}
}
return taglist;
}
CxBuffer* create_csv_taglist(CxList *tags) {
CxBuffer *buf = cxBufferCreate(
NULL,
128, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
int insertsep =
0;
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag*, tag, i) {
if(insertsep) {
cxBufferPut(buf,
',');
}
cxBufferPutString(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_PROPS_NS)) {
if(!strcmp(c->name,
"name")) {
name = value;
}
if(!strcmp(c->name,
"color")) {
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;
}
CxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) {
CxList *tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(tags, free_dav_tag);
DavXmlNode *node = taglistnode;
while(node) {
if(node->type ==
DAV_XML_ELEMENT) {
if(!strcmp(node->namespace,
DAV_PROPS_NS) && !strcmp(node->name,
"tag")) {
DavTag *tag = parse_xml_dav_tag(node);
if(tag) {
cxListAdd(tags, tag);
}
}
}
node = node->next;
}
return tags;
}
DavXmlNode* create_xml_taglist(CxList *tags) {
DavXmlNode *tag1 =
NULL;
DavXmlNode *lasttag =
NULL;
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag*, tag, i) {
DavXmlNode *tagelm = dav_xml_createnode(
DAV_PROPS_NS,
"tag");
DavXmlNode *tagname = dav_xml_createnode_with_text(
DAV_PROPS_NS,
"name", tag->name);
tagelm->children = tagname;
if(tag->color) {
DavXmlNode *tagcolor = dav_xml_createnode_with_text(
DAV_PROPS_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;
}
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;
}
CxList* parse_macos_taglist(
const char *buf,
size_t length) {
CxList *taglist = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
taglist->simple_destructor = (cx_destructor_func)free_dav_tag;
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) {
cxListAdd(taglist, tag);
}
}
free(cstr);
}
CFRelease(propertylist);
CFRelease(data);
return taglist;
}
CxBuffer* create_macos_taglist(CxList *tags) {
size_t count = tags->size;
if(count ==
0) {
return NULL;
}
CFStringRef *strings = calloc(
sizeof(CFStringRef), count);
int i =
0;
CxIterator iter = cxListIterator(tags);
cx_foreach(DavTag*, tag, iter) {
CFStringRef str =
NULL;
if(tag->color) {
cxmutstr s = cx_strcat(
3, cx_mutstr(tag->name),
CX_STR(
"\n"), cx_str(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);
CxBuffer *buf =
NULL;
if(data) {
int datalen = CFDataGetLength(data);
CFRange range;
range.location =
0;
range.length = datalen;
buf = cxBufferCreate(
NULL, datalen, cxDefaultAllocator,
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
CxList* parse_macos_taglist(
const char *buf,
size_t length) {
fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
return NULL;
}
CxBuffer* create_macos_taglist(CxList *tags) {
fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
return NULL;
}
#endif
int compare_taglists(CxList *tags1, CxList *tags2) {
if(!tags1) {
return tags2 ?
0 :
1;
}
if(!tags2) {
return tags1 ?
0 :
1;
}
CxMap *map1 = taglist2map(tags1);
int equal =
1;
int i =
0;
CxIterator iter = cxListIterator(tags2);
cx_foreach(DavTag*, t, iter) {
if(!cxMapGet(map1, cx_hash_key_str(t->name))) {
equal =
0;
break;
}
i++;
}
if(i != cxMapSize(map1)) {
equal =
0;
}
cxMapDestroy(map1);
return equal;
}
char* create_tags_hash(CxList *tags) {
if(!tags) {
return NULL;
}
CxBuffer *buf = create_text_taglist(tags);
char *hash = dav_create_hash(buf->space, buf->size);
cxBufferDestroy(buf);
return hash;
}
CxList* merge_tags(CxList *tags1, CxList *tags2) {
CxMap *tag_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
32);
CxList *new_tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(new_tags, free_dav_tag);
if(tags1) {
CxIterator iter = cxListIterator(tags1);
cx_foreach(DavTag*, t, iter) {
cxMapPut(tag_map, cx_hash_key_str(t->name), t);
DavTag *newt = calloc(
1,
sizeof(DavTag));
newt->color = t->color ? strdup(t->color) :
NULL;
newt->name = strdup(t->name);
cxListAdd(new_tags, newt);
}
}
if(tags2) {
CxIterator iter = cxListIterator(tags2);
cx_foreach(DavTag*, t, iter) {
if(!cxMapGet(tag_map, cx_hash_key_str(t->name))) {
DavTag *newt = calloc(
1,
sizeof(DavTag));
newt->color = t->color ? strdup(t->color) :
NULL;
newt->name = strdup(t->name);
cxListAdd(new_tags, newt);
}
}
}
cxMapDestroy(tag_map);
return new_tags;
}
void add_tag_colors(CxList *taglist, CxList *colored) {
CxMap *tagmap = taglist2map(taglist);
CxIterator i = cxListIterator(colored);
cx_foreach(DavTag*, colored_tag, i) {
if(colored_tag->color) {
DavTag *tag = cxMapGet(tagmap, cx_hash_key_str(colored_tag->name));
if(tag && !tag->color) {
tag->color = strdup(colored_tag->color);
}
}
}
cxMapDestroy(tagmap);
}
static size_t rtrimskip(cxstring str,
size_t skip) {
while (skip < str.length && isspace(str.ptr[skip])) skip++;
return skip;
}
static size_t parse_tagfilter_taglist(cxstring 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(cxstring fs, SyncTagFilter* tagfilter);
static size_t parse_tagfilter_filter(cxstring fs, SyncTagFilter* tagfilter) {
size_t consumed = rtrimskip(fs,
0);
fs = cx_strsubs(fs, consumed);
if (fs.length ==
0) {
return consumed;
}
else {
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 {
tagfilter->mode =
DAV_SYNC_TAGFILTER_AND;
}
if (hasop) {
size_t skip = rtrimskip(fs,
1);
consumed += skip;
fs = cx_strsubs(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);
}
}
}
static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* f) {
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 {
c = rtrimskip(fs,
1);
fs = cx_strsubs(fs, c);
total_consumed += c;
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();
}
}
SyncTagFilter* subf = calloc(
1,
sizeof(SyncTagFilter));
c = parse_tagfilter_filter(fs, subf);
if (c >
0 && fs.ptr[c] ==
')') {
f->subfilters[f->subfilter_count++] = subf;
c = rtrimskip(fs,
1+c);
fs = cx_strsubs(fs, c);
total_consumed += c;
if (fs.length ==
0 || fs.ptr[
0] ==
')') {
break;
}
else if (fs.ptr[
0] !=
'(') {
return 0;
}
}
else {
free(subf);
break;
}
}
while(
1);
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;
}
cxstring fs = cx_str(filterstring);
size_t consumed = parse_tagfilter_filter(fs, tagfilter);
if (!consumed) {
free_tagfilter(tagfilter);
return NULL;
}
consumed = rtrimskip(fs, consumed);
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(CxList *dav_tags, CxList *tags,
int ignorecase) {
int ret =
1;
CxMap *tagmap = taglist2map(dav_tags);
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag *, tag, i) {
if (cxMapGet(tagmap, cx_hash_key_str(tag->name)) ==
NULL) {
ret =
0;
break;
}
}
cxMapDestroy(tagmap);
return ret;
}
static int matches_tags_or(CxList *dav_tags, CxList *tags,
int ignorecase) {
int ret =
0;
CxMap *tagmap = taglist2map(dav_tags);
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag *, tag, i) {
if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
ret =
1;
break;
}
}
cxMapDestroy(tagmap);
return ret;
}
static int matches_tags_one(CxList *dav_tags, CxList *tags,
int ignorecase) {
int matches_exactly_one =
0;
CxMap *tagmap = taglist2map(dav_tags);
CxIterator i = cxListIterator(tags);
cx_foreach(DavTag *, tag, i) {
if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
if (matches_exactly_one) {
cxMapDestroy(tagmap);
return 0;
}
else {
matches_exactly_one =
1;
}
}
}
cxMapDestroy(tagmap);
return matches_exactly_one;
}
static int matches_subfilters_and(CxList *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(CxList *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(CxList *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(CxList *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;
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();
}
}
}