982 lines
22 KiB
C
982 lines
22 KiB
C
/*
|
|
* tvheadend, channel functions
|
|
* Copyright (C) 2007 Andreas Öman
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "settings.h"
|
|
|
|
#include "tvheadend.h"
|
|
#include "epg.h"
|
|
#include "epggrab.h"
|
|
#include "channels.h"
|
|
#include "dtable.h"
|
|
#include "notify.h"
|
|
#include "dvr/dvr.h"
|
|
#include "htsp_server.h"
|
|
#include "imagecache.h"
|
|
#include "service_mapper.h"
|
|
#include "htsbuf.h"
|
|
|
|
struct channel_tree channels;
|
|
|
|
struct channel_tag_queue channel_tags;
|
|
|
|
static void channel_tag_init ( void );
|
|
static void channel_tag_done ( void );
|
|
static void channel_tag_mapping_destroy(channel_tag_mapping_t *ctm,
|
|
int flags);
|
|
static void channel_tag_destroy(channel_tag_t *ct, int delconf);
|
|
|
|
|
|
#define CTM_DESTROY_UPDATE_TAG 0x1
|
|
#define CTM_DESTROY_UPDATE_CHANNEL 0x2
|
|
|
|
static int
|
|
ch_id_cmp ( channel_t *a, channel_t *b )
|
|
{
|
|
return channel_get_id(a) - channel_get_id(b);
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Class definition
|
|
* *************************************************************************/
|
|
|
|
static void
|
|
channel_class_save ( idnode_t *self )
|
|
{
|
|
channel_save((channel_t*)self);
|
|
}
|
|
|
|
static void
|
|
channel_class_delete ( idnode_t *self )
|
|
{
|
|
channel_delete((channel_t*)self, 1);
|
|
}
|
|
|
|
static const void *
|
|
channel_class_services_get ( void *obj )
|
|
{
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
channel_t *ch = obj;
|
|
channel_service_mapping_t *csm;
|
|
|
|
/* Add all */
|
|
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
|
|
htsmsg_add_str(l, NULL, idnode_uuid_as_str(&csm->csm_svc->s_id));
|
|
|
|
return l;
|
|
}
|
|
|
|
static char *
|
|
channel_class_services_rend ( void *obj )
|
|
{
|
|
char *str;
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
channel_t *ch = obj;
|
|
channel_service_mapping_t *csm;
|
|
|
|
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
|
|
htsmsg_add_str(l, NULL, idnode_get_title(&csm->csm_svc->s_id) ?: "");
|
|
|
|
str = htsmsg_list_2_csv(l);
|
|
htsmsg_destroy(l);
|
|
return str;
|
|
}
|
|
|
|
static int
|
|
channel_class_services_set ( void *obj, const void *p )
|
|
{
|
|
return channel_set_services_by_list(obj, (htsmsg_t*)p);
|
|
}
|
|
|
|
static htsmsg_t *
|
|
channel_class_services_enum ( void *obj )
|
|
{
|
|
htsmsg_t *e, *m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "type", "api");
|
|
htsmsg_add_str(m, "uri", "service/list");
|
|
htsmsg_add_str(m, "event", "service");
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_bool(e, "enum", 1);
|
|
htsmsg_add_msg(m, "params", e);
|
|
return m;
|
|
}
|
|
|
|
static const void *
|
|
channel_class_tags_get ( void *obj )
|
|
{
|
|
channel_tag_mapping_t *ctm;
|
|
channel_t *ch = obj;
|
|
htsmsg_t *m = htsmsg_create_list();
|
|
|
|
/* Add all */
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link)
|
|
htsmsg_add_str(m, NULL, idnode_uuid_as_str(&ctm->ctm_tag->ct_id));
|
|
|
|
return m;
|
|
}
|
|
|
|
static char *
|
|
channel_class_tags_rend ( void *obj )
|
|
{
|
|
char *str;
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
channel_t *ch = obj;
|
|
channel_tag_mapping_t *ctm;
|
|
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link)
|
|
htsmsg_add_str(l, NULL, ctm->ctm_tag->ct_name);
|
|
|
|
str = htsmsg_list_2_csv(l);
|
|
htsmsg_destroy(l);
|
|
return str;
|
|
}
|
|
|
|
static int
|
|
channel_class_tags_set ( void *obj, const void *p )
|
|
{
|
|
return channel_set_tags_by_list(obj, (htsmsg_t*)p);
|
|
}
|
|
|
|
static void
|
|
channel_class_icon_notify ( void *obj )
|
|
{
|
|
channel_t *ch = obj;
|
|
if (ch->ch_icon)
|
|
imagecache_get_id(ch->ch_icon);
|
|
}
|
|
|
|
static const void *
|
|
channel_class_get_imagecache ( void *obj )
|
|
{
|
|
static char buf[512], *r;
|
|
uint32_t id;
|
|
channel_t *ch = obj;
|
|
|
|
if (!ch->ch_icon) {
|
|
r = NULL;
|
|
} else if ((id = imagecache_get_id(ch->ch_icon))) {
|
|
snprintf(buf, sizeof(buf), "imagecache/%d", id);
|
|
r = buf;
|
|
} else {
|
|
strncpy(buf, ch->ch_icon, sizeof(buf));
|
|
r = buf;
|
|
}
|
|
|
|
return &r;
|
|
}
|
|
|
|
static const char *
|
|
channel_class_get_title ( idnode_t *self )
|
|
{
|
|
return channel_get_name((channel_t*)self);
|
|
}
|
|
|
|
/* exported for others */
|
|
htsmsg_t *
|
|
channel_class_get_list(void *o)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "type", "api");
|
|
htsmsg_add_str(m, "uri", "channel/list");
|
|
htsmsg_add_str(m, "event", "channel");
|
|
return m;
|
|
}
|
|
|
|
static const void *
|
|
channel_class_get_name ( void *p )
|
|
{
|
|
static const char *s;
|
|
s = channel_get_name(p);
|
|
return &s;
|
|
}
|
|
|
|
static const void *
|
|
channel_class_get_number ( void *p )
|
|
{
|
|
static int64_t i;
|
|
i = channel_get_number(p);
|
|
return &i;
|
|
}
|
|
|
|
static const void *
|
|
channel_class_epggrab_get ( void *o )
|
|
{
|
|
channel_t *ch = o;
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
epggrab_channel_link_t *ecl;
|
|
LIST_FOREACH(ecl, &ch->ch_epggrab, ecl_chn_link) {
|
|
if (!epggrab_channel_is_ota(ecl->ecl_epggrab))
|
|
htsmsg_add_str(l, NULL, epggrab_channel_get_id(ecl->ecl_epggrab));
|
|
}
|
|
return l;
|
|
}
|
|
|
|
static int
|
|
channel_class_epggrab_set ( void *o, const void *v )
|
|
{
|
|
int save = 0;
|
|
channel_t *ch = o;
|
|
htsmsg_t *l = (htsmsg_t*)v;
|
|
htsmsg_field_t *f;
|
|
epggrab_channel_t *ec;
|
|
epggrab_channel_link_t *ecl, *n;
|
|
|
|
/* mark for deletion */
|
|
LIST_FOREACH(ecl, &ch->ch_epggrab, ecl_chn_link) {
|
|
if (!epggrab_channel_is_ota(ecl->ecl_epggrab))
|
|
ecl->ecl_mark = 1;
|
|
}
|
|
|
|
/* Link */
|
|
HTSMSG_FOREACH(f, l) {
|
|
if ((ec = epggrab_channel_find_by_id(htsmsg_field_get_str(f))))
|
|
save |= epggrab_channel_link(ec, ch);
|
|
}
|
|
|
|
/* Delete */
|
|
for (ecl = LIST_FIRST(&ch->ch_epggrab); ecl != NULL; ecl = n) {
|
|
n = LIST_NEXT(ecl, ecl_chn_link);
|
|
if (ecl->ecl_mark) {
|
|
epggrab_channel_link_delete(ecl);
|
|
save = 1;
|
|
}
|
|
}
|
|
return save;
|
|
}
|
|
|
|
static htsmsg_t *
|
|
channel_class_epggrab_list ( void *o )
|
|
{
|
|
htsmsg_t *e, *m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "type", "api");
|
|
htsmsg_add_str(m, "uri", "epggrab/channel/list");
|
|
htsmsg_add_str(m, "event", "epggrabchannel");
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_bool(e, "enum", 1);
|
|
htsmsg_add_msg(m, "params", e);
|
|
return m;
|
|
}
|
|
|
|
const idclass_t channel_class = {
|
|
.ic_class = "channel",
|
|
.ic_caption = "Channel",
|
|
.ic_event = "channel",
|
|
.ic_save = channel_class_save,
|
|
.ic_get_title = channel_class_get_title,
|
|
.ic_delete = channel_class_delete,
|
|
.ic_properties = (const property_t[]){
|
|
#if 0
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "enabled",
|
|
.name = "Enabled",
|
|
.off = offsetof(channel_t, ch_enabled),
|
|
},
|
|
#endif
|
|
{
|
|
.type = PT_STR,
|
|
.id = "name",
|
|
.name = "Name",
|
|
.off = offsetof(channel_t, ch_name),
|
|
.get = channel_class_get_name,
|
|
},
|
|
{
|
|
.type = PT_S64,
|
|
.intsplit = CHANNEL_SPLIT,
|
|
.id = "number",
|
|
.name = "Number",
|
|
.off = offsetof(channel_t, ch_number),
|
|
.get = channel_class_get_number,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "icon",
|
|
.name = "Icon",
|
|
.off = offsetof(channel_t, ch_icon),
|
|
.notify = channel_class_icon_notify,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "icon_public_url",
|
|
.name = "Icon URL",
|
|
.get = channel_class_get_imagecache,
|
|
.opts = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.islist = 1,
|
|
.id = "epggrab",
|
|
.name = "EPG Source",
|
|
.set = channel_class_epggrab_set,
|
|
.get = channel_class_epggrab_get,
|
|
.list = channel_class_epggrab_list,
|
|
.opts = PO_NOSAVE,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "dvr_pre_time",
|
|
.name = "DVR Pre", // TODO: better text?
|
|
.off = offsetof(channel_t, ch_dvr_extra_time_pre),
|
|
.opts = PO_ADVANCED
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "dvr_pst_time",
|
|
.name = "DVR Post", // TODO: better text?
|
|
.off = offsetof(channel_t, ch_dvr_extra_time_post),
|
|
.opts = PO_ADVANCED
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.islist = 1,
|
|
.id = "services",
|
|
.name = "Services",
|
|
.get = channel_class_services_get,
|
|
.set = channel_class_services_set,
|
|
.list = channel_class_services_enum,
|
|
.rend = channel_class_services_rend,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.islist = 1,
|
|
.id = "tags",
|
|
.name = "Tags",
|
|
.get = channel_class_tags_get,
|
|
.set = channel_class_tags_set,
|
|
.list = channel_tag_class_get_list,
|
|
.rend = channel_class_tags_rend
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
/* **************************************************************************
|
|
* Find
|
|
* *************************************************************************/
|
|
|
|
// Note: since channel names are no longer unique this method will simply
|
|
// return the first entry encountered, so could be somewhat random
|
|
channel_t *
|
|
channel_find_by_name ( const char *name )
|
|
{
|
|
channel_t *ch;
|
|
CHANNEL_FOREACH(ch)
|
|
if (!strcmp(channel_get_name(ch), name))
|
|
break;
|
|
return ch;
|
|
}
|
|
|
|
channel_t *
|
|
channel_find_by_id ( uint32_t i )
|
|
{
|
|
channel_t skel;
|
|
memcpy(skel.ch_id.in_uuid, &i, sizeof(i));
|
|
|
|
return RB_FIND(&channels, &skel, ch_link, ch_id_cmp);
|
|
}
|
|
|
|
channel_t *
|
|
channel_find_by_number ( int no )
|
|
{
|
|
channel_t *ch;
|
|
CHANNEL_FOREACH(ch)
|
|
if(channel_get_number(ch) == no)
|
|
break;
|
|
return ch;
|
|
}
|
|
|
|
/**
|
|
* Check if user can access the channel
|
|
*/
|
|
int
|
|
channel_access(channel_t *ch, access_t *a, const char *username)
|
|
{
|
|
/* Channel number check */
|
|
if (ch && (a->aa_chmin || a->aa_chmax)) {
|
|
int chnum = channel_get_number(ch);
|
|
if (chnum < a->aa_chmin || chnum > a->aa_chmax)
|
|
return 0;
|
|
}
|
|
|
|
/* Channel tag check */
|
|
if (ch && a->aa_chtags) {
|
|
channel_tag_mapping_t *ctm;
|
|
htsmsg_field_t *f;
|
|
HTSMSG_FOREACH(f, a->aa_chtags) {
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
|
|
if (!strcmp(htsmsg_field_get_str(f) ?: "",
|
|
idnode_uuid_as_str(&ctm->ctm_tag->ct_id)))
|
|
goto chtags_ok;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
chtags_ok:
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Property updating
|
|
* *************************************************************************/
|
|
|
|
int
|
|
channel_set_services_by_list ( channel_t *ch, htsmsg_t *svcs )
|
|
{
|
|
int save = 0;
|
|
const char *str;
|
|
service_t *svc;
|
|
htsmsg_field_t *f;
|
|
channel_service_mapping_t *csm;
|
|
|
|
/* Mark all for deletion */
|
|
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
|
|
csm->csm_mark = 1;
|
|
|
|
/* Link */
|
|
HTSMSG_FOREACH(f, svcs) {
|
|
if ((str = htsmsg_field_get_str(f)))
|
|
if ((svc = service_find(str)))
|
|
save |= service_mapper_link(svc, ch, ch);
|
|
}
|
|
|
|
/* Remove */
|
|
save |= service_mapper_clean(NULL, ch, ch);
|
|
|
|
return save;
|
|
}
|
|
|
|
int
|
|
channel_set_tags_by_list ( channel_t *ch, htsmsg_t *tags )
|
|
{
|
|
int save = 0;
|
|
const char *uuid;
|
|
channel_tag_mapping_t *ctm, *n;
|
|
channel_tag_t *ct;
|
|
htsmsg_field_t *f;
|
|
|
|
/* Mark for deletion */
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link)
|
|
ctm->ctm_mark = 1;
|
|
|
|
/* Link */
|
|
HTSMSG_FOREACH(f, tags)
|
|
if ((uuid = htsmsg_field_get_str(f)) != NULL) {
|
|
if ((ct = channel_tag_find_by_uuid(uuid)))
|
|
save |= channel_tag_map(ch, ct);
|
|
}
|
|
|
|
/* Remove */
|
|
for (ctm = LIST_FIRST(&ch->ch_ctms); ctm != NULL; ctm = n) {
|
|
n = LIST_NEXT(ctm, ctm_channel_link);
|
|
if (ctm->ctm_mark) {
|
|
LIST_REMOVE(ctm, ctm_channel_link);
|
|
LIST_REMOVE(ctm, ctm_tag_link);
|
|
free(ctm);
|
|
save = 1;
|
|
}
|
|
}
|
|
|
|
return save;
|
|
}
|
|
|
|
const char *
|
|
channel_get_name ( channel_t *ch )
|
|
{
|
|
static const char *blank = "";
|
|
const char *s;
|
|
channel_service_mapping_t *csm;
|
|
if (ch->ch_name && *ch->ch_name) return ch->ch_name;
|
|
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
|
|
if ((s = service_get_channel_name(csm->csm_svc)))
|
|
return s;
|
|
return blank;
|
|
}
|
|
|
|
int64_t
|
|
channel_get_number ( channel_t *ch )
|
|
{
|
|
int n;
|
|
channel_service_mapping_t *csm;
|
|
if (ch->ch_number) return ch->ch_number;
|
|
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
|
|
if ((n = service_get_channel_number(csm->csm_svc)))
|
|
return n;
|
|
return 0;
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Creation/Deletion
|
|
* *************************************************************************/
|
|
|
|
channel_t *
|
|
channel_create0
|
|
( channel_t *ch, const idclass_t *idc, const char *uuid, htsmsg_t *conf,
|
|
const char *name )
|
|
{
|
|
lock_assert(&global_lock);
|
|
|
|
LIST_INIT(&ch->ch_services);
|
|
LIST_INIT(&ch->ch_subscriptions);
|
|
LIST_INIT(&ch->ch_epggrab);
|
|
LIST_INIT(&ch->ch_autorecs);
|
|
LIST_INIT(&ch->ch_timerecs);
|
|
|
|
if (idnode_insert(&ch->ch_id, uuid, idc, IDNODE_SHORT_UUID)) {
|
|
if (uuid)
|
|
tvherror("channel", "invalid uuid '%s'", uuid);
|
|
free(ch);
|
|
return NULL;
|
|
}
|
|
if (RB_INSERT_SORTED(&channels, ch, ch_link, ch_id_cmp)) {
|
|
tvherror("channel", "id collision!");
|
|
abort();
|
|
}
|
|
|
|
if (conf)
|
|
idnode_load(&ch->ch_id, conf);
|
|
|
|
/* Override the name */
|
|
if (name) {
|
|
free(ch->ch_name);
|
|
ch->ch_name = strdup(name);
|
|
}
|
|
|
|
/* EPG */
|
|
epggrab_channel_add(ch);
|
|
|
|
/* HTSP */
|
|
htsp_channel_add(ch);
|
|
|
|
return ch;
|
|
}
|
|
|
|
void
|
|
channel_delete ( channel_t *ch, int delconf )
|
|
{
|
|
th_subscription_t *s;
|
|
channel_tag_mapping_t *ctm;
|
|
channel_service_mapping_t *csm;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
if (delconf)
|
|
tvhinfo("channel", "%s - deleting", channel_get_name(ch));
|
|
|
|
/* Tags */
|
|
while((ctm = LIST_FIRST(&ch->ch_ctms)) != NULL)
|
|
channel_tag_mapping_destroy(ctm, CTM_DESTROY_UPDATE_TAG);
|
|
|
|
/* DVR */
|
|
autorec_destroy_by_channel(ch, delconf);
|
|
timerec_destroy_by_channel(ch, delconf);
|
|
dvr_destroy_by_channel(ch, delconf);
|
|
|
|
/* Services */
|
|
while((csm = LIST_FIRST(&ch->ch_services)) != NULL)
|
|
service_mapper_unlink(csm->csm_svc, ch, ch);
|
|
|
|
/* Subscriptions */
|
|
while((s = LIST_FIRST(&ch->ch_subscriptions)) != NULL) {
|
|
LIST_REMOVE(s, ths_channel_link);
|
|
s->ths_channel = NULL;
|
|
}
|
|
|
|
/* EPG */
|
|
epggrab_channel_rem(ch);
|
|
epg_channel_unlink(ch);
|
|
|
|
/* HTSP */
|
|
htsp_channel_delete(ch);
|
|
|
|
/* Settings */
|
|
if (delconf)
|
|
hts_settings_remove("channel/config/%s", idnode_uuid_as_str(&ch->ch_id));
|
|
|
|
/* Free memory */
|
|
RB_REMOVE(&channels, ch, ch_link);
|
|
idnode_unlink(&ch->ch_id);
|
|
free(ch->ch_name);
|
|
free(ch->ch_icon);
|
|
free(ch);
|
|
}
|
|
|
|
/*
|
|
* Save
|
|
*/
|
|
void
|
|
channel_save ( channel_t *ch )
|
|
{
|
|
htsmsg_t *c = htsmsg_create_map();
|
|
idnode_save(&ch->ch_id, c);
|
|
hts_settings_save(c, "channel/config/%s", idnode_uuid_as_str(&ch->ch_id));
|
|
htsmsg_destroy(c);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
channel_init ( void )
|
|
{
|
|
htsmsg_t *c, *e;
|
|
htsmsg_field_t *f;
|
|
RB_INIT(&channels);
|
|
|
|
/* Tags */
|
|
channel_tag_init();
|
|
|
|
/* Channels */
|
|
if (!(c = hts_settings_load("channel/config")))
|
|
return;
|
|
|
|
HTSMSG_FOREACH(f, c) {
|
|
if (!(e = htsmsg_field_get_map(f))) continue;
|
|
(void)channel_create(f->hmf_name, e, NULL);
|
|
}
|
|
htsmsg_destroy(c);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
channel_done ( void )
|
|
{
|
|
channel_t *ch;
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
while ((ch = RB_FIRST(&channels)) != NULL)
|
|
channel_delete(ch, 0);
|
|
pthread_mutex_unlock(&global_lock);
|
|
channel_tag_done();
|
|
}
|
|
|
|
/* ***
|
|
* Channel tags TODO
|
|
*/
|
|
|
|
/**
|
|
*
|
|
*/
|
|
int
|
|
channel_tag_map(channel_t *ch, channel_tag_t *ct)
|
|
{
|
|
channel_tag_mapping_t *ctm;
|
|
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link)
|
|
if(ctm->ctm_tag == ct)
|
|
break;
|
|
if (!ctm)
|
|
LIST_FOREACH(ctm, &ct->ct_ctms, ctm_tag_link)
|
|
if(ctm->ctm_channel == ch)
|
|
break;
|
|
|
|
if (ctm) {
|
|
ctm->ctm_mark = 0;
|
|
return 0;
|
|
}
|
|
|
|
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link)
|
|
assert(ctm->ctm_tag != ct);
|
|
|
|
LIST_FOREACH(ctm, &ct->ct_ctms, ctm_tag_link)
|
|
assert(ctm->ctm_channel != ch);
|
|
|
|
ctm = malloc(sizeof(channel_tag_mapping_t));
|
|
|
|
ctm->ctm_channel = ch;
|
|
LIST_INSERT_HEAD(&ch->ch_ctms, ctm, ctm_channel_link);
|
|
|
|
ctm->ctm_tag = ct;
|
|
LIST_INSERT_HEAD(&ct->ct_ctms, ctm, ctm_tag_link);
|
|
|
|
ctm->ctm_mark = 0;
|
|
|
|
if(ct->ct_enabled && !ct->ct_internal) {
|
|
htsp_tag_update(ct);
|
|
htsp_channel_update(ch);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
channel_tag_mapping_destroy(channel_tag_mapping_t *ctm, int flags)
|
|
{
|
|
channel_tag_t *ct = ctm->ctm_tag;
|
|
channel_t *ch = ctm->ctm_channel;
|
|
|
|
LIST_REMOVE(ctm, ctm_channel_link);
|
|
LIST_REMOVE(ctm, ctm_tag_link);
|
|
free(ctm);
|
|
|
|
if(ct->ct_enabled && !ct->ct_internal) {
|
|
if(flags & CTM_DESTROY_UPDATE_TAG)
|
|
htsp_tag_update(ct);
|
|
if(flags & CTM_DESTROY_UPDATE_CHANNEL)
|
|
htsp_channel_update(ch);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
channel_tag_t *
|
|
channel_tag_create(const char *uuid, htsmsg_t *conf)
|
|
{
|
|
channel_tag_t *ct;
|
|
|
|
ct = calloc(1, sizeof(channel_tag_t));
|
|
LIST_INIT(&ct->ct_ctms);
|
|
LIST_INIT(&ct->ct_autorecs);
|
|
LIST_INIT(&ct->ct_accesses);
|
|
|
|
if (idnode_insert(&ct->ct_id, uuid, &channel_tag_class, IDNODE_SHORT_UUID)) {
|
|
if (uuid)
|
|
tvherror("channel", "invalid tag uuid '%s'", uuid);
|
|
free(ct);
|
|
return NULL;
|
|
}
|
|
|
|
if (conf)
|
|
idnode_load(&ct->ct_id, conf);
|
|
|
|
if (ct->ct_name == NULL)
|
|
ct->ct_name = strdup("New tag");
|
|
if (ct->ct_comment == NULL)
|
|
ct->ct_comment = strdup("");
|
|
if (ct->ct_icon == NULL)
|
|
ct->ct_icon = strdup("");
|
|
|
|
TAILQ_INSERT_TAIL(&channel_tags, ct, ct_link);
|
|
return ct;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
channel_tag_destroy(channel_tag_t *ct, int delconf)
|
|
{
|
|
channel_tag_mapping_t *ctm;
|
|
channel_t *ch;
|
|
|
|
if (delconf) {
|
|
while((ctm = LIST_FIRST(&ct->ct_ctms)) != NULL) {
|
|
ch = ctm->ctm_channel;
|
|
channel_tag_mapping_destroy(ctm, CTM_DESTROY_UPDATE_CHANNEL);
|
|
channel_save(ch);
|
|
}
|
|
hts_settings_remove("channel/tag/%s", idnode_uuid_as_str(&ct->ct_id));
|
|
}
|
|
|
|
if(ct->ct_enabled && !ct->ct_internal)
|
|
htsp_tag_delete(ct);
|
|
|
|
TAILQ_REMOVE(&channel_tags, ct, ct_link);
|
|
idnode_unlink(&ct->ct_id);
|
|
|
|
autorec_destroy_by_channel_tag(ct, delconf);
|
|
access_destroy_by_channel_tag(ct, delconf);
|
|
|
|
free(ct->ct_name);
|
|
free(ct->ct_comment);
|
|
free(ct->ct_icon);
|
|
free(ct);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
channel_tag_save(channel_tag_t *ct)
|
|
{
|
|
htsmsg_t *c = htsmsg_create_map();
|
|
idnode_save(&ct->ct_id, c);
|
|
hts_settings_save(c, "channel/tag/%s", idnode_uuid_as_str(&ct->ct_id));
|
|
htsmsg_destroy(c);
|
|
}
|
|
|
|
|
|
/* **************************************************************************
|
|
* Channel Tag Class definition
|
|
* **************************************************************************/
|
|
|
|
static void
|
|
channel_tag_class_save(idnode_t *self)
|
|
{
|
|
channel_tag_save((channel_tag_t *)self);
|
|
}
|
|
|
|
static void
|
|
channel_tag_class_delete(idnode_t *self)
|
|
{
|
|
channel_tag_destroy((channel_tag_t *)self, 1);
|
|
}
|
|
|
|
static const char *
|
|
channel_tag_class_get_title (idnode_t *self)
|
|
{
|
|
channel_tag_t *ct = (channel_tag_t *)self;
|
|
return ct->ct_name ?: "";
|
|
}
|
|
|
|
/* exported for others */
|
|
htsmsg_t *
|
|
channel_tag_class_get_list(void *o)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "type", "api");
|
|
htsmsg_add_str(m, "uri", "channeltag/list");
|
|
htsmsg_add_str(m, "event", "channeltag");
|
|
return m;
|
|
}
|
|
|
|
const idclass_t channel_tag_class = {
|
|
.ic_class = "channeltag",
|
|
.ic_caption = "Channel Tag",
|
|
.ic_event = "channeltag",
|
|
.ic_save = channel_tag_class_save,
|
|
.ic_get_title = channel_tag_class_get_title,
|
|
.ic_delete = channel_tag_class_delete,
|
|
.ic_properties = (const property_t[]) {
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "enabled",
|
|
.name = "Enabled",
|
|
.off = offsetof(channel_tag_t, ct_enabled),
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "name",
|
|
.name = "Name",
|
|
.off = offsetof(channel_tag_t, ct_name),
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "internal",
|
|
.name = "Internal",
|
|
.off = offsetof(channel_tag_t, ct_internal),
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "icon",
|
|
.name = "Icon (full URL)",
|
|
.off = offsetof(channel_tag_t, ct_icon),
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "titled_icon",
|
|
.name = "Icon has title",
|
|
.off = offsetof(channel_tag_t, ct_titled_icon),
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "comment",
|
|
.name = "Comment",
|
|
.off = offsetof(channel_tag_t, ct_comment),
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
channel_tag_t *
|
|
channel_tag_find_by_name(const char *name, int create)
|
|
{
|
|
channel_tag_t *ct;
|
|
|
|
TAILQ_FOREACH(ct, &channel_tags, ct_link)
|
|
if(!strcasecmp(ct->ct_name, name))
|
|
return ct;
|
|
|
|
if(!create)
|
|
return NULL;
|
|
|
|
ct = channel_tag_create(NULL, NULL);
|
|
ct->ct_enabled = 1;
|
|
tvh_str_update(&ct->ct_name, name);
|
|
|
|
channel_tag_save(ct);
|
|
return ct;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
channel_tag_t *
|
|
channel_tag_find_by_identifier(uint32_t id) {
|
|
channel_tag_t *ct;
|
|
|
|
TAILQ_FOREACH(ct, &channel_tags, ct_link) {
|
|
if(idnode_get_short_uuid(&ct->ct_id) == id)
|
|
return ct;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Init / Done
|
|
*/
|
|
|
|
static void
|
|
channel_tag_init ( void )
|
|
{
|
|
htsmsg_t *c, *m;
|
|
htsmsg_field_t *f;
|
|
|
|
TAILQ_INIT(&channel_tags);
|
|
if ((c = hts_settings_load("channel/tag")) != NULL) {
|
|
HTSMSG_FOREACH(f, c) {
|
|
if (!(m = htsmsg_field_get_map(f))) continue;
|
|
(void)channel_tag_create(f->hmf_name, m);
|
|
}
|
|
htsmsg_destroy(c);
|
|
}
|
|
}
|
|
|
|
static void
|
|
channel_tag_done ( void )
|
|
{
|
|
channel_tag_t *ct;
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
while ((ct = TAILQ_FIRST(&channel_tags)) != NULL)
|
|
channel_tag_destroy(ct, 0);
|
|
pthread_mutex_unlock(&global_lock);
|
|
}
|