]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
* Fix various issues realted to the Recording Schedule user interface.
authorAndreas Öman <andreas@lonelycoder.com>
Sun, 21 Mar 2010 10:08:16 +0000 (10:08 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Sun, 21 Mar 2010 10:08:16 +0000 (10:08 +0000)
    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

17 files changed:
debian/changelog
src/dvr/dvr.h
src/dvr/dvr_db.c
src/dvr/dvr_rec.c
src/htsp.c
src/streaming.c
src/streaming.h
src/subscriptions.c
src/subscriptions.h
src/transports.c
src/transports.h
src/tvhead.h
src/webui/extjs.c
src/webui/static/app/dvr.js
src/webui/static/app/ext.css
src/webui/static/icons/exclamation.png [new file with mode: 0644]
src/webui/static/icons/tick.png [new file with mode: 0644]

index 693d518fb337200c098883d2a7b8b52eba64e24d..86d12b8595cc8e2b6c34be9745b0944b118453d3 100644 (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.
index 08455bcf4833e7279e8610d2b7067e7e25e31595..7e8fd216d1c7c5c9b8fc8d1536c24d602cfa7d94 100644 (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);
 
index 1a8dee5597f264172858f237e08b82d26b45ebed..5bee340ebb662400c811ba8eae1233090c97ed06 100644 (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);
 }
index a59af58ba374721513610949e3da26e15827e080..ac249ac8d6202bc36ace3474a3de9664401b22de 100644 (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 */
 
index 51061dba973648d091951411e8caac3525d856b7..0f3b18a171452427b69f19664c49dc5c1024f887 100644 (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";
index d679e89869e0d762b0b52c827d7c44708fe3af44..b35febbefffe536d35973603c52e54b60453923f 100644 (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";
   }
index 1c91aacea235187c4b9e07c133ee3dbb4aa1f85d..d3be0a31be7239c39fbfe9ec9400c888f4a84e15 100644 (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_ */
index 905e06bf71bb2670a79b9479deb6ecfb0fb7a1ca..7b0a11b7518ae7daf499e76ec7b616ea9f3bd793 100644 (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;
index b3c179243b7b06a9442dcc2df21985d907f1aeb9..ca7b9059c109f2f0293da408e30463d890c46524 100644 (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
index 1a9e9e3bf7310c477a8602148d59a041acf97eb0..badf2d41f4b3f1d6a2c1a9c7612f629f65451d4c 100644 (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;
+}
+
+
 /**
  *
  */
index 254c4aefa522dbe69facb7c205881174e0fb261d..975e122127aa98ddd3182bfafbe16f937e01dd89 100644 (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 */
index c7c9bdcfcb26d750330c16a439212fab261a105c..07b7f263076b7684a573543c8511f6fbf76fc014 100644 (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
  */
index c81c3cd82044c5460c63e7f36389ff650baba25d..f5770f45118373bd0fef2c7d0ae9b7e25552f3ae 100644 (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) {
index ba58d9eba7c831b7cae1d208695d00a0157472e0..1e6b20a44785bada41ea29e964bec4d01af099c1 100644 (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);
+       }
     });
 
     
index 31dde14ab06bc6354103cedb4f5d3fe450072d22..b4bd001dfdbdf45829f38b85f6e7b38d9e9d6c06 100644 (file)
     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 (file)
index 0000000..c37bd06
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 (file)
index 0000000..a9925a0
Binary files /dev/null and b/src/webui/static/icons/tick.png differ