322 fprintf(stderr, "Error: macos tags not supported on this platform.\n"); |
324 fprintf(stderr, "Error: macos tags not supported on this platform.\n"); |
323 return NULL; |
325 return NULL; |
324 } |
326 } |
325 #endif |
327 #endif |
326 |
328 |
|
329 |
|
330 /* ----------- ----------- tag filter ---------------------- */ |
|
331 |
|
332 // TODO: use scstr_t after update to UCX 2.0 |
|
333 static size_t rtrimskip(sstr_t str, size_t skip) { |
|
334 while (skip < str.length && isspace(str.ptr[skip])) skip++; |
|
335 return skip; |
|
336 } |
|
337 |
|
338 static size_t parse_tagfilter_taglist(sstr_t fs, SyncTagFilter* tagfilter) { |
|
339 size_t csvlen; |
|
340 for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) { |
|
341 if (fs.ptr[csvlen] == ')') break; |
|
342 } |
|
343 fs.length = csvlen; |
|
344 |
|
345 tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length); |
|
346 |
|
347 return csvlen; |
|
348 } |
|
349 |
|
350 static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* tagfilter); |
|
351 |
|
352 static size_t parse_tagfilter_filter(sstr_t fs, SyncTagFilter* tagfilter) { |
|
353 |
|
354 size_t consumed = rtrimskip(fs, 0); |
|
355 fs = sstrsubs(fs, consumed); |
|
356 |
|
357 if (fs.length == 0) { |
|
358 return consumed; |
|
359 } else { |
|
360 |
|
361 // optional operator |
|
362 int hasop = 0; |
|
363 if (fs.ptr[0] == '&') { |
|
364 tagfilter->mode = DAV_SYNC_TAGFILTER_AND; |
|
365 hasop = 1; |
|
366 } else if (fs.ptr[0] == '|') { |
|
367 tagfilter->mode = DAV_SYNC_TAGFILTER_OR; |
|
368 hasop = 1; |
|
369 } else if (fs.ptr[0] == '0') { |
|
370 tagfilter->mode = DAV_SYNC_TAGFILTER_NONE; |
|
371 hasop = 1; |
|
372 } else if (fs.ptr[0] == '1') { |
|
373 tagfilter->mode = DAV_SYNC_TAGFILTER_ONE; |
|
374 hasop = 1; |
|
375 } else { |
|
376 // default operator is AND |
|
377 tagfilter->mode = DAV_SYNC_TAGFILTER_AND; |
|
378 } |
|
379 |
|
380 if (hasop) { |
|
381 size_t skip = rtrimskip(fs, 1); |
|
382 consumed += skip; |
|
383 fs = sstrsubs(fs, skip); |
|
384 } |
|
385 |
|
386 if (fs.length > 0 && fs.ptr[0] == '(') { |
|
387 size_t c = parse_tagfilter_subfilters(fs, tagfilter); |
|
388 if (c) { |
|
389 return consumed + c; |
|
390 } else { |
|
391 return 0; |
|
392 } |
|
393 } else { |
|
394 tagfilter->subfilter_count = 0; |
|
395 tagfilter->subfilters = NULL; |
|
396 return consumed + parse_tagfilter_taglist(fs, tagfilter); |
|
397 } |
|
398 } |
|
399 } |
|
400 |
|
401 /* |
|
402 * Parses: ( "(" , filter , ")" )+ |
|
403 */ |
|
404 static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* f) { |
|
405 |
|
406 // strategy: allocate much and give back later (instead of reallocs in loop) |
|
407 size_t subfilter_cap = 8; |
|
408 f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*)); |
|
409 f->subfilter_count = 0; |
|
410 |
|
411 size_t total_consumed = 0; |
|
412 size_t c; |
|
413 do { |
|
414 // skip leading parenthesis (and white spaces) |
|
415 c = rtrimskip(fs, 1); |
|
416 fs = sstrsubs(fs, c); |
|
417 total_consumed += c; |
|
418 |
|
419 // increase array capacity, if necessary |
|
420 if (f->subfilter_count >= subfilter_cap) { |
|
421 subfilter_cap *= 2; |
|
422 SyncTagFilter** newarr = realloc(f->subfilters, |
|
423 subfilter_cap * sizeof(SyncTagFilter*)); |
|
424 if (newarr) { |
|
425 f->subfilters = newarr; |
|
426 } else { |
|
427 abort(); // no error handling reachable, so we are fucked |
|
428 } |
|
429 } |
|
430 |
|
431 // allocate space for a new filter |
|
432 SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter)); |
|
433 |
|
434 // parse that filter |
|
435 c = parse_tagfilter_filter(fs, subf); |
|
436 |
|
437 // sanity check: we must end with a closing parenthesis |
|
438 if (c > 0 && fs.ptr[c] == ')') { |
|
439 f->subfilters[f->subfilter_count++] = subf; |
|
440 |
|
441 // consume ')' and find the next parenthesis or the end-of-string |
|
442 c = rtrimskip(fs, 1+c); |
|
443 fs = sstrsubs(fs, c); |
|
444 total_consumed += c; |
|
445 |
|
446 if (fs.length == 0 || fs.ptr[0] == ')') { |
|
447 // our job is done |
|
448 break; |
|
449 } else if (fs.ptr[0] != '(') { |
|
450 // anything else than a parenthesis or end-of-string is an error |
|
451 return 0; |
|
452 } |
|
453 } else { |
|
454 free(subf); |
|
455 break; |
|
456 } |
|
457 |
|
458 } while(1); |
|
459 |
|
460 // try to shrink the array |
|
461 if (f->subfilter_count > 0) { |
|
462 SyncTagFilter** shrinked_array = realloc(f->subfilters, |
|
463 f->subfilter_count * sizeof(SyncTagFilter*)); |
|
464 if (shrinked_array) { |
|
465 f->subfilters = shrinked_array; |
|
466 } |
|
467 } else { |
|
468 free(f->subfilters); |
|
469 f->subfilters = NULL; |
|
470 } |
|
471 |
|
472 return total_consumed; |
|
473 } |
|
474 |
|
475 SyncTagFilter* parse_tagfilter_string(const char* filterstring) { |
|
476 SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter)); |
|
477 if (!filterstring) { |
|
478 return tagfilter; |
|
479 } |
|
480 |
|
481 // TODO: use scstr_t after update to UCX 2.0 |
|
482 sstr_t fs = sstr((char*) filterstring); |
|
483 size_t consumed = parse_tagfilter_filter(fs, tagfilter); |
|
484 if (!consumed) { |
|
485 free_tagfilter(tagfilter); |
|
486 return NULL; |
|
487 } |
|
488 |
|
489 // consume trailing white spaces |
|
490 consumed = rtrimskip(fs, consumed); |
|
491 |
|
492 // sanity check: have we consumed the whole string? |
|
493 if (consumed != fs.length) { |
|
494 free_tagfilter(tagfilter); |
|
495 return NULL; |
|
496 } |
|
497 |
|
498 return tagfilter; |
|
499 } |
|
500 |
|
501 void free_tagfilter(SyncTagFilter* filter) { |
|
502 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
503 free_tagfilter(filter->subfilters[i]); |
|
504 } |
|
505 free(filter->subfilters); |
|
506 free(filter); |
|
507 } |
|
508 |
|
509 |
|
510 static int matches_tags_and(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
511 UCX_FOREACH(e, tags) { |
|
512 if (!ucx_list_contains(dav_tags, e->data, |
|
513 (cmp_func) compare_tagname, &ignorecase)) { |
|
514 return 0; |
|
515 } |
|
516 } |
|
517 return 1; |
|
518 } |
|
519 |
|
520 static int matches_tags_or(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
521 UCX_FOREACH(e, tags) { |
|
522 if (ucx_list_contains(dav_tags, e->data, |
|
523 (cmp_func) compare_tagname, &ignorecase)) { |
|
524 return 1; |
|
525 } |
|
526 } |
|
527 return 0; |
|
528 } |
|
529 |
|
530 static int matches_tags_one(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
531 int matches_exactly_one = 0; |
|
532 UCX_FOREACH(e, tags) { |
|
533 if (ucx_list_contains(dav_tags, e->data, |
|
534 (cmp_func) compare_tagname, &ignorecase)) { |
|
535 if (matches_exactly_one) { |
|
536 return 0; |
|
537 } else { |
|
538 matches_exactly_one = 1; |
|
539 } |
|
540 } |
|
541 } |
|
542 return matches_exactly_one; |
|
543 } |
|
544 |
|
545 static int matches_subfilters_and(UcxList *dav_tags, SyncTagFilter *filter) { |
|
546 int ret = 1; |
|
547 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
548 ret &= matches_tagfilter(dav_tags, filter->subfilters[i]); |
|
549 } |
|
550 return ret; |
|
551 } |
|
552 |
|
553 static int matches_subfilters_or(UcxList *dav_tags, SyncTagFilter *filter) { |
|
554 int ret = 0; |
|
555 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
556 ret |= matches_tagfilter(dav_tags, filter->subfilters[i]); |
|
557 } |
|
558 return ret; |
|
559 } |
|
560 |
|
561 static int matches_subfilters_one(UcxList *dav_tags, SyncTagFilter *filter) { |
|
562 int one = 0; |
|
563 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
564 if (matches_tagfilter(dav_tags, filter->subfilters[i])) { |
|
565 if (one) { |
|
566 return 0; |
|
567 } else { |
|
568 one = 1; |
|
569 } |
|
570 } |
|
571 } |
|
572 return one; |
|
573 } |
|
574 |
|
575 int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter) { |
|
576 |
|
577 if (tagfilter->subfilter_count > 0) { |
|
578 switch (tagfilter->mode) { |
|
579 case DAV_SYNC_TAGFILTER_OR: |
|
580 return matches_subfilters_or(dav_tags, tagfilter); |
|
581 case DAV_SYNC_TAGFILTER_AND: |
|
582 return matches_subfilters_and(dav_tags, tagfilter); |
|
583 case DAV_SYNC_TAGFILTER_NONE: |
|
584 return !matches_subfilters_or(dav_tags, tagfilter); |
|
585 case DAV_SYNC_TAGFILTER_ONE: |
|
586 return matches_subfilters_one(dav_tags, tagfilter); |
|
587 default: |
|
588 abort(); |
|
589 } |
|
590 } else { |
|
591 int ignorecase = 0; // TODO: maybe add support later |
|
592 switch (tagfilter->mode) { |
|
593 case DAV_SYNC_TAGFILTER_OR: |
|
594 return matches_tags_or(dav_tags, tagfilter->tags, ignorecase); |
|
595 case DAV_SYNC_TAGFILTER_AND: |
|
596 return matches_tags_and(dav_tags, tagfilter->tags, ignorecase); |
|
597 case DAV_SYNC_TAGFILTER_NONE: |
|
598 return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase); |
|
599 case DAV_SYNC_TAGFILTER_ONE: |
|
600 return matches_tags_one(dav_tags, tagfilter->tags, ignorecase); |
|
601 default: |
|
602 abort(); |
|
603 } |
|
604 } |
|
605 } |
|
606 |