1 /*
2 * EPG Grabber - channel functions
3 * Copyright (C) 2012 Adam Sutton
4 * Copyright (C) 2015 Jaroslav Kysela
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "tvheadend.h"
21 #include "settings.h"
22 #include "htsmsg.h"
23 #include "channels.h"
24 #include "epg.h"
25 #include "epggrab.h"
26 #include "epggrab/private.h"
27
28 #include <assert.h>
29 #include <string.h>
30
31 struct epggrab_channel_queue epggrab_channel_entries;
32
33 SKEL_DECLARE(epggrab_channel_skel, epggrab_channel_t);
34
35 /* **************************************************************************
36 * EPG Grab Channel functions
37 * *************************************************************************/
38
39 static inline int
is_paired(epggrab_channel_t * ec)40 is_paired( epggrab_channel_t *ec )
41 {
42 return ec->only_one && LIST_FIRST(&ec->channels);
43 }
44
45 static inline int
epggrab_channel_check(epggrab_channel_t * ec,channel_t * ch)46 epggrab_channel_check ( epggrab_channel_t *ec, channel_t *ch )
47 {
48 if (!ec || !ch || !ch->ch_epgauto || !ch->ch_enabled)
49 return 0;
50 if (ch->ch_epg_parent || !ec->enabled)
51 return 0;
52 if (is_paired(ec))
53 return 0; // ignore already paired
54 return 1;
55 }
56
57 /* Check if channels match by epgid */
epggrab_channel_match_epgid(epggrab_channel_t * ec,channel_t * ch)58 int epggrab_channel_match_epgid ( epggrab_channel_t *ec, channel_t *ch )
59 {
60 const char *chid;
61
62 if (ec->id == NULL)
63 return 0;
64 if (!epggrab_channel_check(ec, ch))
65 return 0;
66 chid = channel_get_epgid(ch);
67 if (chid && !strcmp(ec->id, chid))
68 return 1;
69 return 0;
70 }
71
72 /* Check if channels match by name */
epggrab_channel_match_name(epggrab_channel_t * ec,channel_t * ch)73 int epggrab_channel_match_name ( epggrab_channel_t *ec, channel_t *ch )
74 {
75 const char *name, *s;
76 htsmsg_field_t *f;
77
78 if (!epggrab_channel_check(ec, ch))
79 return 0;
80
81 name = channel_get_name(ch);
82 if (name == NULL)
83 return 0;
84
85 if (ec->name && !strcasecmp(ec->name, name))
86 return 1;
87
88 if (ec->names)
89 HTSMSG_FOREACH(f, ec->names)
90 if ((s = htsmsg_field_get_str(f)) != NULL)
91 if (!strcasecmp(s, name))
92 return 1;
93
94 return 0;
95 }
96
97 /* Check if channels match by number */
epggrab_channel_match_number(epggrab_channel_t * ec,channel_t * ch)98 int epggrab_channel_match_number ( epggrab_channel_t *ec, channel_t *ch )
99 {
100 if (ec->lcn == 0)
101 return 0;
102
103 if (!epggrab_channel_check(ec, ch))
104 return 0;
105
106 if (ec->lcn && ec->lcn == channel_get_number(ch)) return 1;
107 return 0;
108 }
109
110 /* Delete ilm */
111 static void
_epgggrab_channel_link_delete(idnode_list_mapping_t * ilm,int delconf)112 _epgggrab_channel_link_delete(idnode_list_mapping_t *ilm, int delconf)
113 {
114 epggrab_channel_t *ec = (epggrab_channel_t *)ilm->ilm_in1;
115 channel_t *ch = (channel_t *)ilm->ilm_in2;
116 tvhdebug(ec->mod->subsys, "%s: unlinking %s from %s",
117 ec->mod->id, ec->id, channel_get_name(ch));
118 idnode_list_unlink(ilm, delconf ? ec : NULL);
119 }
120
121 /* Destroy */
122 void
epggrab_channel_link_delete(epggrab_channel_t * ec,channel_t * ch,int delconf)123 epggrab_channel_link_delete
124 ( epggrab_channel_t *ec, channel_t *ch, int delconf )
125 {
126 idnode_list_mapping_t *ilm;
127 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link)
128 if (ilm->ilm_in1 == &ec->idnode && ilm->ilm_in2 == &ch->ch_id) {
129 _epgggrab_channel_link_delete(ilm, delconf);
130 return;
131 }
132 }
133
134 /* Destroy all links */
epggrab_channel_links_delete(epggrab_channel_t * ec,int delconf)135 static void epggrab_channel_links_delete( epggrab_channel_t *ec, int delconf )
136 {
137 idnode_list_mapping_t *ilm;
138 while ((ilm = LIST_FIRST(&ec->channels)))
139 _epgggrab_channel_link_delete(ilm, delconf);
140 }
141
142 /* Do update */
143 static void
epggrab_channel_sync(epggrab_channel_t * ec,channel_t * ch)144 epggrab_channel_sync( epggrab_channel_t *ec, channel_t *ch )
145 {
146 int save = 0;
147
148 if (ec->update_chname && ec->name && epggrab_conf.channel_rename)
149 save |= channel_set_name(ch, ec->name);
150 if (ec->update_chnum && ec->lcn > 0 && epggrab_conf.channel_renumber)
151 save |= channel_set_number(ch, ec->lcn / CHANNEL_SPLIT, ec->lcn % CHANNEL_SPLIT);
152 if (ec->update_chicon && ec->icon && epggrab_conf.channel_reicon)
153 save |= channel_set_icon(ch, ec->icon);
154 if (save)
155 idnode_changed(&ch->ch_id);
156 }
157
158 /* Link epggrab channel to real channel */
159 int
epggrab_channel_link(epggrab_channel_t * ec,channel_t * ch,void * origin)160 epggrab_channel_link ( epggrab_channel_t *ec, channel_t *ch, void *origin )
161 {
162 idnode_list_mapping_t *ilm;
163
164 /* No change */
165 if (!ch || !ch->ch_enabled) return 0;
166
167 /* Already linked */
168 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link)
169 if (ilm->ilm_in2 == &ch->ch_id) {
170 ilm->ilm_mark = 0;
171 epggrab_channel_sync(ec, ch);
172 return 0;
173 }
174
175 /* New link */
176 tvhdebug(ec->mod->subsys, "%s: linking %s to %s",
177 ec->mod->id, ec->id, channel_get_name(ch));
178
179 ilm = idnode_list_link(&ec->idnode, &ec->channels,
180 &ch->ch_id, &ch->ch_epggrab,
181 origin, 1);
182 if (ilm == NULL)
183 return 0;
184
185 epggrab_channel_sync(ec, ch);
186
187 if (origin == NULL)
188 idnode_changed(&ec->idnode);
189 return 1;
190 }
191
192 int
epggrab_channel_map(idnode_t * ec,idnode_t * ch,void * origin)193 epggrab_channel_map ( idnode_t *ec, idnode_t *ch, void *origin )
194 {
195 return epggrab_channel_link((epggrab_channel_t *)ec, (channel_t *)ch, origin);
196 }
197
198 /* Set name */
epggrab_channel_set_name(epggrab_channel_t * ec,const char * name)199 int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name )
200 {
201 idnode_list_mapping_t *ilm;
202 channel_t *ch;
203 int save = 0;
204 if (!ec || !name) return 0;
205 if (!ec->newnames && (!ec->name || strcmp(ec->name, name))) {
206 if (ec->name) free(ec->name);
207 ec->name = strdup(name);
208 if (epggrab_conf.channel_rename) {
209 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
210 ch = (channel_t *)ilm->ilm_in2;
211 if (channel_set_name(ch, name))
212 idnode_changed(&ch->ch_id);
213 }
214 }
215 save = 1;
216 }
217 if (ec->newnames == NULL)
218 ec->newnames = htsmsg_create_list();
219 htsmsg_add_str(ec->newnames, NULL, name);
220 ec->updated |= save;
221 return save;
222 }
223
224 /* Set icon */
epggrab_channel_set_icon(epggrab_channel_t * ec,const char * icon)225 int epggrab_channel_set_icon ( epggrab_channel_t *ec, const char *icon )
226 {
227 idnode_list_mapping_t *ilm;
228 channel_t *ch;
229 int save = 0;
230 if (!ec || !icon) return 0;
231 if (!ec->icon || strcmp(ec->icon, icon) ) {
232 if (ec->icon) free(ec->icon);
233 ec->icon = strdup(icon);
234 if (epggrab_conf.channel_reicon) {
235 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
236 ch = (channel_t *)ilm->ilm_in2;
237 if (channel_set_icon(ch, icon))
238 idnode_changed(&ch->ch_id);
239 }
240 }
241 save = 1;
242 }
243 ec->updated |= save;
244 return save;
245 }
246
247 /* Set channel number */
epggrab_channel_set_number(epggrab_channel_t * ec,int major,int minor)248 int epggrab_channel_set_number ( epggrab_channel_t *ec, int major, int minor )
249 {
250 idnode_list_mapping_t *ilm;
251 channel_t *ch;
252 int64_t lcn;
253 int save = 0;
254 if (!ec || (major <= 0 && minor <= 0)) return 0;
255 lcn = (major * CHANNEL_SPLIT) + minor;
256 if (ec->lcn != lcn) {
257 ec->lcn = lcn;
258 if (epggrab_conf.channel_renumber) {
259 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
260 ch = (channel_t *)ilm->ilm_in2;
261 if (channel_set_number(ch,
262 lcn / CHANNEL_SPLIT,
263 lcn % CHANNEL_SPLIT))
264 idnode_changed(&ch->ch_id);
265 }
266 }
267 save = 1;
268 }
269 ec->updated |= save;
270 return save;
271 }
272
273 /* Autolink EPG channel to channel */
274 static int
epggrab_channel_autolink_one(epggrab_channel_t * ec,channel_t * ch)275 epggrab_channel_autolink_one( epggrab_channel_t *ec, channel_t *ch )
276 {
277 if (epggrab_channel_match_epgid(ec, ch))
278 return epggrab_channel_link(ec, ch, NULL);
279 else if (epggrab_channel_match_name(ec, ch))
280 return epggrab_channel_link(ec, ch, NULL);
281 else if (epggrab_channel_match_number(ec, ch))
282 return epggrab_channel_link(ec, ch, NULL);
283 return 0;
284 }
285
286 /* Autolink EPG channel to channel */
287 static int
epggrab_channel_autolink(epggrab_channel_t * ec)288 epggrab_channel_autolink( epggrab_channel_t *ec )
289 {
290 channel_t *ch;
291
292 CHANNEL_FOREACH(ch)
293 if (epggrab_channel_match_epgid(ec, ch))
294 if (epggrab_channel_link(ec, ch, NULL))
295 return 1;
296 CHANNEL_FOREACH(ch)
297 if (epggrab_channel_match_name(ec, ch))
298 if (epggrab_channel_link(ec, ch, NULL))
299 return 1;
300 CHANNEL_FOREACH(ch)
301 if (epggrab_channel_match_number(ec, ch))
302 if (epggrab_channel_link(ec, ch, NULL))
303 return 1;
304 return 0;
305 }
306
307 /* Channel settings updated */
epggrab_channel_updated(epggrab_channel_t * ec)308 void epggrab_channel_updated ( epggrab_channel_t *ec )
309 {
310 if (!ec) return;
311
312 /* Find a link */
313 if (!is_paired(ec))
314 epggrab_channel_autolink(ec);
315
316 /* Save */
317 idnode_changed(&ec->idnode);
318 }
319
320 /* ID comparison */
_ch_id_cmp(void * a,void * b)321 static int _ch_id_cmp ( void *a, void *b )
322 {
323 return strcmp(((epggrab_channel_t*)a)->id,
324 ((epggrab_channel_t*)b)->id);
325 }
326
327 /* Create new entry */
epggrab_channel_create(epggrab_module_t * owner,htsmsg_t * conf,const char * uuid)328 epggrab_channel_t *epggrab_channel_create
329 ( epggrab_module_t *owner, htsmsg_t *conf, const char *uuid )
330 {
331 epggrab_channel_t *ec;
332
333 ec = calloc(1, sizeof(*ec));
334 if (idnode_insert(&ec->idnode, uuid, &epggrab_channel_class, 0)) {
335 if (uuid)
336 tvherror(LS_EPGGRAB, "invalid uuid '%s'", uuid);
337 free(ec);
338 return NULL;
339 }
340
341 ec->mod = owner;
342 ec->enabled = 1;
343 ec->update_chicon = 1;
344 ec->update_chnum = 1;
345 ec->update_chname = 1;
346
347 if (conf)
348 idnode_load(&ec->idnode, conf);
349
350 if (ec->id == NULL)
351 ec->id = strdup("");
352
353 TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
354 if (RB_INSERT_SORTED(&owner->channels, ec, link, _ch_id_cmp)) {
355 tvherror(LS_EPGGRAB, "removing duplicate channel id '%s' (uuid '%s')", ec->id, uuid);
356 epggrab_channel_destroy(ec, 1, 0);
357 return NULL;
358 }
359
360 return ec;
361 }
362
363 /* Find/Create channel in the list */
epggrab_channel_find(epggrab_module_t * mod,const char * id,int create,int * save)364 epggrab_channel_t *epggrab_channel_find
365 ( epggrab_module_t *mod, const char *id, int create, int *save )
366 {
367 char *s;
368 epggrab_channel_t *ec;
369
370 if (id == NULL || id[0] == '\0') {
371 tvhwarn(LS_EPGGRAB, "%s: ignoring empty EPG id source", mod->id);
372 return NULL;
373 }
374
375 SKEL_ALLOC(epggrab_channel_skel);
376 s = epggrab_channel_skel->id = tvh_strdupa(id);
377
378 /* Replace / with # */
379 // Note: this is a bit of a nasty fix for #1774, but will do for now
380 while (*s) {
381 if (*s == '/') *s = '#';
382 s++;
383 }
384
385 /* Find */
386 if (!create) {
387 ec = RB_FIND(&mod->channels, epggrab_channel_skel, link, _ch_id_cmp);
388
389 /* Find/Create */
390 } else {
391 ec = RB_INSERT_SORTED(&mod->channels, epggrab_channel_skel, link, _ch_id_cmp);
392 if (!ec) {
393 ec = epggrab_channel_skel;
394 SKEL_USED(epggrab_channel_skel);
395 ec->enabled = 1;
396 ec->id = strdup(ec->id);
397 ec->mod = mod;
398 TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
399
400 if (idnode_insert(&ec->idnode, NULL, &epggrab_channel_class, 0))
401 abort();
402 *save = 1;
403 return ec;
404 }
405 }
406 return ec;
407 }
408
epggrab_channel_destroy(epggrab_channel_t * ec,int delconf,int rb_remove)409 void epggrab_channel_destroy( epggrab_channel_t *ec, int delconf, int rb_remove )
410 {
411 char ubuf[UUID_HEX_SIZE];
412
413 if (ec == NULL) return;
414
415 idnode_save_check(&ec->idnode, delconf);
416
417 /* Already linked */
418 epggrab_channel_links_delete(ec, 1);
419 if (rb_remove)
420 RB_REMOVE(&ec->mod->channels, ec, link);
421 TAILQ_REMOVE(&epggrab_channel_entries, ec, all_link);
422 idnode_unlink(&ec->idnode);
423
424 if (delconf)
425 hts_settings_remove("epggrab/%s/channels/%s",
426 ec->mod->saveid, idnode_uuid_as_str(&ec->idnode, ubuf));
427
428 htsmsg_destroy(ec->newnames);
429 htsmsg_destroy(ec->names);
430 free(ec->comment);
431 free(ec->name);
432 free(ec->icon);
433 free(ec->id);
434 free(ec);
435 }
436
epggrab_channel_flush(epggrab_module_t * mod,int delconf)437 void epggrab_channel_flush
438 ( epggrab_module_t *mod, int delconf )
439 {
440 epggrab_channel_t *ec;
441 while ((ec = RB_FIRST(&mod->channels)) != NULL)
442 epggrab_channel_destroy(ec, delconf, 1);
443 }
444
epggrab_channel_begin_scan(epggrab_module_t * mod)445 void epggrab_channel_begin_scan ( epggrab_module_t *mod )
446 {
447 epggrab_channel_t *ec;
448 lock_assert(&global_lock);
449 RB_FOREACH(ec, &mod->channels, link) {
450 ec->updated = 0;
451 if (ec->newnames) {
452 htsmsg_destroy(ec->newnames);
453 ec->newnames = NULL;
454 }
455 }
456 }
457
epggrab_channel_end_scan(epggrab_module_t * mod)458 void epggrab_channel_end_scan ( epggrab_module_t *mod )
459 {
460 epggrab_channel_t *ec;
461 lock_assert(&global_lock);
462 RB_FOREACH(ec, &mod->channels, link) {
463 if (ec->newnames) {
464 if (htsmsg_cmp(ec->names, ec->newnames)) {
465 htsmsg_destroy(ec->names);
466 ec->names = ec->newnames;
467 ec->updated = 1;
468 } else {
469 htsmsg_destroy(ec->newnames);
470 }
471 ec->newnames = NULL;
472 }
473 if (ec->updated) {
474 epggrab_channel_updated(ec);
475 ec->updated = 0;
476 }
477 }
478 }
479
480 /* **************************************************************************
481 * Global routines
482 * *************************************************************************/
483
epggrab_channel_add(channel_t * ch)484 void epggrab_channel_add ( channel_t *ch )
485 {
486 epggrab_module_t *mod;
487 epggrab_channel_t *ec;
488
489 LIST_FOREACH(mod, &epggrab_modules, link)
490 RB_FOREACH(ec, &mod->channels, link) {
491 if (!is_paired(ec))
492 epggrab_channel_autolink_one(ec, ch);
493 }
494 }
495
epggrab_channel_rem(channel_t * ch)496 void epggrab_channel_rem ( channel_t *ch )
497 {
498 idnode_list_mapping_t *ilm;
499
500 while ((ilm = LIST_FIRST(&ch->ch_epggrab)) != NULL)
501 idnode_list_unlink(ilm, ch);
502 }
503
epggrab_channel_mod(channel_t * ch)504 void epggrab_channel_mod ( channel_t *ch )
505 {
506 return epggrab_channel_add(ch);
507 }
508
509 const char *
epggrab_channel_get_id(epggrab_channel_t * ec)510 epggrab_channel_get_id ( epggrab_channel_t *ec )
511 {
512 static char buf[1024];
513 epggrab_module_t *m = ec->mod;
514 snprintf(buf, sizeof(buf), "%s|%s", m->id, ec->id);
515 return buf;
516 }
517
518 epggrab_channel_t *
epggrab_channel_find_by_id(const char * id)519 epggrab_channel_find_by_id ( const char *id )
520 {
521 char buf[1024];
522 char *mid, *cid;
523 epggrab_module_t *mod;
524 strlcpy(buf, id, sizeof(buf));
525 if ((mid = strtok_r(buf, "|", &cid)) && cid)
526 if ((mod = epggrab_module_find_by_id(mid)) != NULL)
527 return epggrab_channel_find(mod, cid, 0, NULL);
528 return NULL;
529 }
530
531 int
epggrab_channel_is_ota(epggrab_channel_t * ec)532 epggrab_channel_is_ota ( epggrab_channel_t *ec )
533 {
534 return ec->mod->type == EPGGRAB_OTA;
535 }
536
537 /*
538 * Class
539 */
540
541 static const char *
epggrab_channel_class_get_title(idnode_t * self,const char * lang)542 epggrab_channel_class_get_title(idnode_t *self, const char *lang)
543 {
544 epggrab_channel_t *ec = (epggrab_channel_t*)self;
545
546 snprintf(prop_sbuf, PROP_SBUF_LEN, "%s: %s (%s)",
547 ec->name ?: ec->id, ec->id, ec->mod->name);
548 return prop_sbuf;
549 }
550
551 static htsmsg_t *
epggrab_channel_class_save(idnode_t * self,char * filename,size_t fsize)552 epggrab_channel_class_save(idnode_t *self, char *filename, size_t fsize)
553 {
554 epggrab_channel_t *ec = (epggrab_channel_t *)self;
555 htsmsg_t *m = htsmsg_create_map();
556 char ubuf[UUID_HEX_SIZE];
557 idnode_save(&ec->idnode, m);
558 snprintf(filename, fsize, "epggrab/%s/channels/%s",
559 ec->mod->saveid, idnode_uuid_as_str(&ec->idnode, ubuf));
560 return m;
561 }
562
563 static void
epggrab_channel_class_delete(idnode_t * self)564 epggrab_channel_class_delete(idnode_t *self)
565 {
566 epggrab_channel_destroy((epggrab_channel_t *)self, 1, 1);
567 }
568
569 static const void *
epggrab_channel_class_modid_get(void * obj)570 epggrab_channel_class_modid_get ( void *obj )
571 {
572 epggrab_channel_t *ec = obj;
573 snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ec->mod->id ?: "");
574 return &prop_sbuf_ptr;
575 }
576
577 static int
epggrab_channel_class_modid_set(void * obj,const void * p)578 epggrab_channel_class_modid_set ( void *obj, const void *p )
579 {
580 return 0;
581 }
582
583 static const void *
epggrab_channel_class_module_get(void * obj)584 epggrab_channel_class_module_get ( void *obj )
585 {
586 epggrab_channel_t *ec = obj;
587 snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ec->mod->name ?: "");
588 return &prop_sbuf_ptr;
589 }
590
591 static const void *
epggrab_channel_class_path_get(void * obj)592 epggrab_channel_class_path_get ( void *obj )
593 {
594 epggrab_channel_t *ec = obj;
595 if (ec->mod->type == EPGGRAB_INT || ec->mod->type == EPGGRAB_EXT)
596 snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ((epggrab_module_int_t *)ec->mod)->path ?: "");
597 else
598 prop_sbuf[0] = '\0';
599 return &prop_sbuf_ptr;
600 }
601
602 static const void *
epggrab_channel_class_names_get(void * obj)603 epggrab_channel_class_names_get ( void *obj )
604 {
605 epggrab_channel_t *ec = obj;
606 char *s = ec->names ? htsmsg_list_2_csv(ec->names, ',', 0) : NULL;
607 snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", s ?: "");
608 free(s);
609 return &prop_sbuf_ptr;
610 }
611
612 static int
epggrab_channel_class_names_set(void * obj,const void * p)613 epggrab_channel_class_names_set ( void *obj, const void *p )
614 {
615 htsmsg_t *m = htsmsg_csv_2_list(p, ',');
616 epggrab_channel_t *ec = obj;
617 if (htsmsg_cmp(ec->names, m)) {
618 htsmsg_destroy(ec->names);
619 ec->names = m;
620 } else {
621 htsmsg_destroy(m);
622 }
623 return 0;
624 }
625
626 static void
epggrab_channel_class_enabled_notify(void * obj,const char * lang)627 epggrab_channel_class_enabled_notify ( void *obj, const char *lang )
628 {
629 epggrab_channel_t *ec = obj;
630 if (!ec->enabled) {
631 epggrab_channel_links_delete(ec, 1);
632 } else {
633 epggrab_channel_updated(ec);
634 }
635 }
636
637 static const void *
epggrab_channel_class_channels_get(void * obj)638 epggrab_channel_class_channels_get ( void *obj )
639 {
640 epggrab_channel_t *ec = obj;
641 return idnode_list_get1(&ec->channels);
642 }
643
644 static int
epggrab_channel_class_channels_set(void * obj,const void * p)645 epggrab_channel_class_channels_set ( void *obj, const void *p )
646 {
647 epggrab_channel_t *ec = obj;
648 return idnode_list_set1(&ec->idnode, &ec->channels,
649 &channel_class, (htsmsg_t *)p,
650 epggrab_channel_map);
651 }
652
653 static char *
epggrab_channel_class_channels_rend(void * obj,const char * lang)654 epggrab_channel_class_channels_rend ( void *obj, const char *lang )
655 {
656 epggrab_channel_t *ec = obj;
657 return idnode_list_get_csv1(&ec->channels, lang);
658 }
659
660 static idnode_slist_t epggrab_channel_class_update_slist[] = {
661 {
662 .id = "update_icon",
663 .name = N_("Icon"),
664 .off = offsetof(epggrab_channel_t, update_chicon),
665 },
666 {
667 .id = "update_chnum",
668 .name = N_("Number"),
669 .off = offsetof(epggrab_channel_t, update_chnum),
670 },
671 {
672 .id = "update_chname",
673 .name = N_("Name"),
674 .off = offsetof(epggrab_channel_t, update_chname),
675 },
676 {}
677 };
678
679 static htsmsg_t *
epggrab_channel_class_update_enum(void * obj,const char * lang)680 epggrab_channel_class_update_enum ( void *obj, const char *lang )
681 {
682 return idnode_slist_enum(obj, epggrab_channel_class_update_slist, lang);
683 }
684
685 static const void *
epggrab_channel_class_update_get(void * obj)686 epggrab_channel_class_update_get ( void *obj )
687 {
688 return idnode_slist_get(obj, epggrab_channel_class_update_slist);
689 }
690
691 static char *
epggrab_channel_class_update_rend(void * obj,const char * lang)692 epggrab_channel_class_update_rend ( void *obj, const char *lang )
693 {
694 return idnode_slist_rend(obj, epggrab_channel_class_update_slist, lang);
695 }
696
697 static int
epggrab_channel_class_update_set(void * obj,const void * p)698 epggrab_channel_class_update_set ( void *obj, const void *p )
699 {
700 return idnode_slist_set(obj, epggrab_channel_class_update_slist, p);
701 }
702
703 static void
epggrab_channel_class_update_notify(void * obj,const char * lang)704 epggrab_channel_class_update_notify ( void *obj, const char *lang )
705 {
706 epggrab_channel_t *ec = obj;
707 channel_t *ch;
708 idnode_list_mapping_t *ilm;
709
710 if (!ec->update_chicon && !ec->update_chnum && !ec->update_chname)
711 return;
712 LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
713 ch = (channel_t *)ilm->ilm_in2;
714 epggrab_channel_sync(ec, ch);
715 }
716 }
717
718 static void
epggrab_channel_class_only_one_notify(void * obj,const char * lang)719 epggrab_channel_class_only_one_notify ( void *obj, const char *lang )
720 {
721 epggrab_channel_t *ec = obj;
722 channel_t *ch, *first = NULL;
723 idnode_list_mapping_t *ilm1, *ilm2;
724 if (ec->only_one) {
725 for(ilm1 = LIST_FIRST(&ec->channels); ilm1; ilm1 = ilm2) {
726 ilm2 = LIST_NEXT(ilm1, ilm_in1_link);
727 ch = (channel_t *)ilm1->ilm_in2;
728 if (!first)
729 first = ch;
730 else if (ch->ch_epgauto && first)
731 idnode_list_unlink(ilm1, ec);
732 }
733 } else {
734 epggrab_channel_updated(ec);
735 }
736 }
737
738 CLASS_DOC(epggrabber_channel)
739
740 const idclass_t epggrab_channel_class = {
741 .ic_class = "epggrab_channel",
742 .ic_caption = N_("EPG Grabber Channel"),
743 .ic_doc = tvh_doc_epggrabber_channel_class,
744 .ic_event = "epggrab_channel",
745 .ic_perm_def = ACCESS_ADMIN,
746 .ic_save = epggrab_channel_class_save,
747 .ic_get_title = epggrab_channel_class_get_title,
748 .ic_delete = epggrab_channel_class_delete,
749 .ic_groups = (const property_group_t[]) {
750 {
751 .name = N_("Configuration"),
752 .number = 1,
753 },
754 {}
755 },
756 .ic_properties = (const property_t[]){
757 {
758 .type = PT_BOOL,
759 .id = "enabled",
760 .name = N_("Enabled"),
761 .desc = N_("Enable/disable EPG data for the entry."),
762 .off = offsetof(epggrab_channel_t, enabled),
763 .notify = epggrab_channel_class_enabled_notify,
764 .group = 1
765 },
766 {
767 .type = PT_STR,
768 .id = "modid",
769 .name = N_("Module ID"),
770 .desc = N_("Module ID used to grab EPG data."),
771 .get = epggrab_channel_class_modid_get,
772 .set = epggrab_channel_class_modid_set,
773 .opts = PO_RDONLY | PO_HIDDEN,
774 .group = 1
775 },
776 {
777 .type = PT_STR,
778 .id = "module",
779 .name = N_("Module"),
780 .desc = N_("Name of the module used to grab EPG data."),
781 .get = epggrab_channel_class_module_get,
782 .opts = PO_RDONLY | PO_NOSAVE,
783 .group = 1
784 },
785 {
786 .type = PT_STR,
787 .id = "path",
788 .name = N_("Path"),
789 .desc = N_("Data path (if applicable)."),
790 .get = epggrab_channel_class_path_get,
791 .opts = PO_RDONLY | PO_NOSAVE,
792 .group = 1
793 },
794 {
795 .type = PT_TIME,
796 .id = "updated",
797 .name = N_("Updated"),
798 .desc = N_("Date the EPG data was last updated (not set for OTA "
799 "grabbers)."),
800 .off = offsetof(epggrab_channel_t, laststamp),
801 .opts = PO_RDONLY | PO_NOSAVE,
802 .group = 1
803 },
804 {
805 .type = PT_STR,
806 .id = "id",
807 .name = N_("ID"),
808 .desc = N_("EPG data ID."),
809 .off = offsetof(epggrab_channel_t, id),
810 .group = 1
811 },
812 {
813 .type = PT_STR,
814 .id = "name",
815 .name = N_("Name"),
816 .desc = N_("Service name found in EPG data."),
817 .off = offsetof(epggrab_channel_t, name),
818 .group = 1
819 },
820 {
821 .type = PT_STR,
822 .id = "names",
823 .name = N_("Names"),
824 .desc = N_("Additional service names found in EPG data."),
825 .get = epggrab_channel_class_names_get,
826 .set = epggrab_channel_class_names_set,
827 .group = 1
828 },
829 {
830 .type = PT_S64,
831 .intextra = CHANNEL_SPLIT,
832 .id = "number",
833 .name = N_("Number"),
834 .desc = N_("Channel number as defined in EPG data."),
835 .off = offsetof(epggrab_channel_t, lcn),
836 .group = 1
837 },
838 {
839 .type = PT_STR,
840 .id = "icon",
841 .name = N_("Icon"),
842 .desc = N_("Channel icon as defined in EPG data."),
843 .off = offsetof(epggrab_channel_t, icon),
844 .group = 1
845 },
846 {
847 .type = PT_STR,
848 .islist = 1,
849 .id = "channels",
850 .name = N_("Channels"),
851 .desc = N_("Channels EPG data is used by."),
852 .set = epggrab_channel_class_channels_set,
853 .get = epggrab_channel_class_channels_get,
854 .list = channel_class_get_list,
855 .rend = epggrab_channel_class_channels_rend,
856 .group = 1
857 },
858 {
859 .type = PT_BOOL,
860 .id = "only_one",
861 .name = N_("Once per auto channel"),
862 .desc = N_("Only use this EPG data once when automatically "
863 "determining what EPG data to set for a channel."),
864 .off = offsetof(epggrab_channel_t, only_one),
865 .notify = epggrab_channel_class_only_one_notify,
866 .group = 1
867 },
868 {
869 .type = PT_INT,
870 .islist = 1,
871 .id = "update",
872 .name = N_("Channel update options"),
873 .desc = N_("Options used when updating channels."),
874 .notify = epggrab_channel_class_update_notify,
875 .list = epggrab_channel_class_update_enum,
876 .get = epggrab_channel_class_update_get,
877 .set = epggrab_channel_class_update_set,
878 .rend = epggrab_channel_class_update_rend,
879 .opts = PO_ADVANCED
880 },
881 {
882 .type = PT_STR,
883 .id = "comment",
884 .name = N_("Comment"),
885 .desc = N_("Free-form text field, enter whatever you like."),
886 .off = offsetof(epggrab_channel_t, comment),
887 .group = 1
888 },
889 {}
890 }
891 };
892
893 /*
894 *
895 */
896 void
epggrab_channel_init(void)897 epggrab_channel_init( void )
898 {
899 TAILQ_INIT(&epggrab_channel_entries);
900 idclass_register(&epggrab_channel_class);
901 }
902
903 void
epggrab_channel_done(void)904 epggrab_channel_done( void )
905 {
906 assert(TAILQ_FIRST(&epggrab_channel_entries) == NULL);
907 SKEL_FREE(epggrab_channel_skel);
908 }
909