/*
* EPG Grabber - channel functions
* Copyright (C) 2012 Adam Sutton
* Copyright (C) 2015 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "tvheadend.h"
#include "settings.h"
#include "htsmsg.h"
#include "channels.h"
#include "epg.h"
#include "epggrab.h"
#include "epggrab/private.h"
#include
#include
struct epggrab_channel_queue epggrab_channel_entries;
SKEL_DECLARE(epggrab_channel_skel, epggrab_channel_t);
/* **************************************************************************
* EPG Grab Channel functions
* *************************************************************************/
static inline int
is_paired( epggrab_channel_t *ec )
{
return ec->only_one && LIST_FIRST(&ec->channels);
}
static inline int
epggrab_channel_check ( epggrab_channel_t *ec, channel_t *ch )
{
if (!ec || !ch || !ch->ch_epgauto || !ch->ch_enabled)
return 0;
if (ch->ch_epg_parent || !ec->enabled)
return 0;
if (is_paired(ec))
return 0; // ignore already paired
return 1;
}
/* Check if channels match by epgid */
int epggrab_channel_match_epgid ( epggrab_channel_t *ec, channel_t *ch )
{
const char *chid;
if (ec->id == NULL)
return 0;
if (!epggrab_channel_check(ec, ch))
return 0;
chid = channel_get_epgid(ch);
if (chid && !strcmp(ec->id, chid))
return 1;
return 0;
}
/* Check if channels match by name */
int epggrab_channel_match_name ( epggrab_channel_t *ec, channel_t *ch )
{
const char *name, *s;
htsmsg_field_t *f;
if (!epggrab_channel_check(ec, ch))
return 0;
name = channel_get_name(ch);
if (name == NULL)
return 0;
if (ec->name && !strcasecmp(ec->name, name))
return 1;
if (ec->names)
HTSMSG_FOREACH(f, ec->names)
if ((s = htsmsg_field_get_str(f)) != NULL)
if (!strcasecmp(s, name))
return 1;
return 0;
}
/* Check if channels match by number */
int epggrab_channel_match_number ( epggrab_channel_t *ec, channel_t *ch )
{
if (ec->lcn == 0)
return 0;
if (!epggrab_channel_check(ec, ch))
return 0;
if (ec->lcn && ec->lcn == channel_get_number(ch)) return 1;
return 0;
}
/* Delete ilm */
static void
_epgggrab_channel_link_delete(idnode_list_mapping_t *ilm, int delconf)
{
epggrab_channel_t *ec = (epggrab_channel_t *)ilm->ilm_in1;
channel_t *ch = (channel_t *)ilm->ilm_in2;
tvhdebug(ec->mod->subsys, "%s: unlinking %s from %s",
ec->mod->id, ec->id, channel_get_name(ch));
idnode_list_unlink(ilm, delconf ? ec : NULL);
}
/* Destroy */
void
epggrab_channel_link_delete
( epggrab_channel_t *ec, channel_t *ch, int delconf )
{
idnode_list_mapping_t *ilm;
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link)
if (ilm->ilm_in1 == &ec->idnode && ilm->ilm_in2 == &ch->ch_id) {
_epgggrab_channel_link_delete(ilm, delconf);
return;
}
}
/* Destroy all links */
static void epggrab_channel_links_delete( epggrab_channel_t *ec, int delconf )
{
idnode_list_mapping_t *ilm;
while ((ilm = LIST_FIRST(&ec->channels)))
_epgggrab_channel_link_delete(ilm, delconf);
}
/* Do update */
static void
epggrab_channel_sync( epggrab_channel_t *ec, channel_t *ch )
{
int save = 0;
if (ec->update_chname && ec->name && epggrab_conf.channel_rename)
save |= channel_set_name(ch, ec->name);
if (ec->update_chnum && ec->lcn > 0 && epggrab_conf.channel_renumber)
save |= channel_set_number(ch, ec->lcn / CHANNEL_SPLIT, ec->lcn % CHANNEL_SPLIT);
if (ec->update_chicon && ec->icon && epggrab_conf.channel_reicon)
save |= channel_set_icon(ch, ec->icon);
if (save)
idnode_changed(&ch->ch_id);
}
/* Link epggrab channel to real channel */
int
epggrab_channel_link ( epggrab_channel_t *ec, channel_t *ch, void *origin )
{
idnode_list_mapping_t *ilm;
/* No change */
if (!ch || !ch->ch_enabled) return 0;
/* Already linked */
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link)
if (ilm->ilm_in2 == &ch->ch_id) {
ilm->ilm_mark = 0;
epggrab_channel_sync(ec, ch);
return 0;
}
/* New link */
tvhdebug(ec->mod->subsys, "%s: linking %s to %s",
ec->mod->id, ec->id, channel_get_name(ch));
ilm = idnode_list_link(&ec->idnode, &ec->channels,
&ch->ch_id, &ch->ch_epggrab,
origin, 1);
if (ilm == NULL)
return 0;
epggrab_channel_sync(ec, ch);
if (origin == NULL)
idnode_changed(&ec->idnode);
return 1;
}
int
epggrab_channel_map ( idnode_t *ec, idnode_t *ch, void *origin )
{
return epggrab_channel_link((epggrab_channel_t *)ec, (channel_t *)ch, origin);
}
/* Set name */
int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name )
{
idnode_list_mapping_t *ilm;
channel_t *ch;
int save = 0;
if (!ec || !name) return 0;
if (!ec->newnames && (!ec->name || strcmp(ec->name, name))) {
if (ec->name) free(ec->name);
ec->name = strdup(name);
if (epggrab_conf.channel_rename) {
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
ch = (channel_t *)ilm->ilm_in2;
if (channel_set_name(ch, name))
idnode_changed(&ch->ch_id);
}
}
save = 1;
}
if (ec->newnames == NULL)
ec->newnames = htsmsg_create_list();
htsmsg_add_str(ec->newnames, NULL, name);
ec->updated |= save;
return save;
}
/* Set icon */
int epggrab_channel_set_icon ( epggrab_channel_t *ec, const char *icon )
{
idnode_list_mapping_t *ilm;
channel_t *ch;
int save = 0;
if (!ec || !icon) return 0;
if (!ec->icon || strcmp(ec->icon, icon) ) {
if (ec->icon) free(ec->icon);
ec->icon = strdup(icon);
if (epggrab_conf.channel_reicon) {
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
ch = (channel_t *)ilm->ilm_in2;
if (channel_set_icon(ch, icon))
idnode_changed(&ch->ch_id);
}
}
save = 1;
}
ec->updated |= save;
return save;
}
/* Set channel number */
int epggrab_channel_set_number ( epggrab_channel_t *ec, int major, int minor )
{
idnode_list_mapping_t *ilm;
channel_t *ch;
int64_t lcn;
int save = 0;
if (!ec || (major <= 0 && minor <= 0)) return 0;
lcn = (major * CHANNEL_SPLIT) + minor;
if (ec->lcn != lcn) {
ec->lcn = lcn;
if (epggrab_conf.channel_renumber) {
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
ch = (channel_t *)ilm->ilm_in2;
if (channel_set_number(ch,
lcn / CHANNEL_SPLIT,
lcn % CHANNEL_SPLIT))
idnode_changed(&ch->ch_id);
}
}
save = 1;
}
ec->updated |= save;
return save;
}
/* Autolink EPG channel to channel */
static int
epggrab_channel_autolink_one( epggrab_channel_t *ec, channel_t *ch )
{
if (epggrab_channel_match_epgid(ec, ch))
return epggrab_channel_link(ec, ch, NULL);
else if (epggrab_channel_match_name(ec, ch))
return epggrab_channel_link(ec, ch, NULL);
else if (epggrab_channel_match_number(ec, ch))
return epggrab_channel_link(ec, ch, NULL);
return 0;
}
/* Autolink EPG channel to channel */
static int
epggrab_channel_autolink( epggrab_channel_t *ec )
{
channel_t *ch;
CHANNEL_FOREACH(ch)
if (epggrab_channel_match_epgid(ec, ch))
if (epggrab_channel_link(ec, ch, NULL))
return 1;
CHANNEL_FOREACH(ch)
if (epggrab_channel_match_name(ec, ch))
if (epggrab_channel_link(ec, ch, NULL))
return 1;
CHANNEL_FOREACH(ch)
if (epggrab_channel_match_number(ec, ch))
if (epggrab_channel_link(ec, ch, NULL))
return 1;
return 0;
}
/* Channel settings updated */
void epggrab_channel_updated ( epggrab_channel_t *ec )
{
if (!ec) return;
/* Find a link */
if (!is_paired(ec))
epggrab_channel_autolink(ec);
/* Save */
idnode_changed(&ec->idnode);
}
/* ID comparison */
static int _ch_id_cmp ( void *a, void *b )
{
return strcmp(((epggrab_channel_t*)a)->id,
((epggrab_channel_t*)b)->id);
}
/* Create new entry */
epggrab_channel_t *epggrab_channel_create
( epggrab_module_t *owner, htsmsg_t *conf, const char *uuid )
{
epggrab_channel_t *ec;
ec = calloc(1, sizeof(*ec));
if (idnode_insert(&ec->idnode, uuid, &epggrab_channel_class, 0)) {
if (uuid)
tvherror(LS_EPGGRAB, "invalid uuid '%s'", uuid);
free(ec);
return NULL;
}
ec->mod = owner;
ec->enabled = 1;
ec->update_chicon = 1;
ec->update_chnum = 1;
ec->update_chname = 1;
if (conf)
idnode_load(&ec->idnode, conf);
if (ec->id == NULL)
ec->id = strdup("");
TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
if (RB_INSERT_SORTED(&owner->channels, ec, link, _ch_id_cmp)) {
tvherror(LS_EPGGRAB, "removing duplicate channel id '%s' (uuid '%s')", ec->id, uuid);
epggrab_channel_destroy(ec, 1, 0);
return NULL;
}
return ec;
}
/* Find/Create channel in the list */
epggrab_channel_t *epggrab_channel_find
( epggrab_module_t *mod, const char *id, int create, int *save )
{
char *s;
epggrab_channel_t *ec;
if (id == NULL || id[0] == '\0') {
tvhwarn(LS_EPGGRAB, "%s: ignoring empty EPG id source", mod->id);
return NULL;
}
SKEL_ALLOC(epggrab_channel_skel);
s = epggrab_channel_skel->id = tvh_strdupa(id);
/* Replace / with # */
// Note: this is a bit of a nasty fix for #1774, but will do for now
while (*s) {
if (*s == '/') *s = '#';
s++;
}
/* Find */
if (!create) {
ec = RB_FIND(&mod->channels, epggrab_channel_skel, link, _ch_id_cmp);
/* Find/Create */
} else {
ec = RB_INSERT_SORTED(&mod->channels, epggrab_channel_skel, link, _ch_id_cmp);
if (!ec) {
ec = epggrab_channel_skel;
SKEL_USED(epggrab_channel_skel);
ec->enabled = 1;
ec->id = strdup(ec->id);
ec->mod = mod;
TAILQ_INSERT_TAIL(&epggrab_channel_entries, ec, all_link);
if (idnode_insert(&ec->idnode, NULL, &epggrab_channel_class, 0))
abort();
*save = 1;
return ec;
}
}
return ec;
}
void epggrab_channel_destroy( epggrab_channel_t *ec, int delconf, int rb_remove )
{
char ubuf[UUID_HEX_SIZE];
if (ec == NULL) return;
idnode_save_check(&ec->idnode, delconf);
/* Already linked */
epggrab_channel_links_delete(ec, 1);
if (rb_remove)
RB_REMOVE(&ec->mod->channels, ec, link);
TAILQ_REMOVE(&epggrab_channel_entries, ec, all_link);
idnode_unlink(&ec->idnode);
if (delconf)
hts_settings_remove("epggrab/%s/channels/%s",
ec->mod->saveid, idnode_uuid_as_str(&ec->idnode, ubuf));
htsmsg_destroy(ec->newnames);
htsmsg_destroy(ec->names);
free(ec->comment);
free(ec->name);
free(ec->icon);
free(ec->id);
free(ec);
}
void epggrab_channel_flush
( epggrab_module_t *mod, int delconf )
{
epggrab_channel_t *ec;
while ((ec = RB_FIRST(&mod->channels)) != NULL)
epggrab_channel_destroy(ec, delconf, 1);
}
void epggrab_channel_begin_scan ( epggrab_module_t *mod )
{
epggrab_channel_t *ec;
lock_assert(&global_lock);
RB_FOREACH(ec, &mod->channels, link) {
ec->updated = 0;
if (ec->newnames) {
htsmsg_destroy(ec->newnames);
ec->newnames = NULL;
}
}
}
void epggrab_channel_end_scan ( epggrab_module_t *mod )
{
epggrab_channel_t *ec;
lock_assert(&global_lock);
RB_FOREACH(ec, &mod->channels, link) {
if (ec->newnames) {
if (htsmsg_cmp(ec->names, ec->newnames)) {
htsmsg_destroy(ec->names);
ec->names = ec->newnames;
ec->updated = 1;
} else {
htsmsg_destroy(ec->newnames);
}
ec->newnames = NULL;
}
if (ec->updated) {
epggrab_channel_updated(ec);
ec->updated = 0;
}
}
}
/* **************************************************************************
* Global routines
* *************************************************************************/
void epggrab_channel_add ( channel_t *ch )
{
epggrab_module_t *mod;
epggrab_channel_t *ec;
LIST_FOREACH(mod, &epggrab_modules, link)
RB_FOREACH(ec, &mod->channels, link) {
if (!is_paired(ec))
epggrab_channel_autolink_one(ec, ch);
}
}
void epggrab_channel_rem ( channel_t *ch )
{
idnode_list_mapping_t *ilm;
while ((ilm = LIST_FIRST(&ch->ch_epggrab)) != NULL)
idnode_list_unlink(ilm, ch);
}
void epggrab_channel_mod ( channel_t *ch )
{
return epggrab_channel_add(ch);
}
const char *
epggrab_channel_get_id ( epggrab_channel_t *ec )
{
static char buf[1024];
epggrab_module_t *m = ec->mod;
snprintf(buf, sizeof(buf), "%s|%s", m->id, ec->id);
return buf;
}
epggrab_channel_t *
epggrab_channel_find_by_id ( const char *id )
{
char buf[1024];
char *mid, *cid;
epggrab_module_t *mod;
strlcpy(buf, id, sizeof(buf));
if ((mid = strtok_r(buf, "|", &cid)) && cid)
if ((mod = epggrab_module_find_by_id(mid)) != NULL)
return epggrab_channel_find(mod, cid, 0, NULL);
return NULL;
}
int
epggrab_channel_is_ota ( epggrab_channel_t *ec )
{
return ec->mod->type == EPGGRAB_OTA;
}
/*
* Class
*/
static const char *
epggrab_channel_class_get_title(idnode_t *self, const char *lang)
{
epggrab_channel_t *ec = (epggrab_channel_t*)self;
snprintf(prop_sbuf, PROP_SBUF_LEN, "%s: %s (%s)",
ec->name ?: ec->id, ec->id, ec->mod->name);
return prop_sbuf;
}
static htsmsg_t *
epggrab_channel_class_save(idnode_t *self, char *filename, size_t fsize)
{
epggrab_channel_t *ec = (epggrab_channel_t *)self;
htsmsg_t *m = htsmsg_create_map();
char ubuf[UUID_HEX_SIZE];
idnode_save(&ec->idnode, m);
snprintf(filename, fsize, "epggrab/%s/channels/%s",
ec->mod->saveid, idnode_uuid_as_str(&ec->idnode, ubuf));
return m;
}
static void
epggrab_channel_class_delete(idnode_t *self)
{
epggrab_channel_destroy((epggrab_channel_t *)self, 1, 1);
}
static const void *
epggrab_channel_class_modid_get ( void *obj )
{
epggrab_channel_t *ec = obj;
snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ec->mod->id ?: "");
return &prop_sbuf_ptr;
}
static int
epggrab_channel_class_modid_set ( void *obj, const void *p )
{
return 0;
}
static const void *
epggrab_channel_class_module_get ( void *obj )
{
epggrab_channel_t *ec = obj;
snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ec->mod->name ?: "");
return &prop_sbuf_ptr;
}
static const void *
epggrab_channel_class_path_get ( void *obj )
{
epggrab_channel_t *ec = obj;
if (ec->mod->type == EPGGRAB_INT || ec->mod->type == EPGGRAB_EXT)
snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", ((epggrab_module_int_t *)ec->mod)->path ?: "");
else
prop_sbuf[0] = '\0';
return &prop_sbuf_ptr;
}
static const void *
epggrab_channel_class_names_get ( void *obj )
{
epggrab_channel_t *ec = obj;
char *s = ec->names ? htsmsg_list_2_csv(ec->names, ',', 0) : NULL;
snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", s ?: "");
free(s);
return &prop_sbuf_ptr;
}
static int
epggrab_channel_class_names_set ( void *obj, const void *p )
{
htsmsg_t *m = htsmsg_csv_2_list(p, ',');
epggrab_channel_t *ec = obj;
if (htsmsg_cmp(ec->names, m)) {
htsmsg_destroy(ec->names);
ec->names = m;
} else {
htsmsg_destroy(m);
}
return 0;
}
static void
epggrab_channel_class_enabled_notify ( void *obj, const char *lang )
{
epggrab_channel_t *ec = obj;
if (!ec->enabled) {
epggrab_channel_links_delete(ec, 1);
} else {
epggrab_channel_updated(ec);
}
}
static const void *
epggrab_channel_class_channels_get ( void *obj )
{
epggrab_channel_t *ec = obj;
return idnode_list_get1(&ec->channels);
}
static int
epggrab_channel_class_channels_set ( void *obj, const void *p )
{
epggrab_channel_t *ec = obj;
return idnode_list_set1(&ec->idnode, &ec->channels,
&channel_class, (htsmsg_t *)p,
epggrab_channel_map);
}
static char *
epggrab_channel_class_channels_rend ( void *obj, const char *lang )
{
epggrab_channel_t *ec = obj;
return idnode_list_get_csv1(&ec->channels, lang);
}
static idnode_slist_t epggrab_channel_class_update_slist[] = {
{
.id = "update_icon",
.name = N_("Icon"),
.off = offsetof(epggrab_channel_t, update_chicon),
},
{
.id = "update_chnum",
.name = N_("Number"),
.off = offsetof(epggrab_channel_t, update_chnum),
},
{
.id = "update_chname",
.name = N_("Name"),
.off = offsetof(epggrab_channel_t, update_chname),
},
{}
};
static htsmsg_t *
epggrab_channel_class_update_enum ( void *obj, const char *lang )
{
return idnode_slist_enum(obj, epggrab_channel_class_update_slist, lang);
}
static const void *
epggrab_channel_class_update_get ( void *obj )
{
return idnode_slist_get(obj, epggrab_channel_class_update_slist);
}
static char *
epggrab_channel_class_update_rend ( void *obj, const char *lang )
{
return idnode_slist_rend(obj, epggrab_channel_class_update_slist, lang);
}
static int
epggrab_channel_class_update_set ( void *obj, const void *p )
{
return idnode_slist_set(obj, epggrab_channel_class_update_slist, p);
}
static void
epggrab_channel_class_update_notify ( void *obj, const char *lang )
{
epggrab_channel_t *ec = obj;
channel_t *ch;
idnode_list_mapping_t *ilm;
if (!ec->update_chicon && !ec->update_chnum && !ec->update_chname)
return;
LIST_FOREACH(ilm, &ec->channels, ilm_in1_link) {
ch = (channel_t *)ilm->ilm_in2;
epggrab_channel_sync(ec, ch);
}
}
static void
epggrab_channel_class_only_one_notify ( void *obj, const char *lang )
{
epggrab_channel_t *ec = obj;
channel_t *ch, *first = NULL;
idnode_list_mapping_t *ilm1, *ilm2;
if (ec->only_one) {
for(ilm1 = LIST_FIRST(&ec->channels); ilm1; ilm1 = ilm2) {
ilm2 = LIST_NEXT(ilm1, ilm_in1_link);
ch = (channel_t *)ilm1->ilm_in2;
if (!first)
first = ch;
else if (ch->ch_epgauto && first)
idnode_list_unlink(ilm1, ec);
}
} else {
epggrab_channel_updated(ec);
}
}
CLASS_DOC(epggrabber_channel)
const idclass_t epggrab_channel_class = {
.ic_class = "epggrab_channel",
.ic_caption = N_("EPG Grabber Channel"),
.ic_doc = tvh_doc_epggrabber_channel_class,
.ic_event = "epggrab_channel",
.ic_perm_def = ACCESS_ADMIN,
.ic_save = epggrab_channel_class_save,
.ic_get_title = epggrab_channel_class_get_title,
.ic_delete = epggrab_channel_class_delete,
.ic_groups = (const property_group_t[]) {
{
.name = N_("Configuration"),
.number = 1,
},
{}
},
.ic_properties = (const property_t[]){
{
.type = PT_BOOL,
.id = "enabled",
.name = N_("Enabled"),
.desc = N_("Enable/disable EPG data for the entry."),
.off = offsetof(epggrab_channel_t, enabled),
.notify = epggrab_channel_class_enabled_notify,
.group = 1
},
{
.type = PT_STR,
.id = "modid",
.name = N_("Module ID"),
.desc = N_("Module ID used to grab EPG data."),
.get = epggrab_channel_class_modid_get,
.set = epggrab_channel_class_modid_set,
.opts = PO_RDONLY | PO_HIDDEN,
.group = 1
},
{
.type = PT_STR,
.id = "module",
.name = N_("Module"),
.desc = N_("Name of the module used to grab EPG data."),
.get = epggrab_channel_class_module_get,
.opts = PO_RDONLY | PO_NOSAVE,
.group = 1
},
{
.type = PT_STR,
.id = "path",
.name = N_("Path"),
.desc = N_("Data path (if applicable)."),
.get = epggrab_channel_class_path_get,
.opts = PO_RDONLY | PO_NOSAVE,
.group = 1
},
{
.type = PT_TIME,
.id = "updated",
.name = N_("Updated"),
.desc = N_("Date the EPG data was last updated (not set for OTA "
"grabbers)."),
.off = offsetof(epggrab_channel_t, laststamp),
.opts = PO_RDONLY | PO_NOSAVE,
.group = 1
},
{
.type = PT_STR,
.id = "id",
.name = N_("ID"),
.desc = N_("EPG data ID."),
.off = offsetof(epggrab_channel_t, id),
.group = 1
},
{
.type = PT_STR,
.id = "name",
.name = N_("Name"),
.desc = N_("Service name found in EPG data."),
.off = offsetof(epggrab_channel_t, name),
.group = 1
},
{
.type = PT_STR,
.id = "names",
.name = N_("Names"),
.desc = N_("Additional service names found in EPG data."),
.get = epggrab_channel_class_names_get,
.set = epggrab_channel_class_names_set,
.group = 1
},
{
.type = PT_S64,
.intextra = CHANNEL_SPLIT,
.id = "number",
.name = N_("Number"),
.desc = N_("Channel number as defined in EPG data."),
.off = offsetof(epggrab_channel_t, lcn),
.group = 1
},
{
.type = PT_STR,
.id = "icon",
.name = N_("Icon"),
.desc = N_("Channel icon as defined in EPG data."),
.off = offsetof(epggrab_channel_t, icon),
.group = 1
},
{
.type = PT_STR,
.islist = 1,
.id = "channels",
.name = N_("Channels"),
.desc = N_("Channels EPG data is used by."),
.set = epggrab_channel_class_channels_set,
.get = epggrab_channel_class_channels_get,
.list = channel_class_get_list,
.rend = epggrab_channel_class_channels_rend,
.group = 1
},
{
.type = PT_BOOL,
.id = "only_one",
.name = N_("Once per auto channel"),
.desc = N_("Only use this EPG data once when automatically "
"determining what EPG data to set for a channel."),
.off = offsetof(epggrab_channel_t, only_one),
.notify = epggrab_channel_class_only_one_notify,
.group = 1
},
{
.type = PT_INT,
.islist = 1,
.id = "update",
.name = N_("Channel update options"),
.desc = N_("Options used when updating channels."),
.notify = epggrab_channel_class_update_notify,
.list = epggrab_channel_class_update_enum,
.get = epggrab_channel_class_update_get,
.set = epggrab_channel_class_update_set,
.rend = epggrab_channel_class_update_rend,
.opts = PO_ADVANCED
},
{
.type = PT_STR,
.id = "comment",
.name = N_("Comment"),
.desc = N_("Free-form text field, enter whatever you like."),
.off = offsetof(epggrab_channel_t, comment),
.group = 1
},
{}
}
};
/*
*
*/
void
epggrab_channel_init( void )
{
TAILQ_INIT(&epggrab_channel_entries);
idclass_register(&epggrab_channel_class);
}
void
epggrab_channel_done( void )
{
assert(TAILQ_FIRST(&epggrab_channel_entries) == NULL);
SKEL_FREE(epggrab_channel_skel);
}