diff --git a/channels.c b/channels.c index d1292161..a3b38a59 100644 --- a/channels.c +++ b/channels.c @@ -45,7 +45,11 @@ struct channel_list channels_not_xmltv_mapped; struct channel_tree channel_name_tree; static struct channel_tree channel_identifier_tree; -static struct channel_tag_queue channel_tags; +struct channel_tag_queue channel_tags; + +static void channel_tag_map(channel_t *ch, channel_tag_t *ct, int check); +static channel_tag_t *channel_tag_find(const char *id, int create); +static void channel_tag_mapping_destroy(channel_tag_mapping_t *ctm); static int dictcmp(const char *a, const char *b) @@ -212,6 +216,9 @@ channel_load_one(htsmsg_t *c, int id) channel_t *ch; const char *s; const char *name = htsmsg_get_str(c, "name"); + htsmsg_t *tags; + htsmsg_field_t *f; + channel_tag_t *ct; if(name == NULL) return; @@ -238,6 +245,15 @@ channel_load_one(htsmsg_t *c, int id) LIST_INSERT_HEAD(&ch->ch_xc->xc_channels, ch, ch_xc_link); else LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link); + + if((tags = htsmsg_get_array(c, "tags")) != NULL) { + HTSMSG_FOREACH(f, tags) { + if(f->hmf_type == HMF_STR && + (ct = channel_tag_find(f->hmf_str, 0)) != NULL) { + channel_tag_map(ch, ct, 1); + } + } + } } @@ -267,6 +283,8 @@ static void channel_save(channel_t *ch) { htsmsg_t *m = htsmsg_create(); + htsmsg_t *tags; + channel_tag_mapping_t *ctm; lock_assert(&global_lock); @@ -282,6 +300,12 @@ channel_save(channel_t *ch) val2str(ch->ch_commercial_detection, commercial_detect_tab) ?: "?"); + tags = htsmsg_create_array(); + LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) + htsmsg_add_str(tags, NULL, ctm->ctm_tag->ct_identifier); + + htsmsg_add_msg(m, "tags", tags); + hts_settings_save(m, "channels/%d", ch->ch_id); htsmsg_destroy(m); } @@ -323,9 +347,13 @@ channel_delete(channel_t *ch) { th_transport_t *t; th_subscription_t *s; + channel_tag_mapping_t *ctm; lock_assert(&global_lock); + while((ctm = LIST_FIRST(&ch->ch_ctms)) != NULL) + channel_tag_mapping_destroy(ctm); + tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" deleted", ch->ch_name); @@ -428,6 +456,100 @@ channel_set_xmltv_source(channel_t *ch, xmltv_channel_t *xc) channel_save(ch); } +/** + * + */ +void +channel_set_tags_from_list(channel_t *ch, const char *maplist) +{ + channel_tag_mapping_t *ctm, *n; + channel_tag_t *ct; + char buf[40]; + int i, change = 0; + + lock_assert(&global_lock); + + LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) + ctm->ctm_mark = 1; /* Mark for delete */ + + while(*maplist) { + for(i = 0; i < sizeof(buf) - 1; i++) { + buf[i] = *maplist; + if(buf[i] == 0) + break; + + maplist++; + if(buf[i] == ',') { + break; + } + } + + buf[i] = 0; + if((ct = channel_tag_find(buf, 0)) == NULL) + continue; + + LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) + if(ctm->ctm_tag == ct) { + ctm->ctm_mark = 0; + break; + } + + if(ctm == NULL) { + /* Need to create mapping */ + change = 1; + channel_tag_map(ch, ct, 0); + } + } + + for(ctm = LIST_FIRST(&ch->ch_ctms); ctm != NULL; ctm = n) { + n = LIST_NEXT(ctm, ctm_channel_link); + if(ctm->ctm_mark) { + change = 1; + channel_tag_mapping_destroy(ctm); + } + } + + if(change) + channel_save(ch); +} + + + + +/** + * + */ +static void +channel_tag_map(channel_t *ch, channel_tag_t *ct, int check) +{ + channel_tag_mapping_t *ctm; + + if(check) { + LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) + if(ctm->ctm_tag == ct) + return; + + LIST_FOREACH(ctm, &ct->ct_ctms, ctm_tag_link) + if(ctm->ctm_channel == ch) + return; + } + + 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; +} /** @@ -467,7 +589,7 @@ channel_tag_find(const char *id, int create) snprintf(buf, sizeof(buf), "%d", tally); id = buf; } else { - tally = atoi(id); + tally = MAX(atoi(id), tally); } ct->ct_identifier = strdup(id); @@ -485,9 +607,13 @@ static void channel_tag_destroy(channel_tag_t *ct) { channel_tag_mapping_t *ctm; + channel_t *ch; - while((ctm = LIST_FIRST(&ct->ct_ctms)) != NULL) + while((ctm = LIST_FIRST(&ct->ct_ctms)) != NULL) { + ch = ctm->ctm_channel; channel_tag_mapping_destroy(ctm); + channel_save(ch); + } free(ct->ct_identifier); free(ct->ct_name); diff --git a/channels.h b/channels.h index 49ad4750..27d5944f 100644 --- a/channels.h +++ b/channels.h @@ -22,6 +22,9 @@ LIST_HEAD(channel_tag_mapping_list, channel_tag_mapping); TAILQ_HEAD(channel_tag_queue, channel_tag); +extern struct channel_tag_queue channel_tags; + + /* * Channel definition */ @@ -88,6 +91,8 @@ typedef struct channel_tag_mapping { LIST_ENTRY(channel_tag_mapping) ctm_tag_link; channel_tag_t *ctm_tag; + int ctm_mark; + } channel_tag_mapping_t; @@ -113,6 +118,8 @@ void channel_set_icon(channel_t *ch, const char *icon); struct xmltv_channel; void channel_set_xmltv_source(channel_t *ch, struct xmltv_channel *xc); +void channel_set_tags_from_list(channel_t *ch, const char *maplist); + extern struct channel_list channels_not_xmltv_mapped; #endif /* CHANNELS_H */ diff --git a/webui/extjs.c b/webui/extjs.c index 643a30d6..a29eb183 100644 --- a/webui/extjs.c +++ b/webui/extjs.c @@ -566,6 +566,8 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) th_transport_t *t; int reloadchlist = 0; htsmsg_t *out, *array, *r; + channel_tag_mapping_t *ctm; + char buf[200]; pthread_mutex_lock(&global_lock); @@ -584,6 +586,14 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) if(ch->ch_xc != NULL) htsmsg_add_str(r, "xmltvchannel", ch->ch_xc->xc_displayname); + buf[0] = 0; + LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%s%s", strlen(buf) == 0 ? "" : ",", + ctm->ctm_tag->ct_identifier); + } + htsmsg_add_str(r, "tags", buf); + out = json_single_record(r, "channels"); } else if(!strcmp(op, "gettransports")) { @@ -620,6 +630,9 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) } else if(!strcmp(op, "save")) { + if((s = http_arg_get(&hc->hc_req_args, "tags")) != NULL) + channel_set_tags_from_list(ch, s); + s = http_arg_get(&hc->hc_req_args, "xmltvchannel"); channel_set_xmltv_source(ch, s?xmltv_channel_find_by_displayname(s):NULL); @@ -699,6 +712,52 @@ extjs_xmltv(http_connection_t *hc, const char *remain, void *opaque) } + +/** + * + */ +static int +extjs_channeltags(http_connection_t *hc, const char *remain, void *opaque) +{ + htsbuf_queue_t *hq = &hc->hc_reply; + const char *op = http_arg_get(&hc->hc_req_args, "op"); + htsmsg_t *out, *array, *e; + channel_tag_t *ct; + + pthread_mutex_lock(&global_lock); + + if(!strcmp(op, "listTags")) { + + out = htsmsg_create(); + array = htsmsg_create_array(); + + TAILQ_FOREACH(ct, &channel_tags, ct_link) { + if(!ct->ct_enabled) + continue; + + e = htsmsg_create(); + htsmsg_add_str(e, "identifier", ct->ct_identifier); + htsmsg_add_str(e, "name", ct->ct_name); + htsmsg_add_msg(array, NULL, e); + } + + htsmsg_add_msg(out, "entries", array); + + } else { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_BAD_REQUEST; + } + + pthread_mutex_unlock(&global_lock); + + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + http_output_content(hc, "text/x-json; charset=UTF-8"); + return 0; + +} + + /** * WEB user interface */ @@ -713,4 +772,5 @@ extjs_start(void) http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE); http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE); http_path_add("/xmltv", NULL, extjs_xmltv, ACCESS_WEB_INTERFACE); + http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE); } diff --git a/webui/static/app/chconf.js b/webui/static/app/chconf.js index d3d91eeb..e72c2269 100644 --- a/webui/static/app/chconf.js +++ b/webui/static/app/chconf.js @@ -1,3 +1,17 @@ + +/** + * Channel tags + */ + +tvheadend.channelTags = new Ext.data.JsonStore({ + autoLoad:true, + root:'entries', + fields: [{name: 'identifier'}, {name: 'name'}], + url:'channeltags', + baseParams: {op: 'listTags'} +}); + + /** * Channel details */ @@ -89,7 +103,7 @@ tvheadend.channeldetails = function(chid, chname) { var confreader = new Ext.data.JsonReader({ root: 'channels', - }, ['name', 'comdetect','xmltvchannel']); + }, ['name','xmltvchannel','tags']); var xmltvChannels = new Ext.data.JsonStore({ @@ -99,6 +113,7 @@ tvheadend.channeldetails = function(chid, chname) { baseParams: {op: 'listChannels'} }); + var confpanel = new Ext.FormPanel({ border:false, disabled:true, @@ -108,7 +123,6 @@ tvheadend.channeldetails = function(chid, chname) { labelWidth: 150, waitMsgTarget: true, reader: confreader, - // defaultType: 'textfield', items: [{ layout:'column', @@ -118,68 +132,40 @@ tvheadend.channeldetails = function(chid, chname) { columnWidth:.5, layout: 'form', defaultType: 'textfield', + items: [ + { + fieldLabel: 'Channel name', + name: 'name', + },new Ext.form.ComboBox({ + loadingText: 'Loading...', + fieldLabel: 'XML-TV Source', + name: 'xmltvchannel', + width: 300, + displayField:'xcTitle', + valueField:'xcTitle', + store: xmltvChannels, + forceSelection: true, + mode: 'remote', + editable: false, + triggerAction: 'all', + emptyText: 'None' + }) + ] + },{ + border:false, + columnWidth:.5, + layout: 'form', items: [{ - - fieldLabel: 'Channel name', - name: 'name', - }, - new Ext.form.ComboBox({ - loadingText: 'Loading...', - fieldLabel: 'XML-TV Source', - name: 'xmltvchannel', - width: 300, - displayField:'xcTitle', - valueField:'xcTitle', - store: xmltvChannels, - forceSelection: true, - mode: 'remote', - editable: false, - triggerAction: 'all', - emptyText: 'None' - }) - /* - , - new Ext.form.ComboBox({ - allowBlank: false, - fieldLabel: 'Commercial detection', - name: 'comdetect', - displayField:'mode', - valueField:'imode', - mode: 'local', - triggerAction: 'all', - selectOnFocus:true, - editable:false, - store: new Ext.data.SimpleStore({ - fields: ['imode', 'mode'], - data: [ - ['none', 'None'], - ['tt192', 'Teletext page 192']] - }) - }) - */ - ] - } - /* - ,{ - columnWidth:.5, - layout: 'form', - items: [{ - xtype: 'checkboxgroup', - fieldLabel: 'Tags', - itemCls: 'x-check-group-alt', - columns: 1, - vertical: true, - items: [{ - boxLabel: 'Favourites', name: 'favourite'},{ - boxLabel: 'Sports', name: 'sports'},{ - boxLabel: 'News', name: 'news'},{ - boxLabel: 'Movies', name: 'movies'},{ - boxLabel: 'Children', name: 'children'} - ] - } - ] - } */ - ] + fieldLabel: 'Tags', + xtype:"multiselect", + name:"tags", + valueField:"identifier", + displayField:"name", + width:250, + height:200, + store:tvheadend.channelTags, + }] + }] }] }); diff --git a/webui/static/app/tvheadend.js b/webui/static/app/tvheadend.js index f5aaa734..581efdc8 100644 --- a/webui/static/app/tvheadend.js +++ b/webui/static/app/tvheadend.js @@ -14,6 +14,12 @@ tvheadend.comet_poller = function() { var m = response.messages[x]; switch(m.notificationClass) { + case 'channeltags': + if(m.reload != null) { + tvheadend.channelTags.reload(); + } + break; + case 'logmessage': var sl = Ext.get('systemlog');