555 lines
13 KiB
C
555 lines
13 KiB
C
/*
|
|
* API - idnode related API calls
|
|
*
|
|
* Copyright (C) 2013 Adam Sutton
|
|
*
|
|
* 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 "tvheadend.h"
|
|
#include "access.h"
|
|
#include "idnode.h"
|
|
#include "htsmsg.h"
|
|
#include "api.h"
|
|
|
|
static htsmsg_t *
|
|
api_idnode_flist_conf( htsmsg_t *args, const char *name )
|
|
{
|
|
htsmsg_t *m = NULL;
|
|
const char *s = htsmsg_get_str(args, name);
|
|
char *r, *saveptr = NULL;
|
|
int use = 1;
|
|
if (s && s[0] == '-') {
|
|
use = 0;
|
|
s++;
|
|
}
|
|
if (s && s[0] != '\0') {
|
|
s = r = strdup(s);
|
|
r = strtok_r(r, ",;:", &saveptr);
|
|
while (r) {
|
|
while (*r != '\0' && *r <= ' ')
|
|
r++;
|
|
if (*r != '\0') {
|
|
if (m == NULL)
|
|
m = htsmsg_create_map();
|
|
htsmsg_add_bool(m, r, use);
|
|
}
|
|
r = strtok_r(NULL, ",;:", &saveptr);
|
|
}
|
|
free((char *)s);
|
|
}
|
|
return m;
|
|
}
|
|
|
|
static struct strtab filtcmptab[] = {
|
|
{ "gt", IC_GT },
|
|
{ "lt", IC_LT },
|
|
{ "eq", IC_EQ }
|
|
};
|
|
|
|
static void
|
|
api_idnode_grid_conf
|
|
( htsmsg_t *args, api_idnode_grid_conf_t *conf )
|
|
{
|
|
htsmsg_field_t *f, *f2;
|
|
htsmsg_t *filter, *e;
|
|
const char *str;
|
|
|
|
conf->start = htsmsg_get_u32_or_default(args, "start", 0);
|
|
conf->limit = htsmsg_get_u32_or_default(args, "limit", 50);
|
|
|
|
/* Filter */
|
|
if ((filter = htsmsg_get_list(args, "filter"))) {
|
|
HTSMSG_FOREACH(f, filter) {
|
|
const char *k, *t, *v;
|
|
if (!(e = htsmsg_get_map_by_field(f))) continue;
|
|
if (!(k = htsmsg_get_str(e, "field"))) continue;
|
|
if (!(t = htsmsg_get_str(e, "type"))) continue;
|
|
if (!strcmp(t, "string")) {
|
|
if ((v = htsmsg_get_str(e, "value")))
|
|
idnode_filter_add_str(&conf->filter, k, v, IC_RE);
|
|
} else if (!strcmp(t, "numeric")) {
|
|
f2 = htsmsg_field_find(e, "value");
|
|
if (f2) {
|
|
int t = str2val(htsmsg_get_str(e, "comparison") ?: "", filtcmptab);
|
|
if (f2->hmf_type == HMF_DBL) {
|
|
double dbl;
|
|
if (!htsmsg_field_get_dbl(f2, &dbl))
|
|
idnode_filter_add_dbl(&conf->filter, k, dbl, t == -1 ? IC_EQ : t);
|
|
} else {
|
|
int64_t v;
|
|
int64_t intsplit = 0;
|
|
htsmsg_get_s64(e, "intsplit", &intsplit);
|
|
if (!htsmsg_field_get_s64(f2, &v))
|
|
idnode_filter_add_num(&conf->filter, k, v, t == -1 ? IC_EQ : t, intsplit);
|
|
}
|
|
}
|
|
} else if (!strcmp(t, "boolean")) {
|
|
uint32_t v;
|
|
if (!htsmsg_get_u32(e, "value", &v))
|
|
idnode_filter_add_bool(&conf->filter, k, v, IC_EQ);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sort */
|
|
if ((str = htsmsg_get_str(args, "sort"))) {
|
|
conf->sort.key = str;
|
|
if ((str = htsmsg_get_str(args, "dir")) && !strcasecmp(str, "DESC"))
|
|
conf->sort.dir = IS_DSC;
|
|
else
|
|
conf->sort.dir = IS_ASC;
|
|
} else
|
|
conf->sort.key = NULL;
|
|
}
|
|
|
|
int
|
|
api_idnode_grid
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
int i;
|
|
htsmsg_t *list, *e;
|
|
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
|
|
api_idnode_grid_conf_t conf = { 0 };
|
|
idnode_set_t ins = { 0 };
|
|
api_idnode_grid_callback_t cb = opaque;
|
|
|
|
/* Grid configuration */
|
|
api_idnode_grid_conf(args, &conf);
|
|
|
|
/* Create list */
|
|
pthread_mutex_lock(&global_lock);
|
|
cb(perm, &ins, &conf, args);
|
|
|
|
/* Sort */
|
|
if (conf.sort.key)
|
|
idnode_set_sort(&ins, &conf.sort);
|
|
|
|
/* Paginate */
|
|
list = htsmsg_create_list();
|
|
for (i = conf.start; i < ins.is_count && conf.limit != 0; i++) {
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_str(e, "uuid", idnode_uuid_as_str(ins.is_array[i]));
|
|
idnode_read0(ins.is_array[i], e, flist, 0);
|
|
htsmsg_add_msg(list, NULL, e);
|
|
if (conf.limit > 0) conf.limit--;
|
|
}
|
|
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
/* Output */
|
|
*resp = htsmsg_create_map();
|
|
htsmsg_add_msg(*resp, "entries", list);
|
|
htsmsg_add_u32(*resp, "total", ins.is_count);
|
|
|
|
/* Cleanup */
|
|
free(ins.is_array);
|
|
idnode_filter_clear(&conf.filter);
|
|
htsmsg_destroy(flist);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
api_idnode_load_by_class
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
int i, _enum;
|
|
const idclass_t *idc;
|
|
idnode_set_t *is;
|
|
idnode_t *in;
|
|
htsmsg_t *l, *e;
|
|
|
|
// TODO: this only works if pass as integer
|
|
_enum = htsmsg_get_bool_or_default(args, "enum", 0);
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
/* Find class */
|
|
idc = opaque;
|
|
assert(idc);
|
|
|
|
l = htsmsg_create_list();
|
|
if ((is = idnode_find_all(idc, NULL))) {
|
|
for (i = 0; i < is->is_count; i++) {
|
|
in = is->is_array[i];
|
|
|
|
if (idnode_perm(in, perm, NULL))
|
|
continue;
|
|
|
|
/* Name/UUID only */
|
|
if (_enum) {
|
|
e = htsmsg_create_map();
|
|
htsmsg_add_str(e, "key", idnode_uuid_as_str(in));
|
|
htsmsg_add_str(e, "val", idnode_get_title(in));
|
|
|
|
/* Full record */
|
|
} else {
|
|
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
|
|
e = idnode_serialize0(in, flist, 0);
|
|
htsmsg_destroy(flist);
|
|
}
|
|
|
|
if (e)
|
|
htsmsg_add_msg(l, NULL, e);
|
|
}
|
|
free(is->is_array);
|
|
free(is);
|
|
}
|
|
*resp = htsmsg_create_map();
|
|
htsmsg_add_msg(*resp, "entries", l);
|
|
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
api_idnode_load
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
int err = 0, meta, count = 0;
|
|
idnode_t *in;
|
|
htsmsg_t *uuids, *l = NULL, *m;
|
|
htsmsg_t *flist;
|
|
htsmsg_field_t *f;
|
|
const char *uuid, *class;
|
|
|
|
/* Class based */
|
|
if ((class = htsmsg_get_str(args, "class"))) {
|
|
const idclass_t *idc;
|
|
pthread_mutex_lock(&global_lock);
|
|
idc = idclass_find(class);
|
|
pthread_mutex_unlock(&global_lock);
|
|
if (!idc)
|
|
return EINVAL;
|
|
// TODO: bit naff that 2 locks are required here
|
|
return api_idnode_load_by_class(perm, (void*)idc, NULL, args, resp);
|
|
}
|
|
|
|
/* UUIDs */
|
|
if (!(f = htsmsg_field_find(args, "uuid")))
|
|
return EINVAL;
|
|
if (!(uuids = htsmsg_field_get_list(f)))
|
|
if (!(uuid = htsmsg_field_get_str(f)))
|
|
return EINVAL;
|
|
meta = htsmsg_get_s32_or_default(args, "meta", 0);
|
|
|
|
flist = api_idnode_flist_conf(args, "list");
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
/* Multiple */
|
|
if (uuids) {
|
|
const idnodes_rb_t *domain = NULL;
|
|
l = htsmsg_create_list();
|
|
HTSMSG_FOREACH(f, uuids) {
|
|
if (!(uuid = htsmsg_field_get_str(f))) continue;
|
|
if (!(in = idnode_find(uuid, NULL, domain))) continue;
|
|
domain = in->in_domain;
|
|
if (idnode_perm(in, perm, NULL)) {
|
|
err = EPERM;
|
|
continue;
|
|
}
|
|
m = idnode_serialize0(in, flist, 0);
|
|
if (meta > 0)
|
|
htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0));
|
|
htsmsg_add_msg(l, NULL, m);
|
|
count++;
|
|
}
|
|
|
|
if (count)
|
|
err = 0;
|
|
|
|
/* Single */
|
|
} else {
|
|
if (!(in = idnode_find(uuid, NULL, NULL)))
|
|
err = ENOENT;
|
|
else {
|
|
if (idnode_perm(in, perm, NULL)) {
|
|
err = EPERM;
|
|
} else {
|
|
l = htsmsg_create_list();
|
|
m = idnode_serialize0(in, flist, 0);
|
|
if (meta > 0)
|
|
htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0));
|
|
htsmsg_add_msg(l, NULL, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (l) {
|
|
*resp = htsmsg_create_map();
|
|
htsmsg_add_msg(*resp, "entries", l);
|
|
}
|
|
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
htsmsg_destroy(flist);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
api_idnode_save
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
int err = EINVAL;
|
|
idnode_t *in;
|
|
htsmsg_t *msg, *conf;
|
|
htsmsg_field_t *f;
|
|
const char *uuid;
|
|
int count = 0;
|
|
|
|
if (!(f = htsmsg_field_find(args, "node")))
|
|
return EINVAL;
|
|
if (!(msg = htsmsg_field_get_list(f)))
|
|
if (!(msg = htsmsg_field_get_map(f)))
|
|
return EINVAL;
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
/* Single */
|
|
if (!msg->hm_islist) {
|
|
if (!(uuid = htsmsg_get_str(msg, "uuid")))
|
|
goto exit;
|
|
if (!(in = idnode_find(uuid, NULL, NULL)))
|
|
goto exit;
|
|
if (idnode_perm(in, perm, msg)) {
|
|
err = EPERM;
|
|
goto exit;
|
|
}
|
|
idnode_update(in, msg);
|
|
err = 0;
|
|
|
|
/* Multiple */
|
|
} else {
|
|
const idnodes_rb_t *domain = NULL;
|
|
HTSMSG_FOREACH(f, msg) {
|
|
if (!(conf = htsmsg_field_get_map(f)))
|
|
continue;
|
|
if (!(uuid = htsmsg_get_str(conf, "uuid")))
|
|
continue;
|
|
if (!(in = idnode_find(uuid, NULL, domain)))
|
|
continue;
|
|
domain = in->in_domain;
|
|
if (idnode_perm(in, perm, conf)) {
|
|
err = EPERM;
|
|
continue;
|
|
}
|
|
count++;
|
|
idnode_update(in, conf);
|
|
}
|
|
if (count)
|
|
err = 0;
|
|
}
|
|
|
|
// TODO: return updated UUIDs?
|
|
|
|
exit:
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
api_idnode_tree
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
const char *uuid;
|
|
const char *root = NULL;
|
|
int isroot;
|
|
idnode_t *node = NULL;
|
|
api_idnode_tree_callback_t rootfn = opaque;
|
|
|
|
/* UUID */
|
|
if (!(uuid = htsmsg_get_str(args, "uuid")))
|
|
return EINVAL;
|
|
|
|
/* Root UUID */
|
|
if (!rootfn)
|
|
root = htsmsg_get_str(args, "root");
|
|
|
|
/* Is root? */
|
|
isroot = (strcmp("root", uuid) == 0);
|
|
if (isroot && !(root || rootfn))
|
|
return EINVAL;
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
if (!isroot || root) {
|
|
if (!(node = idnode_find(isroot ? root : uuid, NULL, NULL))) {
|
|
pthread_mutex_unlock(&global_lock);
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
*resp = htsmsg_create_list();
|
|
|
|
/* Root node */
|
|
if (isroot && node) {
|
|
htsmsg_t *m = idnode_serialize(node);
|
|
htsmsg_add_u32(m, "leaf", idnode_is_leaf(node));
|
|
htsmsg_add_msg(*resp, NULL, m);
|
|
|
|
/* Children */
|
|
} else {
|
|
idnode_set_t *v = node ? idnode_get_childs(node) : rootfn(perm);
|
|
if (v) {
|
|
int i;
|
|
idnode_set_sort_by_title(v);
|
|
for(i = 0; i < v->is_count; i++) {
|
|
htsmsg_t *m = idnode_serialize(v->is_array[i]);
|
|
htsmsg_add_u32(m, "leaf", idnode_is_leaf(v->is_array[i]));
|
|
htsmsg_add_msg(*resp, NULL, m);
|
|
}
|
|
idnode_set_free(v);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
api_idnode_class
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
int err = EINVAL;
|
|
const char *name;
|
|
const idclass_t *idc;
|
|
htsmsg_t *flist = api_idnode_flist_conf(args, "list");
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
/* Lookup */
|
|
if (!opaque) {
|
|
if (!(name = htsmsg_get_str(args, "name")))
|
|
goto exit;
|
|
if (!(idc = idclass_find(name)))
|
|
goto exit;
|
|
|
|
} else {
|
|
idc = opaque;
|
|
}
|
|
|
|
err = 0;
|
|
*resp = idclass_serialize0(idc, flist, 0);
|
|
|
|
exit:
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
htsmsg_destroy(flist);
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
api_idnode_handler
|
|
( access_t *perm, htsmsg_t *args, htsmsg_t **resp,
|
|
void (*handler)(access_t *perm, idnode_t *in) )
|
|
{
|
|
int err = 0;
|
|
idnode_t *in;
|
|
htsmsg_t *uuids;
|
|
htsmsg_field_t *f;
|
|
const char *uuid;
|
|
|
|
/* ID based */
|
|
if (!(f = htsmsg_field_find(args, "uuid")))
|
|
return EINVAL;
|
|
if (!(uuids = htsmsg_field_get_list(f)))
|
|
if (!(uuid = htsmsg_field_get_str(f)))
|
|
return EINVAL;
|
|
|
|
pthread_mutex_lock(&global_lock);
|
|
|
|
/* Multiple */
|
|
if (uuids) {
|
|
const idnodes_rb_t *domain = NULL;
|
|
HTSMSG_FOREACH(f, uuids) {
|
|
if (!(uuid = htsmsg_field_get_string(f))) continue;
|
|
if (!(in = idnode_find(uuid, NULL, domain))) continue;
|
|
domain = in->in_domain;
|
|
handler(perm, in);
|
|
}
|
|
|
|
/* Single */
|
|
} else {
|
|
uuid = htsmsg_field_get_string(f);
|
|
if (!(in = idnode_find(uuid, NULL, NULL)))
|
|
err = ENOENT;
|
|
else
|
|
handler(perm, in);
|
|
}
|
|
|
|
pthread_mutex_unlock(&global_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
api_idnode_delete_ (access_t *perm, idnode_t *in)
|
|
{
|
|
return idnode_delete(in);
|
|
}
|
|
|
|
static int
|
|
api_idnode_delete
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
return api_idnode_handler(perm, args, resp, api_idnode_delete_);
|
|
}
|
|
|
|
static void
|
|
api_idnode_moveup_ (access_t *perm, idnode_t *in)
|
|
{
|
|
return idnode_moveup(in);
|
|
}
|
|
|
|
static int
|
|
api_idnode_moveup
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
return api_idnode_handler(perm, args, resp, api_idnode_moveup_);
|
|
}
|
|
|
|
static void
|
|
api_idnode_movedown_ (access_t *perm, idnode_t *in)
|
|
{
|
|
return idnode_movedown(in);
|
|
}
|
|
|
|
static int
|
|
api_idnode_movedown
|
|
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
|
{
|
|
return api_idnode_handler(perm, args, resp, api_idnode_movedown_);
|
|
}
|
|
|
|
void api_idnode_init ( void )
|
|
{
|
|
static api_hook_t ah[] = {
|
|
{ "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
|
|
{ "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
|
|
{ "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
|
|
{ "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
|
|
{ "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
|
|
{ "idnode/moveup", ACCESS_ADMIN, api_idnode_moveup, NULL },
|
|
{ "idnode/movedown", ACCESS_ADMIN, api_idnode_movedown, NULL },
|
|
{ NULL },
|
|
};
|
|
|
|
api_register_all(ah);
|
|
}
|