tvheadend/src/idnode.c
2014-12-05 19:18:17 +01:00

1467 lines
32 KiB
C

/*
* Tvheadend - idnode (class) system
*
* Copyright (C) 2013 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/>.
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "idnode.h"
#include "notify.h"
#include "settings.h"
#include "uuid.h"
#include "access.h"
static const idnodes_rb_t * idnode_domain ( const idclass_t *idc );
static void idclass_root_register ( idnode_t *in );
typedef struct idclass_link
{
const idclass_t *idc;
idnodes_rb_t nodes;
RB_ENTRY(idclass_link) link;
} idclass_link_t;
static idnodes_rb_t idnodes;
static RB_HEAD(,idclass_link) idclasses;
static RB_HEAD(,idclass_link) idrootclasses;
static pthread_cond_t idnode_cond;
static pthread_mutex_t idnode_mutex;
static htsmsg_t *idnode_queue;
static void* idnode_thread(void* p);
SKEL_DECLARE(idclasses_skel, idclass_link_t);
/* **************************************************************************
* Utilities
* *************************************************************************/
/**
*
*/
static int
in_cmp(const idnode_t *a, const idnode_t *b)
{
return memcmp(a->in_uuid, b->in_uuid, sizeof(a->in_uuid));
}
static int
ic_cmp ( const idclass_link_t *a, const idclass_link_t *b )
{
assert(a->idc->ic_class);
assert(b->idc->ic_class);
return strcmp(a->idc->ic_class, b->idc->ic_class);
}
/* **************************************************************************
* Registration
* *************************************************************************/
/**
*
*/
pthread_t idnode_tid;
void
idnode_init(void)
{
idnode_queue = NULL;
RB_INIT(&idnodes);
RB_INIT(&idclasses);
RB_INIT(&idrootclasses);
pthread_mutex_init(&idnode_mutex, NULL);
pthread_cond_init(&idnode_cond, NULL);
tvhthread_create(&idnode_tid, NULL, idnode_thread, NULL);
}
void
idnode_done(void)
{
idclass_link_t *il;
pthread_cond_signal(&idnode_cond);
pthread_join(idnode_tid, NULL);
pthread_mutex_lock(&idnode_mutex);
htsmsg_destroy(idnode_queue);
idnode_queue = NULL;
pthread_mutex_unlock(&idnode_mutex);
while ((il = RB_FIRST(&idclasses)) != NULL) {
RB_REMOVE(&idclasses, il, link);
free(il);
}
while ((il = RB_FIRST(&idrootclasses)) != NULL) {
RB_REMOVE(&idrootclasses, il, link);
free(il);
}
SKEL_FREE(idclasses_skel);
}
static const idclass_t *
idnode_root_class(const idclass_t *idc)
{
while (idc && idc->ic_super)
idc = idc->ic_super;
return idc;
}
/**
*
*/
int
idnode_insert(idnode_t *in, const char *uuid, const idclass_t *class, int flags)
{
idnode_t *c;
tvh_uuid_t u;
int retries = 5;
uint32_t u32;
const idclass_t *idc;;
lock_assert(&global_lock);
in->in_class = class;
do {
if (uuid_init_bin(&u, uuid)) {
in->in_class = NULL;
return -1;
}
memcpy(in->in_uuid, u.bin, sizeof(in->in_uuid));
c = NULL;
if (flags & IDNODE_SHORT_UUID) {
u32 = idnode_get_short_uuid(in);
idc = idnode_root_class(in->in_class);
RB_FOREACH(c, &idnodes, in_link) {
if (idc != idnode_root_class(c->in_class))
continue;
if (idnode_get_short_uuid(c) == u32)
break;
}
}
if (c == NULL)
c = RB_INSERT_SORTED(&idnodes, in, in_link, in_cmp);
} while (c != NULL && --retries > 0);
if(c != NULL) {
fprintf(stderr, "Id node collision (%s) %s\n",
uuid, (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
abort();
}
tvhtrace("idnode", "insert node %s", idnode_uuid_as_str(in));
/* Register the class */
idclass_register(class); // Note: we never actually unregister
idclass_root_register(in);
assert(in->in_domain);
c = RB_INSERT_SORTED(in->in_domain, in, in_domain_link, in_cmp);
assert(c == NULL);
/* Fire event */
idnode_notify_simple(in);
return 0;
}
/**
*
*/
void
idnode_unlink(idnode_t *in)
{
lock_assert(&global_lock);
RB_REMOVE(&idnodes, in, in_link);
RB_REMOVE(in->in_domain, in, in_domain_link);
tvhtrace("idnode", "unlink node %s", idnode_uuid_as_str(in));
idnode_notify_simple(in);
}
/**
*
*/
static void
idnode_handler(size_t off, idnode_t *in)
{
void (**fcn)(idnode_t *);
lock_assert(&global_lock);
const idclass_t *idc = in->in_class;
while (idc) {
fcn = (void *)idc + off;
if (*fcn) {
(*fcn)(in);
break;
}
idc = idc->ic_super;
}
}
void
idnode_delete(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_delete), in);
}
void
idnode_moveup(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_moveup), in);
}
void
idnode_movedown(idnode_t *in)
{
return idnode_handler(offsetof(idclass_t, ic_movedown), in);
}
/* **************************************************************************
* Info
* *************************************************************************/
uint32_t
idnode_get_short_uuid (const idnode_t *in)
{
uint32_t u32;
memcpy(&u32, in->in_uuid, sizeof(u32));
return u32 & 0x7FFFFFFF; // compat needs to be +ve signed
}
/**
*
*/
const char *
idnode_uuid_as_str(const idnode_t *in)
{
static tvh_uuid_t __thread ret[16];
static uint8_t p = 0;
bin2hex(ret[p].hex, sizeof(ret[p].hex), in->in_uuid, sizeof(in->in_uuid));
const char *s = ret[p].hex;
p = (p + 1) % 16;
return s;
}
/**
*
*/
const char *
idnode_get_title(idnode_t *in)
{
const idclass_t *ic = in->in_class;
for(; ic != NULL; ic = ic->ic_super) {
if(ic->ic_get_title != NULL)
return ic->ic_get_title(in);
}
return idnode_uuid_as_str(in);
}
/**
*
*/
idnode_set_t *
idnode_get_childs(idnode_t *in)
{
if(in == NULL)
return NULL;
const idclass_t *ic = in->in_class;
for(; ic != NULL; ic = ic->ic_super) {
if(ic->ic_get_childs != NULL)
return ic->ic_get_childs(in);
}
return NULL;
}
/**
*
*/
int
idnode_is_leaf(idnode_t *in)
{
const idclass_t *ic = in->in_class;
for(; ic != NULL; ic = ic->ic_super) {
if(ic->ic_get_childs != NULL)
return 0;
}
return 1;
}
int
idnode_is_instance(idnode_t *in, const idclass_t *idc)
{
const idclass_t *ic = in->in_class;
for(; ic != NULL; ic = ic->ic_super) {
if (ic == idc) return 1;
}
return 0;
}
/* **************************************************************************
* Properties
* *************************************************************************/
static const property_t *
idnode_find_prop
( idnode_t *self, const char *key )
{
const idclass_t *idc = self->in_class;
const property_t *p;
while (idc) {
if ((p = prop_find(idc->ic_properties, key))) return p;
idc = idc->ic_super;
}
return NULL;
}
/*
* Get display value
*/
static char *
idnode_get_display
( idnode_t *self, const property_t *p )
{
if (p) {
if (p->rend)
return p->rend(self);
else if (p->islist) {
htsmsg_t *l = (htsmsg_t*)p->get(self);
if (l)
return htsmsg_list_2_csv(l);
} else if (p->list) {
htsmsg_t *l = p->list(self), *m;
htsmsg_field_t *f;
uint32_t k, v;
char *r = NULL;
const char *s;
if (l && !idnode_get_u32(self, p->id, &v))
HTSMSG_FOREACH(f, l) {
m = htsmsg_field_get_map(f);
if (!htsmsg_get_u32(m, "key", &k) &&
(s = htsmsg_get_str(m, "val")) != NULL &&
v == k) {
r = strdup(s);
break;
}
}
htsmsg_destroy(l);
return r;
}
}
return NULL;
}
/*
* Get field as string
*/
const char *
idnode_get_str
( idnode_t *self, const char *key )
{
const property_t *p = idnode_find_prop(self, key);
if (p && p->type == PT_STR) {
const void *ptr;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
return *(const char**)ptr;
}
return NULL;
}
/*
* Get field as unsigned int
*/
int
idnode_get_u32
( idnode_t *self, const char *key, uint32_t *u32 )
{
const property_t *p = idnode_find_prop(self, key);
if (p) {
const void *ptr;
if (p->islist)
return 1;
else if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_INT:
case PT_BOOL:
*u32 = *(int*)ptr;
return 0;
case PT_U16:
*u32 = *(uint16_t*)ptr;
return 0;
case PT_U32:
*u32 = *(uint32_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as signed 64-bit int
*/
int
idnode_get_s64
( idnode_t *self, const char *key, int64_t *s64 )
{
const property_t *p = idnode_find_prop(self, key);
if (p) {
const void *ptr;
if (p->islist)
return 1;
else if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_INT:
case PT_BOOL:
*s64 = *(int*)ptr;
return 0;
case PT_U16:
*s64 = *(uint16_t*)ptr;
return 0;
case PT_U32:
*s64 = *(uint32_t*)ptr;
return 0;
case PT_S64:
*s64 = *(int64_t*)ptr;
return 0;
case PT_DBL:
*s64 = *(double*)ptr;
return 0;
case PT_TIME:
*s64 = *(time_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as double
*/
int
idnode_get_dbl
( idnode_t *self, const char *key, double *dbl )
{
const property_t *p = idnode_find_prop(self, key);
if (p) {
const void *ptr;
if (p->islist)
return 1;
else if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_INT:
case PT_BOOL:
*dbl = *(int*)ptr;
return 0;
case PT_U16:
*dbl = *(uint16_t*)ptr;
return 0;
case PT_U32:
*dbl = *(uint32_t*)ptr;
return 0;
case PT_S64:
*dbl = *(int64_t*)ptr;
return 0;
case PT_DBL:
*dbl = *(double *)ptr;
return 0;
case PT_TIME:
*dbl = *(time_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as BOOL
*/
int
idnode_get_bool
( idnode_t *self, const char *key, int *b )
{
const property_t *p = idnode_find_prop(self, key);
if (p) {
const void *ptr;
if (p->islist)
return 1;
else if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_BOOL:
*b = *(int*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/*
* Get field as time
*/
int
idnode_get_time
( idnode_t *self, const char *key, time_t *tm )
{
const property_t *p = idnode_find_prop(self, key);
if (p) {
const void *ptr;
if (p->islist)
return 1;
if (p->get)
ptr = p->get(self);
else
ptr = ((void*)self) + p->off;
switch (p->type) {
case PT_TIME:
*tm = *(time_t*)ptr;
return 0;
default:
break;
}
}
return 1;
}
/* **************************************************************************
* Lookup
* *************************************************************************/
int
idnode_perm(idnode_t *self, struct access *a, htsmsg_t *msg_to_write)
{
const idclass_t *ic = self->in_class;
while (ic) {
if (ic->ic_perm)
return self->in_class->ic_perm(self, a, msg_to_write);
if (ic->ic_perm_def)
return access_verify2(a, self->in_class->ic_perm_def);
ic = ic->ic_super;
}
return 0;
}
/**
*
*/
static const idnodes_rb_t *
idnode_domain(const idclass_t *idc)
{
if (idc) {
idclass_link_t lskel, *l;
const idclass_t *root = idnode_root_class(idc);
lskel.idc = root;
l = RB_FIND(&idrootclasses, &lskel, link, ic_cmp);
if (l == NULL)
return NULL;
return &l->nodes;
} else {
return NULL;
}
}
void *
idnode_find ( const char *uuid, const idclass_t *idc, const idnodes_rb_t *domain )
{
idnode_t skel, *r;
tvhtrace("idnode", "find node %s class %s", uuid, idc ? idc->ic_class : NULL);
if(uuid == NULL || strlen(uuid) != UUID_HEX_SIZE - 1)
return NULL;
if(hex2bin(skel.in_uuid, sizeof(skel.in_uuid), uuid))
return NULL;
if (domain == NULL)
domain = idnode_domain(idc);
if (domain == NULL)
r = RB_FIND(&idnodes, &skel, in_link, in_cmp);
else
r = RB_FIND(domain, &skel, in_domain_link, in_cmp);
if(r != NULL && idc != NULL) {
const idclass_t *c = r->in_class;
for(;c != NULL; c = c->ic_super) {
if(idc == c)
return r;
}
return NULL;
}
return r;
}
idnode_set_t *
idnode_find_all ( const idclass_t *idc, const idnodes_rb_t *domain )
{
idnode_t *in;
const idclass_t *ic;
tvhtrace("idnode", "find class %s", idc->ic_class);
idnode_set_t *is = calloc(1, sizeof(idnode_set_t));
if (domain == NULL)
domain = idnode_domain(idc);
if (domain == NULL) {
RB_FOREACH(in, &idnodes, in_link) {
ic = in->in_class;
while (ic) {
if (ic == idc) {
tvhtrace("idnode", " add node %s", idnode_uuid_as_str(in));
idnode_set_add(is, in, NULL);
break;
}
ic = ic->ic_super;
}
}
} else {
RB_FOREACH(in, domain, in_domain_link) {
ic = in->in_class;
while (ic) {
if (ic == idc) {
tvhtrace("idnode", " add node %s", idnode_uuid_as_str(in));
idnode_set_add(is, in, NULL);
break;
}
ic = ic->ic_super;
}
}
}
return is;
}
/* **************************************************************************
* Set processing
* *************************************************************************/
static int
idnode_cmp_title
( const void *a, const void *b )
{
idnode_t *ina = *(idnode_t**)a;
idnode_t *inb = *(idnode_t**)b;
const char *sa = idnode_get_title(ina);
const char *sb = idnode_get_title(inb);
return strcmp(sa ?: "", sb ?: "");
}
#define safecmp(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0))
static int
idnode_cmp_sort
( const void *a, const void *b, void *s )
{
idnode_t *ina = *(idnode_t**)a;
idnode_t *inb = *(idnode_t**)b;
idnode_sort_t *sort = s;
const property_t *p = idnode_find_prop(ina, sort->key);
if (!p) return 0;
/* Get display string */
if (p->islist || (p->list && !(p->opts & PO_SORTKEY))) {
int r;
char *stra = idnode_get_display(ina, p);
char *strb = idnode_get_display(inb, p);
if (sort->dir == IS_ASC)
r = strcmp(stra ?: "", strb ?: "");
else
r = strcmp(strb ?: "", stra ?: "");
free(stra);
free(strb);
return r;
}
switch (p->type) {
case PT_STR:
{
int r;
const char *stra = tvh_strdupa(idnode_get_str(ina, sort->key) ?: "");
const char *strb = idnode_get_str(inb, sort->key) ?: "";
if (sort->dir == IS_ASC)
r = strcmp(stra, strb);
else
r = strcmp(strb, stra);
return r;
}
break;
case PT_INT:
case PT_U16:
case PT_BOOL:
case PT_PERM:
{
int32_t i32a = 0, i32b = 0;
idnode_get_u32(ina, sort->key, (uint32_t *)&i32a);
idnode_get_u32(inb, sort->key, (uint32_t *)&i32b);
if (sort->dir == IS_ASC)
return safecmp(i32a, i32b);
else
return safecmp(i32b, i32a);
}
break;
case PT_U32:
{
uint32_t u32a = 0, u32b = 0;
idnode_get_u32(ina, sort->key, &u32a);
idnode_get_u32(inb, sort->key, &u32b);
if (sort->dir == IS_ASC)
return safecmp(u32a, u32b);
else
return safecmp(u32b, u32a);
}
break;
case PT_S64:
{
int64_t s64a = 0, s64b = 0;
idnode_get_s64(ina, sort->key, &s64a);
idnode_get_s64(inb, sort->key, &s64b);
if (sort->dir == IS_ASC)
return safecmp(s64a, s64b);
else
return safecmp(s64b, s64a);
}
break;
case PT_DBL:
{
double dbla = 0, dblb = 0;
idnode_get_dbl(ina, sort->key, &dbla);
idnode_get_dbl(inb, sort->key, &dblb);
if (sort->dir == IS_ASC)
return safecmp(dbla, dblb);
else
return safecmp(dblb, dbla);
}
break;
case PT_TIME:
{
time_t ta = 0, tb = 0;
idnode_get_time(ina, sort->key, &ta);
idnode_get_time(inb, sort->key, &tb);
if (sort->dir == IS_ASC)
return safecmp(ta, tb);
else
return safecmp(tb, ta);
}
break;
case PT_LANGSTR:
// TODO?
case PT_NONE:
break;
}
return 0;
}
static void
idnode_filter_init
( idnode_t *in, idnode_filter_t *filter )
{
idnode_filter_ele_t *f;
const property_t *p;
LIST_FOREACH(f, filter, link) {
if (f->type == IF_NUM) {
p = idnode_find_prop(in, f->key);
if (p) {
if (p->type == PT_U32 || p->type == PT_S64 ||
p->type == PT_TIME) {
int64_t v = f->u.n.n;
if (p->intsplit != f->u.n.intsplit) {
v = (v / MIN(1, f->u.n.intsplit)) * p->intsplit;
f->u.n.n = v;
}
}
}
}
f->checked = 1;
}
}
int
idnode_filter
( idnode_t *in, idnode_filter_t *filter )
{
idnode_filter_ele_t *f;
LIST_FOREACH(f, filter, link) {
if (!f->checked)
idnode_filter_init(in, filter);
if (f->type == IF_STR) {
const char *str;
char *strdisp;
int r = 1;
str = strdisp = idnode_get_display(in, idnode_find_prop(in, f->key));
if (!str)
if (!(str = idnode_get_str(in, f->key)))
return 1;
switch(f->comp) {
case IC_IN: r = strstr(str, f->u.s) == NULL; break;
case IC_EQ: r = strcmp(str, f->u.s) != 0; break;
case IC_LT: r = strcmp(str, f->u.s) > 0; break;
case IC_GT: r = strcmp(str, f->u.s) < 0; break;
case IC_RE: r = !!regexec(&f->u.re, str, 0, NULL, 0); break;
}
if (strdisp)
free(strdisp);
if (r)
return r;
} else if (f->type == IF_NUM || f->type == IF_BOOL) {
int64_t a, b;
if (idnode_get_s64(in, f->key, &a))
return 1;
b = (f->type == IF_NUM) ? f->u.n.n : f->u.b;
switch (f->comp) {
case IC_IN:
case IC_RE:
break; // Note: invalid
case IC_EQ:
if (a != b)
return 1;
break;
case IC_LT:
if (a > b)
return 1;
break;
case IC_GT:
if (a < b)
return 1;
break;
}
} else if (f->type == IF_DBL) {
double a, b;
if (idnode_get_dbl(in, f->key, &a))
return 1;
b = f->u.dbl;
switch (f->comp) {
case IC_IN:
case IC_RE:
break; // Note: invalid
case IC_EQ:
if (a != b)
return 1;
break;
case IC_LT:
if (a > b)
return 1;
break;
case IC_GT:
if (a < b)
return 1;
break;
}
}
}
return 0;
}
void
idnode_filter_add_str
( idnode_filter_t *filt, const char *key, const char *val, int comp )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_STR;
ele->comp = comp;
if (comp == IC_RE) {
if (regcomp(&ele->u.re, val, REG_ICASE | REG_EXTENDED | REG_NOSUB)) {
free(ele);
return;
}
} else
ele->u.s = strdup(val);
LIST_INSERT_HEAD(filt, ele, link);
}
void
idnode_filter_add_num
( idnode_filter_t *filt, const char *key, int64_t val, int comp, int64_t intsplit )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_NUM;
ele->comp = comp;
ele->u.n.n = val;
ele->u.n.intsplit = intsplit;
LIST_INSERT_HEAD(filt, ele, link);
}
void
idnode_filter_add_dbl
( idnode_filter_t *filt, const char *key, double dbl, int comp )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_DBL;
ele->comp = comp;
ele->u.dbl = dbl;
LIST_INSERT_HEAD(filt, ele, link);
}
void
idnode_filter_add_bool
( idnode_filter_t *filt, const char *key, int val, int comp )
{
idnode_filter_ele_t *ele = calloc(1, sizeof(idnode_filter_ele_t));
ele->key = strdup(key);
ele->type = IF_BOOL;
ele->comp = comp;
ele->u.b = val;
LIST_INSERT_HEAD(filt, ele, link);
}
void
idnode_filter_clear
( idnode_filter_t *filt )
{
idnode_filter_ele_t *ele;
while ((ele = LIST_FIRST(filt))) {
LIST_REMOVE(ele, link);
if (ele->type == IF_STR) {
if (ele->comp == IC_RE)
regfree(&ele->u.re);
else
free(ele->u.s);
}
free(ele->key);
free(ele);
}
}
void
idnode_set_add
( idnode_set_t *is, idnode_t *in, idnode_filter_t *filt )
{
if (filt && idnode_filter(in, filt))
return;
/* Allocate more space */
if (is->is_alloc == is->is_count) {
is->is_alloc = MAX(100, is->is_alloc * 2);
is->is_array = realloc(is->is_array, is->is_alloc * sizeof(idnode_t*));
}
if (is->is_sorted) {
size_t i;
idnode_t **a = is->is_array;
for (i = is->is_count++; i > 0 && a[i - 1] > in; i--)
a[i] = a[i - 1];
a[i] = in;
} else {
is->is_array[is->is_count++] = in;
}
}
ssize_t
idnode_set_find_index
( idnode_set_t *is, idnode_t *in )
{
ssize_t i;
if (is->is_sorted) {
idnode_t **a = is->is_array;
ssize_t first = 0, last = is->is_count - 1;
i = last / 2;
while (first <= last) {
if (a[i] < in)
first = i + 1;
else if (a[i] == in)
return i;
else
last = i - 1;
i = (first + last) / 2;
}
} else {
for (i = 0; i < is->is_count; i++)
if (is->is_array[i] == in)
return 1;
}
return -1;
}
void
idnode_set_remove
( idnode_set_t *is, idnode_t *in )
{
ssize_t i = idnode_set_find_index(is, in);
if (i >= 0) {
memmove(&is->is_array[i], &is->is_array[i+1],
(is->is_count - i - 1) * sizeof(idnode_t *));
is->is_count--;
}
}
void
idnode_set_sort
( idnode_set_t *is, idnode_sort_t *sort )
{
tvh_qsort_r(is->is_array, is->is_count, sizeof(idnode_t*), idnode_cmp_sort, (void*)sort);
}
void
idnode_set_sort_by_title
( idnode_set_t *is )
{
qsort(is->is_array, is->is_count, sizeof(idnode_t*), idnode_cmp_title);
}
htsmsg_t *
idnode_set_as_htsmsg
( idnode_set_t *is )
{
htsmsg_t *l = htsmsg_create_list();
int i;
for (i = 0; i < is->is_count; i++)
htsmsg_add_str(l, NULL, idnode_uuid_as_str(is->is_array[i]));
return l;
}
void
idnode_set_free ( idnode_set_t *is )
{
free(is->is_array);
free(is);
}
/* **************************************************************************
* Write
* *************************************************************************/
static int
idnode_class_write_values
( idnode_t *self, const idclass_t *idc, htsmsg_t *c, int optmask )
{
int save = 0;
if (idc->ic_super)
save |= idnode_class_write_values(self, idc->ic_super, c, optmask);
save |= prop_write_values(self, idc->ic_properties, c, optmask, NULL);
return save;
}
static void
idnode_savefn ( idnode_t *self )
{
const idclass_t *idc = self->in_class;
while (idc) {
if (idc->ic_save) {
idc->ic_save(self);
break;
}
idc = idc->ic_super;
}
}
int
idnode_write0 ( idnode_t *self, htsmsg_t *c, int optmask, int dosave )
{
int save = 0;
const idclass_t *idc = self->in_class;
save = idnode_class_write_values(self, idc, c, optmask);
if (save && dosave)
idnode_savefn(self);
if (dosave)
idnode_notify_simple(self);
// Note: always output event if "dosave", reason is that UI updates on
// these, but there are some subtle cases where it will expect
// an update and not get one. This include fields being set for
// which there is user-configurable value and auto fallback so
// the UI state might not atually reflect the user config
return save;
}
void
idnode_changed( idnode_t *self )
{
idnode_notify_simple(self);
idnode_savefn(self);
}
/* **************************************************************************
* Read
* *************************************************************************/
void
idnode_read0 ( idnode_t *self, htsmsg_t *c, htsmsg_t *list, int optmask )
{
const idclass_t *idc = self->in_class;
for (; idc; idc = idc->ic_super)
prop_read_values(self, idc->ic_properties, c, list, optmask);
}
/**
* Recursive to get superclass nodes first
*/
static void
add_params
(struct idnode *self, const idclass_t *ic, htsmsg_t *p, htsmsg_t *list, int optmask)
{
/* Parent first */
if(ic->ic_super != NULL)
add_params(self, ic->ic_super, p, list, optmask);
/* Seperator (if not empty) */
#if 0
if(TAILQ_FIRST(&p->hm_fields) != NULL) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "caption", ic->ic_caption ?: ic->ic_class);
htsmsg_add_str(m, "type", "separator");
htsmsg_add_msg(p, NULL, m);
}
#endif
/* Properties */
prop_serialize(self, ic->ic_properties, p, list, optmask);
}
static htsmsg_t *
idnode_params (const idclass_t *idc, idnode_t *self, htsmsg_t *list, int optmask)
{
htsmsg_t *p = htsmsg_create_list();
add_params(self, idc, p, list, optmask);
return p;
}
static const char *
idclass_get_caption (const idclass_t *idc )
{
while (idc) {
if (idc->ic_caption)
return idc->ic_caption;
idc = idc->ic_super;
}
return NULL;
}
static const char *
idclass_get_class (const idclass_t *idc)
{
while (idc) {
if (idc->ic_class)
return idc->ic_class;
idc = idc->ic_super;
}
return NULL;
}
static const char *
idclass_get_event (const idclass_t *idc)
{
while (idc) {
if (idc->ic_event)
return idc->ic_event;
idc = idc->ic_super;
}
return NULL;
}
static const char *
idclass_get_order (const idclass_t *idc)
{
while (idc) {
if (idc->ic_order)
return idc->ic_order;
idc = idc->ic_super;
}
return NULL;
}
static htsmsg_t *
idclass_get_property_groups (const idclass_t *idc)
{
const property_group_t *g;
htsmsg_t *e, *m;
int count;
while (idc) {
if (idc->ic_groups) {
m = htsmsg_create_list();
count = 0;
for (g = idc->ic_groups; g->number && g->name; g++) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "number", g->number);
htsmsg_add_str(e, "name", g->name);
if (g->parent)
htsmsg_add_u32(e, "parent", g->parent);
if (g->column)
htsmsg_add_u32(e, "column", g->column);
htsmsg_add_msg(m, NULL, e);
count++;
}
if (count)
return m;
htsmsg_destroy(m);
break;
}
idc = idc->ic_super;
}
return NULL;
}
void
idclass_register(const idclass_t *idc)
{
while (idc) {
SKEL_ALLOC(idclasses_skel);
idclasses_skel->idc = idc;
if (RB_INSERT_SORTED(&idclasses, idclasses_skel, link, ic_cmp))
break;
RB_INIT(&idclasses_skel->nodes); /* not used, but for sure */
SKEL_USED(idclasses_skel);
tvhtrace("idnode", "register class %s", idc->ic_class);
idc = idc->ic_super;
}
}
static void
idclass_root_register(idnode_t *in)
{
const idclass_t *idc = in->in_class;
idclass_link_t *r;
idc = idnode_root_class(idc);
SKEL_ALLOC(idclasses_skel);
idclasses_skel->idc = idc;
r = RB_INSERT_SORTED(&idrootclasses, idclasses_skel, link, ic_cmp);
if (r) {
in->in_domain = &r->nodes;
return;
}
RB_INIT(&idclasses_skel->nodes);
r = idclasses_skel;
SKEL_USED(idclasses_skel);
tvhtrace("idnode", "register root class %s", idc->ic_class);
in->in_domain = &r->nodes;
}
const idclass_t *
idclass_find ( const char *class )
{
idclass_link_t *t, skel;
idclass_t idc;
skel.idc = &idc;
idc.ic_class = class;
tvhtrace("idnode", "find class %s", class);
t = RB_FIND(&idclasses, &skel, link, ic_cmp);
return t ? t->idc : NULL;
}
/*
* Just get the class definition
*/
htsmsg_t *
idclass_serialize0(const idclass_t *idc, htsmsg_t *list, int optmask)
{
const char *s;
htsmsg_t *p, *m = htsmsg_create_map();
/* Caption and name */
if ((s = idclass_get_caption(idc)))
htsmsg_add_str(m, "caption", s);
if ((s = idclass_get_class(idc)))
htsmsg_add_str(m, "class", s);
if ((s = idclass_get_event(idc)))
htsmsg_add_str(m, "event", s);
if ((s = idclass_get_order(idc)))
htsmsg_add_str(m, "order", s);
if ((p = idclass_get_property_groups(idc)))
htsmsg_add_msg(m, "groups", p);
/* Props */
if ((p = idnode_params(idc, NULL, list, optmask)))
htsmsg_add_msg(m, "props", p);
return m;
}
/**
*
*/
htsmsg_t *
idnode_serialize0(idnode_t *self, htsmsg_t *list, int optmask)
{
const idclass_t *idc = self->in_class;
const char *uuid, *s;
htsmsg_t *m = htsmsg_create_map();
uuid = idnode_uuid_as_str(self);
htsmsg_add_str(m, "uuid", uuid);
htsmsg_add_str(m, "id", uuid);
htsmsg_add_str(m, "text", idnode_get_title(self) ?: "");
if ((s = idclass_get_caption(idc)))
htsmsg_add_str(m, "caption", s);
if ((s = idclass_get_class(idc)))
htsmsg_add_str(m, "class", s);
if ((s = idclass_get_event(idc)))
htsmsg_add_str(m, "event", s);
htsmsg_add_msg(m, "params", idnode_params(idc, self, list, optmask));
return m;
}
/* **************************************************************************
* Notification
* *************************************************************************/
/**
* Delayed notification
*/
static void
idnode_notify_delayed ( idnode_t *in, const char *uuid, const char *event )
{
pthread_mutex_lock(&idnode_mutex);
if (!idnode_queue)
idnode_queue = htsmsg_create_map();
htsmsg_set_str(idnode_queue, uuid, event);
pthread_cond_signal(&idnode_cond);
pthread_mutex_unlock(&idnode_mutex);
}
/**
* Update internal event pipes
*/
static void
idnode_notify_event ( idnode_t *in )
{
const idclass_t *ic = in->in_class;
const char *uuid = idnode_uuid_as_str(in);
while (ic) {
if (ic->ic_event)
idnode_notify_delayed(in, uuid, ic->ic_event);
ic = ic->ic_super;
}
}
/**
* Notify on a given channel
*/
void
idnode_notify
(idnode_t *in, int event)
{
const char *uuid = idnode_uuid_as_str(in);
if (!tvheadend_running)
return;
/* Immediate */
if (!event) {
const idclass_t *ic = in->in_class;
while (ic) {
if (ic->ic_event) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", uuid);
notify_by_msg(ic->ic_event, m);
}
ic = ic->ic_super;
}
/* Rate-limited */
} else {
idnode_notify_event(in);
}
}
void
idnode_notify_simple (void *in)
{
idnode_notify(in, 1);
}
void
idnode_notify_title_changed (void *in)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", idnode_uuid_as_str(in));
htsmsg_add_str(m, "text", idnode_get_title(in));
notify_by_msg("title", m);
idnode_notify_event(in);
}
/*
* Thread for handling notifications
*/
void*
idnode_thread ( void *p )
{
idnode_t *node;
htsmsg_t *m, *q = NULL;
htsmsg_field_t *f;
const char *event;
pthread_mutex_lock(&idnode_mutex);
while (tvheadend_running) {
/* Get queue */
if (!idnode_queue) {
pthread_cond_wait(&idnode_cond, &idnode_mutex);
continue;
}
q = idnode_queue;
idnode_queue = NULL;
pthread_mutex_unlock(&idnode_mutex);
/* Process */
pthread_mutex_lock(&global_lock);
HTSMSG_FOREACH(f, q) {
node = idnode_find(f->hmf_name, NULL, NULL);
event = htsmsg_field_get_str(f);
m = htsmsg_create_map();
htsmsg_add_str(m, "uuid", f->hmf_name);
if (!node)
htsmsg_add_u32(m, "removed", 1);
notify_by_msg(event, m);
}
/* Finished */
pthread_mutex_unlock(&global_lock);
htsmsg_destroy(q);
/* Wait */
usleep(500000);
pthread_mutex_lock(&idnode_mutex);
}
pthread_mutex_unlock(&idnode_mutex);
return NULL;
}
/******************************************************************************
* Editor Configuration
*
* vim:sts=2:ts=2:sw=2:et
*****************************************************************************/