From: Andreas Ă–man Date: Sun, 21 Mar 2010 10:08:16 +0000 (+0000) Subject: * Fix various issues realted to the Recording Schedule user interface. X-Git-Tag: 2.12~206 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d57985481c976eb56bb8ae0961dab8685000296c;p=thirdparty%2Ftvheadend.git * 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 --- diff --git a/debian/changelog b/debian/changelog index 693d518fb..86d12b859 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 08455bcf4..7e8fd216d 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 1a8dee559..5bee340eb 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 a59af58ba..ac249ac8d 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 51061dba9..0f3b18a17 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 d679e8986..b35febbef 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 1c91aacea..d3be0a31b 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 905e06bf7..7b0a11b75 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 b3c179243..ca7b9059c 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 1a9e9e3bf..badf2d41f 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 254c4aefa..975e12212 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 c7c9bdcfc..07b7f2630 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 c81c3cd82..f5770f451 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 ba58d9eba..1e6b20a44 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 31dde14ab..b4bd001df 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 000000000..c37bd062e 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 000000000..a9925a06a Binary files /dev/null and b/src/webui/static/icons/tick.png differ