diff --git a/debian/changelog b/debian/changelog index 693d518f..86d12b85 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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. diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 08455bcf..7e8fd216 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -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); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 1a8dee55..5bee340e 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -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); } diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index a59af58b..ac249ac8 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -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 */ diff --git a/src/htsp.c b/src/htsp.c index 51061dba..0f3b18a1 100644 --- a/src/htsp.c +++ b/src/htsp.c @@ -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"; diff --git a/src/streaming.c b/src/streaming.c index d679e898..b35febbe 100644 --- a/src/streaming.c +++ b/src/streaming.c @@ -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"; } diff --git a/src/streaming.h b/src/streaming.h index 1c91aace..d3be0a31 100644 --- a/src/streaming.h +++ b/src/streaming.h @@ -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_ */ diff --git a/src/subscriptions.c b/src/subscriptions.c index 905e06bf..7b0a11b7 100644 --- a/src/subscriptions.c +++ b/src/subscriptions.c @@ -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; diff --git a/src/subscriptions.h b/src/subscriptions.h index b3c17924..ca7b9059 100644 --- a/src/subscriptions.h +++ b/src/subscriptions.h @@ -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 diff --git a/src/transports.c b/src/transports.c index 1a9e9e3b..badf2d41 100644 --- a/src/transports.c +++ b/src/transports.c @@ -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; +} + + /** * */ diff --git a/src/transports.h b/src/transports.h index 254c4aef..975e1221 100644 --- a/src/transports.h +++ b/src/transports.h @@ -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 */ diff --git a/src/tvhead.h b/src/tvhead.h index c7c9bdcf..07b7f263 100644 --- a/src/tvhead.h +++ b/src/tvhead.h @@ -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 */ diff --git a/src/webui/extjs.c b/src/webui/extjs.c index c81c3cd8..f5770f45 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -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) { diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index ba58d9eb..1e6b20a4 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -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); + } }); diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css index 31dde14a..b4bd001d 100644 --- a/src/webui/static/app/ext.css +++ b/src/webui/static/app/ext.css @@ -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 { diff --git a/src/webui/static/icons/exclamation.png b/src/webui/static/icons/exclamation.png new file mode 100644 index 00000000..c37bd062 Binary files /dev/null and b/src/webui/static/icons/exclamation.png differ diff --git a/src/webui/static/icons/tick.png b/src/webui/static/icons/tick.png new file mode 100644 index 00000000..a9925a06 Binary files /dev/null and b/src/webui/static/icons/tick.png differ