diff --git a/debian/changelog b/debian/changelog index 06b7670d..693d518f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -33,6 +33,9 @@ hts-tvheadend (2.11-WIP) hts; urgency=low CWC entries were not correctly updated when a connection was established or lost. This is now fixed. Ticket #144 + * Add support for prioritized recordings. The user can chose among five + different priorities. + hts-tvheadend (2.10) hts; urgency=high * Fix a crash in HTSP server. diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 9b1fe306..fc436a28 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -44,6 +44,15 @@ extern struct dvr_entry_list dvrentries; #define DVR_DIR_PER_TITLE 0x40 #define DVR_EPISODE_IN_TITLE 0x80 +typedef enum { + DVR_PRIO_IMPORTANT, + DVR_PRIO_HIGH, + DVR_PRIO_NORMAL, + DVR_PRIO_LOW, + DVR_PRIO_UNIMPORTANT, +} dvr_prio_t; + + LIST_HEAD(dvr_rec_stream_list, dvr_rec_stream); @@ -93,6 +102,8 @@ typedef struct dvr_entry { date and time pre/post/fixed */ char *de_desc; /* Description in UTF-8 (from EPG) */ + dvr_prio_t de_pri; + epg_episode_t de_episode; char *de_error; @@ -165,6 +176,8 @@ typedef struct dvr_autorec_entry { channel_tag_t *dae_channel_tag; LIST_ENTRY(dvr_autorec_entry) dae_channel_tag_link; + dvr_prio_t dae_pri; + struct dvr_entry_list dae_spawns; } dvr_autorec_entry_t; @@ -181,7 +194,7 @@ dvr_entry_t *dvr_entry_create_by_event(event_t *e, const char *creator, dvr_entry_t *dvr_entry_create(channel_t *ch, time_t start, time_t stop, const char *title, const char *description, const char *creator, dvr_autorec_entry_t *dae, - epg_episode_t *ee); + epg_episode_t *ee, dvr_prio_t pri); void dvr_init(void); @@ -241,4 +254,11 @@ void autorec_destroy_by_channel(channel_t *ch); dvr_autorec_entry_t *autorec_entry_find(const char *id, int create); +/** + * + */ +dvr_prio_t dvr_pri2val(const char *s); + +const char *dvr_val2pri(dvr_prio_t v); + #endif /* DVR_H */ diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index 0e5daa90..940f7a65 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -137,6 +137,7 @@ autorec_entry_find(const char *id, int create) tally = MAX(atoi(id), tally); } dae->dae_weekdays = 0x7f; + dae->dae_pri = DVR_PRIO_NORMAL; dae->dae_id = strdup(id); TAILQ_INSERT_TAIL(&autorec_entries, dae, dae_link); @@ -235,6 +236,8 @@ autorec_record_build(dvr_autorec_entry_t *dae) build_weekday_tags(str, sizeof(str), dae->dae_weekdays); htsmsg_add_str(e, "weekdays", str); + htsmsg_add_str(e, "pri", dvr_val2pri(dae->dae_pri)); + return e; } @@ -340,6 +343,9 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values, if(!htsmsg_get_u32(values, "enabled", &u32)) dae->dae_enabled = u32; + if((s = htsmsg_get_str(values, "pri")) != NULL) + dae->dae_pri = dvr_pri2val(s); + dvr_autorec_changed(dae); return autorec_record_build(dae); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index ec5d2361..a3abee7f 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -49,7 +49,6 @@ static void dvr_timer_expire(void *aux); static void dvr_timer_start_recording(void *aux); - /** * */ @@ -149,7 +148,7 @@ dvr_entry_t * dvr_entry_create(channel_t *ch, time_t start, time_t stop, const char *title, const char *description, const char *creator, dvr_autorec_entry_t *dae, - epg_episode_t *ee) + epg_episode_t *ee, dvr_prio_t pri) { dvr_entry_t *de; char tbuf[30]; @@ -168,6 +167,7 @@ dvr_entry_create(channel_t *ch, time_t start, time_t stop, de->de_start = start; de->de_stop = stop; + de->de_pri = pri; if (ch->ch_dvr_extra_time_pre) de->de_start_extra = ch->ch_dvr_extra_time_pre; else @@ -219,7 +219,8 @@ dvr_entry_create_by_event(event_t *e, const char *creator, return NULL; return dvr_entry_create(e->e_channel, e->e_start, e->e_stop, - e->e_title, e->e_desc, creator, dae, &e->e_episode); + e->e_title, e->e_desc, creator, dae, &e->e_episode, + dae->dae_pri); } @@ -326,8 +327,7 @@ dvr_db_load_one(htsmsg_t *c, int id) de->de_stop = stop; de->de_creator = strdup(creator); de->de_title = strdup(title); - - + de->de_pri = dvr_pri2val(htsmsg_get_str(c, "pri")); if(htsmsg_get_s32(c, "start_extra", &d)) de->de_start_extra = dvr_extra_time_pre; @@ -339,6 +339,7 @@ dvr_db_load_one(htsmsg_t *c, int id) else de->de_stop_extra = d; + tvh_str_set(&de->de_desc, htsmsg_get_str(c, "description")); tvh_str_set(&de->de_filename, htsmsg_get_str(c, "filename")); tvh_str_set(&de->de_error, htsmsg_get_str(c, "error")); @@ -416,6 +417,7 @@ dvr_entry_save(dvr_entry_t *de) if(de->de_desc != NULL) htsmsg_add_str(m, "description", de->de_desc); + htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri)); if(de->de_error != NULL) htsmsg_add_str(m, "error", de->de_error); @@ -875,3 +877,28 @@ dvr_get_filesize(dvr_entry_t *de) return st.st_size; } + + + +/** + * + */ +static struct strtab priotab[] = { + { "important", DVR_PRIO_IMPORTANT }, + { "high", DVR_PRIO_HIGH }, + { "normal", DVR_PRIO_NORMAL }, + { "low", DVR_PRIO_LOW }, + { "unimportant", DVR_PRIO_UNIMPORTANT }, +}; + +dvr_prio_t +dvr_pri2val(const char *s) +{ + return str2val_def(s, priotab, DVR_PRIO_NORMAL); +} + +const char * +dvr_val2pri(dvr_prio_t v) +{ + return val2str(v, priotab) ?: "invalid"; +} diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index d41ebad6..e7300386 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -52,6 +52,15 @@ static void dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt); static void dvr_spawn_postproc(dvr_entry_t *de); static void dvr_thread_epilog(dvr_entry_t *de); + +const static int prio2weight[5] = { + [DVR_PRIO_IMPORTANT] = 500, + [DVR_PRIO_HIGH] = 400, + [DVR_PRIO_NORMAL] = 300, + [DVR_PRIO_LOW] = 200, + [DVR_PRIO_UNIMPORTANT] = 100, +}; + /** * */ @@ -59,6 +68,7 @@ void dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; + int weight; assert(de->de_s == NULL); @@ -68,8 +78,13 @@ dvr_rec_subscribe(dvr_entry_t *de) pthread_create(&de->de_thread, NULL, dvr_thread, de); - de->de_s = subscription_create_from_channel(de->de_channel, 1000, buf, - &de->de_sq.sq_st, 0); + if(de->de_pri) + weight = prio2weight[de->de_pri]; + else + weight = 300; + + de->de_s = subscription_create_from_channel(de->de_channel, weight, + buf, &de->de_sq.sq_st, 0); } /** diff --git a/src/hts_strtab.h b/src/hts_strtab.h index b068b991..f85f7fa6 100644 --- a/src/hts_strtab.h +++ b/src/hts_strtab.h @@ -42,6 +42,26 @@ str2val0(const char *str, struct strtab tab[], int l) #define str2val(str, tab) str2val0(str, tab, sizeof(tab) / sizeof(tab[0])) + + +static int str2val0_def(const char *str, struct strtab tab[], int l, int def) + __attribute((unused)); + +static int +str2val0_def(const char *str, struct strtab tab[], int l, int def) +{ + int i; + if(str) + for(i = 0; i < l; i++) + if(!strcasecmp(str, tab[i].str)) + return tab[i].val; + return def; +} + +#define str2val_def(str, tab, def) \ + str2val0_def(str, tab, sizeof(tab) / sizeof(tab[0]), def) + + static const char * val2str0(int val, struct strtab tab[], int l) __attribute__((unused)); diff --git a/src/htsp.c b/src/htsp.c index 0f55a541..77538c32 100644 --- a/src/htsp.c +++ b/src/htsp.c @@ -766,7 +766,7 @@ htsp_method_subscribe(htsp_connection_t *htsp, htsmsg_t *in) LIST_INSERT_HEAD(&htsp->htsp_subscriptions, hs, hs_link); streaming_target_init(&hs->hs_input, htsp_streaming_input, hs, 0); - hs->hs_s = subscription_create_from_channel(ch, 500, htsp->htsp_logname, + hs->hs_s = subscription_create_from_channel(ch, 150, htsp->htsp_logname, &hs->hs_input, 0); return NULL; } diff --git a/src/rtsp.c b/src/rtsp.c index eec71245..eae6f580 100644 --- a/src/rtsp.c +++ b/src/rtsp.c @@ -490,7 +490,7 @@ rtsp_subscribe(http_connection_t *hc, rtsp_t *rtsp, char *components[5]; int nc; char *url = hc->hc_url; - int pri = 500; + int pri = 150; int subflags = 0; th_subscription_t *s; char urlprefix[128]; diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 347eb924..95bb6488 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -751,6 +751,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) const char *startstr = http_arg_get(&hc->hc_req_args, "starttime"); const char *stopstr = http_arg_get(&hc->hc_req_args, "stoptime"); const char *channel = http_arg_get(&hc->hc_req_args, "channelid"); + const char *pri = http_arg_get(&hc->hc_req_args, "pri"); channel_t *ch = channel ? channel_find_by_identifier(atoi(channel)) : NULL; @@ -781,7 +782,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) stop += 86400; dvr_entry_create(ch, start, stop, title, NULL, hc->hc_representative, - NULL, NULL); + NULL, NULL, dvr_pri2val(pri)); out = htsmsg_create_map(); htsmsg_add_u32(out, "success", 1); @@ -941,6 +942,8 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque) htsmsg_add_str(m, "creator", de->de_creator); + htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri)); + switch(de->de_sched_state) { case DVR_SCHEDULED: s = "Scheduled for recording"; diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index b307277d..61d36b05 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -13,6 +13,20 @@ tvheadend.weekdays = new Ext.data.SimpleStore({ ] }); + +// This should be loaded from tvheadend +tvheadend.dvrprio = new Ext.data.SimpleStore({ + fields: ['identifier', 'name'], + id: 0, + data: [ + ['important', 'Important'], + ['high', 'High'], + ['normal', 'Normal'], + ['low', 'Low'], + ['unimportant', 'Unimportant'], + ] +}); + /** * */ @@ -110,6 +124,10 @@ tvheadend.dvrschedule = function() { } } + function renderPri(value) { + return tvheadend.dvrprio.getById(value).data.name; + } + var dvrCm = new Ext.grid.ColumnModel([ { width: 250, @@ -117,10 +135,16 @@ tvheadend.dvrschedule = function() { header: "Title", dataIndex: 'title' },{ - width: 250, + width: 100, id:'episode', header: "Episode", dataIndex: 'episode' + },{ + width: 100, + id:'pri', + header: "Priority", + dataIndex: 'pri', + renderer: renderPri },{ width: 100, id:'start', @@ -215,6 +239,16 @@ tvheadend.dvrschedule = function() { increment: 10, format: 'H:i' }), + new Ext.form.ComboBox({ + store: tvheadend.dvrprio, + value: "normal", + triggerAction: 'all', + mode: 'local', + fieldLabel: 'Priority', + valueField: 'identifier', + displayField: 'name', + name: 'pri' + }), { allowBlank: false, fieldLabel: 'Title', @@ -363,6 +397,20 @@ tvheadend.autoreceditor = function() { valueField: 'identifier', displayField: 'name' }) + }, { + header: "Priority", + dataIndex: 'pri', + width: 100, + renderer: function(value, metadata, record, row, col, store) { + return tvheadend.dvrprio.getById(value).data.name; + }, + editor: new fm.ComboBox({ + store: tvheadend.dvrprio, + triggerAction: 'all', + mode: 'local', + valueField: 'identifier', + displayField: 'name' + }) },{ header: "Created by", dataIndex: 'creator', @@ -394,6 +442,7 @@ tvheadend.dvr = function() { {name: 'channel'}, {name: 'title'}, {name: 'episode'}, + {name: 'pri'}, {name: 'description'}, {name: 'chicon'}, {name: 'start', type: 'date', dateFormat: 'U' /* unix time */}, @@ -419,7 +468,7 @@ tvheadend.dvr = function() { tvheadend.autorecRecord = Ext.data.Record.create([ 'enabled','title','channel','tag','creator','contentgrp','comment', - 'weekdays' + 'weekdays', 'pri' ]);