253 } |
253 } |
254 return 0; |
254 return 0; |
255 } |
255 } |
256 } |
256 } |
257 return 1; |
257 return 1; |
258 } |
|
259 |
|
260 // TODO: use scstr_t after update to UCX 2.0 |
|
261 static size_t rtrimskip(sstr_t str, size_t skip) { |
|
262 while (skip < str.length && isspace(str.ptr[skip])) skip++; |
|
263 return skip; |
|
264 } |
|
265 |
|
266 static size_t parse_tagfilter_taglist(sstr_t fs, SyncTagFilter* tagfilter) { |
|
267 size_t csvlen; |
|
268 for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) { |
|
269 if (fs.ptr[csvlen] == ')') break; |
|
270 } |
|
271 fs.length = csvlen; |
|
272 |
|
273 tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length); |
|
274 |
|
275 return csvlen; |
|
276 } |
|
277 |
|
278 static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* tagfilter); |
|
279 |
|
280 static size_t parse_tagfilter_filter(sstr_t fs, SyncTagFilter* tagfilter) { |
|
281 |
|
282 size_t consumed = rtrimskip(fs, 0); |
|
283 fs = sstrsubs(fs, consumed); |
|
284 |
|
285 if (fs.length == 0) { |
|
286 return consumed; |
|
287 } else { |
|
288 |
|
289 // optional operator |
|
290 int hasop = 0; |
|
291 if (fs.ptr[0] == '&') { |
|
292 tagfilter->mode = DAV_SYNC_TAGFILTER_AND; |
|
293 hasop = 1; |
|
294 } else if (fs.ptr[0] == '|') { |
|
295 tagfilter->mode = DAV_SYNC_TAGFILTER_OR; |
|
296 hasop = 1; |
|
297 } else if (fs.ptr[0] == '0') { |
|
298 tagfilter->mode = DAV_SYNC_TAGFILTER_NONE; |
|
299 hasop = 1; |
|
300 } else if (fs.ptr[0] == '1') { |
|
301 tagfilter->mode = DAV_SYNC_TAGFILTER_ONE; |
|
302 hasop = 1; |
|
303 } else { |
|
304 // default operator is AND |
|
305 tagfilter->mode = DAV_SYNC_TAGFILTER_AND; |
|
306 } |
|
307 |
|
308 if (hasop) { |
|
309 size_t skip = rtrimskip(fs, 1); |
|
310 consumed += skip; |
|
311 fs = sstrsubs(fs, skip); |
|
312 } |
|
313 |
|
314 if (fs.length > 0 && fs.ptr[0] == '(') { |
|
315 size_t c = parse_tagfilter_subfilters(fs, tagfilter); |
|
316 if (c) { |
|
317 return consumed + c; |
|
318 } else { |
|
319 return 0; |
|
320 } |
|
321 } else { |
|
322 tagfilter->subfilter_count = 0; |
|
323 tagfilter->subfilters = NULL; |
|
324 return consumed + parse_tagfilter_taglist(fs, tagfilter); |
|
325 } |
|
326 } |
|
327 } |
|
328 |
|
329 /* |
|
330 * Parses: ( "(" , filter , ")" )+ |
|
331 */ |
|
332 static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* f) { |
|
333 |
|
334 // strategy: allocate much and give back later (instead of reallocs in loop) |
|
335 size_t subfilter_cap = 8; |
|
336 f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*)); |
|
337 f->subfilter_count = 0; |
|
338 |
|
339 size_t total_consumed = 0; |
|
340 size_t c; |
|
341 do { |
|
342 // skip leading parenthesis (and white spaces) |
|
343 c = rtrimskip(fs, 1); |
|
344 fs = sstrsubs(fs, c); |
|
345 total_consumed += c; |
|
346 |
|
347 // increase array capacity, if necessary |
|
348 if (f->subfilter_count >= subfilter_cap) { |
|
349 subfilter_cap *= 2; |
|
350 SyncTagFilter** newarr = realloc(f->subfilters, |
|
351 subfilter_cap * sizeof(SyncTagFilter*)); |
|
352 if (newarr) { |
|
353 f->subfilters = newarr; |
|
354 } else { |
|
355 abort(); // no error handling reachable, so we are fucked |
|
356 } |
|
357 } |
|
358 |
|
359 // allocate space for a new filter |
|
360 SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter)); |
|
361 |
|
362 // parse that filter |
|
363 c = parse_tagfilter_filter(fs, subf); |
|
364 |
|
365 // sanity check: we must end with a closing parenthesis |
|
366 if (c > 0 && fs.ptr[c] == ')') { |
|
367 f->subfilters[f->subfilter_count++] = subf; |
|
368 |
|
369 // consume ')' and find the next parenthesis or the end-of-string |
|
370 c = rtrimskip(fs, 1+c); |
|
371 fs = sstrsubs(fs, c); |
|
372 total_consumed += c; |
|
373 |
|
374 if (fs.length == 0 || fs.ptr[0] == ')') { |
|
375 // our job is done |
|
376 break; |
|
377 } else if (fs.ptr[0] != '(') { |
|
378 // anything else than a parenthesis or end-of-string is an error |
|
379 return 0; |
|
380 } |
|
381 } else { |
|
382 free(subf); |
|
383 break; |
|
384 } |
|
385 |
|
386 } while(1); |
|
387 |
|
388 // try to shrink the array |
|
389 if (f->subfilter_count > 0) { |
|
390 SyncTagFilter** shrinked_array = realloc(f->subfilters, |
|
391 f->subfilter_count * sizeof(SyncTagFilter*)); |
|
392 if (shrinked_array) { |
|
393 f->subfilters = shrinked_array; |
|
394 } |
|
395 } else { |
|
396 free(f->subfilters); |
|
397 f->subfilters = NULL; |
|
398 } |
|
399 |
|
400 return total_consumed; |
|
401 } |
|
402 |
|
403 SyncTagFilter* parse_tagfilter_string(const char* filterstring) { |
|
404 SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter)); |
|
405 if (!filterstring) { |
|
406 return tagfilter; |
|
407 } |
|
408 |
|
409 // TODO: use scstr_t after update to UCX 2.0 |
|
410 sstr_t fs = sstr((char*) filterstring); |
|
411 size_t consumed = parse_tagfilter_filter(fs, tagfilter); |
|
412 if (!consumed) { |
|
413 free_tagfilter(tagfilter); |
|
414 return NULL; |
|
415 } |
|
416 |
|
417 // consume trailing white spaces |
|
418 consumed = rtrimskip(fs, consumed); |
|
419 |
|
420 // sanity check: have we consumed the whole string? |
|
421 if (consumed != fs.length) { |
|
422 free_tagfilter(tagfilter); |
|
423 return NULL; |
|
424 } |
|
425 |
|
426 return tagfilter; |
|
427 } |
|
428 |
|
429 void free_tagfilter(SyncTagFilter* filter) { |
|
430 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
431 free_tagfilter(filter->subfilters[i]); |
|
432 } |
|
433 free(filter->subfilters); |
|
434 free(filter); |
|
435 } |
|
436 |
|
437 static int matches_tags_and(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
438 UCX_FOREACH(e, tags) { |
|
439 if (!ucx_list_contains(dav_tags, e->data, |
|
440 (cmp_func) compare_tagname, &ignorecase)) { |
|
441 return 0; |
|
442 } |
|
443 } |
|
444 return 1; |
|
445 } |
|
446 |
|
447 static int matches_tags_or(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
448 UCX_FOREACH(e, tags) { |
|
449 if (ucx_list_contains(dav_tags, e->data, |
|
450 (cmp_func) compare_tagname, &ignorecase)) { |
|
451 return 1; |
|
452 } |
|
453 } |
|
454 return 0; |
|
455 } |
|
456 |
|
457 static int matches_tags_one(UcxList *dav_tags, UcxList *tags, int ignorecase) { |
|
458 int matches_exactly_one = 0; |
|
459 UCX_FOREACH(e, tags) { |
|
460 if (ucx_list_contains(dav_tags, e->data, |
|
461 (cmp_func) compare_tagname, &ignorecase)) { |
|
462 if (matches_exactly_one) { |
|
463 return 0; |
|
464 } else { |
|
465 matches_exactly_one = 1; |
|
466 } |
|
467 } |
|
468 } |
|
469 return matches_exactly_one; |
|
470 } |
|
471 |
|
472 static int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter); |
|
473 |
|
474 static int matches_subfilters_and(UcxList *dav_tags, SyncTagFilter *filter) { |
|
475 int ret = 1; |
|
476 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
477 ret &= matches_tagfilter(dav_tags, filter->subfilters[i]); |
|
478 } |
|
479 return ret; |
|
480 } |
|
481 |
|
482 static int matches_subfilters_or(UcxList *dav_tags, SyncTagFilter *filter) { |
|
483 int ret = 0; |
|
484 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
485 ret |= matches_tagfilter(dav_tags, filter->subfilters[i]); |
|
486 } |
|
487 return ret; |
|
488 } |
|
489 |
|
490 static int matches_subfilters_one(UcxList *dav_tags, SyncTagFilter *filter) { |
|
491 int one = 0; |
|
492 for (size_t i = 0 ; i < filter->subfilter_count ; i++) { |
|
493 if (matches_tagfilter(dav_tags, filter->subfilters[i])) { |
|
494 if (one) { |
|
495 return 0; |
|
496 } else { |
|
497 one = 1; |
|
498 } |
|
499 } |
|
500 } |
|
501 return one; |
|
502 } |
|
503 |
|
504 static int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter) { |
|
505 |
|
506 if (tagfilter->subfilter_count > 0) { |
|
507 switch (tagfilter->mode) { |
|
508 case DAV_SYNC_TAGFILTER_OR: |
|
509 return matches_subfilters_or(dav_tags, tagfilter); |
|
510 case DAV_SYNC_TAGFILTER_AND: |
|
511 return matches_subfilters_and(dav_tags, tagfilter); |
|
512 case DAV_SYNC_TAGFILTER_NONE: |
|
513 return !matches_subfilters_or(dav_tags, tagfilter); |
|
514 case DAV_SYNC_TAGFILTER_ONE: |
|
515 return matches_subfilters_one(dav_tags, tagfilter); |
|
516 default: |
|
517 abort(); |
|
518 } |
|
519 } else { |
|
520 int ignorecase = 0; // TODO: maybe add support later |
|
521 switch (tagfilter->mode) { |
|
522 case DAV_SYNC_TAGFILTER_OR: |
|
523 return matches_tags_or(dav_tags, tagfilter->tags, ignorecase); |
|
524 case DAV_SYNC_TAGFILTER_AND: |
|
525 return matches_tags_and(dav_tags, tagfilter->tags, ignorecase); |
|
526 case DAV_SYNC_TAGFILTER_NONE: |
|
527 return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase); |
|
528 case DAV_SYNC_TAGFILTER_ONE: |
|
529 return matches_tags_one(dav_tags, tagfilter->tags, ignorecase); |
|
530 default: |
|
531 abort(); |
|
532 } |
|
533 } |
|
534 } |
258 } |
535 |
259 |
536 static int res_matches_tags(DavResource *res, SyncTagFilter *tagfilter) { |
260 static int res_matches_tags(DavResource *res, SyncTagFilter *tagfilter) { |
537 if(tagfilter->mode == DAV_SYNC_TAGFILTER_OFF) { |
261 if(tagfilter->mode == DAV_SYNC_TAGFILTER_OFF) { |
538 return 1; |
262 return 1; |