From d57985481c976eb56bb8ae0961dab8685000296c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Sun, 21 Mar 2010 10:08:16 +0000 Subject: [PATCH] * 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 --- debian/changelog | 5 ++ src/dvr/dvr.h | 47 ++++++++--- src/dvr/dvr_db.c | 112 +++++++++++++++++++++---- src/dvr/dvr_rec.c | 95 ++++++++++++++++----- src/htsp.c | 4 +- src/streaming.c | 10 +++ src/streaming.h | 2 +- src/subscriptions.c | 14 ++-- src/subscriptions.h | 2 + src/transports.c | 36 +++++--- src/transports.h | 2 + src/tvhead.h | 8 ++ src/webui/extjs.c | 23 +---- src/webui/static/app/dvr.js | 41 ++++++++- src/webui/static/app/ext.css | 20 +++++ src/webui/static/icons/exclamation.png | Bin 0 -> 701 bytes src/webui/static/icons/tick.png | Bin 0 -> 537 bytes 17 files changed, 335 insertions(+), 86 deletions(-) create mode 100644 src/webui/static/icons/exclamation.png create mode 100644 src/webui/static/icons/tick.png 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 0000000000000000000000000000000000000000..c37bd062e60c3b38fc82e4d1f236a8ac2fae9d8c GIT binary patch literal 701 zcmV;u0z&N#0$9Ug7g~-`rQ^qx~m@y2OU8A z#zh~=7n#Z$Z*fx-GOtDf07cgx0suCz_W(2~Y(0tf@FX@P6EPuM_dgn$vj9LucO)%W zw%HgMW>=#oL>nZ>M&NEf08>)#)k<{$fCT_r>rPi=BV=hFh6WS^qqze>C6Ek}o{M5% za|@JGowu0t{&hgNzySHZxy@LTNh);YzZ2zSp_ zl$^T&Dnc|NLb&RD_!4>pt@VHdP)ZGER%5ZmWEe$lryR&y;2u^3cOkO4#6c%-(EY6a{600000NkvXXu0mjfxS2AI literal 0 HcmV?d00001 diff --git a/src/webui/static/icons/tick.png b/src/webui/static/icons/tick.png new file mode 100644 index 0000000000000000000000000000000000000000..a9925a06ab02db30c1e7ead9c701c15bc63145cb GIT binary patch literal 537 zcmV+!0_OdRP)Hs{AQG2a)rMyf zFQK~pm1x3+7!nu%-M`k}``c>^00{o_1pjWJUTfl8mg=3qGEl8H@}^@w`VUx0_$uy4 z2FhRqKX}xI*?Tv1DJd8z#F#0c%*~rM30HE1@2o5m~}ZyoWhqv>ql{V z1ZGE0lgcoK^lx+eqc*rAX1Ky;Xx3U%u#zG!m-;eD1Qsn@kf3|F9qz~|95=&g3(7!X zB}JAT>RU;a%vaNOGnJ%e1=K6eAh43c(QN8RQ6~GP%O}Jju$~Ld*%`mO1p