From b2ca635b440e28ed878f72faa55990801abbce48 Mon Sep 17 00:00:00 2001 From: "E.Smith" <31170571+azlm8t@users.noreply.github.com> Date: Tue, 5 Dec 2017 20:11:10 +0000 Subject: [PATCH] ui: Allow filtering/autorec from EPG by category. (#4777). If we have categories on the server (from xmltv) then we create a second toolbar on the EPG and add filters for filtering by category. These are then included in the autorec rule created from the EPG. We use a second toolbar since the primary toolbar is a too cramped to fit more search drop-down boxes. Issue: #4777. --- src/api/api_epg.c | 9 ++++ src/dvr/dvr_db.c | 6 +++ src/epg.c | 26 ++++++++++ src/epg.h | 3 ++ src/webui/static/app/epg.js | 99 +++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+) diff --git a/src/api/api_epg.c b/src/api/api_epg.c index 94b4a52ac..60847c5d7 100644 --- a/src/api/api_epg.c +++ b/src/api/api_epg.c @@ -349,6 +349,15 @@ api_epg_grid str = htsmsg_get_str(args, "channelTag"); if (str) eq.channel_tag = strdup(str); + str = htsmsg_get_str(args, "cat1"); + if (str) + eq.cat1 = strdup(str); + str = htsmsg_get_str(args, "cat2"); + if (str) + eq.cat2 = strdup(str); + str = htsmsg_get_str(args, "cat3"); + if (str) + eq.cat3 = strdup(str); if (mode != NULL) { if (!strcmp(mode, "now")) { diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index f548a4ca7..f60563e18 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1024,6 +1024,12 @@ dvr_entry_create_(int enabled, const char *config_uuid, epg_broadcast_t *e, { htsmsg_add_str(conf, "autorec", idnode_uuid_as_str(&dae->dae_id, ubuf)); htsmsg_add_str(conf, "directory", dae->dae_directory ?: ""); + if (dae->dae_cat1 && *dae->dae_cat1) + htsmsg_add_str(conf, "cat1", dae->dae_cat1); + if (dae->dae_cat2 && *dae->dae_cat2) + htsmsg_add_str(conf, "cat2", dae->dae_cat2); + if (dae->dae_cat3 && *dae->dae_cat3) + htsmsg_add_str(conf, "cat3", dae->dae_cat3); } if (dte) { diff --git a/src/epg.c b/src/epg.c index 01a5589bf..9977f5784 100644 --- a/src/epg.c +++ b/src/epg.c @@ -2917,6 +2917,28 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e ) if (_eq_comp_num(&eq->channel_num, channel_get_number(e->channel))) return; if (eq->channel_name.comp != EC_NO) if (_eq_comp_str(&eq->channel_name, channel_get_name(e->channel, NULL))) return; + if (eq->cat1 && *eq->cat1) { + /* No category? Can't match our requested category */ + if (!e->category) + return; + if (!string_list_contains_string(e->category, eq->cat1)) + return; + } + if (eq->cat2 && *eq->cat2) { + /* No category? Can't match our requested category */ + if (!e->category) + return; + if (!string_list_contains_string(e->category, eq->cat2)) + return; + } + if (eq->cat3 && *eq->cat3) { + /* No category? Can't match our requested category */ + if (!e->category) + return; + if (!string_list_contains_string(e->category, eq->cat3)) + return; + } + if (eq->genre_count) { epg_genre_t genre; uint32_t i, r = 0; @@ -3277,6 +3299,10 @@ fin: free(eq->channel); eq->channel = NULL; free(eq->channel_tag); eq->channel_tag = NULL; free(eq->stitle); eq->stitle = NULL; + free(eq->cat1); eq->cat1 = NULL; + free(eq->cat2); eq->cat2 = NULL; + free(eq->cat3); eq->cat3 = NULL; + if (eq->genre != eq->genre_static) free(eq->genre); eq->genre = NULL; diff --git a/src/epg.h b/src/epg.h index c1d5aa862..7ce6a4205 100644 --- a/src/epg.h +++ b/src/epg.h @@ -697,6 +697,9 @@ typedef struct epg_query { uint32_t genre_count; uint8_t *genre; uint8_t genre_static[16]; + char *cat1; + char *cat2; + char *cat3; enum { ESK_START, diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js index 68085e261..76ba7ee01 100644 --- a/src/webui/static/app/epg.js +++ b/src/webui/static/app/epg.js @@ -10,6 +10,27 @@ tvheadend.ContentGroupStore = tvheadend.idnode_get_enum({ } }); +insertCategoryClearOption = function( scope, records, options ){ + var placeholder = Ext.data.Record.create(['key', 'val']); + scope.insert(0,new placeholder({key: '-1', val: _('(Clear filter)')})); +}; + +tvheadend.category = tvheadend.idnode_get_enum({ + url: 'api/channelcategory/list', + event: 'channelcategory', + listeners: { + 'load': function(scope, records, options) { + insertCategoryClearOption(scope, records, options); + // If we have categories then we create the category + // search toolbar. + if (records.length) { + tvheadend.createToolbar2(); + } + } + } +}); + + tvheadend.contentGroupLookupName = function(code) { ret = ""; if (!code) @@ -774,6 +795,59 @@ tvheadend.epg = function() { }); + /// "cat" is the name of the category field. + /// We have to pass the name, not the field, since the + /// field is deleted and re-created inside clear filter. + function createFilterCat(clearFilter, cat) { + var filter = new Ext.form.ComboBox({ + loadingText: _('Loading...'), + width: 200, + displayField: 'val', + store: tvheadend.category, + mode: 'local', + editable: true, + forceSelection: true, + triggerAction: 'all', + typeAhead: true, + emptyText: _('Filter category...'), + listeners: { + blur: function () { + if(this.getRawValue() == "" ) { + clearFilter(); + epgView.reset(); + } + } + } + }); + filter.on('select', function(c, r) { + if (r.data.key == -1) + clearFilter(); + else if (epgStore.baseParams[cat] !== r.data.key) + epgStore.baseParams[cat] = r.data.key; + epgView.reset(); + }); + return filter; + } + + clearCat1Filter = function() { + delete epgStore.baseParams.cat1; + epgFilterCat1.setValue(""); + } + + clearCat2Filter = function() { + delete epgStore.baseParams.cat2; + epgFilterCat2.setValue(""); + } + + clearCat3Filter = function() { + delete epgStore.baseParams.cat3; + epgFilterCat3.setValue(""); + } + + var epgFilterCat1 = createFilterCat(clearCat1Filter, "cat1"); + var epgFilterCat2 = createFilterCat(clearCat2Filter, "cat2"); + var epgFilterCat3 = createFilterCat(clearCat3Filter, "cat3"); + // Content groups var epgFilterContentGroup = new Ext.form.ComboBox({ @@ -867,6 +941,9 @@ tvheadend.epg = function() { clearChannelTagsFilter(); clearDurationFilter(); clearContentGroupFilter(); + clearCat1Filter(); + clearCat2Filter(); + clearCat3Filter(); filter.clearFilters(); delete epgStore.sortInfo; epgView.reset(); @@ -1043,6 +1120,18 @@ tvheadend.epg = function() { epgView.reset(); }); + /* Extra toolbar. Only created if we have categories on the server */ + tvheadend.createToolbar2 = function() { + var tbar2 = new Ext.Toolbar({ + items: [ + epgFilterCat1, '-', + epgFilterCat2, '-', + epgFilterCat3, '-', + ] + }); + panel.add(tbar2); + } + /** * Listener for EPG and DVR notifications. * We want to update the EPG grid when a recording is finished/deleted etc. @@ -1155,6 +1244,12 @@ tvheadend.epg = function() { var duration = epgStore.baseParams.durationMin ? tvheadend.durationLookupRange(epgStore.baseParams.durationMin) : "" + _("Don't care") + ""; + var cat1 = epgStore.baseParams.cat1 ? + '
' + _('Category') + ':
' + epgStore.baseParams.cat1 + '
' : ""; + var cat2 = epgStore.baseParams.cat2 ? + '
' + _('Category') + ':
' + epgStore.baseParams.cat2 + '
' : ""; + var cat3 = epgStore.baseParams.cat3 ? + '
' + _('Category') + ':
' + epgStore.baseParams.cat3 + '
' : ""; Ext.MessageBox.confirm(_('Auto Recorder'), _('This will create an automatic rule that ' + 'continuously scans the EPG for programs ' @@ -1164,6 +1259,7 @@ tvheadend.epg = function() { + '
' + _('Tag') + ':
' + tag + '
' + '
' + _('Genre') + ':
' + contentType + '
' + '
' + _('Duration') + ':
' + duration + '
' + + cat1 + cat2 + cat3 + '

' + sprintf(_('Currently this will match (and record) %d events.'), epgStore.getTotalCount()) + ' ' + 'Are you sure?', @@ -1187,6 +1283,9 @@ tvheadend.epg = function() { if (params.contentType) conf.content_type = params.contentType; if (params.durationMin) conf.minduration = params.durationMin; if (params.durationMax) conf.maxduration = params.durationMax; + if (params.cat1) conf.cat1 = params.cat1; + if (params.cat2) conf.cat2 = params.cat2; + if (params.cat3) conf.cat3 = params.cat3; Ext.Ajax.request({ url: 'api/dvr/autorec/create', params: { conf: Ext.encode(conf) } -- 2.47.3