1 /*
2  *  tvheadend, channel functions
3  *  Copyright (C) 2007 Andreas Öman
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <pthread.h>
20 #include <assert.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/ioctl.h>
24 #include <fcntl.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "settings.h"
32 #include "config.h"
33 
34 #include "tvheadend.h"
35 #include "epg.h"
36 #include "epggrab.h"
37 #include "channels.h"
38 #include "access.h"
39 #include "notify.h"
40 #include "dvr/dvr.h"
41 #include "htsp_server.h"
42 #include "imagecache.h"
43 #include "service_mapper.h"
44 #include "htsbuf.h"
45 #include "bouquet.h"
46 #include "intlconv.h"
47 #include "memoryinfo.h"
48 
49 #define CHANNEL_BLANK_NAME  "{name-not-set}"
50 
51 struct channel_tree channels;
52 
53 struct channel_tag_queue channel_tags;
54 
55 static int channel_in_load;
56 
57 static void channel_tag_init ( void );
58 static void channel_tag_done ( void );
59 static void channel_tag_mapping_destroy(idnode_list_mapping_t *ilm, void *origin);
60 static void channel_epg_update_all ( channel_t *ch );
61 
62 static int
ch_id_cmp(channel_t * a,channel_t * b)63 ch_id_cmp ( channel_t *a, channel_t *b )
64 {
65   return channel_get_id(a) - channel_get_id(b);
66 }
67 
68 /* **************************************************************************
69  * Class definition
70  * *************************************************************************/
71 
72 static void
channel_class_changed(idnode_t * self)73 channel_class_changed ( idnode_t *self )
74 {
75   channel_t *ch = (channel_t *)self;
76 
77   tvhdebug(LS_CHANNEL, "channel '%s' changed", channel_get_name(ch));
78 
79   /* update the EPG channel <-> channel mapping here */
80   if (ch->ch_enabled && ch->ch_epgauto)
81     epggrab_channel_add(ch);
82 
83   /* HTSP */
84   htsp_channel_update(ch);
85 }
86 
87 static htsmsg_t *
channel_class_save(idnode_t * self,char * filename,size_t fsize)88 channel_class_save ( idnode_t *self, char *filename, size_t fsize )
89 {
90   channel_t *ch = (channel_t *)self;
91   htsmsg_t *c = NULL;
92   char ubuf[UUID_HEX_SIZE];
93   /* save channel (on demand) */
94   if (ch->ch_dont_save == 0) {
95     tvhdebug(LS_CHANNEL, "channel '%s' save", channel_get_name(ch));
96     c = htsmsg_create_map();
97     idnode_save(&ch->ch_id, c);
98     snprintf(filename, fsize, "channel/config/%s", idnode_uuid_as_str(&ch->ch_id, ubuf));
99   }
100   return c;
101 }
102 
103 static void
channel_class_delete(idnode_t * self)104 channel_class_delete ( idnode_t *self )
105 {
106   channel_delete((channel_t*)self, 1);
107 }
108 
109 static int
channel_class_autoname_set(void * obj,const void * p)110 channel_class_autoname_set ( void *obj, const void *p )
111 {
112   channel_t *ch = (channel_t *)obj;
113   const char *s;
114   int b = *(int *)p;
115   if (ch->ch_autoname != b) {
116     if (b == 0 && tvh_str_default(ch->ch_name, NULL) == NULL) {
117       s = channel_get_name(ch);
118       free(ch->ch_name);
119       ch->ch_name = strdup(s);
120     } else if (b) {
121       if (ch->ch_name)
122         ch->ch_name[0] = '\0';
123     }
124     ch->ch_autoname = b;
125     return 1;
126   }
127   return 0;
128 }
129 
130 static const void *
channel_class_services_get(void * obj)131 channel_class_services_get ( void *obj )
132 {
133   channel_t *ch = obj;
134   return idnode_list_get2(&ch->ch_services);
135 }
136 
137 static char *
channel_class_services_rend(void * obj,const char * lang)138 channel_class_services_rend ( void *obj, const char *lang )
139 {
140   channel_t *ch = obj;
141   return idnode_list_get_csv2(&ch->ch_services, lang);
142 }
143 
144 static int
channel_class_services_set(void * obj,const void * p)145 channel_class_services_set ( void *obj, const void *p )
146 {
147   channel_t *ch = obj;
148   return idnode_list_set2(&ch->ch_id, &ch->ch_services,
149                           &service_class, (htsmsg_t *)p,
150                           service_mapper_create);
151 }
152 
153 static htsmsg_t *
channel_class_services_enum(void * obj,const char * lang)154 channel_class_services_enum ( void *obj, const char *lang )
155 {
156   htsmsg_t *e, *m = htsmsg_create_map();
157   htsmsg_add_str(m, "type",  "api");
158   htsmsg_add_str(m, "uri",   "service/list");
159   htsmsg_add_str(m, "event", "service");
160   e = htsmsg_create_map();
161   htsmsg_add_bool(e, "enum", 1);
162   htsmsg_add_msg(m, "params", e);
163   return m;
164 }
165 
166 static const void *
channel_class_tags_get(void * obj)167 channel_class_tags_get ( void *obj )
168 {
169   channel_t *ch = obj;
170   return idnode_list_get2(&ch->ch_ctms);
171 }
172 
173 static char *
channel_class_tags_rend(void * obj,const char * lang)174 channel_class_tags_rend ( void *obj, const char *lang )
175 {
176   channel_t *ch = obj;
177   return idnode_list_get_csv2(&ch->ch_ctms, lang);
178 }
179 
180 static int
channel_class_tags_set_cb(idnode_t * in1,idnode_t * in2,void * origin)181 channel_class_tags_set_cb ( idnode_t *in1, idnode_t *in2, void *origin )
182 {
183   return channel_tag_map((channel_tag_t *)in1, (channel_t *)in2, origin);
184 }
185 
186 static int
channel_class_tags_set(void * obj,const void * p)187 channel_class_tags_set ( void *obj, const void *p )
188 {
189   channel_t *ch = obj;
190   return idnode_list_set2(&ch->ch_id, &ch->ch_ctms,
191                           &channel_tag_class, (htsmsg_t *)p,
192                           channel_class_tags_set_cb);
193 }
194 
195 static void
channel_class_icon_notify(void * obj,const char * lang)196 channel_class_icon_notify ( void *obj, const char *lang )
197 {
198   channel_t *ch = obj;
199   if (!ch->ch_load)
200     (void)channel_get_icon(obj);
201 }
202 
203 static const void *
channel_class_get_icon(void * obj)204 channel_class_get_icon ( void *obj )
205 {
206   prop_ptr = channel_get_icon(obj);
207   return &prop_ptr;
208 }
209 
210 static const char *
channel_class_get_title(idnode_t * self,const char * lang)211 channel_class_get_title ( idnode_t *self, const char *lang )
212 {
213   return channel_get_name((channel_t*)self);
214 }
215 
216 /* exported for others */
217 htsmsg_t *
channel_class_get_list(void * o,const char * lang)218 channel_class_get_list(void *o, const char *lang)
219 {
220   htsmsg_t *m = htsmsg_create_map();
221   htsmsg_t *p = htsmsg_create_map();
222   htsmsg_add_str(m, "type",  "api");
223   htsmsg_add_str(m, "uri",   "channel/list");
224   htsmsg_add_str(m, "event", "channel");
225   htsmsg_add_u32(p, "all",  1);
226   htsmsg_add_msg(m, "params", p);
227   return m;
228 }
229 
230 static int
channel_class_set_name(void * o,const void * p)231 channel_class_set_name ( void *o, const void *p )
232 {
233   channel_t *ch = o;
234   const char *s = p;
235   if (ch->ch_load)
236     ch->ch_autoname = p == NULL || *s == 0;
237   if (strcmp(s ?: "", ch->ch_name ?: "")) {
238     free(ch->ch_name);
239     ch->ch_name = s ? strdup(s) : NULL;
240     return 1;
241   }
242   return 0;
243 }
244 
245 static const void *
channel_class_get_name(void * o)246 channel_class_get_name ( void *o )
247 {
248   prop_ptr = channel_get_name(o);
249   return &prop_ptr;
250 }
251 
252 static const void *
channel_class_get_number(void * o)253 channel_class_get_number ( void *o )
254 {
255   static int64_t i;
256   i = channel_get_number(o);
257   return &i;
258 }
259 
260 static const void *
channel_class_epggrab_get(void * o)261 channel_class_epggrab_get ( void *o )
262 {
263   channel_t *ch = o;
264   return idnode_list_get2(&ch->ch_epggrab);
265 }
266 
267 static int
channel_class_epggrab_set(void * o,const void * v)268 channel_class_epggrab_set ( void *o, const void *v )
269 {
270   channel_t *ch = o;
271   return idnode_list_set2(&ch->ch_id, &ch->ch_epggrab,
272                           &epggrab_channel_class, (htsmsg_t *)v,
273                           epggrab_channel_map);
274 }
275 
276 static htsmsg_t *
channel_class_epggrab_list(void * o,const char * lang)277 channel_class_epggrab_list ( void *o, const char *lang )
278 {
279   htsmsg_t *e, *m = htsmsg_create_map();
280   htsmsg_add_str(m, "type",  "api");
281   htsmsg_add_str(m, "uri",   "epggrab/channel/list");
282   htsmsg_add_str(m, "event", "epggrab_channel");
283   e = htsmsg_create_map();
284   htsmsg_add_bool(e, "enum", 1);
285   htsmsg_add_msg(m, "params", e);
286   return m;
287 }
288 
289 static const void *
channel_class_bouquet_get(void * o)290 channel_class_bouquet_get ( void *o )
291 {
292   channel_t *ch = o;
293   if (ch->ch_bouquet)
294     idnode_uuid_as_str(&ch->ch_bouquet->bq_id, prop_sbuf);
295   else
296     prop_sbuf[0] = '\0';
297   return &prop_sbuf_ptr;
298 }
299 
300 static int
channel_class_bouquet_set(void * o,const void * v)301 channel_class_bouquet_set ( void *o, const void *v )
302 {
303   channel_t *ch = o;
304   bouquet_t *bq = bouquet_find_by_uuid(v);
305   if (bq == NULL && ch->ch_bouquet) {
306     ch->ch_bouquet = NULL;
307     return 1;
308   } else if (bq != ch->ch_bouquet) {
309     ch->ch_bouquet = bq;
310     return 1;
311   }
312   return 0;
313 }
314 
315 static int
channel_class_epg_parent_set_noupdate(void * o,const void * v,channel_t ** parent)316 channel_class_epg_parent_set_noupdate
317   ( void *o, const void *v, channel_t **parent )
318 {
319   channel_t *ch = o;
320   const char *uuid = v;
321   if (strcmp(v ?: "", ch->ch_epg_parent ?: "")) {
322     if (ch->ch_epg_parent) {
323       LIST_REMOVE(ch, ch_epg_slave_link);
324       free(ch->ch_epg_parent);
325     }
326     ch->ch_epg_parent = NULL;
327     epg_channel_unlink(ch);
328     if (uuid && uuid[0] != '\0') {
329       if (channel_in_load) {
330         ch->ch_epg_parent = strdup(uuid);
331       } else {
332         *parent = channel_find_by_uuid(uuid);
333         if (*parent) {
334           ch->ch_epg_parent = strdup(uuid);
335           LIST_INSERT_HEAD(&(*parent)->ch_epg_slaves, ch, ch_epg_slave_link);
336         }
337       }
338     }
339     return 1;
340   }
341   return 0;
342 }
343 
344 static int
channel_class_epg_parent_set(void * o,const void * v)345 channel_class_epg_parent_set ( void *o, const void *v )
346 {
347   channel_t *parent = NULL;
348   int save = channel_class_epg_parent_set_noupdate(o, v, &parent);
349   if (parent)
350     channel_epg_update_all(parent);
351   return save;
352 }
353 
354 static htsmsg_t *
channel_class_epg_running_list(void * o,const char * lang)355 channel_class_epg_running_list ( void *o, const char *lang )
356 {
357   static const struct strtab tab[] = {
358     { N_("Not set"),   -1 },
359     { N_("Disabled"),   0 },
360     { N_("Enabled"),    1 },
361   };
362   return strtab2htsmsg(tab, 1, lang);
363 }
364 
365 CLASS_DOC(channel)
366 PROP_DOC(runningstate)
367 
368 const idclass_t channel_class = {
369   .ic_class      = "channel",
370   .ic_caption    = N_("Channels"),
371   .ic_doc        = tvh_doc_channel_class,
372   .ic_event      = "channel",
373   .ic_changed    = channel_class_changed,
374   .ic_save       = channel_class_save,
375   .ic_get_title  = channel_class_get_title,
376   .ic_delete     = channel_class_delete,
377   .ic_properties = (const property_t[]){
378     {
379       .type     = PT_BOOL,
380       .id       = "enabled",
381       .name     = N_("Enabled"),
382       .desc     = N_("Enable/disable the channel."),
383       .def.i    = 1,
384       .off      = offsetof(channel_t, ch_enabled),
385     },
386     {
387       .type     = PT_BOOL,
388       .id       = "autoname",
389       .name     = N_("Automatically name from network"),
390       .desc     = N_("Always use the name defined by the network."),
391       .off      = offsetof(channel_t, ch_autoname),
392       .set      = channel_class_autoname_set,
393       .opts     = PO_ADVANCED | PO_NOSAVE,
394     },
395     {
396       .type     = PT_STR,
397       .id       = "name",
398       .name     = N_("Name"),
399       .desc     = N_("The name given to/of the channel (This is "
400                      "how it'll appear in your EPG.)"),
401       .off      = offsetof(channel_t, ch_name),
402       .set      = channel_class_set_name,
403       .get      = channel_class_get_name,
404       .notify   = channel_class_icon_notify, /* try to re-render default icon path */
405     },
406     {
407       .type     = PT_S64,
408       .intextra = CHANNEL_SPLIT,
409       .id       = "number",
410       .name     = N_("Number"),
411       .desc     = N_("The position the channel will appear on "
412                      "your EPG. This is not used by Tvheadend "
413                      "internally, but rather intended to be used by "
414                      "HTSP clients for mapping to remote control "
415                      "buttons, presentation order, etc."),
416       .off      = offsetof(channel_t, ch_number),
417       .get      = channel_class_get_number,
418     },
419     {
420       .type     = PT_STR,
421       .id       = "icon",
422       .name     = N_("User icon"),
423       .desc     = N_("The URL to the icon to use/used for the channel. "
424                      "The local files are referred using file:/// URLs."),
425       .off      = offsetof(channel_t, ch_icon),
426       .notify   = channel_class_icon_notify,
427       .opts     = PO_ADVANCED,
428     },
429     {
430       .type     = PT_STR,
431       .id       = "icon_public_url",
432       .name     = N_("Icon URL"),
433       .desc     = N_("The imagecache path to the icon to use/used "
434                      "for the channel."),
435       .get      = channel_class_get_icon,
436       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN | PO_EXPERT,
437     },
438     {
439       .type     = PT_BOOL,
440       .id       = "epgauto",
441       .name     = N_("Automatically map EPG source"),
442       .desc     = N_("Automatically link EPG data to the channel "
443                      "(using the channel name for matching). If you "
444                      "turn this option off, only the OTA EPG grabber "
445                      "will be used for this channel unless you've "
446                      "specifically set a different EPG Source."),
447       .def.i    = 1,
448       .off      = offsetof(channel_t, ch_epgauto),
449       .opts     = PO_ADVANCED,
450     },
451     {
452       .type     = PT_STR,
453       .islist   = 1,
454       .id       = "epggrab",
455       .name     = N_("EPG source"),
456       .desc     = N_("Name of the module, grabber or channel "
457                      "that should be used to update this channels "
458                      "EPG info."),
459       .set      = channel_class_epggrab_set,
460       .get      = channel_class_epggrab_get,
461       .list     = channel_class_epggrab_list,
462       .opts     = PO_NOSAVE | PO_ADVANCED,
463     },
464     {
465       .type     = PT_INT,
466       .id       = "dvr_pre_time",
467       .name     = N_("Pre-recording padding"), // TODO: better text?
468       .desc     = N_("Start recording earlier "
469                      "than the EPG/timer defined "
470                      "start time by x minutes, for example if a program "
471                      "is to start at 13:00 and you set a padding of 5 "
472                      "minutes it will start recording at 12:54:30 "
473                      "(including a warming-up time of 30 seconds). If this "
474                      "isn't set the pre-recording padding if set in the "
475                      "DVR entry or DVR profile will be used."),
476       .off      = offsetof(channel_t, ch_dvr_extra_time_pre),
477       .opts     = PO_ADVANCED
478     },
479     {
480       .type     = PT_INT,
481       .id       = "dvr_pst_time",
482       .name     = N_("Post-recording padding"), // TODO: better text?
483       .desc     = N_("Continue recording for x "
484                      "minutes after scheduled stop time."),
485       .off      = offsetof(channel_t, ch_dvr_extra_time_post),
486       .opts     = PO_ADVANCED
487     },
488     {
489       .type     = PT_INT,
490       .id       = "epg_running",
491       .name     = N_("Use EPG running state"),
492       .desc     = N_("Use EITp/f to decide "
493                      "event start/stop. This is also known as "
494                      "\"Accurate Recording\". See Help for details."),
495       .doc      = prop_doc_runningstate,
496       .off      = offsetof(channel_t, ch_epg_running),
497       .list     = channel_class_epg_running_list,
498       .opts     = PO_EXPERT | PO_DOC_NLIST,
499     },
500     {
501       .type     = PT_STR,
502       .islist   = 1,
503       .id       = "services",
504       .name     = N_("Services"),
505       .desc     = N_("Services associated with the channel."),
506       .get      = channel_class_services_get,
507       .set      = channel_class_services_set,
508       .list     = channel_class_services_enum,
509       .rend     = channel_class_services_rend,
510     },
511     {
512       .type     = PT_STR,
513       .islist   = 1,
514       .id       = "tags",
515       .name     = N_("Tags"),
516       .desc     = N_("Tags linked/to link to the channel."),
517       .get      = channel_class_tags_get,
518       .set      = channel_class_tags_set,
519       .list     = channel_tag_class_get_list,
520       .rend     = channel_class_tags_rend,
521     },
522     {
523       .type     = PT_STR,
524       .id       = "bouquet",
525       .name     = N_("Bouquet (auto)"),
526       .desc     = N_("The bouquet the channel is "
527                      "associated with."),
528       .get      = channel_class_bouquet_get,
529       .set      = channel_class_bouquet_set,
530       .list     = bouquet_class_get_list,
531       .opts     = PO_RDONLY | PO_ADVANCED
532     },
533     {
534       .type     = PT_STR,
535       .id       = "epg_parent",
536       .name     = N_("Reuse EPG from"),
537       .desc     = N_("Reuse the EPG from another "
538                      "channel."),
539       .set      = channel_class_epg_parent_set,
540       .list     = channel_class_get_list,
541       .off      = offsetof(channel_t, ch_epg_parent),
542       .opts     = PO_EXPERT
543     },
544     {}
545   }
546 };
547 
548 /* **************************************************************************
549  * Find
550  * *************************************************************************/
551 
552 // Note: since channel names are no longer unique this method will simply
553 //       return the first entry encountered, so could be somewhat random
554 channel_t *
channel_find_by_name(const char * name)555 channel_find_by_name ( const char *name )
556 {
557   channel_t *ch;
558   if (name == NULL)
559     return NULL;
560   CHANNEL_FOREACH(ch)
561     if (ch->ch_enabled && !strcmp(channel_get_name(ch), name))
562       break;
563   return ch;
564 }
565 
566 channel_t *
channel_find_by_id(uint32_t i)567 channel_find_by_id ( uint32_t i )
568 {
569   channel_t skel;
570   memcpy(skel.ch_id.in_uuid.bin, &i, sizeof(i));
571 
572   return RB_FIND(&channels, &skel, ch_link, ch_id_cmp);
573 }
574 
575 channel_t *
channel_find_by_number(const char * no)576 channel_find_by_number ( const char *no )
577 {
578   channel_t *ch;
579   uint32_t maj, min = 0;
580   uint64_t cno;
581   char *s;
582 
583   if (no == NULL)
584     return NULL;
585   if ((s = strchr(no, '.')) != NULL) {
586     *s = '\0';
587     min = atoi(s + 1);
588   }
589   maj = atoi(no);
590   cno = (uint64_t)maj * CHANNEL_SPLIT + (uint64_t)min;
591   CHANNEL_FOREACH(ch)
592     if(channel_get_number(ch) == cno)
593       break;
594   return ch;
595 }
596 
597 /**
598  * Check if user can access the channel
599  */
600 int
channel_access(channel_t * ch,access_t * a,int disabled)601 channel_access(channel_t *ch, access_t *a, int disabled)
602 {
603   char ubuf[UUID_HEX_SIZE];
604 
605   if (!a)
606     return 0;
607 
608   if (!ch) {
609     /* If user has full rights, allow access to removed chanels */
610     if (a->aa_chrange == NULL && a->aa_chtags == NULL)
611       return 1;
612     return 0;
613   }
614 
615   if (!disabled && !ch->ch_enabled)
616     return 0;
617 
618   /* Channel number check */
619   if (a->aa_chrange) {
620     int64_t chnum = channel_get_number(ch);
621     int i;
622     for (i = 0; i < a->aa_chrange_count; i += 2)
623       if (chnum < a->aa_chrange[i] || chnum > a->aa_chrange[i+1])
624         return 0;
625   }
626 
627   /* Channel tag check */
628   if (a->aa_chtags) {
629     idnode_list_mapping_t *ilm;
630     htsmsg_field_t *f;
631     HTSMSG_FOREACH(f, a->aa_chtags) {
632       LIST_FOREACH(ilm, &ch->ch_ctms, ilm_in2_link) {
633         if (!strcmp(htsmsg_field_get_str(f) ?: "",
634                     idnode_uuid_as_str(ilm->ilm_in1, ubuf)))
635           goto chtags_ok;
636       }
637     }
638     return 0;
639   }
640 chtags_ok:
641 
642   return 1;
643 }
644 
645 /**
646  *
647  */
648 void
channel_event_updated(epg_broadcast_t * e)649 channel_event_updated ( epg_broadcast_t *e )
650 {
651   channel_t *ch;
652   int save;
653 
654   LIST_FOREACH(ch, &e->channel->ch_epg_slaves, ch_epg_slave_link)
655     epg_broadcast_clone(ch, e, &save);
656 }
657 
658 /**
659  *
660  */
661 static void
channel_epg_update_all(channel_t * ch)662 channel_epg_update_all ( channel_t *ch )
663 {
664   epg_broadcast_t *e;
665 
666   RB_FOREACH(e, &ch->ch_epg_schedule, sched_link)
667    channel_event_updated(e);
668 }
669 
670 /* **************************************************************************
671  * Property updating
672  * *************************************************************************/
673 
674 const char *
channel_get_name(channel_t * ch)675 channel_get_name ( channel_t *ch )
676 {
677   static const char *blank = CHANNEL_BLANK_NAME;
678   const char *s;
679   idnode_list_mapping_t *ilm;
680   if (ch->ch_name && *ch->ch_name) return ch->ch_name;
681   LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link)
682     if ((s = service_get_channel_name((service_t *)ilm->ilm_in1)))
683       return s;
684   return blank;
685 }
686 
687 int
channel_set_name(channel_t * ch,const char * name)688 channel_set_name ( channel_t *ch, const char *name )
689 {
690   int save = 0;
691   if (!ch || !name) return 0;
692   if (!ch->ch_name || strcmp(ch->ch_name, name) ) {
693     if (ch->ch_name) free(ch->ch_name);
694     ch->ch_name = strdup(name);
695     save = 1;
696   }
697   return save;
698 }
699 
700 int64_t
channel_get_number(channel_t * ch)701 channel_get_number ( channel_t *ch )
702 {
703   int64_t n = 0;
704   idnode_list_mapping_t *ilm;
705   if (ch->ch_number) {
706     n = ch->ch_number;
707   } else {
708     if (ch->ch_bouquet) {
709       LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
710         if ((n = bouquet_get_channel_number(ch->ch_bouquet, (service_t *)ilm->ilm_in1)))
711           break;
712       }
713     }
714     if (n == 0) {
715       LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
716         if ((n = service_get_channel_number((service_t *)ilm->ilm_in1)))
717           break;
718       }
719     }
720   }
721   if (n) {
722     if (ch->ch_bouquet)
723       n += (int64_t)ch->ch_bouquet->bq_lcn_offset * CHANNEL_SPLIT;
724     return n;
725   }
726   return 0;
727 }
728 
729 int
channel_set_number(channel_t * ch,uint32_t major,uint32_t minor)730 channel_set_number ( channel_t *ch, uint32_t major, uint32_t minor )
731 {
732   int save = 0;
733   int64_t chnum = (uint64_t)major * CHANNEL_SPLIT + (uint64_t)minor;
734   if (!ch || !chnum) return 0;
735   if (!ch->ch_number || ch->ch_number != chnum) {
736     ch->ch_number = chnum;
737     save = 1;
738   }
739   return save;
740 }
741 
742 static char *
svcnamepicons(const char * svcname)743 svcnamepicons(const char *svcname)
744 {
745   const char *s;
746   char *r, *d;
747   int count;
748   char c;
749 
750   for (s = svcname, count = 0; *s; s++) {
751     c = *s;
752     if (c == '&' || c == '+' || c == '*')
753       count++;
754   }
755   r = d = malloc(count * 4 + (s - svcname) + 1);
756   for (s = svcname; *s; s++) {
757     c = *s;
758     if (c == '&') {
759       d[0] = 'a'; d[1] = 'n'; d[2] = 'd'; d += 3;
760     } else if (c == '+') {
761       d[0] = 'p'; d[1] = 'l'; d[2] = 'u'; d[3] = 's'; d += 4;
762     } else if (c == '*') {
763       d[0] = 's'; d[1] = 't'; d[2] = 'a'; d[3] = 'r'; d += 4;
764     } else {
765       if (c >= 'a' && c <= 'z')
766         *d++ = c;
767       else if (c >= 'A' && c <= 'Z')
768         *d++ = c - 'A' + 'a';
769       else if (c >= '0' && c <= '9')
770         *d++ = c;
771     }
772   }
773   *d = '\0';
774   return r;
775 }
776 
777 static int
check_file(const char * url)778 check_file( const char *url )
779 {
780   if (url && !strncmp(url, "file://", 7)) {
781     char *s = tvh_strdupa(url + 7);
782     http_deescape(s);
783     return access(s, R_OK) == 0;
784   }
785   return 1;
786 }
787 
788 const char *
channel_get_icon(channel_t * ch)789 channel_get_icon ( channel_t *ch )
790 {
791   static char buf[512], buf2[512];
792   idnode_list_mapping_t *ilm;
793   const char *chicon = config.chicon_path,
794              *picon  = config.picon_path,
795              *icon   = ch->ch_icon,
796              *chname, *icn;
797   int id, i, pick, prefer = config.prefer_picon ? 1 : 0;
798   char c;
799 
800   if (tvh_str_default(icon, NULL) == NULL)
801     icon = NULL;
802 
803   /*
804    * Initial lookup - for services with predefined icons (like M3U sources)
805    */
806   if (icon == NULL) {
807     LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
808       if (!(icn = service_get_channel_icon((service_t *)ilm->ilm_in1))) continue;
809       if (strncmp(icn, "picon://", 8) == 0) continue;
810       if (check_file(icn)) {
811         icon = ch->ch_icon = strdup(icn);
812         idnode_changed(&ch->ch_id);
813         goto found;
814       }
815     }
816   }
817 
818   /*
819    * 4 iterations:
820    * 0,1: try channel name or picon
821    * 2,3: force channel name or picon
822    */
823   for (i = 0; icon == NULL && i < 4; i++) {
824 
825     pick = (i ^ prefer) & 1;
826 
827     /* No user icon - try to get the channel icon by name */
828     if (!pick && chicon && chicon[0] >= ' ' && chicon[0] <= 122 &&
829         (chname = channel_get_name(ch)) != NULL && chname[0] &&
830         strcmp(chname, CHANNEL_BLANK_NAME)) {
831       const char *chi, *send, *sname, *s;
832       chi = strdup(chicon);
833 
834       /* Check for and replace placeholders */
835       if ((send = strstr(chi, "%C")) || (send = strstr(chi, "%c"))) {
836         sname = intlconv_utf8safestr(intlconv_charset_id("ASCII", 1, 1),
837                                      chname, strlen(chname) * 2);
838         if (sname == NULL)
839           sname = strdup(chname);
840 
841         /* Remove problematic characters */
842         if (config.chicon_scheme == CHICON_SVCNAME) {
843           s = svcnamepicons(sname);
844           free((char *)sname);
845           sname = s;
846         } else {
847           s = sname;
848           while (s && *s) {
849             c = *s;
850             if (send[1] == 'C' && (c > 122 || strchr(":<>|*?'\"", c) != NULL))
851               *(char *)s = '_';
852             else if (config.chicon_scheme == CHICON_LOWERCASE && c >= 'A' && c <= 'Z')
853               *(char *)s = c - 'A' + 'a';
854             s++;
855           }
856         }
857       } else if ((send = strstr(chi, "%U"))) {
858         sname = strdup(chname);
859 
860         if (config.chicon_scheme == CHICON_LOWERCASE) {
861           utf8_lowercase_inplace((char *)sname);
862         } else if (config.chicon_scheme == CHICON_SVCNAME) {
863           s = svcnamepicons(sname);
864           free((char *)sname);
865           sname = (char *)s;
866         }
867       } else {
868         buf[0] = '\0';
869         sname = NULL;
870       }
871 
872       if (send) {
873         *(char *)send = '\0';
874         send = url_encode(send + 2);
875       }
876 
877       if (sname) {
878         char *aname;
879 
880         for (s = sname; *s == '.'; s++)
881           *(char *)s = '_';
882 
883         for ( ; *s; s++)
884           if (*s == '/' || *s == '\\')
885             *(char *)s = '-';
886           else if (*s < ' ')
887             *(char *)s = '_';
888 
889         aname = url_encode(sname);
890         free((char *)sname);
891         sname = aname;
892       }
893 
894       snprintf(buf, sizeof(buf), "%s%s%s", chi, sname ?: "", send ?: "");
895 
896       free((char *)sname);
897       free((char *)send);
898       free((char *)chi);
899 
900       if (i > 1 || check_file(buf)) {
901         icon = ch->ch_icon = strdup(buf);
902         idnode_changed(&ch->ch_id);
903       }
904     }
905 
906     /* No user icon - try access from services */
907     if (pick && picon) {
908       LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
909         if (!(icn = service_get_channel_icon((service_t *)ilm->ilm_in1))) continue;
910         if (strncmp(icn, "picon://", 8))
911           continue;
912         snprintf(buf2, sizeof(buf2), "%s/%s", picon, icn+8);
913         if (i > 1 || check_file(buf2)) {
914           icon = ch->ch_icon = strdup(icn);
915           idnode_changed(&ch->ch_id);
916           break;
917         }
918       }
919     }
920 
921   }
922 
923 found:
924 
925   /* Nothing */
926   if (!icon || !*icon)
927     return NULL;
928 
929   /* Picon? */
930   if (!strncmp(icon, "picon://", 8)) {
931     if (!picon) return NULL;
932     sprintf(buf2, "%s/%s", picon, icon+8);
933     icon = buf2;
934   }
935 
936   /* Lookup imagecache ID */
937   if ((id = imagecache_get_id(icon))) {
938     snprintf(buf, sizeof(buf), "imagecache/%d", id);
939   } else {
940     strlcpy(buf, icon, sizeof(buf));
941   }
942 
943   return buf;
944 }
945 
channel_set_icon(channel_t * ch,const char * icon)946 int channel_set_icon ( channel_t *ch, const char *icon )
947 {
948   int save = 0;
949   if (!ch || !icon) return 0;
950   if (!ch->ch_icon || strcmp(ch->ch_icon, icon)) {
951     if (ch->ch_icon) free(ch->ch_icon);
952     ch->ch_icon = strdup(icon);
953     save = 1;
954   }
955   return save;
956 }
957 
958 const char *
channel_get_epgid(channel_t * ch)959 channel_get_epgid ( channel_t *ch )
960 {
961   const char *s;
962   idnode_list_mapping_t *ilm;
963   LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link)
964     if ((s = service_get_channel_epgid((service_t *)ilm->ilm_in1)))
965       return s;
966   return channel_get_name(ch);
967 }
968 
969 /* **************************************************************************
970  * Creation/Deletion
971  * *************************************************************************/
972 
973 channel_t *
channel_create0(channel_t * ch,const idclass_t * idc,const char * uuid,htsmsg_t * conf,const char * name)974 channel_create0
975   ( channel_t *ch, const idclass_t *idc, const char *uuid, htsmsg_t *conf,
976     const char *name )
977 {
978   lock_assert(&global_lock);
979 
980   LIST_INIT(&ch->ch_services);
981   LIST_INIT(&ch->ch_subscriptions);
982   LIST_INIT(&ch->ch_epggrab);
983   LIST_INIT(&ch->ch_autorecs);
984   LIST_INIT(&ch->ch_timerecs);
985 
986   if (idnode_insert(&ch->ch_id, uuid, idc, IDNODE_SHORT_UUID)) {
987     if (uuid)
988       tvherror(LS_CHANNEL, "invalid uuid '%s'", uuid);
989     free(ch);
990     return NULL;
991   }
992   if (RB_INSERT_SORTED(&channels, ch, ch_link, ch_id_cmp)) {
993     tvherror(LS_CHANNEL, "id collision!");
994     abort();
995   }
996 
997   /* Defaults */
998   ch->ch_enabled  = 1;
999   ch->ch_autoname = 1;
1000   ch->ch_epgauto  = 1;
1001   ch->ch_epg_running = -1;
1002 
1003   if (conf) {
1004     ch->ch_load = 1;
1005     idnode_load(&ch->ch_id, conf);
1006     ch->ch_load = 0;
1007   }
1008 
1009   /* Override the name */
1010   if (name) {
1011     free(ch->ch_name);
1012     ch->ch_name = strdup(name);
1013   }
1014 
1015   /* EPG */
1016   epggrab_channel_add(ch);
1017 
1018   /* HTSP */
1019   htsp_channel_add(ch);
1020 
1021   /* determine icon URL */
1022   (void)channel_get_icon(ch);
1023 
1024   return ch;
1025 }
1026 
1027 void
channel_delete(channel_t * ch,int delconf)1028 channel_delete ( channel_t *ch, int delconf )
1029 {
1030   th_subscription_t *s;
1031   idnode_list_mapping_t *ilm;
1032   channel_t *ch1, *ch2;
1033   char ubuf[UUID_HEX_SIZE];
1034 
1035   lock_assert(&global_lock);
1036 
1037   idnode_save_check(&ch->ch_id, delconf);
1038 
1039   if (delconf)
1040     tvhinfo(LS_CHANNEL, "%s - deleting", channel_get_name(ch));
1041 
1042   /* Tags */
1043   while((ilm = LIST_FIRST(&ch->ch_ctms)) != NULL)
1044     channel_tag_mapping_destroy(ilm, delconf ? ch : NULL);
1045 
1046   /* DVR */
1047   autorec_destroy_by_channel(ch, delconf);
1048   timerec_destroy_by_channel(ch, delconf);
1049   dvr_destroy_by_channel(ch, delconf);
1050 
1051   /* Services */
1052   while((ilm = LIST_FIRST(&ch->ch_services)) != NULL)
1053     idnode_list_unlink(ilm, delconf ? ch : NULL);
1054 
1055   /* Subscriptions */
1056   while((s = LIST_FIRST(&ch->ch_subscriptions)) != NULL) {
1057     LIST_REMOVE(s, ths_channel_link);
1058     s->ths_channel = NULL;
1059   }
1060 
1061   /* EPG */
1062   epggrab_channel_rem(ch);
1063   epg_channel_unlink(ch);
1064   if (ch->ch_epg_parent) {
1065     LIST_SAFE_REMOVE(ch, ch_epg_slave_link);
1066     free(ch->ch_epg_parent);
1067     ch->ch_epg_parent = NULL;
1068   }
1069   for (ch1 = LIST_FIRST(&ch->ch_epg_slaves); ch1; ch1 = ch2) {
1070     ch2 = LIST_NEXT(ch1, ch_epg_slave_link);
1071     LIST_SAFE_REMOVE(ch1, ch_epg_slave_link);
1072     if (delconf) {
1073       free(ch1->ch_epg_parent);
1074       ch1->ch_epg_parent = NULL;
1075       idnode_changed(&ch1->ch_id);
1076     }
1077   }
1078 
1079   /* HTSP */
1080   htsp_channel_delete(ch);
1081 
1082   /* Settings */
1083   if (delconf)
1084     hts_settings_remove("channel/config/%s", idnode_uuid_as_str(&ch->ch_id, ubuf));
1085 
1086   /* Free memory */
1087   RB_REMOVE(&channels, ch, ch_link);
1088   idnode_unlink(&ch->ch_id);
1089   free(ch->ch_epg_parent);
1090   free(ch->ch_name);
1091   free(ch->ch_icon);
1092   free(ch);
1093 }
1094 
1095 /**
1096  *
1097  */
1098 
channels_memoryinfo_update(memoryinfo_t * my)1099 static void channels_memoryinfo_update(memoryinfo_t *my)
1100 {
1101   channel_t *ch;
1102   int64_t size = 0, count = 0;
1103 
1104   lock_assert(&global_lock);
1105   CHANNEL_FOREACH(ch) {
1106     size += sizeof(*ch);
1107     size += tvh_strlen(ch->ch_epg_parent);
1108     size += tvh_strlen(ch->ch_name);
1109     size += tvh_strlen(ch->ch_icon);
1110     count++;
1111   }
1112   memoryinfo_update(my, size, count);
1113 }
1114 
1115 static memoryinfo_t channels_memoryinfo = {
1116   .my_name = "Channels",
1117   .my_update = channels_memoryinfo_update
1118 };
1119 
1120 /**
1121  *
1122  */
1123 void
channel_init(void)1124 channel_init ( void )
1125 {
1126   htsmsg_t *c, *e;
1127   htsmsg_field_t *f;
1128   channel_t *ch, *parent;
1129   char *s;
1130 
1131   RB_INIT(&channels);
1132   memoryinfo_register(&channels_memoryinfo);
1133   idclass_register(&channel_class);
1134   idclass_register(&channel_tag_class);
1135 
1136   /* Tags */
1137   channel_tag_init();
1138 
1139   /* Channels */
1140   if (!(c = hts_settings_load("channel/config")))
1141     return;
1142 
1143   channel_in_load = 1;
1144   HTSMSG_FOREACH(f, c) {
1145     if (!(e = htsmsg_field_get_map(f))) continue;
1146     (void)channel_create(f->hmf_name, e, NULL);
1147   }
1148   channel_in_load = 0;
1149   htsmsg_destroy(c);
1150 
1151   /* Pair slave EPG, set parent again without channel_in_load */
1152   CHANNEL_FOREACH(ch)
1153     if ((s = ch->ch_epg_parent) != NULL) {
1154       ch->ch_epg_parent = NULL;
1155       channel_class_epg_parent_set_noupdate(ch, s, &parent);
1156       free(s);
1157     }
1158   CHANNEL_FOREACH(ch)
1159     channel_epg_update_all(ch);
1160 }
1161 
1162 /**
1163  *
1164  */
1165 void
channel_done(void)1166 channel_done ( void )
1167 {
1168   channel_t *ch;
1169 
1170   pthread_mutex_lock(&global_lock);
1171   while ((ch = RB_FIRST(&channels)) != NULL)
1172     channel_delete(ch, 0);
1173   memoryinfo_unregister(&channels_memoryinfo);
1174   pthread_mutex_unlock(&global_lock);
1175   channel_tag_done();
1176 }
1177 
1178 /* ***
1179  * Channel tags TODO
1180  */
1181 
1182 /**
1183  *
1184  */
1185 int
channel_tag_map(channel_tag_t * ct,channel_t * ch,void * origin)1186 channel_tag_map(channel_tag_t *ct, channel_t *ch, void *origin)
1187 {
1188   idnode_list_mapping_t *ilm;
1189 
1190   if (ct == NULL || ch == NULL)
1191     return 0;
1192 
1193   ilm = idnode_list_link(&ct->ct_id, &ct->ct_ctms,
1194                          &ch->ch_id, &ch->ch_ctms,
1195                          origin, 2);
1196   if (ilm) {
1197     if(ct->ct_enabled && !ct->ct_internal) {
1198       htsp_tag_update(ct);
1199       htsp_channel_update(ch);
1200     }
1201     return 1;
1202   }
1203   return 0;
1204 }
1205 
1206 
1207 /**
1208  *
1209  */
1210 static void
channel_tag_mapping_destroy(idnode_list_mapping_t * ilm,void * origin)1211 channel_tag_mapping_destroy(idnode_list_mapping_t *ilm, void *origin)
1212 {
1213   channel_tag_t *ct = (channel_tag_t *)ilm->ilm_in1;
1214   channel_t *ch = (channel_t *)ilm->ilm_in2;
1215 
1216   idnode_list_unlink(ilm, origin);
1217 
1218   if(ct->ct_enabled && !ct->ct_internal) {
1219     if(origin == ch)
1220       htsp_tag_update(ct);
1221     if(origin == ct)
1222       htsp_channel_update(ch);
1223   }
1224 }
1225 
1226 /**
1227  *
1228  */
1229 void
channel_tag_unmap(channel_t * ch,void * origin)1230 channel_tag_unmap(channel_t *ch, void *origin)
1231 {
1232   idnode_list_mapping_t *ilm, *n;
1233 
1234   for (ilm = LIST_FIRST(&ch->ch_ctms); ilm != NULL; ilm = n) {
1235     n = LIST_NEXT(ilm, ilm_in2_link);
1236     if ((channel_t *)ilm->ilm_in2 == ch) {
1237       channel_tag_mapping_destroy(ilm, origin);
1238       return;
1239     }
1240   }
1241 }
1242 
1243 /**
1244  *
1245  */
1246 channel_tag_t *
channel_tag_create(const char * uuid,htsmsg_t * conf)1247 channel_tag_create(const char *uuid, htsmsg_t *conf)
1248 {
1249   channel_tag_t *ct;
1250 
1251   ct = calloc(1, sizeof(channel_tag_t));
1252   LIST_INIT(&ct->ct_ctms);
1253   LIST_INIT(&ct->ct_autorecs);
1254   LIST_INIT(&ct->ct_accesses);
1255 
1256   if (idnode_insert(&ct->ct_id, uuid, &channel_tag_class, IDNODE_SHORT_UUID)) {
1257     if (uuid)
1258       tvherror(LS_CHANNEL, "invalid tag uuid '%s'", uuid);
1259     free(ct);
1260     return NULL;
1261   }
1262 
1263   if (conf)
1264     idnode_load(&ct->ct_id, conf);
1265 
1266   /* Defaults */
1267   if (ct->ct_name == NULL)
1268     ct->ct_name = strdup("New tag");
1269   if (ct->ct_comment == NULL)
1270     ct->ct_comment = strdup("");
1271   if (ct->ct_icon == NULL)
1272     ct->ct_icon = strdup("");
1273 
1274   /* HTSP */
1275   htsp_tag_add(ct);
1276 
1277   TAILQ_INSERT_TAIL(&channel_tags, ct, ct_link);
1278   return ct;
1279 }
1280 
1281 /**
1282  *
1283  */
1284 static void
channel_tag_destroy(channel_tag_t * ct,int delconf)1285 channel_tag_destroy(channel_tag_t *ct, int delconf)
1286 {
1287   idnode_list_mapping_t *ilm;
1288   char ubuf[UUID_HEX_SIZE];
1289 
1290   idnode_save_check(&ct->ct_id, delconf);
1291 
1292   while((ilm = LIST_FIRST(&ct->ct_ctms)) != NULL)
1293     channel_tag_mapping_destroy(ilm, delconf ? ilm->ilm_in1 : NULL);
1294 
1295   if (delconf)
1296     hts_settings_remove("channel/tag/%s", idnode_uuid_as_str(&ct->ct_id, ubuf));
1297 
1298   /* HTSP */
1299   htsp_tag_delete(ct);
1300 
1301   TAILQ_REMOVE(&channel_tags, ct, ct_link);
1302   idnode_unlink(&ct->ct_id);
1303 
1304   bouquet_destroy_by_channel_tag(ct);
1305   autorec_destroy_by_channel_tag(ct, delconf);
1306   access_destroy_by_channel_tag(ct, delconf);
1307 
1308   free(ct->ct_name);
1309   free(ct->ct_comment);
1310   free(ct->ct_icon);
1311   free(ct);
1312 }
1313 
1314 /**
1315  *
1316  */
1317 const char *
channel_tag_get_icon(channel_tag_t * ct)1318 channel_tag_get_icon(channel_tag_t *ct)
1319 {
1320   static char buf[64];
1321   const char *icon  = ct->ct_icon;
1322   int id;
1323 
1324   /* Lookup imagecache ID */
1325   if ((id = imagecache_get_id(icon))) {
1326     snprintf(buf, sizeof(buf), "imagecache/%d", id);
1327     return buf;
1328   }
1329   return icon;
1330 }
1331 
1332 /**
1333  * Check if user can access the channel tag
1334  */
1335 int
channel_tag_access(channel_tag_t * ct,access_t * a,int disabled)1336 channel_tag_access(channel_tag_t *ct, access_t *a, int disabled)
1337 {
1338   if (!ct)
1339     return 0;
1340 
1341   if (!disabled && (!ct->ct_enabled || ct->ct_internal))
1342     return 0;
1343 
1344   if (!ct->ct_private)
1345     return 1;
1346 
1347   /* Channel tag check */
1348   if (a->aa_chtags) {
1349     htsmsg_field_t *f;
1350     char ubuf[UUID_HEX_SIZE];
1351     const char *uuid = idnode_uuid_as_str(&ct->ct_id, ubuf);
1352     HTSMSG_FOREACH(f, a->aa_chtags)
1353       if (!strcmp(htsmsg_field_get_str(f) ?: "", uuid))
1354         goto chtags_ok;
1355     return 0;
1356   }
1357 chtags_ok:
1358 
1359   return 1;
1360 }
1361 
1362 /* **************************************************************************
1363  * Channel Tag Class definition
1364  * **************************************************************************/
1365 
1366 static void
channel_tag_class_changed(idnode_t * self)1367 channel_tag_class_changed(idnode_t *self)
1368 {
1369   /* HTSP */
1370   htsp_tag_update((channel_tag_t *)self);
1371 }
1372 
1373 static htsmsg_t *
channel_tag_class_save(idnode_t * self,char * filename,size_t fsize)1374 channel_tag_class_save(idnode_t *self, char *filename, size_t fsize)
1375 {
1376   channel_tag_t *ct = (channel_tag_t *)self;
1377   htsmsg_t *c = htsmsg_create_map();
1378   char ubuf[UUID_HEX_SIZE];
1379   idnode_save(&ct->ct_id, c);
1380   snprintf(filename, fsize, "channel/tag/%s", idnode_uuid_as_str(&ct->ct_id, ubuf));
1381   return c;
1382 }
1383 
1384 static void
channel_tag_class_delete(idnode_t * self)1385 channel_tag_class_delete(idnode_t *self)
1386 {
1387   channel_tag_destroy((channel_tag_t *)self, 1);
1388 }
1389 
1390 static const char *
channel_tag_class_get_title(idnode_t * self,const char * lang)1391 channel_tag_class_get_title (idnode_t *self, const char *lang)
1392 {
1393   channel_tag_t *ct = (channel_tag_t *)self;
1394   return ct->ct_name ?: "";
1395 }
1396 
1397 static void
channel_tag_class_icon_notify(void * obj,const char * lang)1398 channel_tag_class_icon_notify ( void *obj, const char *lang )
1399 {
1400   (void)channel_tag_get_icon(obj);
1401 }
1402 
1403 static const void *
channel_tag_class_get_icon(void * obj)1404 channel_tag_class_get_icon ( void *obj )
1405 {
1406   prop_ptr = channel_tag_get_icon(obj);
1407   return &prop_ptr;
1408 }
1409 
1410 /* exported for others */
1411 htsmsg_t *
channel_tag_class_get_list(void * o,const char * lang)1412 channel_tag_class_get_list(void *o, const char *lang)
1413 {
1414   htsmsg_t *m = htsmsg_create_map();
1415   htsmsg_t *p = htsmsg_create_map();
1416   htsmsg_add_str(m, "type",  "api");
1417   htsmsg_add_str(m, "uri",   "channeltag/list");
1418   htsmsg_add_str(m, "event", "channeltag");
1419   htsmsg_add_u32(p, "all",  1);
1420   htsmsg_add_msg(m, "params", p);
1421   return m;
1422 }
1423 
1424 CLASS_DOC(channeltag)
1425 
1426 const idclass_t channel_tag_class = {
1427   .ic_class      = "channeltag",
1428   .ic_caption    = N_("Channel Tags"),
1429   .ic_doc        = tvh_doc_channeltag_class,
1430   .ic_event      = "channeltag",
1431   .ic_changed    = channel_tag_class_changed,
1432   .ic_save       = channel_tag_class_save,
1433   .ic_get_title  = channel_tag_class_get_title,
1434   .ic_delete     = channel_tag_class_delete,
1435   .ic_properties = (const property_t[]) {
1436     {
1437       .type     = PT_BOOL,
1438       .id       = "enabled",
1439       .name     = N_("Enabled"),
1440       .desc     = N_("Enable/disable the tag."),
1441       .def.i    = 1,
1442       .off      = offsetof(channel_tag_t, ct_enabled),
1443     },
1444     {
1445       .type     = PT_U32,
1446       .id       = "index",
1447       .name     = N_("Sort index"),
1448       .desc     = N_("Sort index."),
1449       .off      = offsetof(channel_tag_t, ct_index),
1450       .opts     = PO_ADVANCED,
1451     },
1452     {
1453       .type     = PT_STR,
1454       .id       = "name",
1455       .name     = N_("Name"),
1456       .desc     = N_("Name of the tag."),
1457       .off      = offsetof(channel_tag_t, ct_name),
1458     },
1459     {
1460       .type     = PT_BOOL,
1461       .id       = "internal",
1462       .name     = N_("Internal"),
1463       .desc     = N_("Use tag internally (don't expose to clients)."),
1464       .off      = offsetof(channel_tag_t, ct_internal),
1465       .opts     = PO_ADVANCED
1466     },
1467     {
1468       .type     = PT_BOOL,
1469       .id       = "private",
1470       .name     = N_("Private"),
1471       .desc     = N_("Only allow users with this tag (or those with "
1472                      "no tags at all) set in "
1473                      "access configuration to use the tag."),
1474       .off      = offsetof(channel_tag_t, ct_private),
1475       .opts     = PO_EXPERT
1476     },
1477     {
1478       .type     = PT_STR,
1479       .id       = "icon",
1480       .name     = N_("Icon (full URL)"),
1481       .desc     = N_("Full path to an icon used to depict the tag. "
1482                      "This can be a TV network logotype, etc."),
1483       .off      = offsetof(channel_tag_t, ct_icon),
1484       .notify   = channel_tag_class_icon_notify,
1485       .opts     = PO_ADVANCED
1486     },
1487     {
1488       .type     = PT_STR,
1489       .id       = "icon_public_url",
1490       .name     = N_("Icon URL"),
1491       .desc     = N_("Relative path to the imagecache copy of the icon."),
1492       .get      = channel_tag_class_get_icon,
1493       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN | PO_EXPERT,
1494     },
1495     {
1496       .type     = PT_BOOL,
1497       .id       = "titled_icon",
1498       .name     = N_("Icon has title"),
1499       .desc     = N_("If set, presentation of the tag icon will not "
1500                      "superimpose the tag name on top of the icon."),
1501       .off      = offsetof(channel_tag_t, ct_titled_icon),
1502       .opts     = PO_EXPERT
1503     },
1504     {
1505       .type     = PT_STR,
1506       .id       = "comment",
1507       .name     = N_("Comment"),
1508       .desc     = N_("Free-form text field, enter whatever you like here."),
1509       .off      = offsetof(channel_tag_t, ct_comment),
1510     },
1511     {}
1512   }
1513 };
1514 
1515 /**
1516  *
1517  */
1518 channel_tag_t *
channel_tag_find_by_name(const char * name,int create)1519 channel_tag_find_by_name(const char *name, int create)
1520 {
1521   channel_tag_t *ct;
1522 
1523   if (tvh_str_default(name, NULL) == NULL)
1524     return NULL;
1525 
1526   TAILQ_FOREACH(ct, &channel_tags, ct_link)
1527     if(!strcasecmp(ct->ct_name, name))
1528       return ct;
1529 
1530   if(!create)
1531     return NULL;
1532 
1533   ct = channel_tag_create(NULL, NULL);
1534   ct->ct_enabled = 1;
1535   tvh_str_update(&ct->ct_name, name);
1536 
1537   idnode_changed(&ct->ct_id);
1538   return ct;
1539 }
1540 
1541 
1542 /**
1543  *
1544  */
1545 channel_tag_t *
channel_tag_find_by_identifier(uint32_t id)1546 channel_tag_find_by_identifier(uint32_t id) {
1547   channel_tag_t *ct;
1548 
1549   TAILQ_FOREACH(ct, &channel_tags, ct_link) {
1550     if(idnode_get_short_uuid(&ct->ct_id) == id)
1551       return ct;
1552   }
1553 
1554   return NULL;
1555 }
1556 
1557 /**
1558  *
1559  */
1560 
channel_tags_memoryinfo_update(memoryinfo_t * my)1561 static void channel_tags_memoryinfo_update(memoryinfo_t *my)
1562 {
1563   channel_tag_t *ct;
1564   int64_t size = 0, count = 0;
1565 
1566   lock_assert(&global_lock);
1567   TAILQ_FOREACH(ct, &channel_tags, ct_link) {
1568     size += sizeof(*ct);
1569     size += tvh_strlen(ct->ct_name);
1570     size += tvh_strlen(ct->ct_comment);
1571     size += tvh_strlen(ct->ct_icon);
1572     count++;
1573   }
1574   memoryinfo_update(my, size, count);
1575 }
1576 
1577 static memoryinfo_t channel_tags_memoryinfo = {
1578   .my_name = "Channel tags",
1579   .my_update = channel_tags_memoryinfo_update
1580 };
1581 
1582 /**
1583  *  Init / Done
1584  */
1585 
1586 static void
channel_tag_init(void)1587 channel_tag_init ( void )
1588 {
1589   htsmsg_t *c, *m;
1590   htsmsg_field_t *f;
1591 
1592   memoryinfo_register(&channel_tags_memoryinfo);
1593   TAILQ_INIT(&channel_tags);
1594   if ((c = hts_settings_load("channel/tag")) != NULL) {
1595     HTSMSG_FOREACH(f, c) {
1596       if (!(m = htsmsg_field_get_map(f))) continue;
1597       (void)channel_tag_create(f->hmf_name, m);
1598     }
1599     htsmsg_destroy(c);
1600   }
1601 }
1602 
1603 static void
channel_tag_done(void)1604 channel_tag_done ( void )
1605 {
1606   channel_tag_t *ct;
1607 
1608   pthread_mutex_lock(&global_lock);
1609   while ((ct = TAILQ_FIRST(&channel_tags)) != NULL)
1610     channel_tag_destroy(ct, 0);
1611   pthread_mutex_unlock(&global_lock);
1612 }
1613