* Fix various issues realted to the Recording Schedule user interface.

The UI now includes better visual feedback on what's going on and if
    there are errors. Also recordings that do not complete successfully
    are correctly reported as failed entries. Ticket #131
This commit is contained in:
Andreas Öman 2010-03-21 10:08:16 +00:00
parent bdb3e95818
commit d57985481c
17 changed files with 335 additions and 86 deletions

5
debian/changelog vendored
View file

@ -36,6 +36,11 @@ hts-tvheadend (2.11-WIP) hts; urgency=low
* Add support for prioritized recordings. The user can chose among five
different priorities.
* Fix various issues realted to the Recording Schedule user interface.
The UI now includes better visual feedback on what's going on and if
there are errors. Also recordings that do not complete successfully
are correctly reported as failed entries. Ticket #131
hts-tvheadend (2.10) hts; urgency=high
* Fix a crash in HTSP server.

View file

@ -65,6 +65,16 @@ typedef enum {
} dvr_entry_sched_state_t;
typedef enum {
DVR_RS_PENDING,
DVR_RS_WAIT_AUDIO_LOCK,
DVR_RS_WAIT_VIDEO_LOCK,
DVR_RS_RUNNING,
DVR_RS_COMMERCIAL,
DVR_RS_ERROR,
} dvr_rs_state_t;
typedef struct dvr_entry {
int de_refcnt; /* Modification is protected under global_lock */
@ -106,12 +116,29 @@ typedef struct dvr_entry {
epg_episode_t de_episode;
char *de_error;
uint32_t de_dont_reschedule;
/**
* Major State
*/
dvr_entry_sched_state_t de_sched_state;
uint32_t de_dont_reschedule;
/**
* Recording state (onyl valid if de_sched_state == DVR_RECORDING)
*/
dvr_rs_state_t de_rec_state;
/**
* Number of errors (only to be modified by the recording thread)
*/
uint32_t de_errors;
/**
* Last error, see SM_CODE_ defines
*/
uint32_t de_last_error;
/**
* Autorec linkage
*/
@ -140,13 +167,6 @@ typedef struct dvr_entry {
struct dvr_rec_stream_list de_streams;
AVFormatContext *de_fctx;
enum {
DE_RS_WAIT_AUDIO_LOCK = 0,
DE_RS_WAIT_VIDEO_LOCK,
DE_RS_RUNNING,
DE_RS_COMMERCIAL,
} de_rec_state;
int de_header_written;
} dvr_entry_t;
@ -186,6 +206,13 @@ typedef struct dvr_autorec_entry {
/**
* Prototypes
*/
void dvr_entry_notify(dvr_entry_t *de);
const char *dvr_entry_status(dvr_entry_t *de);
const char *dvr_entry_schedstatus(dvr_entry_t *de);
void dvr_entry_create_by_autorec(event_t *e, dvr_autorec_entry_t *dae);
dvr_entry_t *dvr_entry_create_by_event(event_t *e, const char *creator,
@ -205,7 +232,7 @@ void dvr_destroy_by_channel(channel_t *ch);
void dvr_rec_subscribe(dvr_entry_t *de);
void dvr_rec_unsubscribe(dvr_entry_t *de);
void dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode);
dvr_entry_t *dvr_entry_find_by_id(int id);

View file

@ -28,6 +28,7 @@
#include "dvr.h"
#include "notify.h"
#include "htsp.h"
#include "streaming.h"
char *dvr_storage;
char *dvr_format;
@ -48,6 +49,71 @@ static void dvr_entry_save(dvr_entry_t *de);
static void dvr_timer_expire(void *aux);
static void dvr_timer_start_recording(void *aux);
/**
* Return printable status for a dvr entry
*/
const char *
dvr_entry_status(dvr_entry_t *de)
{
switch(de->de_sched_state) {
case DVR_SCHEDULED:
return "Scheduled for recording";
case DVR_RECORDING:
switch(de->de_rec_state) {
case DVR_RS_PENDING:
return "Pending start";
case DVR_RS_WAIT_AUDIO_LOCK:
return "Waiting for audio lock";
case DVR_RS_WAIT_VIDEO_LOCK:
return "Waiting for video lock";
case DVR_RS_RUNNING:
return "Running";
case DVR_RS_COMMERCIAL:
return "Commercial break";
case DVR_RS_ERROR:
return streaming_code2txt(de->de_last_error);
default:
return "Invalid";
}
case DVR_COMPLETED:
if(de->de_last_error)
return streaming_code2txt(de->de_last_error);
else
return "Completed OK";
default:
return "Invalid";
}
}
/**
*
*/
const char *
dvr_entry_schedstatus(dvr_entry_t *de)
{
switch(de->de_sched_state) {
case DVR_SCHEDULED:
return "scheduled";
case DVR_RECORDING:
if(de->de_last_error)
return "recordingError";
else
return "recording";
case DVR_COMPLETED:
if(de->de_last_error)
return "completedError";
else
return "completed";
default:
return "unknown";
}
}
/**
*
@ -61,6 +127,21 @@ dvrdb_changed(void)
}
/**
*
*/
void
dvr_entry_notify(dvr_entry_t *de)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "updateEntry", 1);
htsmsg_add_u32(m, "id", de->de_id);
htsmsg_add_str(m, "status", dvr_entry_status(de));
htsmsg_add_str(m, "schedstate", dvr_entry_schedstatus(de));
notify_by_msg("dvrdb", m);
}
/**
*
@ -342,7 +423,9 @@ dvr_db_load_one(htsmsg_t *c, int id)
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"));
htsmsg_get_u32(c, "errorcode", &de->de_last_error);
htsmsg_get_u32(c, "errors", &de->de_errors);
htsmsg_get_u32(c, "noresched", &de->de_dont_reschedule);
@ -419,8 +502,11 @@ dvr_entry_save(dvr_entry_t *de)
htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri));
if(de->de_error != NULL)
htsmsg_add_str(m, "error", de->de_error);
if(de->de_last_error)
htsmsg_add_u32(m, "errorcode", de->de_last_error);
if(de->de_errors)
htsmsg_add_u32(m, "errors", de->de_errors);
htsmsg_add_u32(m, "noresched", de->de_dont_reschedule);
@ -457,21 +543,20 @@ dvr_timer_expire(void *aux)
*
*/
static void
dvr_stop_recording(dvr_entry_t *de, const char *errmsg)
dvr_stop_recording(dvr_entry_t *de, int stopcode)
{
dvr_rec_unsubscribe(de);
dvr_rec_unsubscribe(de, stopcode);
de->de_sched_state = DVR_COMPLETED;
tvh_str_set(&de->de_error, errmsg);
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": "
"End of program: %s",
de->de_title, de->de_channel->ch_name,
de->de_error ?: "Program ended");
streaming_code2txt(de->de_last_error) ?: "Program ended");
dvrdb_changed();
dvr_entry_save(de);
htsp_dvr_entry_update(de);
dvr_entry_notify(de);
gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de,
de->de_stop + dvr_retention_days * 86400);
@ -485,8 +570,7 @@ dvr_stop_recording(dvr_entry_t *de, const char *errmsg)
static void
dvr_timer_stop_recording(void *aux)
{
dvr_entry_t *de = aux;
dvr_stop_recording(de, NULL);
dvr_stop_recording(aux, 0);
}
@ -500,13 +584,13 @@ dvr_timer_start_recording(void *aux)
dvr_entry_t *de = aux;
de->de_sched_state = DVR_RECORDING;
de->de_rec_state = DVR_RS_PENDING;
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" recorder starting",
de->de_title, de->de_channel->ch_name);
dvrdb_changed();
dvr_entry_notify(de);
htsp_dvr_entry_update(de);
dvr_rec_subscribe(de);
gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de,
@ -556,7 +640,7 @@ dvr_entry_cancel(dvr_entry_t *de)
case DVR_RECORDING:
de->de_dont_reschedule = 1;
dvr_stop_recording(de, "Aborted by user");
dvr_stop_recording(de, SM_CODE_ABORTED);
return de;
case DVR_COMPLETED:
@ -576,7 +660,7 @@ static void
dvr_entry_purge(dvr_entry_t *de)
{
if(de->de_sched_state == DVR_RECORDING)
dvr_stop_recording(de, "Channel removed from system");
dvr_stop_recording(de, SM_CODE_SOURCE_DELETED);
dvr_entry_remove(de);
}

View file

@ -91,7 +91,7 @@ dvr_rec_subscribe(dvr_entry_t *de)
*
*/
void
dvr_rec_unsubscribe(dvr_entry_t *de)
dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
{
assert(de->de_s != NULL);
@ -101,6 +101,8 @@ dvr_rec_unsubscribe(dvr_entry_t *de)
pthread_join(de->de_thread, NULL);
de->de_s = NULL;
de->de_last_error = stopcode;
}
@ -277,6 +279,19 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
}
/**
*
*/
static void
dvr_rec_set_state(dvr_entry_t *de, dvr_rs_state_t newstate, int error)
{
de->de_rec_state = newstate;
de->de_last_error = error;
if(error)
de->de_errors++;
dvr_entry_notify(de);
}
/**
*
*/
@ -458,30 +473,71 @@ dvr_thread(void *aux)
pthread_mutex_lock(&global_lock);
dvr_rec_start(de, sm->sm_data);
de->de_header_written = 0;
de->de_rec_state = DE_RS_WAIT_AUDIO_LOCK;
dvr_rec_set_state(de, DVR_RS_WAIT_AUDIO_LOCK, 0);
pthread_mutex_unlock(&global_lock);
break;
case SMT_STOP:
tvhlog(sm->sm_code ? LOG_ERR : LOG_INFO,
"pvr", "Recording stopped: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
if(sm->sm_code == 0) {
/* Completed */
de->de_last_error = 0;
tvhlog(LOG_INFO,
"pvr", "Recording completed: \"%s\"",
de->de_filename ?: de->de_title);
} else {
if(de->de_last_error != sm->sm_code) {
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
"pvr", "Recording stopped: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
}
dvr_thread_epilog(de);
break;
case SMT_TRANSPORT_STATUS:
printf("TSS: %x\n", sm->sm_code);
if(sm->sm_code & TSS_PACKETS) {
} else if(sm->sm_code & (TSS_GRACEPERIOD | TSS_ERRORS)) {
dvr_rec_fatal_error(de, "Source problems: %s",
transport_tss2text(sm->sm_code));
int code = SM_CODE_UNDEFINED_ERROR;
if(sm->sm_code & TSS_NO_DESCRAMBLER)
code = SM_CODE_NO_DESCRAMBLER;
if(sm->sm_code & TSS_NO_ACCESS)
code = SM_CODE_NO_ACCESS;
if(de->de_last_error != code) {
dvr_rec_set_state(de, DVR_RS_ERROR, code);
tvhlog(LOG_ERR,
"pvr", "Streaming error: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(code));
}
}
break;
case SMT_NOSTART:
dvr_rec_fatal_error(de, "Unable to start -- %s",
streaming_code2txt(sm->sm_code));
if(de->de_last_error != sm->sm_code) {
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
"pvr", "Recording unable to start: \"%s\": %s",
de->de_filename ?: de->de_title,
streaming_code2txt(sm->sm_code));
}
break;
case SMT_MPEGTS:
@ -554,7 +610,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
default:
break;
case DE_RS_WAIT_AUDIO_LOCK:
case DVR_RS_WAIT_AUDIO_LOCK:
if(ctx->codec_type != CODEC_TYPE_AUDIO || drs->drs_decoded)
break;
@ -575,10 +631,10 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
}
if(is_all_decoded(de, CODEC_TYPE_AUDIO))
de->de_rec_state = DE_RS_WAIT_VIDEO_LOCK;
dvr_rec_set_state(de, DVR_RS_WAIT_VIDEO_LOCK, 0);
break;
case DE_RS_WAIT_VIDEO_LOCK:
case DVR_RS_WAIT_VIDEO_LOCK:
if(ctx->codec_type != CODEC_TYPE_VIDEO || drs->drs_decoded)
break;
@ -601,7 +657,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
/* All Audio & Video decoded, start recording */
de->de_rec_state = DE_RS_RUNNING;
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
if(!de->de_header_written) {
@ -631,7 +687,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
}
/* FALLTHRU */
case DE_RS_RUNNING:
case DVR_RS_RUNNING:
if(de->de_header_written == 0)
break;
@ -642,7 +698,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
de->de_ts_com_start = pkt->pkt_dts;
de->de_rec_state = DE_RS_COMMERCIAL;
dvr_rec_set_state(de, DVR_RS_COMMERCIAL, 0);
break;
}
outputpacket:
@ -665,7 +721,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
break;
case DE_RS_COMMERCIAL:
case DVR_RS_COMMERCIAL:
if(pkt->pkt_commercial != COMMERCIAL_YES &&
st->codec->codec->type == CODEC_TYPE_VIDEO &&
@ -674,7 +730,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
/* Switch out of commercial mode */
de->de_ts_com_offset += (pkt->pkt_dts - de->de_ts_com_start);
de->de_rec_state = DE_RS_RUNNING;
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
tvhlog(LOG_INFO, "dvr",
"%s - Skipped %" PRId64 " seconds of commercials",
@ -716,7 +772,8 @@ dvr_spawn_postproc(dvr_entry_t *de)
fmap['C'] = de->de_creator; /* user who created this recording */
fmap['t'] = de->de_title; /* program title */
fmap['d'] = de->de_desc; /* program description */
fmap['e'] = de->de_error; /* error message, empty if no error (FIXME:?) */
/* error message, empty if no error (FIXME:?) */
fmap['e'] = tvh_strdupa(streaming_code2txt(de->de_last_error));
fmap['S'] = start; /* start time, unix epoch */
fmap['E'] = stop; /* stop time, unix epoch */

View file

@ -387,8 +387,8 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method)
break;
case DVR_COMPLETED:
s = "completed";
if(de->de_error)
error = de->de_error;
if(de->de_last_error)
error = streaming_code2txt(de->de_last_error);
break;
case DVR_NOSTATE:
s = "invalid";

View file

@ -326,6 +326,16 @@ streaming_code2txt(int code)
case SM_CODE_NO_SOURCE:
return "No source available";
case SM_CODE_ABORTED:
return "Aborted by user";
case SM_CODE_NO_DESCRAMBLER:
return "No descrambler";
case SM_CODE_NO_ACCESS:
return "No access";
default:
return "Unknown reason";
}

View file

@ -87,5 +87,5 @@ int streaming_pad_probe_type(streaming_pad_t *sp,
streaming_message_type_t smt);
const char *streaming_code2txt(int code);
#endif /* STREAMING_H_ */

View file

@ -144,11 +144,12 @@ subscription_reschedule(void)
th_transport_t *t, *skip;
streaming_message_t *sm;
char buf[128];
int errorcode;
int error;
lock_assert(&global_lock);
gtimer_arm(&subscription_reschedule_timer,
subscription_reschedule_cb, NULL, 60);
subscription_reschedule_cb, NULL, 2);
LIST_FOREACH(s, &subscriptions, ths_global_link) {
if(s->ths_channel == NULL)
@ -160,18 +161,20 @@ subscription_reschedule(void)
if(s->ths_state != SUBSCRIPTION_BAD_TRANSPORT)
continue; /* And it seems to work ok, so we're happy */
skip = s->ths_transport;
transport_remove_subscriber(s->ths_transport, s, SM_CODE_BAD_SOURCE);
error = s->ths_testing_error;
transport_remove_subscriber(s->ths_transport, s, s->ths_testing_error);
} else {
error = 0;
skip = NULL;
}
snprintf(buf, sizeof(buf), "Subscription \"%s\"", s->ths_title);
t = transport_find(s->ths_channel, s->ths_weight, buf, &errorcode, skip);
t = transport_find(s->ths_channel, s->ths_weight, buf, &error, skip);
if(t == NULL) {
/* No transport available */
sm = streaming_msg_create_code(SMT_NOSTART, errorcode);
sm = streaming_msg_create_code(SMT_NOSTART, error);
streaming_target_deliver(s->ths_output, sm);
continue;
}
@ -237,6 +240,7 @@ subscription_input(void *opauqe, streaming_message_t *sm)
sm->sm_code & (TSS_GRACEPERIOD | TSS_ERRORS)) {
// No, mark our subscription as bad_transport
// the scheduler will take care of things
s->ths_testing_error = tss2errcode(sm->sm_code);
s->ths_state = SUBSCRIPTION_BAD_TRANSPORT;
streaming_msg_free(sm);
return;

View file

@ -32,6 +32,8 @@ typedef struct th_subscription {
SUBSCRIPTION_BAD_TRANSPORT,
} ths_state;
int ths_testing_error;
LIST_ENTRY(th_subscription) ths_channel_link;
struct channel *ths_channel; /* May be NULL if channel has been
destroyed during the

View file

@ -358,7 +358,6 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
{
th_transport_t *t, **vec;
int cnt = 0, i, r, off;
int error = 0;
lock_assert(&global_lock);
@ -372,7 +371,6 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) {
if(!t->tht_enabled) {
error = SM_CODE_SVC_NOT_ENABLED;
if(loginfo != NULL) {
tvhlog(LOG_NOTICE, "Transport", "%s: Skipping \"%s\" -- not enabled",
loginfo, transport_nicename(t));
@ -381,7 +379,6 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
}
if(t->tht_quality_index(t) < 10) {
error = SM_CODE_BAD_SIGNAL;
if(loginfo != NULL) {
tvhlog(LOG_NOTICE, "Transport",
"%s: Skipping \"%s\" -- Quality below 10%",
@ -410,8 +407,6 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
off = 0;
}
error = SM_CODE_NO_SOURCE;
/* First, try all transports without stealing */
for(i = off; i < cnt; i++) {
t = vec[i];
@ -419,7 +414,8 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
return t;
if((r = transport_start(t, 0, 0)) == 0)
return t;
tvhlog(LOG_DEBUG, "Transport", "%s: Unable to use \"%s\" -- %s",
if(loginfo != NULL)
tvhlog(LOG_DEBUG, "Transport", "%s: Unable to use \"%s\" -- %s",
loginfo, transport_nicename(t), streaming_code2txt(r));
}
@ -430,14 +426,8 @@ transport_find(channel_t *ch, unsigned int weight, const char *loginfo,
t = vec[i];
if((r = transport_start(t, weight, 0)) == 0)
return t;
error = r;
if(loginfo != NULL)
tvhlog(LOG_NOTICE, "Transport",
"%s: Skipping \"%s\" -- %s",
loginfo, transport_nicename(t), streaming_code2txt(r));
*errorp = r;
}
if(errorp != NULL)
*errorp = error;
return NULL;
}
@ -1036,6 +1026,26 @@ transport_tss2text(int flags)
return "No status";
}
/**
*
*/
int
tss2errcode(int tss)
{
if(tss & TSS_NO_ACCESS)
return SM_CODE_NO_ACCESS;
if(tss & TSS_NO_DESCRAMBLER)
return SM_CODE_NO_DESCRAMBLER;
if(tss & TSS_GRACEPERIOD)
return SM_CODE_NO_INPUT;
return SM_CODE_OK;
}
/**
*
*/

View file

@ -108,4 +108,6 @@ static inline int transport_tss_is_error(int flags)
void transport_refresh_channel(th_transport_t *t);
int tss2errcode(int tss);
#endif /* TRANSPORTS_H */

View file

@ -244,6 +244,8 @@ typedef enum {
#define SM_CODE_OK 0
#define SM_CODE_UNDEFINED_ERROR 1
#define SM_CODE_SOURCE_RECONFIGURED 100
#define SM_CODE_BAD_SOURCE 101
#define SM_CODE_SOURCE_DELETED 102
@ -257,6 +259,12 @@ typedef enum {
#define SM_CODE_BAD_SIGNAL 205
#define SM_CODE_NO_SOURCE 206
#define SM_CODE_ABORTED 300
#define SM_CODE_NO_DESCRAMBLER 400
#define SM_CODE_NO_ACCESS 401
#define SM_CODE_NO_INPUT 402
/**
* Streaming messages are sent from the pad to its receivers
*/

View file

@ -884,7 +884,7 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
dvr_query_result_t dqr;
dvr_entry_t *de;
int start = 0, end, limit, i;
const char *s, *t = NULL;
const char *s;
off_t fsize;
if((s = http_arg_get(&hc->hc_req_args, "start")) != NULL)
@ -944,25 +944,8 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri));
switch(de->de_sched_state) {
case DVR_SCHEDULED:
s = "Scheduled for recording";
t = "sched";
break;
case DVR_RECORDING:
s = "Recording";
t = "rec";
break;
case DVR_COMPLETED:
s = de->de_error ?: "Completed OK";
t = "done";
break;
default:
s = "Invalid";
break;
}
htsmsg_add_str(m, "status", s);
if(t != NULL) htsmsg_add_str(m, "schedstate", t);
htsmsg_add_str(m, "status", dvr_entry_status(de));
htsmsg_add_str(m, "schedstate", dvr_entry_schedstatus(de));
if(de->de_sched_state == DVR_COMPLETED) {

View file

@ -61,14 +61,15 @@ tvheadend.dvrDetails = function(entry) {
});
switch(entry.schedstate) {
case 'sched':
case 'scheduled':
win.addButton({
handler: cancelEvent,
text: "Remove from schedule"
});
break;
case 'rec':
case 'recording':
case 'recordingError':
win.addButton({
handler: cancelEvent,
text: "Abort recording"
@ -103,6 +104,17 @@ tvheadend.dvrDetails = function(entry) {
*/
tvheadend.dvrschedule = function() {
var actions = new Ext.ux.grid.RowActions({
header:'',
dataIndex: 'actions',
width: 45,
actions: [
{
iconIndex:'schedstate'
}
]
});
function renderDate(value){
var dt = new Date(value);
return dt.format('l H:i');
@ -129,6 +141,7 @@ tvheadend.dvrschedule = function() {
}
var dvrCm = new Ext.grid.ColumnModel([
actions,
{
width: 250,
id:'title',
@ -282,6 +295,7 @@ tvheadend.dvrschedule = function() {
iconCls: 'clock',
store: tvheadend.dvrStore,
cm: dvrCm,
plugins: [actions],
viewConfig: {forceFit:true},
tbar: [
{
@ -461,8 +475,31 @@ tvheadend.dvr = function() {
});
tvheadend.comet.on('dvrdb', function(m) {
console.log(m);
if(m.reload != null)
tvheadend.dvrStore.reload();
if(m.updateEntry != null) {
r = tvheadend.dvrStore.getById(m.id)
if(typeof r === 'undefined') {
tvheadend.dvrStore.reload();
return;
}
console.log(r);
r.data.status = m.status;
r.data.schedstate = m.schedstate;
console.log(r);
tvheadend.dvrStore.afterEdit(r);
tvheadend.dvrStore.fireEvent('updated',
tvheadend.dvrStore, r,
Ext.data.Record.COMMIT);
}
});

View file

@ -193,6 +193,26 @@
background-image:url(../icons/layers.png) !important;
}
.scheduled {
background-image:url(../icons/clock.png) !important;
}
.recordingError {
background-image:url(../icons/exclamation.png) !important;
}
.completed {
background-image:url(../icons/tick.png) !important;
}
.completedError {
background-image:url(../icons/exclamation.png) !important;
}
.recording {
background-image:url(../icons/rec.png) !important;
}
.x-smallhdr {

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B