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