#include "redblack.h"
#include "access.h"
-#define TVH_API_VERSION 15
+#define TVH_API_VERSION 16
/*
* Command hook
( access_t *perm, htsmsg_t *args, htsmsg_t **resp,
void (*handler)(access_t *perm, idnode_t *in), const char *op );
+int api_idnode_load_simple
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
+
+int api_idnode_save_simple
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp );
+
/*
* Service mapper
*/
{
int err = 0, meta, count = 0;
idnode_t *in;
- htsmsg_t *uuids, *l = NULL, *m;
- htsmsg_t *flist;
+ htsmsg_t *uuids, *l = NULL, *m, *flist;
htsmsg_field_t *f;
const char *uuid = NULL, *class;
return err;
}
+int
+api_idnode_load_simple
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ int err = 0, meta;
+ htsmsg_t *l = NULL, *m, *flist;
+ const char *class;
+ idnode_t *in = (idnode_t *)opaque;
+
+ /* Class based */
+ if ((class = htsmsg_get_str(args, "class"))) {
+ pthread_mutex_lock(&global_lock);
+ err = api_idnode_load_by_class0(perm, (void*)in->in_class, NULL, args, resp);
+ pthread_mutex_unlock(&global_lock);
+ return err;
+ }
+
+ /* UUIDs */
+ meta = htsmsg_get_s32_or_default(args, "meta", 0);
+
+ flist = api_idnode_flist_conf(args, "list");
+
+ pthread_mutex_lock(&global_lock);
+
+ if (!idnode_perm(in, perm, NULL)) {
+ l = htsmsg_create_list();
+ m = idnode_serialize0(in, flist, 0, perm->aa_lang);
+ if (meta > 0)
+ htsmsg_add_msg(m, "meta", idclass_serialize0(in->in_class, flist, 0, perm->aa_lang));
+ htsmsg_add_msg(l, NULL, m);
+ } else {
+ err = EPERM;
+ }
+
+ pthread_mutex_unlock(&global_lock);
+
+ if (l) {
+ *resp = htsmsg_create_map();
+ htsmsg_add_msg(*resp, "entries", l);
+ }
+
+ htsmsg_destroy(flist);
+
+ return err;
+}
+
static int
api_idnode_save
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
return err;
}
+int
+api_idnode_save_simple
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ int err = 0;
+ htsmsg_t *msg;
+ htsmsg_field_t *f;
+ idnode_t *in = (idnode_t *)opaque;
+
+ if (!(f = htsmsg_field_find(args, "node")))
+ return EINVAL;
+ if (!(msg = htsmsg_field_get_map(f)))
+ return EINVAL;
+
+ pthread_mutex_lock(&global_lock);
+
+ /* Single */
+ if (!idnode_perm(in, perm, msg)) {
+ idnode_update(in, msg);
+ idnode_perm_unset(in);
+ } else {
+ err = EPERM;
+ }
+
+ 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 )
#if ENABLE_IMAGECACHE
static int
-api_imagecache_load
- ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
-{
- htsmsg_t *l;
- pthread_mutex_lock(&global_lock);
- *resp = htsmsg_create_map();
- l = htsmsg_create_list();
- htsmsg_add_msg(l, NULL, imagecache_get_config());
- htsmsg_add_msg(*resp, "entries", l);
- pthread_mutex_unlock(&global_lock);
- return 0;
-}
-
-static int
-api_imagecache_save
+api_imagecache_clean
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int b;
-
- pthread_mutex_lock(&global_lock);
- if (imagecache_set_config(args))
- imagecache_save();
- if (!htsmsg_get_bool(args, "clean", &b) && b)
+ if (htsmsg_get_bool(args, "clean", &b))
+ return EINVAL;
+ if (b) {
+ pthread_mutex_lock(&global_lock);
imagecache_clean();
- pthread_mutex_unlock(&global_lock);
- *resp = htsmsg_create_map();
- htsmsg_add_u32(*resp, "success", 1);
+ pthread_mutex_unlock(&global_lock);
+ }
return 0;
}
api_imagecache_init ( void )
{
static api_hook_t ah[] = {
- { "imagecache/config/load", ACCESS_ADMIN, api_imagecache_load, NULL },
- { "imagecache/config/save", ACCESS_ADMIN, api_imagecache_save, NULL },
+ { "imagecache/config/load", ACCESS_ADMIN, api_idnode_load_simple, &imagecache_conf },
+ { "imagecache/config/save", ACCESS_ADMIN, api_idnode_save_simple, &imagecache_conf },
+ { "imagecache/config/clean", ACCESS_ADMIN, api_imagecache_clean, NULL },
{ NULL },
};
const char *uuid, *s;
htsmsg_t *m = htsmsg_create_map();
- uuid = idnode_uuid_as_sstr(self);
- htsmsg_add_str(m, "uuid", uuid);
- htsmsg_add_str(m, "id", uuid);
+ if (!idc->ic_snode) {
+ uuid = idnode_uuid_as_sstr(self);
+ htsmsg_add_str(m, "uuid", uuid);
+ htsmsg_add_str(m, "id", uuid);
+ }
htsmsg_add_str(m, "text", idnode_get_title(self, lang) ?: "");
if ((s = idclass_get_caption(idc, lang)))
htsmsg_add_str(m, "caption", s);
return;
while (ic) {
- if (ic->ic_event)
- notify_delayed(uuid, ic->ic_event, action);
+ if (ic->ic_event) {
+ if (!ic->ic_snode)
+ notify_delayed(uuid, ic->ic_event, action);
+ else
+ notify_reload(ic->ic_event);
+ }
ic = ic->ic_super;
}
}
const property_t *ic_properties; ///< Property list
const char *ic_event; ///< Events to fire on add/delete/title
uint32_t ic_perm_def; ///< Default permissions
+ idnode_t *ic_snode; ///< Simple node
/* Callbacks */
idnode_set_t *(*ic_get_childs) (idnode_t *self);
SKEL_DECLARE(imagecache_skel, imagecache_image_t);
#if ENABLE_IMAGECACHE
-struct imagecache_config imagecache_conf;
-static const property_t imagecache_props[] = {
- {
- .type = PT_BOOL,
- .id = "enabled",
- .name = N_("Enabled"),
- .off = offsetof(struct imagecache_config, enabled),
- },
- {
- .type = PT_BOOL,
- .id = "ignore_sslcert",
- .name = N_("Ignore invalid SSL certificate"),
- .off = offsetof(struct imagecache_config, ignore_sslcert),
- },
- {
- .type = PT_U32,
- .id = "ok_period",
- .name = N_("Re-try period"),
- .off = offsetof(struct imagecache_config, ok_period),
- },
- {
- .type = PT_U32,
- .id = "fail_period",
- .name = N_("Re-try period of failed images"),
- .off = offsetof(struct imagecache_config, fail_period),
- },
- {}
+struct imagecache_config imagecache_conf = {
+ .idnode.in_class = &imagecache_class,
+};
+
+static void imagecache_save(idnode_t *self);
+
+const idclass_t imagecache_class = {
+ .ic_snode = (idnode_t *)&imagecache_conf,
+ .ic_class = "imagecache",
+ .ic_caption = N_("Image Cache"),
+ .ic_event = "imagecache",
+ .ic_perm_def = ACCESS_ADMIN,
+ .ic_save = imagecache_save,
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = N_("Enabled"),
+ .off = offsetof(struct imagecache_config, enabled),
+ },
+ {
+ .type = PT_BOOL,
+ .id = "ignore_sslcert",
+ .name = N_("Ignore invalid SSL certificate"),
+ .off = offsetof(struct imagecache_config, ignore_sslcert),
+ },
+ {
+ .type = PT_U32,
+ .id = "ok_period",
+ .name = N_("Re-fetch period"),
+ .off = offsetof(struct imagecache_config, ok_period),
+ },
+ {
+ .type = PT_U32,
+ .id = "fail_period",
+ .name = N_("Re-try period (hours)"),
+ .off = offsetof(struct imagecache_config, fail_period),
+ },
+ {}
+ }
};
static pthread_cond_t imagecache_cond;
/* Load settings */
#if ENABLE_IMAGECACHE
if ((m = hts_settings_load("imagecache/config"))) {
- imagecache_set_config(m);
+ idnode_load(&imagecache_conf.idnode, m);
htsmsg_destroy(m);
}
#endif
#if ENABLE_IMAGECACHE
-/*
- * Get config
- */
-htsmsg_t *
-imagecache_get_config ( void )
-{
- htsmsg_t *m = htsmsg_create_map();
- prop_read_values(&imagecache_conf, imagecache_props, m, NULL, 0, NULL);
- return m;
-}
-
-/*
- * Set config
- */
-int
-imagecache_set_config ( htsmsg_t *m )
-{
- int save = prop_write_values(&imagecache_conf, imagecache_props, m, 0, NULL);
- if (save)
- pthread_cond_broadcast(&imagecache_cond);
- return save;
-}
-
/*
* Save
*/
-void
-imagecache_save ( void )
+static void
+imagecache_save ( idnode_t *self )
{
- htsmsg_t *m = imagecache_get_config();
- hts_settings_save(m, "imagecache/config");
- notify_reload("imagecache");
+ htsmsg_t *c = htsmsg_create_map();
+ idnode_save(&imagecache_conf.idnode, c);
+ hts_settings_save(c, "imagecache/config");
+ htsmsg_destroy(c);
+ pthread_cond_broadcast(&imagecache_cond);
}
/*
#define __IMAGE_CACHE_H__
#include <pthread.h>
+#include "idnode.h"
struct imagecache_config {
- int __unused__; // to avoid assert in prop.c (first member should be idnode_t)
+ idnode_t idnode;
int enabled;
int ignore_sslcert;
uint32_t ok_period;
};
extern struct imagecache_config imagecache_conf;
+extern const idclass_t imagecache_class;
extern pthread_mutex_t imagecache_mutex;
void imagecache_init ( void );
void imagecache_done ( void );
-htsmsg_t *imagecache_get_config ( void );
-int imagecache_set_config ( htsmsg_t *c );
-void imagecache_save ( void );
-void imagecache_clean ( void );
+void imagecache_clean ( void );
// Note: will return 0 if invalid (must serve original URL)
uint32_t imagecache_get_id ( const char *url );
if (tvheadend.capabilities.indexOf('imagecache') === -1)
return;
- var imagecache_reader = new Ext.data.JsonReader({root: 'entries'},
- [ 'enabled', 'ok_period', 'fail_period', 'ignore_sslcert' ]);
-
- var imagecacheEnabled = new Ext.ux.form.XCheckbox({
- name: 'enabled',
- fieldLabel: _('Enabled')
- });
-
- var imagecacheOkPeriod = new Ext.form.NumberField({
- name: 'ok_period',
- fieldLabel: _('Re-fetch period (hours)')
- });
-
- var imagecacheFailPeriod = new Ext.form.NumberField({
- name: 'fail_period',
- fieldLabel: _('Re-try period (hours)')
- });
-
- var imagecacheIgnoreSSLCert = new Ext.ux.form.XCheckbox({
- name: 'ignore_sslcert',
- fieldLabel: _('Ignore invalid SSL certificate')
- });
-
- var imagecachePanel = new Ext.form.FieldSet({
- title: _('Image Caching'),
- width: 700,
- autoHeight: true,
- collapsible: true,
- animCollapse: true,
- items: [imagecacheEnabled, imagecacheOkPeriod, imagecacheFailPeriod,
- imagecacheIgnoreSSLCert]
- });
-
- var imagecache_form = new Ext.form.FormPanel({
- border: false,
- labelAlign: 'left',
- labelWidth: 300,
- waitMsgTarget: true,
- reader: imagecache_reader,
- layout: 'form',
- defaultType: 'textfield',
- autoHeight: true,
- items: [imagecachePanel]
- });
-
- var saveButton = new Ext.Button({
- text: _("Save configuration"),
- tooltip: _('Save changes made to configuration below'),
- iconCls: 'save',
- handler: saveChanges
- });
-
- var imagecacheButton = new Ext.Button({
- text: _("Clean image (icon) cache"),
- tooltip: _('Clean image cache on storage'),
- iconCls: 'clean',
- handler: cleanImagecache
- });
-
- var _tbar = [saveButton, '-', imagecacheButton];
+ var cleanButton = {
+ name: 'clean',
+ builder: function() {
+ return new Ext.Toolbar.Button({
+ tooltip: _('Clean image cache on storage'),
+ iconCls: 'clean',
+ text: _('Clean image (icon) cache')
+ });
+ },
+ callback: function(conf) {
+ tvheadend.Ajax({
+ url: 'api/imagecache/config/clean',
+ params: { clean: 1 },
+ });
+ }
+ };
- var mpanel = new Ext.Panel({
+ tvheadend.idnode_simple(panel, {
+ url: 'api/imagecache/config',
title: _('Image cache'),
iconCls: 'imgcacheconf',
- border: false,
- autoScroll: true,
- bodyStyle: 'padding:15px',
- layout: 'form',
- items: [imagecache_form],
- tbar: _tbar
- });
-
- tvheadend.paneladd(panel, mpanel, index);
-
- mpanel.on('render', function() {
- imagecache_form.getForm().load({
- url: 'api/imagecache/config/load',
- success: function(form, action) {
- imagecache_form.enable();
- },
- failure: function(form, action) {
- alert(_("FAILED"));
- }
- });
+ tabIndex: index,
+ comet: 'imagecache',
+ tbar: [cleanButton],
+ help: function() {
+ new tvheadend.help(_('General Configuration'), 'config_general.html');
+ }
});
- function saveChanges(params) {
- if (imagecache_form)
- imagecache_form.getForm().submit({
- url: 'api/imagecache/config/save',
- params: params || {},
- waitMsg: _('Saving data...'),
- failure: function(form, action) {
- Ext.Msg.alert(_('Imagecache save failed'), action.result.errormsg);
- }
- });
- }
-
- function cleanImagecache() {
- saveChanges({'clean': 1});
- }
};
/*
tvheadend.paneladd(panel, dpanel, conf.tabIndex);
tvheadend.panelreg(panel, dpanel, builder, destroyer);
};
+
+/*
+ * Simple Node Editor
+ */
+tvheadend.idnode_simple = function(panel, conf)
+{
+ var mpanel = null;
+ var update_cb = null;
+
+ function update() {
+ if (update_cb)
+ update_cb(1);
+ }
+
+ function builder() {
+ if (mpanel)
+ return;
+
+ if (conf.builder)
+ conf.builder(conf);
+
+ var buttons = [];
+ var abuttons = {};
+ var current = null;
+
+ /* Top bar */
+ abuttons.save = new Ext.Toolbar.Button({
+ tooltip: _('Save pending changes (marked with red border)'),
+ iconCls: 'save',
+ text: _('Save'),
+ disabled: true,
+ handler: function() {
+ var node = current.getForm().getFieldValues();
+ tvheadend.Ajax({
+ url: conf.url + '/save',
+ params: {
+ node: Ext.encode(node)
+ },
+ success: function() {
+ roweditor_destroy();
+ form_load(true);
+ }
+ });
+ }
+ });
+ buttons.push(abuttons.save);
+ abuttons.undo = new Ext.Toolbar.Button({
+ tooltip: _('Revert pending changes (marked with red border)'),
+ iconCls: 'undo',
+ text: _('Undo'),
+ disabled: true,
+ handler: function() {
+ if (current)
+ current.getForm().reset();
+ }
+ });
+ buttons.push(abuttons.undo);
+
+ /* Extra buttons */
+ if (conf.tbar) {
+ buttons.push('-');
+ for (i = 0; i < conf.tbar.length; i++) {
+ var t = conf.tbar[i];
+ if (t.name && t.builder) {
+ var b = t.builder();
+ if (t.callback) {
+ b.callback = t.callback;
+ b.handler = function(b, e) {
+ this.callback(this, e);
+ }
+ }
+ abuttons[t.name] = b;
+ buttons.push(b);
+ } else if (t.name)
+ buttons.push(t.name);
+ }
+ }
+
+ /* Help */
+ if (conf.help) {
+ buttons.push('->');
+ buttons.push({
+ text: _('Help'),
+ iconCls: 'help',
+ handler: conf.help
+ });
+ }
+
+ function form_build(d) {
+
+ fpanel = new Ext.form.FormPanel({
+ //title: conf.title || null,
+ frame: true,
+ border: true,
+ bodyStyle: 'padding: 5px',
+ labelAlign: 'left',
+ labelWidth: conf.labelWidth || 300,
+ autoWidth: false,
+ autoHeight: false,
+ width: conf.nowidth ? null : (conf.width || 730),
+ defaultType: 'textfield',
+ buttonAlign: 'left',
+ autoScroll: true
+ });
+
+ tvheadend.idnode_editor_form(d.props || d.params, d.meta,
+ fpanel, { showpwd: conf.showpwd });
+
+ return fpanel;
+
+ }
+
+ function form_destroy() {
+ if (current)
+ mpanel.remove(current);
+ current = null;
+ }
+
+ function form_load(force) {
+ if (!force && current)
+ return;
+ var params = conf.edit ? (conf.edit.params || {}) : {};
+ params.uuid = r.id;
+ params.meta = 1;
+ tvheadend.Ajax({
+ url: conf.url + '/load',
+ params: params,
+ success: function(d) {
+ d = json_decode(d);
+ form_destroy();
+ current = new form_build(d[0]);
+ abuttons.save.setDisabled(false);
+ abuttons.undo.setDisabled(false);
+ if (abuttons.del)
+ abuttons.del.setDisabled(false);
+ mpanel.add(current);
+ mpanel.doLayout();
+ }
+ });
+ }
+
+ var mpanel = new Ext.Panel({
+ tbar: buttons,
+ layout: 'hbox',
+ padding: 5,
+ border: false,
+ layoutConfig: {
+ align: 'stretch'
+ }
+ });
+
+ dpanel.add(mpanel);
+ dpanel.doLayout(false, true);
+
+ if (conf.comet) {
+ update_cb = form_load;
+ tvheadend.comet.on(conf.comet, update);
+ }
+
+ form_load(true);
+ }
+
+ function destroyer() {
+ if (mpanel === null || !tvheadend.dynamic)
+ return;
+ if (conf.comet) {
+ update_cb = null;
+ tvheadend.comet.un(conf.comet, update);
+ }
+ dpanel.removeAll(true);
+ mpanel = null;
+ if (conf.destroyer)
+ conf.destroyer(conf);
+ }
+
+ var dpanel = new Ext.Panel({
+ border: false,
+ header: false,
+ layout: 'fit',
+ title: conf.title || '',
+ iconCls: conf.iconCls || ''
+ });
+
+ tvheadend.paneladd(panel, dpanel, conf.tabIndex);
+ tvheadend.panelreg(panel, dpanel, builder, destroyer);
+};