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