1 /*
2 * Tvheadend - idnode (class) system
3 *
4 * Copyright (C) 2013 Andreas Öman
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #define _GNU_SOURCE
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27
28 #include "idnode.h"
29 #include "notify.h"
30 #include "settings.h"
31 #include "uuid.h"
32 #include "access.h"
33
34 static const idnodes_rb_t * idnode_domain ( const idclass_t *idc );
35 static idnodes_rb_t * idclass_find_domain ( const idclass_t *idc );
36
37 typedef struct idclass_link
38 {
39 const idclass_t *idc;
40 idnodes_rb_t nodes;
41 RB_ENTRY(idclass_link) link;
42 } idclass_link_t;
43
44 static idnodes_rb_t idnodes;
45 static RB_HEAD(,idclass_link) idclasses;
46 static RB_HEAD(,idclass_link) idrootclasses;
47 static TAILQ_HEAD(,idnode_save) idnodes_save;
48
49 tvh_cond_t save_cond;
50 pthread_t save_tid;
51 static int save_running;
52 static mtimer_t save_timer;
53
54 SKEL_DECLARE(idclasses_skel, idclass_link_t);
55
56 /* **************************************************************************
57 * Utilities
58 * *************************************************************************/
59
60 /**
61 *
62 */
63 static int
in_cmp(const idnode_t * a,const idnode_t * b)64 in_cmp(const idnode_t *a, const idnode_t *b)
65 {
66 return uuid_cmp(&a->in_uuid, &b->in_uuid);
67 }
68
69 static int
ic_cmp(const idclass_link_t * a,const idclass_link_t * b)70 ic_cmp ( const idclass_link_t *a, const idclass_link_t *b )
71 {
72 assert(a->idc->ic_class);
73 assert(b->idc->ic_class);
74 return strcmp(a->idc->ic_class, b->idc->ic_class);
75 }
76
77 /* **************************************************************************
78 * Registration
79 * *************************************************************************/
80
81 static const idclass_t *
idnode_root_class(const idclass_t * idc)82 idnode_root_class(const idclass_t *idc)
83 {
84 while (idc && idc->ic_super)
85 idc = idc->ic_super;
86 return idc;
87 }
88
89 /**
90 *
91 */
92 int
idnode_insert(idnode_t * in,const char * uuid,const idclass_t * class,int flags)93 idnode_insert(idnode_t *in, const char *uuid, const idclass_t *class, int flags)
94 {
95 idnode_t *c;
96 tvh_uuid_t u;
97 int retries = 5;
98 uint32_t u32;
99 const idclass_t *idc;
100 char ubuf[UUID_HEX_SIZE];
101
102 lock_assert(&global_lock);
103
104 in->in_class = class;
105 do {
106
107 if (uuid_init_bin(&u, uuid)) {
108 in->in_class = NULL;
109 return -1;
110 }
111 uuid_duplicate(&in->in_uuid, &u);
112
113 c = NULL;
114 if (flags & IDNODE_SHORT_UUID) {
115 u32 = idnode_get_short_uuid(in);
116 idc = idnode_root_class(in->in_class);
117 RB_FOREACH(c, &idnodes, in_link) {
118 if (idc != idnode_root_class(c->in_class))
119 continue;
120 if (idnode_get_short_uuid(c) == u32)
121 break;
122 }
123 }
124
125 if (c == NULL)
126 c = RB_INSERT_SORTED(&idnodes, in, in_link, in_cmp);
127
128 } while (c != NULL && --retries > 0);
129
130 if(c != NULL) {
131 tvherror(LS_IDNODE, "Id node collission (%s) %s",
132 uuid, (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
133 fprintf(stderr, "Id node collision (%s) %s\n",
134 uuid, (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
135 abort();
136 }
137 tvhtrace(LS_IDNODE, "insert node %s", idnode_uuid_as_str(in, ubuf));
138
139 /* Register the class */
140 in->in_domain = idclass_find_domain(class);
141 if (in->in_domain == NULL) {
142 tvherror(LS_IDNODE, "class '%s' is not registered", class->ic_class);
143 abort();
144 }
145 c = RB_INSERT_SORTED(in->in_domain, in, in_domain_link, in_cmp);
146 assert(c == NULL);
147
148 /* Fire event */
149 idnode_notify(in, "create");
150
151 return 0;
152 }
153
154 /**
155 *
156 */
157 void
idnode_unlink(idnode_t * in)158 idnode_unlink(idnode_t *in)
159 {
160 char ubuf[UUID_HEX_SIZE];
161
162 lock_assert(&global_lock);
163 RB_REMOVE(&idnodes, in, in_link);
164 RB_REMOVE(in->in_domain, in, in_domain_link);
165 tvhtrace(LS_IDNODE, "unlink node %s", idnode_uuid_as_str(in, ubuf));
166 idnode_notify(in, "delete");
167 assert(in->in_save == NULL || in->in_save == SAVEPTR_OUTOFSERVICE);
168 }
169
170 /**
171 *
172 */
173 static void
idnode_handler(size_t off,idnode_t * in,const char * action)174 idnode_handler(size_t off, idnode_t *in, const char *action)
175 {
176 void (**fcn)(idnode_t *);
177 lock_assert(&global_lock);
178 const idclass_t *idc = in->in_class;
179 while (idc) {
180 fcn = (void *)idc + off;
181 if (*fcn) {
182 if (action)
183 idnode_notify(in, action);
184 (*fcn)(in);
185 break;
186 }
187 idc = idc->ic_super;
188 }
189 }
190
191 void
idnode_delete(idnode_t * in)192 idnode_delete(idnode_t *in)
193 {
194 idnode_handler(offsetof(idclass_t, ic_delete), in, NULL);
195 }
196
197 void
idnode_moveup(idnode_t * in)198 idnode_moveup(idnode_t *in)
199 {
200 return idnode_handler(offsetof(idclass_t, ic_moveup), in, "moveup");
201 }
202
203 void
idnode_movedown(idnode_t * in)204 idnode_movedown(idnode_t *in)
205 {
206 return idnode_handler(offsetof(idclass_t, ic_movedown), in, "movedown");
207 }
208
209 /* **************************************************************************
210 * Info
211 * *************************************************************************/
212
213 uint32_t
idnode_get_short_uuid(const idnode_t * in)214 idnode_get_short_uuid (const idnode_t *in)
215 {
216 uint32_t u32;
217 memcpy(&u32, in->in_uuid.bin, sizeof(u32));
218 return u32 & 0x7FFFFFFF; // compat needs to be +ve signed
219 }
220
221 /**
222 *
223 */
224 const char *
idnode_uuid_as_str(const idnode_t * in,char * uuid)225 idnode_uuid_as_str(const idnode_t *in, char *uuid)
226 {
227 bin2hex(uuid, UUID_HEX_SIZE, in->in_uuid.bin, sizeof(in->in_uuid.bin));
228 return uuid;
229 }
230
231 /**
232 *
233 */
234 const char *
idnode_get_title(idnode_t * in,const char * lang)235 idnode_get_title(idnode_t *in, const char *lang)
236 {
237 static char ubuf[UUID_HEX_SIZE];
238 const idclass_t *ic = in->in_class;
239 for(; ic != NULL; ic = ic->ic_super) {
240 if(ic->ic_get_title != NULL)
241 return ic->ic_get_title(in, lang);
242 }
243 return idnode_uuid_as_str(in, ubuf);
244 }
245
246
247 /**
248 *
249 */
250 idnode_set_t *
idnode_get_childs(idnode_t * in)251 idnode_get_childs(idnode_t *in)
252 {
253 if(in == NULL)
254 return NULL;
255
256 const idclass_t *ic = in->in_class;
257 for(; ic != NULL; ic = ic->ic_super) {
258 if(ic->ic_get_childs != NULL)
259 return ic->ic_get_childs(in);
260 }
261 return NULL;
262 }
263
264 /**
265 *
266 */
267 int
idnode_is_leaf(idnode_t * in)268 idnode_is_leaf(idnode_t *in)
269 {
270 const idclass_t *ic = in->in_class;
271 for(; ic != NULL; ic = ic->ic_super) {
272 if(ic->ic_get_childs != NULL)
273 return 0;
274 }
275 return 1;
276 }
277
278 int
idnode_is_instance(idnode_t * in,const idclass_t * idc)279 idnode_is_instance(idnode_t *in, const idclass_t *idc)
280 {
281 const idclass_t *ic = in->in_class;
282 for(; ic != NULL; ic = ic->ic_super) {
283 if (ic == idc) return 1;
284 }
285 return 0;
286 }
287
288 /* **************************************************************************
289 * Properties
290 * *************************************************************************/
291
292 static const property_t *
idnode_find_prop(idnode_t * self,const char * key)293 idnode_find_prop
294 ( idnode_t *self, const char *key )
295 {
296 const idclass_t *idc = self->in_class;
297 const property_t *p;
298 while (idc) {
299 if ((p = prop_find(idc->ic_properties, key))) return p;
300 idc = idc->ic_super;
301 }
302 return NULL;
303 }
304
305 /*
306 * Get display value
307 */
308 static char *
idnode_get_display(idnode_t * self,const property_t * p,const char * lang)309 idnode_get_display
310 ( idnode_t *self, const property_t *p, const char *lang )
311 {
312 char *r = NULL;
313 if (p) {
314 if (p->rend)
315 r = p->rend(self, lang);
316 else if (p->islist) {
317 htsmsg_t *l = (htsmsg_t*)p->get(self);
318 if (l) {
319 r = htsmsg_list_2_csv(l, ',', 1);
320 htsmsg_destroy(l);
321 }
322 } else if (p->list) {
323 htsmsg_t *l = p->list(self, lang), *m;
324 htsmsg_field_t *f;
325 uint32_t k, v;
326 const char *s;
327 if (l && !idnode_get_u32(self, p->id, &v))
328 HTSMSG_FOREACH(f, l) {
329 m = htsmsg_field_get_map(f);
330 if (!htsmsg_get_u32(m, "key", &k) &&
331 (s = htsmsg_get_str(m, "val")) != NULL &&
332 v == k) {
333 r = strdup(s);
334 break;
335 }
336 }
337 htsmsg_destroy(l);
338 }
339 }
340 return r;
341 }
342
343 /*
344 * Get field as string
345 */
346 const char *
idnode_get_str(idnode_t * self,const char * key)347 idnode_get_str
348 ( idnode_t *self, const char *key )
349 {
350 const property_t *p = idnode_find_prop(self, key);
351 if (p && p->type == PT_STR) {
352 const void *ptr;
353 if (p->get)
354 ptr = p->get(self);
355 else
356 ptr = ((void*)self) + p->off;
357 return *(const char**)ptr;
358 }
359
360 return NULL;
361 }
362
363 /*
364 * Get field as unsigned int
365 */
366 int
idnode_get_u32(idnode_t * self,const char * key,uint32_t * u32)367 idnode_get_u32
368 ( idnode_t *self, const char *key, uint32_t *u32 )
369 {
370 const property_t *p = idnode_find_prop(self, key);
371 if (p) {
372 const void *ptr;
373 if (p->islist)
374 return 1;
375 else if (p->get)
376 ptr = p->get(self);
377 else
378 ptr = ((void*)self) + p->off;
379 switch (p->type) {
380 case PT_INT:
381 case PT_BOOL:
382 *u32 = *(int*)ptr;
383 return 0;
384 case PT_U16:
385 *u32 = *(uint16_t*)ptr;
386 return 0;
387 case PT_U32:
388 *u32 = *(uint32_t*)ptr;
389 return 0;
390 default:
391 break;
392 }
393 }
394 return 1;
395 }
396
397 /*
398 * Get field as signed 64-bit int
399 */
400 int
idnode_get_s64(idnode_t * self,const char * key,int64_t * s64)401 idnode_get_s64
402 ( idnode_t *self, const char *key, int64_t *s64 )
403 {
404 const property_t *p = idnode_find_prop(self, key);
405 if (p) {
406 const void *ptr;
407 if (p->islist)
408 return 1;
409 else if (p->get)
410 ptr = p->get(self);
411 else
412 ptr = ((void*)self) + p->off;
413 switch (p->type) {
414 case PT_INT:
415 case PT_BOOL:
416 *s64 = *(int*)ptr;
417 return 0;
418 case PT_U16:
419 *s64 = *(uint16_t*)ptr;
420 return 0;
421 case PT_U32:
422 *s64 = *(uint32_t*)ptr;
423 return 0;
424 case PT_S64:
425 *s64 = *(int64_t*)ptr;
426 return 0;
427 case PT_DBL:
428 *s64 = *(double*)ptr;
429 return 0;
430 case PT_TIME:
431 *s64 = *(time_t*)ptr;
432 return 0;
433 default:
434 break;
435 }
436 }
437 return 1;
438 }
439
440 /*
441 * Get field as signed 64-bit int
442 */
443 int
idnode_get_s64_atomic(idnode_t * self,const char * key,int64_t * s64)444 idnode_get_s64_atomic
445 ( idnode_t *self, const char *key, int64_t *s64 )
446 {
447 const property_t *p = idnode_find_prop(self, key);
448 if (p) {
449 const void *ptr;
450 if (p->islist)
451 return 1;
452 else if (p->get)
453 ptr = p->get(self);
454 else
455 ptr = ((void*)self) + p->off;
456 switch (p->type) {
457 case PT_S64_ATOMIC:
458 *s64 = atomic_get_s64((int64_t*)ptr);
459 return 0;
460 default:
461 break;
462 }
463 }
464 return 1;
465 }
466
467 /*
468 * Get field as double
469 */
470 int
idnode_get_dbl(idnode_t * self,const char * key,double * dbl)471 idnode_get_dbl
472 ( idnode_t *self, const char *key, double *dbl )
473 {
474 const property_t *p = idnode_find_prop(self, key);
475 if (p) {
476 const void *ptr;
477 if (p->islist)
478 return 1;
479 else if (p->get)
480 ptr = p->get(self);
481 else
482 ptr = ((void*)self) + p->off;
483 switch (p->type) {
484 case PT_INT:
485 case PT_BOOL:
486 *dbl = *(int*)ptr;
487 return 0;
488 case PT_U16:
489 *dbl = *(uint16_t*)ptr;
490 return 0;
491 case PT_U32:
492 *dbl = *(uint32_t*)ptr;
493 return 0;
494 case PT_S64:
495 *dbl = *(int64_t*)ptr;
496 return 0;
497 case PT_DBL:
498 *dbl = *(double *)ptr;
499 return 0;
500 case PT_TIME:
501 *dbl = *(time_t*)ptr;
502 return 0;
503 default:
504 break;
505 }
506 }
507 return 1;
508 }
509
510 /*
511 * Get field as BOOL
512 */
513 int
idnode_get_bool(idnode_t * self,const char * key,int * b)514 idnode_get_bool
515 ( idnode_t *self, const char *key, int *b )
516 {
517 const property_t *p = idnode_find_prop(self, key);
518 if (p) {
519 const void *ptr;
520 if (p->islist)
521 return 1;
522 else if (p->get)
523 ptr = p->get(self);
524 else
525 ptr = ((void*)self) + p->off;
526 switch (p->type) {
527 case PT_BOOL:
528 *b = *(int*)ptr;
529 return 0;
530 default:
531 break;
532 }
533 }
534 return 1;
535 }
536
537 /*
538 * Get field as time
539 */
540 int
idnode_get_time(idnode_t * self,const char * key,time_t * tm)541 idnode_get_time
542 ( idnode_t *self, const char *key, time_t *tm )
543 {
544 const property_t *p = idnode_find_prop(self, key);
545 if (p) {
546 const void *ptr;
547 if (p->islist)
548 return 1;
549 if (p->get)
550 ptr = p->get(self);
551 else
552 ptr = ((void*)self) + p->off;
553 switch (p->type) {
554 case PT_TIME:
555 *tm = *(time_t*)ptr;
556 return 0;
557 default:
558 break;
559 }
560 }
561 return 1;
562 }
563
564 /* **************************************************************************
565 * Lookup
566 * *************************************************************************/
567
568 int
idnode_perm(idnode_t * self,struct access * a,htsmsg_t * msg_to_write)569 idnode_perm(idnode_t *self, struct access *a, htsmsg_t *msg_to_write)
570 {
571 const idclass_t *ic = self->in_class;
572 int r;
573
574 while (ic) {
575 if (ic->ic_perm)
576 r = self->in_class->ic_perm(self, a, msg_to_write);
577 else if (ic->ic_perm_def)
578 r = access_verify2(a, ic->ic_perm_def);
579 else {
580 ic = ic->ic_super;
581 continue;
582 }
583 if (!r) {
584 self->in_access = a;
585 return 0;
586 }
587 return r;
588 }
589 return 0;
590 }
591
592 /**
593 *
594 */
595 static const idnodes_rb_t *
idnode_domain(const idclass_t * idc)596 idnode_domain(const idclass_t *idc)
597 {
598 if (idc) {
599 idclass_link_t lskel, *l;
600 const idclass_t *root = idnode_root_class(idc);
601 lskel.idc = root;
602 l = RB_FIND(&idrootclasses, &lskel, link, ic_cmp);
603 if (l == NULL)
604 return NULL;
605 return &l->nodes;
606 } else {
607 return NULL;
608 }
609 }
610
611 void *
idnode_find(const char * uuid,const idclass_t * idc,const idnodes_rb_t * domain)612 idnode_find ( const char *uuid, const idclass_t *idc, const idnodes_rb_t *domain )
613 {
614 idnode_t skel, *r;
615
616 tvhtrace(LS_IDNODE, "find node %s class %s", uuid, idc ? idc->ic_class : NULL);
617 if(uuid == NULL || strlen(uuid) != UUID_HEX_SIZE - 1)
618 return NULL;
619 if(hex2bin(skel.in_uuid.bin, sizeof(skel.in_uuid.bin), uuid))
620 return NULL;
621 if (domain == NULL)
622 domain = idnode_domain(idc);
623 if (domain == NULL)
624 r = RB_FIND(&idnodes, &skel, in_link, in_cmp);
625 else
626 r = RB_FIND(domain, &skel, in_domain_link, in_cmp);
627 if(r != NULL && idc != NULL) {
628 const idclass_t *c = r->in_class;
629 for(;c != NULL; c = c->ic_super) {
630 if(idc == c)
631 return r;
632 }
633 return NULL;
634 }
635 return r;
636 }
637
638 idnode_set_t *
idnode_find_all(const idclass_t * idc,const idnodes_rb_t * domain)639 idnode_find_all ( const idclass_t *idc, const idnodes_rb_t *domain )
640 {
641 idnode_t *in;
642 const idclass_t *ic;
643 char ubuf[UUID_HEX_SIZE];
644 tvhtrace(LS_IDNODE, "find class %s", idc->ic_class);
645 idnode_set_t *is = calloc(1, sizeof(idnode_set_t));
646 if (domain == NULL)
647 domain = idnode_domain(idc);
648 if (domain == NULL) {
649 RB_FOREACH(in, &idnodes, in_link) {
650 ic = in->in_class;
651 while (ic) {
652 if (ic == idc) {
653 tvhtrace(LS_IDNODE, " add node %s", idnode_uuid_as_str(in, ubuf));
654 idnode_set_add(is, in, NULL, NULL);
655 break;
656 }
657 ic = ic->ic_super;
658 }
659 }
660 } else {
661 RB_FOREACH(in, domain, in_domain_link) {
662 ic = in->in_class;
663 while (ic) {
664 if (ic == idc) {
665 tvhtrace(LS_IDNODE, " add node %s", idnode_uuid_as_str(in, ubuf));
666 idnode_set_add(is, in, NULL, NULL);
667 break;
668 }
669 ic = ic->ic_super;
670 }
671 }
672 }
673 return is;
674 }
675
676 /* **************************************************************************
677 * Set processing
678 * *************************************************************************/
679
680 static int
idnode_cmp_title(const void * a,const void * b,void * lang)681 idnode_cmp_title
682 ( const void *a, const void *b, void *lang )
683 {
684 idnode_t *ina = *(idnode_t**)a;
685 idnode_t *inb = *(idnode_t**)b;
686 const char *sa = idnode_get_title(ina, (const char *)lang);
687 const char *sb = idnode_get_title(inb, (const char *)lang);
688 return strcmp(sa ?: "", sb ?: "");
689 }
690
691 #define safecmp(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0))
692
693 static int
idnode_cmp_sort(const void * a,const void * b,void * s)694 idnode_cmp_sort
695 ( const void *a, const void *b, void *s )
696 {
697 idnode_t *ina = *(idnode_t**)a;
698 idnode_t *inb = *(idnode_t**)b;
699 idnode_sort_t *sort = s;
700 const property_t *p = idnode_find_prop(ina, sort->key);
701 if (!p) return 0;
702
703 /* Get display string */
704 if (p->islist || (p->list && !(p->opts & PO_SORTKEY))) {
705 int r;
706 char *stra = idnode_get_display(ina, p, sort->lang);
707 char *strb = idnode_get_display(inb, p, sort->lang);
708 if (sort->dir == IS_ASC)
709 r = strcmp(stra ?: "", strb ?: "");
710 else
711 r = strcmp(strb ?: "", stra ?: "");
712 free(stra);
713 free(strb);
714 return r;
715 }
716
717 switch (p->type) {
718 case PT_STR:
719 {
720 int r;
721 const char *stra = tvh_strdupa(idnode_get_str(ina, sort->key) ?: "");
722 const char *strb = idnode_get_str(inb, sort->key) ?: "";
723 if (sort->dir == IS_ASC)
724 r = strcmp(stra, strb);
725 else
726 r = strcmp(strb, stra);
727 return r;
728 }
729 break;
730 case PT_INT:
731 case PT_U16:
732 case PT_BOOL:
733 case PT_PERM:
734 {
735 int32_t i32a = 0, i32b = 0;
736 idnode_get_u32(ina, sort->key, (uint32_t *)&i32a);
737 idnode_get_u32(inb, sort->key, (uint32_t *)&i32b);
738 if (sort->dir == IS_ASC)
739 return safecmp(i32a, i32b);
740 else
741 return safecmp(i32b, i32a);
742 }
743 break;
744 case PT_U32:
745 {
746 uint32_t u32a = 0, u32b = 0;
747 idnode_get_u32(ina, sort->key, &u32a);
748 idnode_get_u32(inb, sort->key, &u32b);
749 if (sort->dir == IS_ASC)
750 return safecmp(u32a, u32b);
751 else
752 return safecmp(u32b, u32a);
753 }
754 break;
755 case PT_S64:
756 {
757 int64_t s64a = 0, s64b = 0;
758 idnode_get_s64(ina, sort->key, &s64a);
759 idnode_get_s64(inb, sort->key, &s64b);
760 if (sort->dir == IS_ASC)
761 return safecmp(s64a, s64b);
762 else
763 return safecmp(s64b, s64a);
764 }
765 break;
766 case PT_S64_ATOMIC:
767 {
768 int64_t s64a = 0, s64b = 0;
769 idnode_get_s64_atomic(ina, sort->key, &s64a);
770 idnode_get_s64_atomic(inb, sort->key, &s64b);
771 if (sort->dir == IS_ASC)
772 return safecmp(s64a, s64b);
773 else
774 return safecmp(s64b, s64a);
775 }
776 break;
777 case PT_DBL:
778 {
779 double dbla = 0, dblb = 0;
780 idnode_get_dbl(ina, sort->key, &dbla);
781 idnode_get_dbl(inb, sort->key, &dblb);
782 if (sort->dir == IS_ASC)
783 return safecmp(dbla, dblb);
784 else
785 return safecmp(dblb, dbla);
786 }
787 break;
788 case PT_TIME:
789 {
790 time_t ta = 0, tb = 0;
791 idnode_get_time(ina, sort->key, &ta);
792 idnode_get_time(inb, sort->key, &tb);
793 if (sort->dir == IS_ASC)
794 return safecmp(ta, tb);
795 else
796 return safecmp(tb, ta);
797 }
798 break;
799 case PT_LANGSTR:
800 // TODO?
801 case PT_NONE:
802 break;
803 }
804 return 0;
805 }
806
807 static void
idnode_filter_init(idnode_t * in,idnode_filter_t * filter)808 idnode_filter_init
809 ( idnode_t *in, idnode_filter_t *filter )
810 {
811 idnode_filter_ele_t *f;
812 const property_t *p;
813
814 LIST_FOREACH(f, filter, link) {
815 if (f->type == IF_NUM) {
816 p = idnode_find_prop(in, f->key);
817 if (p) {
818 if (p->type == PT_U32 || p->type == PT_S64 ||
819 p->type == PT_TIME) {
820 int64_t v = f->u.n.n;
821 if (INTEXTRA_IS_SPLIT(p->intextra) && p->intextra != f->u.n.intsplit) {
822 v = (v / MIN(1, f->u.n.intsplit)) * p->intextra;
823 f->u.n.n = v;
824 }
825 }
826 }
827 }
828 f->checked = 1;
829 }
830 }
831
832 int
idnode_filter(idnode_t * in,idnode_filter_t * filter,const char * lang)833 idnode_filter
834 ( idnode_t *in, idnode_filter_t *filter, const char *lang )
835 {
836 idnode_filter_ele_t *f;
837
838 LIST_FOREACH(f, filter, link) {
839 if (!f->checked)
840 idnode_filter_init(in, filter);
841 if (f->type == IF_STR) {
842 const char *str;
843 char *strdisp;
844 int r = 1;
845 str = strdisp = idnode_get_display(in, idnode_find_prop(in, f->key), lang);
846 if (!str)
847 if (!(str = idnode_get_str(in, f->key)))
848 return 1;
849 switch(f->comp) {
850 case IC_IN: r = strstr(str, f->u.s) == NULL; break;
851 case IC_EQ: r = strcmp(str, f->u.s) != 0; break;
852 case IC_LT: r = strcmp(str, f->u.s) > 0; break;
853 case IC_GT: r = strcmp(str, f->u.s) < 0; break;
854 case IC_RE: r = !!regexec(&f->u.re, str, 0, NULL, 0); break;
855 }
856 if (strdisp)
857 free(strdisp);
858 if (r)
859 return r;
860 } else if (f->type == IF_NUM || f->type == IF_BOOL) {
861 int64_t a, b;
862 if (idnode_get_s64(in, f->key, &a))
863 return 1;
864 b = (f->type == IF_NUM) ? f->u.n.n : f->u.b;
865 switch (f->comp) {
866 case IC_IN:
867 case IC_RE:
868 break; // Note: invalid
869 case IC_EQ:
870 if (a != b)
871 return 1;
872 break;
873 case IC_LT:
874 if (a > b)
875 return 1;
876 break;
877 case IC_GT:
878 if (a < b)
879 return 1;
880 break;
881 }
882 } else if (f->type == IF_DBL) {
883 double a, b;
884 if (idnode_get_dbl(in, f->key, &a))
885 return 1;
886 b = f->u.dbl;
887 switch (f->comp) {
888 case IC_IN:
889 case IC_RE:
890 break; // Note: invalid
891 case IC_EQ:
892 if (a != b)
893 return 1;
894 break;
895 case IC_LT:
896 if (a > b)
897 return 1;
898 break;
899 case IC_GT:
900 if (a < b)
901 return 1;
902 break;
903 }
904 }
905 }
906
907 return 0;
908 }
909
910 void
idnode_filter_add_str(idnode_filter_t * filt,const char * key,const char * val,int comp)911 idnode_filter_add_str
912 ( idnode_filter_t *filt, const char *key, const char *val, int comp )
913 {
914 idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
915 ele->key = strdup(key);
916 ele->type = IF_STR;
917 ele->comp = comp;
918 if (comp == IC_RE) {
919 if (regcomp(&ele->u.re, val, REG_ICASE | REG_EXTENDED | REG_NOSUB)) {
920 free(ele);
921 return;
922 }
923 } else
924 ele->u.s = strdup(val);
925 LIST_INSERT_HEAD(filt, ele, link);
926 }
927
928 void
idnode_filter_add_num(idnode_filter_t * filt,const char * key,int64_t val,int comp,int64_t intsplit)929 idnode_filter_add_num
930 ( idnode_filter_t *filt, const char *key, int64_t val, int comp, int64_t intsplit )
931 {
932 idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
933 ele->key = strdup(key);
934 ele->type = IF_NUM;
935 ele->comp = comp;
936 ele->u.n.n = val;
937 ele->u.n.intsplit = intsplit;
938 LIST_INSERT_HEAD(filt, ele, link);
939 }
940
941 void
idnode_filter_add_dbl(idnode_filter_t * filt,const char * key,double dbl,int comp)942 idnode_filter_add_dbl
943 ( idnode_filter_t *filt, const char *key, double dbl, int comp )
944 {
945 idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
946 ele->key = strdup(key);
947 ele->type = IF_DBL;
948 ele->comp = comp;
949 ele->u.dbl = dbl;
950 LIST_INSERT_HEAD(filt, ele, link);
951 }
952
953 void
idnode_filter_add_bool(idnode_filter_t * filt,const char * key,int val,int comp)954 idnode_filter_add_bool
955 ( idnode_filter_t *filt, const char *key, int val, int comp )
956 {
957 idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
958 ele->key = strdup(key);
959 ele->type = IF_BOOL;
960 ele->comp = comp;
961 ele->u.b = val;
962 LIST_INSERT_HEAD(filt, ele, link);
963 }
964
965 void
idnode_filter_clear(idnode_filter_t * filt)966 idnode_filter_clear
967 ( idnode_filter_t *filt )
968 {
969 idnode_filter_ele_t *ele;
970 while ((ele = LIST_FIRST(filt))) {
971 LIST_REMOVE(ele, link);
972 if (ele->type == IF_STR) {
973 if (ele->comp == IC_RE)
974 regfree(&ele->u.re);
975 else
976 free(ele->u.s);
977 }
978 free(ele->key);
979 free(ele);
980 }
981 }
982
983 void
idnode_set_alloc(idnode_set_t * is,size_t alloc)984 idnode_set_alloc
985 ( idnode_set_t *is, size_t alloc )
986 {
987 if (is->is_alloc < alloc) {
988 is->is_alloc = alloc;
989 is->is_array = realloc(is->is_array, alloc * sizeof(idnode_t*));
990 }
991 }
992
993 void
idnode_set_add(idnode_set_t * is,idnode_t * in,idnode_filter_t * filt,const char * lang)994 idnode_set_add
995 ( idnode_set_t *is, idnode_t *in, idnode_filter_t *filt, const char *lang )
996 {
997 if (filt && idnode_filter(in, filt, lang))
998 return;
999
1000 /* Allocate more space */
1001 if (is->is_alloc == is->is_count)
1002 idnode_set_alloc(is, MAX(100, is->is_alloc * 2));
1003 if (is->is_sorted) {
1004 size_t i;
1005 idnode_t **a = is->is_array;
1006 for (i = is->is_count++; i > 0 && a[i - 1] > in; i--)
1007 a[i] = a[i - 1];
1008 a[i] = in;
1009 } else {
1010 is->is_array[is->is_count++] = in;
1011 }
1012 }
1013
1014 ssize_t
idnode_set_find_index(idnode_set_t * is,idnode_t * in)1015 idnode_set_find_index
1016 ( idnode_set_t *is, idnode_t *in )
1017 {
1018 ssize_t i;
1019
1020 if (is->is_sorted) {
1021 idnode_t **a = is->is_array;
1022 ssize_t first = 0, last = is->is_count - 1;
1023 i = last / 2;
1024 while (first <= last) {
1025 if (a[i] < in)
1026 first = i + 1;
1027 else if (a[i] == in)
1028 return i;
1029 else
1030 last = i - 1;
1031 i = (first + last) / 2;
1032 }
1033 } else {
1034 for (i = 0; i < is->is_count; i++)
1035 if (is->is_array[i] == in)
1036 return 1;
1037 }
1038 return -1;
1039 }
1040
1041 int
idnode_set_remove(idnode_set_t * is,idnode_t * in)1042 idnode_set_remove
1043 ( idnode_set_t *is, idnode_t *in )
1044 {
1045 ssize_t i = idnode_set_find_index(is, in);
1046 if (i >= 0) {
1047 if (is->is_count > 1)
1048 memmove(&is->is_array[i], &is->is_array[i+1],
1049 (is->is_count - i - 1) * sizeof(idnode_t *));
1050 is->is_count--;
1051 return 1;
1052 }
1053 return 0;
1054 }
1055
1056 void
idnode_set_sort(idnode_set_t * is,idnode_sort_t * sort)1057 idnode_set_sort
1058 ( idnode_set_t *is, idnode_sort_t *sort )
1059 {
1060 tvh_qsort_r(is->is_array, is->is_count, sizeof(idnode_t*), idnode_cmp_sort, (void*)sort);
1061 }
1062
1063 void
idnode_set_sort_by_title(idnode_set_t * is,const char * lang)1064 idnode_set_sort_by_title
1065 ( idnode_set_t *is, const char *lang )
1066 {
1067 tvh_qsort_r(is->is_array, is->is_count, sizeof(idnode_t*), idnode_cmp_title, (void*)lang);
1068 }
1069
1070 htsmsg_t *
idnode_set_as_htsmsg(idnode_set_t * is)1071 idnode_set_as_htsmsg
1072 ( idnode_set_t *is )
1073 {
1074 htsmsg_t *l = htsmsg_create_list();
1075 char ubuf[UUID_HEX_SIZE];
1076 int i;
1077 for (i = 0; i < is->is_count; i++)
1078 htsmsg_add_str(l, NULL, idnode_uuid_as_str(is->is_array[i], ubuf));
1079 return l;
1080 }
1081
1082 void
idnode_set_clear(idnode_set_t * is)1083 idnode_set_clear ( idnode_set_t *is )
1084 {
1085 free(is->is_array);
1086 is->is_array = NULL;
1087 is->is_count = is->is_alloc = 0;
1088 }
1089
1090 void
idnode_set_free(idnode_set_t * is)1091 idnode_set_free ( idnode_set_t *is )
1092 {
1093 free(is->is_array);
1094 free(is);
1095 }
1096
1097 /* **************************************************************************
1098 * Write
1099 * *************************************************************************/
1100
1101 static int
idnode_class_write_values(idnode_t * self,const idclass_t * idc,htsmsg_t * c,int optmask)1102 idnode_class_write_values
1103 ( idnode_t *self, const idclass_t *idc, htsmsg_t *c, int optmask )
1104 {
1105 int save = 0;
1106 if (idc->ic_super)
1107 save |= idnode_class_write_values(self, idc->ic_super, c, optmask);
1108 save |= prop_write_values(self, idc->ic_properties, c, optmask, NULL);
1109 return save;
1110 }
1111
1112 static void
idnode_changedfn(idnode_t * self)1113 idnode_changedfn ( idnode_t *self )
1114 {
1115 const idclass_t *idc = self->in_class;
1116 while (idc) {
1117 if (idc->ic_changed) {
1118 idc->ic_changed(self);
1119 break;
1120 }
1121 idc = idc->ic_super;
1122 }
1123 }
1124
1125 static htsmsg_t *
idnode_savefn(idnode_t * self,char * filename,size_t fsize)1126 idnode_savefn ( idnode_t *self, char *filename, size_t fsize )
1127 {
1128 const idclass_t *idc = self->in_class;
1129 while (idc) {
1130 if (idc->ic_save)
1131 return idc->ic_save(self, filename, fsize);
1132 idc = idc->ic_super;
1133 }
1134 return NULL;
1135 }
1136
1137 static void
idnode_save_trigger_thread_cb(void * aux)1138 idnode_save_trigger_thread_cb( void *aux )
1139 {
1140 tvh_cond_signal(&save_cond, 0);
1141 }
1142
1143 static void
idnode_save_queue(idnode_t * self)1144 idnode_save_queue ( idnode_t *self )
1145 {
1146 idnode_save_t *ise;
1147
1148 if (self->in_save)
1149 return;
1150 ise = malloc(sizeof(*ise));
1151 ise->ise_node = self;
1152 ise->ise_reqtime = mclk();
1153 if (TAILQ_EMPTY(&idnodes_save) && atomic_get(&save_running))
1154 mtimer_arm_rel(&save_timer, idnode_save_trigger_thread_cb, NULL, IDNODE_SAVE_DELAY);
1155 TAILQ_INSERT_TAIL(&idnodes_save, ise, ise_link);
1156 self->in_save = ise;
1157 }
1158
1159 void
idnode_save_check(idnode_t * self,int weak)1160 idnode_save_check ( idnode_t *self, int weak )
1161 {
1162 char filename[PATH_MAX];
1163 htsmsg_t *m;
1164
1165 if (self->in_save == NULL || self->in_save == SAVEPTR_OUTOFSERVICE)
1166 return;
1167
1168 TAILQ_REMOVE(&idnodes_save, self->in_save, ise_link);
1169 free(self->in_save);
1170 self->in_save = SAVEPTR_OUTOFSERVICE;
1171
1172 if (weak)
1173 return;
1174
1175 m = idnode_savefn(self, filename, sizeof(filename));
1176 if (m) {
1177 hts_settings_save(m, "%s", filename);
1178 htsmsg_destroy(m);
1179 }
1180 }
1181
1182 int
idnode_write0(idnode_t * self,htsmsg_t * c,int optmask,int dosave)1183 idnode_write0 ( idnode_t *self, htsmsg_t *c, int optmask, int dosave )
1184 {
1185 int save = 0;
1186 const idclass_t *idc = self->in_class;
1187 save = idnode_class_write_values(self, idc, c, optmask);
1188 if ((idc->ic_flags & IDCLASS_ALWAYS_SAVE) != 0 || (save && dosave)) {
1189 idnode_changedfn(self);
1190 idnode_save_queue(self);
1191 }
1192 if (dosave)
1193 idnode_notify_changed(self);
1194 // Note: always output event if "dosave", reason is that UI updates on
1195 // these, but there are some subtle cases where it will expect
1196 // an update and not get one. This include fields being set for
1197 // which there is user-configurable value and auto fallback so
1198 // the UI state might not atually reflect the user config
1199 return save;
1200 }
1201
1202 void
idnode_changed(idnode_t * self)1203 idnode_changed( idnode_t *self )
1204 {
1205 idnode_notify_changed(self);
1206 idnode_changedfn(self);
1207 idnode_save_queue(self);
1208 }
1209
1210 /* **************************************************************************
1211 * Read
1212 * *************************************************************************/
1213
1214 void
idnode_read0(idnode_t * self,htsmsg_t * c,htsmsg_t * list,int optmask,const char * lang)1215 idnode_read0 ( idnode_t *self, htsmsg_t *c, htsmsg_t *list,
1216 int optmask, const char *lang )
1217 {
1218 const idclass_t *idc = self->in_class;
1219 for (; idc; idc = idc->ic_super)
1220 prop_read_values(self, idc->ic_properties, c, list, optmask, lang);
1221 }
1222
1223 /**
1224 * Recursive to get superclass nodes first
1225 */
1226 static void
add_params(struct idnode * self,const idclass_t * ic,htsmsg_t * p,htsmsg_t * list,int optmask,const char * lang)1227 add_params
1228 (struct idnode *self, const idclass_t *ic, htsmsg_t *p, htsmsg_t *list,
1229 int optmask, const char *lang)
1230 {
1231 /* Parent first */
1232 if(ic->ic_super != NULL)
1233 add_params(self, ic->ic_super, p, list, optmask, lang);
1234
1235 /* Seperator (if not empty) */
1236 #if 0
1237 if(TAILQ_FIRST(&p->hm_fields) != NULL) {
1238 htsmsg_t *m = htsmsg_create_map();
1239 htsmsg_add_str(m, "caption", tvh_gettext_lang(lang, ic->ic_caption) ?: ic->ic_class);
1240 htsmsg_add_str(m, "type", "separator");
1241 htsmsg_add_msg(p, NULL, m);
1242 }
1243 #endif
1244
1245 /* Properties */
1246 prop_serialize(self, ic->ic_properties, p, list, optmask, lang);
1247 }
1248
1249 static htsmsg_t *
idnode_params(const idclass_t * idc,idnode_t * self,htsmsg_t * list,int optmask,const char * lang)1250 idnode_params
1251 (const idclass_t *idc, idnode_t *self, htsmsg_t *list,
1252 int optmask, const char *lang)
1253 {
1254 htsmsg_t *p = htsmsg_create_list();
1255 add_params(self, idc, p, list, optmask, lang);
1256 return p;
1257 }
1258
1259 const char *
idclass_get_caption(const idclass_t * idc,const char * lang)1260 idclass_get_caption (const idclass_t *idc, const char *lang)
1261 {
1262 while (idc) {
1263 if (idc->ic_caption)
1264 return tvh_gettext_lang(lang, idc->ic_caption);
1265 idc = idc->ic_super;
1266 }
1267 return NULL;
1268 }
1269
1270 static const char *
idclass_get_class(const idclass_t * idc)1271 idclass_get_class (const idclass_t *idc)
1272 {
1273 while (idc) {
1274 if (idc->ic_class)
1275 return idc->ic_class;
1276 idc = idc->ic_super;
1277 }
1278 return NULL;
1279 }
1280
1281 static const char *
idclass_get_event(const idclass_t * idc)1282 idclass_get_event (const idclass_t *idc)
1283 {
1284 while (idc) {
1285 if (idc->ic_event)
1286 return idc->ic_event;
1287 idc = idc->ic_super;
1288 }
1289 return NULL;
1290 }
1291
1292 static const char *
idclass_get_order(const idclass_t * idc)1293 idclass_get_order (const idclass_t *idc)
1294 {
1295 while (idc) {
1296 if (idc->ic_order)
1297 return idc->ic_order;
1298 idc = idc->ic_super;
1299 }
1300 return NULL;
1301 }
1302
1303 const char **
idclass_get_doc(const idclass_t * idc)1304 idclass_get_doc (const idclass_t *idc)
1305 {
1306 while (idc) {
1307 if (idc->ic_doc)
1308 return idc->ic_doc;
1309 idc = idc->ic_super;
1310 }
1311 return NULL;
1312 }
1313
1314 static htsmsg_t *
idclass_get_property_groups(const idclass_t * idc,const char * lang)1315 idclass_get_property_groups (const idclass_t *idc, const char *lang)
1316 {
1317 const property_group_t *g;
1318 htsmsg_t *e, *m;
1319 int count;
1320 while (idc) {
1321 if (idc->ic_groups) {
1322 m = htsmsg_create_list();
1323 count = 0;
1324 for (g = idc->ic_groups; g->number && g->name; g++) {
1325 e = htsmsg_create_map();
1326 htsmsg_add_u32(e, "number", g->number);
1327 htsmsg_add_str(e, "name", tvh_gettext_lang(lang, g->name));
1328 if (g->parent)
1329 htsmsg_add_u32(e, "parent", g->parent);
1330 if (g->column)
1331 htsmsg_add_u32(e, "column", g->column);
1332 htsmsg_add_msg(m, NULL, e);
1333 count++;
1334 }
1335 if (count)
1336 return m;
1337 htsmsg_destroy(m);
1338 break;
1339 }
1340 idc = idc->ic_super;
1341 }
1342 return NULL;
1343 }
1344
1345 static idnodes_rb_t *
idclass_find_domain(const idclass_t * idc)1346 idclass_find_domain(const idclass_t *idc)
1347 {
1348 idclass_link_t *r;
1349 idc = idnode_root_class(idc);
1350 SKEL_ALLOC(idclasses_skel);
1351 idclasses_skel->idc = idc;
1352 r = RB_FIND(&idrootclasses, idclasses_skel, link, ic_cmp);
1353 if (r)
1354 return &r->nodes;
1355 return NULL;
1356 }
1357
1358 static void
idclass_root_register(const idclass_t * idc)1359 idclass_root_register(const idclass_t *idc)
1360 {
1361 idclass_link_t *r;
1362 SKEL_ALLOC(idclasses_skel);
1363 idclasses_skel->idc = idc;
1364 r = RB_INSERT_SORTED(&idrootclasses, idclasses_skel, link, ic_cmp);
1365 if (r) return;
1366 RB_INIT(&idclasses_skel->nodes);
1367 SKEL_USED(idclasses_skel);
1368 tvhtrace(LS_IDNODE, "register root class %s", idc->ic_class);
1369 }
1370
1371 void
idclass_register(const idclass_t * idc)1372 idclass_register(const idclass_t *idc)
1373 {
1374 const idclass_t *prev = NULL;
1375 while (idc) {
1376 SKEL_ALLOC(idclasses_skel);
1377 idclasses_skel->idc = idc;
1378 if (RB_INSERT_SORTED(&idclasses, idclasses_skel, link, ic_cmp)) {
1379 prev = NULL;
1380 break;
1381 }
1382 RB_INIT(&idclasses_skel->nodes); /* not used, but for sure */
1383 SKEL_USED(idclasses_skel);
1384 tvhtrace(LS_IDNODE, "register class %s", idc->ic_class);
1385 prev = idc;
1386 idc = idc->ic_super;
1387 }
1388 if (prev)
1389 idclass_root_register(prev);
1390 }
1391
1392 const idclass_t *
idclass_find(const char * class)1393 idclass_find ( const char *class )
1394 {
1395 idclass_link_t *t, skel;
1396 idclass_t idc;
1397 skel.idc = &idc;
1398 idc.ic_class = class;
1399 tvhtrace(LS_IDNODE, "find class %s", class);
1400 t = RB_FIND(&idclasses, &skel, link, ic_cmp);
1401 return t ? t->idc : NULL;
1402 }
1403
1404 idclass_t const **
idclass_find_all(void)1405 idclass_find_all(void)
1406 {
1407 idclass_link_t *l;
1408 idclass_t const **ret;
1409 int i = 0, count = 0;
1410 RB_FOREACH(l, &idclasses, link)
1411 count++;
1412 if (count == 0)
1413 return NULL;
1414 ret = calloc(count + 1, sizeof(idclass_t *));
1415 RB_FOREACH(l, &idclasses, link)
1416 ret[i++] = l->idc;
1417 ret[i] = NULL;
1418 return ret;
1419 }
1420
1421 idclass_t const **
idclass_find_children(const char * clazz)1422 idclass_find_children(const char *clazz)
1423 {
1424 const idclass_t *ic = idclass_find(clazz), *root;
1425 idclass_link_t *l;
1426 idclass_t const **ret;
1427 int i, count;
1428
1429 if (ic == NULL)
1430 return NULL;
1431 root = idnode_root_class(ic);
1432 if (root == NULL)
1433 return NULL;
1434 ret = NULL;
1435 count = i = 0;
1436 RB_FOREACH(l, &idclasses, link) {
1437 if (root == idnode_root_class(l->idc)) {
1438 if (i <= count) {
1439 count += 50;
1440 ret = realloc(ret, count * sizeof(const idclass_t *));
1441 }
1442 ret[i++] = ic;
1443 }
1444 }
1445 if (i <= count)
1446 ret = realloc(ret, (count + 1) * sizeof(const idclass_t *));
1447 ret[i] = NULL;
1448 return ret;
1449 }
1450
1451 /*
1452 * Just get the class definition
1453 */
1454 htsmsg_t *
idclass_serialize0(const idclass_t * idc,htsmsg_t * list,int optmask,const char * lang)1455 idclass_serialize0(const idclass_t *idc, htsmsg_t *list, int optmask, const char *lang)
1456 {
1457 const char *s;
1458 htsmsg_t *p, *m = htsmsg_create_map();
1459
1460 /* Caption and name */
1461 if ((s = idclass_get_caption(idc, lang)))
1462 htsmsg_add_str(m, "caption", s);
1463 if ((s = idclass_get_class(idc)))
1464 htsmsg_add_str(m, "class", s);
1465 if ((s = idclass_get_event(idc)))
1466 htsmsg_add_str(m, "event", s);
1467 if ((s = idclass_get_order(idc)))
1468 htsmsg_add_str(m, "order", s);
1469 if ((p = idclass_get_property_groups(idc, lang)))
1470 htsmsg_add_msg(m, "groups", p);
1471
1472 /* Props */
1473 if ((p = idnode_params(idc, NULL, list, optmask, lang)))
1474 htsmsg_add_msg(m, "props", p);
1475
1476 return m;
1477 }
1478
1479 /**
1480 *
1481 */
1482 htsmsg_t *
idnode_serialize0(idnode_t * self,htsmsg_t * list,int optmask,const char * lang)1483 idnode_serialize0(idnode_t *self, htsmsg_t *list, int optmask, const char *lang)
1484 {
1485 const idclass_t *idc = self->in_class;
1486 const char *uuid, *s;
1487 char ubuf[UUID_HEX_SIZE];
1488
1489 htsmsg_t *m = htsmsg_create_map();
1490 if (!idc->ic_snode) {
1491 uuid = idnode_uuid_as_str(self, ubuf);
1492 htsmsg_add_str(m, "uuid", uuid);
1493 htsmsg_add_str(m, "id", uuid);
1494 }
1495 htsmsg_add_str(m, "text", idnode_get_title(self, lang) ?: "");
1496 if ((s = idclass_get_caption(idc, lang)))
1497 htsmsg_add_str(m, "caption", s);
1498 if ((s = idclass_get_class(idc)))
1499 htsmsg_add_str(m, "class", s);
1500 if ((s = idclass_get_event(idc)))
1501 htsmsg_add_str(m, "event", s);
1502
1503 htsmsg_add_msg(m, "params", idnode_params(idc, self, list, optmask, lang));
1504
1505 return m;
1506 }
1507
1508 /* **************************************************************************
1509 * Simple list helpers
1510 * *************************************************************************/
1511
1512 htsmsg_t *
idnode_slist_enum(idnode_t * in,idnode_slist_t * options,const char * lang)1513 idnode_slist_enum ( idnode_t *in, idnode_slist_t *options, const char *lang )
1514 {
1515 htsmsg_t *l = htsmsg_create_list(), *m;
1516
1517 for (; options->id; options++) {
1518 m = htsmsg_create_key_val(options->id, tvh_gettext_lang(lang, options->name));
1519 htsmsg_add_msg(l, NULL, m);
1520 }
1521 return l;
1522 }
1523
1524 htsmsg_t *
idnode_slist_get(idnode_t * in,idnode_slist_t * options)1525 idnode_slist_get ( idnode_t *in, idnode_slist_t *options )
1526 {
1527 htsmsg_t *l = htsmsg_create_list();
1528 int *ip;
1529
1530 for (; options->id; options++) {
1531 ip = (void *)in + options->off;
1532 if (*ip)
1533 htsmsg_add_str(l, NULL, options->id);
1534 }
1535 return l;
1536 }
1537
1538 int
idnode_slist_set(idnode_t * in,idnode_slist_t * options,const htsmsg_t * vals)1539 idnode_slist_set ( idnode_t *in, idnode_slist_t *options, const htsmsg_t *vals )
1540 {
1541 idnode_slist_t *o;
1542 htsmsg_field_t *f;
1543 int *ip, changed = 0;
1544 const char *s;
1545
1546 for (o = options; o->id; o++) {
1547 ip = (void *)in + o->off;
1548 if (!changed) {
1549 HTSMSG_FOREACH(f, vals) {
1550 if ((s = htsmsg_field_get_str(f)) == NULL)
1551 continue;
1552 if (strcmp(s, o->id))
1553 continue;
1554 if (*ip == 0) changed = 1;
1555 break;
1556 }
1557 if (f == NULL && *ip) changed = 1;
1558 }
1559 *ip = 0;
1560 }
1561 HTSMSG_FOREACH(f, vals) {
1562 if ((s = htsmsg_field_get_str(f)) == NULL)
1563 continue;
1564 for (o = options; o->id; o++) {
1565 if (strcmp(o->id, s)) continue;
1566 ip = (void *)in + o->off;
1567 *ip = 1;
1568 break;
1569 }
1570 }
1571 return changed;
1572 }
1573
1574 char *
idnode_slist_rend(idnode_t * in,idnode_slist_t * options,const char * lang)1575 idnode_slist_rend ( idnode_t *in, idnode_slist_t *options, const char *lang )
1576 {
1577 int *ip;
1578 size_t l = 0;
1579
1580 prop_sbuf[0] = '\0';
1581 for (; options->id; options++) {
1582 ip = (void *)in + options->off;
1583 if (*ip)
1584 tvh_strlcatf(prop_sbuf, PROP_SBUF_LEN, l, "%s%s", prop_sbuf[0] ? "," : "",
1585 tvh_gettext_lang(lang, options->name));
1586 }
1587 return strdup(prop_sbuf);
1588 }
1589
1590 /* **************************************************************************
1591 * List helpers
1592 * *************************************************************************/
1593
1594 static void
idnode_list_notify(idnode_list_mapping_t * ilm,void * origin)1595 idnode_list_notify ( idnode_list_mapping_t *ilm, void *origin )
1596 {
1597 if (origin == NULL)
1598 return;
1599 if (origin == ilm->ilm_in1) {
1600 idnode_notify_changed(ilm->ilm_in2);
1601 if (ilm->ilm_in2_save)
1602 idnode_save_queue(ilm->ilm_in2);
1603 }
1604 if (origin == ilm->ilm_in2) {
1605 idnode_notify_changed(ilm->ilm_in1);
1606 if (ilm->ilm_in1_save)
1607 idnode_save_queue(ilm->ilm_in1);
1608 }
1609 }
1610
1611 /*
1612 * Link class1 and class2
1613 */
1614 idnode_list_mapping_t *
idnode_list_link(idnode_t * in1,idnode_list_head_t * in1_list,idnode_t * in2,idnode_list_head_t * in2_list,void * origin,uint32_t savemask)1615 idnode_list_link ( idnode_t *in1, idnode_list_head_t *in1_list,
1616 idnode_t *in2, idnode_list_head_t *in2_list,
1617 void *origin, uint32_t savemask )
1618 {
1619 idnode_list_mapping_t *ilm;
1620
1621 /* Already linked */
1622 LIST_FOREACH(ilm, in1_list, ilm_in1_link)
1623 if (ilm->ilm_in2 == in2) {
1624 ilm->ilm_mark = 0;
1625 return NULL;
1626 }
1627 LIST_FOREACH(ilm, in2_list, ilm_in2_link)
1628 if (ilm->ilm_in1 == in1) {
1629 ilm->ilm_mark = 0;
1630 return NULL;
1631 }
1632
1633 /* Link */
1634 ilm = calloc(1, sizeof(idnode_list_mapping_t));
1635 ilm->ilm_in1 = in1;
1636 ilm->ilm_in2 = in2;
1637 LIST_INSERT_HEAD(in1_list, ilm, ilm_in1_link);
1638 LIST_INSERT_HEAD(in2_list, ilm, ilm_in2_link);
1639 ilm->ilm_in1_save = savemask & 1;
1640 ilm->ilm_in2_save = (savemask >> 1) & 1;
1641 idnode_list_notify(ilm, origin);
1642 return ilm;
1643 }
1644
1645 void
idnode_list_unlink(idnode_list_mapping_t * ilm,void * origin)1646 idnode_list_unlink ( idnode_list_mapping_t *ilm, void *origin )
1647 {
1648 LIST_REMOVE(ilm, ilm_in1_link);
1649 LIST_REMOVE(ilm, ilm_in2_link);
1650 idnode_list_notify(ilm, origin);
1651 free(ilm);
1652 }
1653
1654 void
idnode_list_destroy(idnode_list_head_t * ilh,void * origin)1655 idnode_list_destroy(idnode_list_head_t *ilh, void *origin)
1656 {
1657 idnode_list_mapping_t *ilm;
1658
1659 while ((ilm = LIST_FIRST(ilh)) != NULL)
1660 idnode_list_unlink(ilm, origin);
1661 }
1662
1663 static int
idnode_list_clean1(idnode_t * in1,idnode_list_head_t * in1_list)1664 idnode_list_clean1
1665 ( idnode_t *in1, idnode_list_head_t *in1_list )
1666 {
1667 int save = 0;
1668 idnode_list_mapping_t *ilm, *n;
1669
1670 for (ilm = LIST_FIRST(in1_list); ilm != NULL; ilm = n) {
1671 n = LIST_NEXT(ilm, ilm_in1_link);
1672 if (ilm->ilm_mark) {
1673 idnode_list_unlink(ilm, in1);
1674 save = 1;
1675 }
1676 }
1677 return save;
1678 }
1679
1680 static int
idnode_list_clean2(idnode_t * in2,idnode_list_head_t * in2_list)1681 idnode_list_clean2
1682 ( idnode_t *in2, idnode_list_head_t *in2_list )
1683 {
1684 int save = 0;
1685 idnode_list_mapping_t *ilm, *n;
1686
1687 for (ilm = LIST_FIRST(in2_list); ilm != NULL; ilm = n) {
1688 n = LIST_NEXT(ilm, ilm_in2_link);
1689 if (ilm->ilm_mark) {
1690 idnode_list_unlink(ilm, in2);
1691 save = 1;
1692 }
1693 }
1694 return save;
1695 }
1696
1697 htsmsg_t *
idnode_list_get1(idnode_list_head_t * in1_list)1698 idnode_list_get1
1699 ( idnode_list_head_t *in1_list )
1700 {
1701 idnode_list_mapping_t *ilm;
1702 htsmsg_t *l = htsmsg_create_list();
1703 char ubuf[UUID_HEX_SIZE];
1704
1705 LIST_FOREACH(ilm, in1_list, ilm_in1_link)
1706 htsmsg_add_str(l, NULL, idnode_uuid_as_str(ilm->ilm_in2, ubuf));
1707 return l;
1708 }
1709
1710 htsmsg_t *
idnode_list_get2(idnode_list_head_t * in2_list)1711 idnode_list_get2
1712 ( idnode_list_head_t *in2_list )
1713 {
1714 idnode_list_mapping_t *ilm;
1715 htsmsg_t *l = htsmsg_create_list();
1716 char ubuf[UUID_HEX_SIZE];
1717
1718 LIST_FOREACH(ilm, in2_list, ilm_in2_link)
1719 htsmsg_add_str(l, NULL, idnode_uuid_as_str(ilm->ilm_in1, ubuf));
1720 return l;
1721 }
1722
1723 char *
idnode_list_get_csv1(idnode_list_head_t * in1_list,const char * lang)1724 idnode_list_get_csv1
1725 ( idnode_list_head_t *in1_list, const char *lang )
1726 {
1727 char *str;
1728 idnode_list_mapping_t *ilm;
1729 htsmsg_t *l = htsmsg_create_list();
1730
1731 LIST_FOREACH(ilm, in1_list, ilm_in1_link)
1732 htsmsg_add_str(l, NULL, idnode_get_title(ilm->ilm_in2, lang));
1733
1734 str = htsmsg_list_2_csv(l, ',', 1);
1735 htsmsg_destroy(l);
1736 return str;
1737 }
1738
1739 char *
idnode_list_get_csv2(idnode_list_head_t * in2_list,const char * lang)1740 idnode_list_get_csv2
1741 ( idnode_list_head_t *in2_list, const char *lang )
1742 {
1743 char *str;
1744 idnode_list_mapping_t *ilm;
1745 htsmsg_t *l = htsmsg_create_list();
1746
1747 LIST_FOREACH(ilm, in2_list, ilm_in2_link)
1748 htsmsg_add_str(l, NULL, idnode_get_title(ilm->ilm_in1, lang));
1749
1750 str = htsmsg_list_2_csv(l, ',', 1);
1751 htsmsg_destroy(l);
1752 return str;
1753 }
1754
1755 int
idnode_list_set1(idnode_t * in1,idnode_list_head_t * in1_list,const idclass_t * in2_class,htsmsg_t * in2_list,int (* in2_create)(idnode_t * in1,idnode_t * in2,void * origin))1756 idnode_list_set1
1757 ( idnode_t *in1, idnode_list_head_t *in1_list,
1758 const idclass_t *in2_class, htsmsg_t *in2_list,
1759 int (*in2_create)(idnode_t *in1, idnode_t *in2, void *origin) )
1760 {
1761 const char *str;
1762 htsmsg_field_t *f;
1763 idnode_t *in2;
1764 idnode_list_mapping_t *ilm;
1765 int save = 0;
1766
1767 /* Mark all for deletion */
1768 LIST_FOREACH(ilm, in1_list, ilm_in1_link)
1769 ilm->ilm_mark = 1;
1770
1771 /* Make new links */
1772 HTSMSG_FOREACH(f, in2_list)
1773 if ((str = htsmsg_field_get_str(f)))
1774 if ((in2 = idnode_find(str, in2_class, NULL)) != NULL)
1775 if (in2_create(in1, in2, in1))
1776 save = 1;
1777
1778 /* Delete unlinked */
1779 if (idnode_list_clean1(in1, in1_list))
1780 save = 1;
1781
1782 /* Change notification */
1783 if (save)
1784 idnode_notify_changed(in1);
1785
1786 /* Save only on demand */
1787 ilm = LIST_FIRST(in1_list);
1788 if (ilm && !ilm->ilm_in1_save)
1789 save = 0;
1790
1791 return save;
1792 }
1793
1794 int
idnode_list_set2(idnode_t * in2,idnode_list_head_t * in2_list,const idclass_t * in1_class,htsmsg_t * in1_list,int (* in1_create)(idnode_t * in1,idnode_t * in2,void * origin))1795 idnode_list_set2
1796 ( idnode_t *in2, idnode_list_head_t *in2_list,
1797 const idclass_t *in1_class, htsmsg_t *in1_list,
1798 int (*in1_create)(idnode_t *in1, idnode_t *in2, void *origin) )
1799 {
1800 const char *str;
1801 htsmsg_field_t *f;
1802 idnode_t *in1;
1803 idnode_list_mapping_t *ilm;
1804 int save = 0;
1805
1806 /* Mark all for deletion */
1807 LIST_FOREACH(ilm, in2_list, ilm_in2_link)
1808 ilm->ilm_mark = 1;
1809
1810 /* Make new links */
1811 HTSMSG_FOREACH(f, in1_list)
1812 if ((str = htsmsg_field_get_str(f)))
1813 if ((in1 = idnode_find(str, in1_class, NULL)) != NULL)
1814 if (in1_create(in1, in2, in2))
1815 save = 1;
1816
1817 /* Delete unlinked */
1818 if (idnode_list_clean2(in2, in2_list))
1819 save = 1;
1820
1821 /* Change notification */
1822 if (save)
1823 idnode_notify_changed(in2);
1824
1825 /* Save only on demand */
1826 ilm = LIST_FIRST(in2_list);
1827 if (ilm && !ilm->ilm_in2_save)
1828 save = 0;
1829
1830 return save;
1831 }
1832
1833
1834 /* **************************************************************************
1835 * Notification
1836 * *************************************************************************/
1837
1838 /**
1839 * Notify about a change
1840 */
1841 void
idnode_notify(idnode_t * in,const char * action)1842 idnode_notify ( idnode_t *in, const char *action )
1843 {
1844 const idclass_t *ic = in->in_class;
1845 char ubuf[UUID_HEX_SIZE];
1846 const char *uuid = idnode_uuid_as_str(in, ubuf);
1847
1848 if (!tvheadend_is_running())
1849 return;
1850
1851 while (ic) {
1852 if (ic->ic_event) {
1853 if (!ic->ic_snode)
1854 notify_delayed(uuid, ic->ic_event, action);
1855 else
1856 notify_reload(ic->ic_event);
1857 }
1858 ic = ic->ic_super;
1859 }
1860 }
1861
1862 void
idnode_notify_changed(void * in)1863 idnode_notify_changed (void *in)
1864 {
1865 idnode_notify(in, "change");
1866 }
1867
1868 void
idnode_notify_title_changed(void * in,const char * lang)1869 idnode_notify_title_changed (void *in, const char *lang)
1870 {
1871 char ubuf[UUID_HEX_SIZE];
1872 htsmsg_t *m = htsmsg_create_map();
1873 htsmsg_add_str(m, "uuid", idnode_uuid_as_str(in, ubuf));
1874 htsmsg_add_str(m, "text", idnode_get_title(in, lang));
1875 notify_by_msg("title", m, 0);
1876 idnode_notify_changed(in);
1877 }
1878
1879 /* **************************************************************************
1880 * Save thread
1881 * *************************************************************************/
1882
1883 static void *
save_thread(void * aux)1884 save_thread ( void *aux )
1885 {
1886 idnode_save_t *ise;
1887 htsmsg_t *m;
1888 char filename[PATH_MAX];
1889
1890 tvhthread_renice(15);
1891
1892 pthread_mutex_lock(&global_lock);
1893
1894 while (atomic_get(&save_running)) {
1895 if ((ise = TAILQ_FIRST(&idnodes_save)) == NULL ||
1896 (ise->ise_reqtime + IDNODE_SAVE_DELAY > mclk())) {
1897 if (ise)
1898 mtimer_arm_abs(&save_timer, idnode_save_trigger_thread_cb, NULL,
1899 ise->ise_reqtime + IDNODE_SAVE_DELAY);
1900 tvh_cond_wait(&save_cond, &global_lock);
1901 continue;
1902 }
1903 m = idnode_savefn(ise->ise_node, filename, sizeof(filename));
1904 ise->ise_node->in_save = NULL;
1905 TAILQ_REMOVE(&idnodes_save, ise, ise_link);
1906 pthread_mutex_unlock(&global_lock);
1907 free(ise);
1908 if (m) {
1909 hts_settings_save(m, "%s", filename);
1910 htsmsg_destroy(m);
1911 }
1912 pthread_mutex_lock(&global_lock);
1913 }
1914
1915 mtimer_disarm(&save_timer);
1916
1917 while ((ise = TAILQ_FIRST(&idnodes_save)) != NULL) {
1918 m = idnode_savefn(ise->ise_node, filename, sizeof(filename));
1919 ise->ise_node->in_save = NULL;
1920 TAILQ_REMOVE(&idnodes_save, ise, ise_link);
1921 pthread_mutex_unlock(&global_lock);
1922 free(ise);
1923 if (m) {
1924 hts_settings_save(m, "%s", filename);
1925 htsmsg_destroy(m);
1926 }
1927 pthread_mutex_lock(&global_lock);
1928 }
1929
1930 pthread_mutex_unlock(&global_lock);
1931 return NULL;
1932 }
1933
1934 /* **************************************************************************
1935 * Initialization
1936 * *************************************************************************/
1937
1938 void
idnode_boot(void)1939 idnode_boot(void)
1940 {
1941 RB_INIT(&idnodes);
1942 RB_INIT(&idclasses);
1943 RB_INIT(&idrootclasses);
1944 TAILQ_INIT(&idnodes_save);
1945 tvh_cond_init(&save_cond);
1946 }
1947
1948 void
idnode_init(void)1949 idnode_init(void)
1950 {
1951 atomic_set(&save_running, 1);
1952 tvhthread_create(&save_tid, NULL, save_thread, NULL, "save");
1953 }
1954
1955 void
idnode_done(void)1956 idnode_done(void)
1957 {
1958 idclass_link_t *il;
1959
1960 atomic_set(&save_running, 0);
1961 tvh_cond_signal(&save_cond, 0);
1962 pthread_join(save_tid, NULL);
1963 mtimer_disarm(&save_timer);
1964
1965 while ((il = RB_FIRST(&idclasses)) != NULL) {
1966 RB_REMOVE(&idclasses, il, link);
1967 free(il);
1968 }
1969 while ((il = RB_FIRST(&idrootclasses)) != NULL) {
1970 RB_REMOVE(&idrootclasses, il, link);
1971 free(il);
1972 }
1973 SKEL_FREE(idclasses_skel);
1974 }
1975
1976 /******************************************************************************
1977 * Editor Configuration
1978 *
1979 * vim:sts=2:ts=2:sw=2:et
1980 *****************************************************************************/
1981