]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
HTSP: Add option to serve files over HTSP
authorAndreas Öman <andreas@lonelycoder.com>
Thu, 15 Nov 2012 12:20:18 +0000 (13:20 +0100)
committerAndreas Öman <andreas@lonelycoder.com>
Thu, 15 Nov 2012 12:20:52 +0000 (13:20 +0100)
Currently this is only used to serve recorded files

src/htsp_server.c

index da8d34bddd7b0298f16498eff975e7cfd24c005b..f2db2617ed973762d3b5b14b741adcc3d79ed64c 100644 (file)
@@ -28,6 +28,7 @@
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
+#include <sys/stat.h>
 
 #include "tvheadend.h"
 #include "channels.h"
@@ -64,6 +65,7 @@ extern char *dvr_storage;
 
 LIST_HEAD(htsp_connection_list, htsp_connection);
 LIST_HEAD(htsp_subscription_list, htsp_subscription);
+LIST_HEAD(htsp_file_list, htsp_file);
 
 TAILQ_HEAD(htsp_msg_queue, htsp_msg);
 TAILQ_HEAD(htsp_msg_q_queue, htsp_msg_q);
@@ -138,10 +140,9 @@ typedef struct htsp_connection {
   htsp_msg_q_t htsp_hmq_epg;
   htsp_msg_q_t htsp_hmq_qstatus;
 
-  /**
-   *
-   */
   struct htsp_subscription_list htsp_subscriptions;
+  struct htsp_file_list htsp_files;
+  int htsp_file_id;
 
   uint32_t htsp_granted_access;
 
@@ -177,6 +178,19 @@ typedef struct htsp_subscription {
 
 } htsp_subscription_t;
 
+
+/**
+ *
+ */
+typedef struct htsp_file {
+  LIST_ENTRY(htsp_file) hf_link;
+  int hf_id;  // ID sent to client
+  int hf_fd;  // Our file descriptor
+  char *hf_path; // For logging
+} htsp_file_t;
+
+
+
 #define HTSP_DEFAULT_QUEUE_DEPTH 500000
 
 /* **************************************************************************
@@ -1235,6 +1249,169 @@ htsp_method_change_weight(htsp_connection_t *htsp, htsmsg_t *in)
   return NULL;
 }
 
+
+/**
+ *
+ */
+static htsmsg_t *
+htsp_method_open_path(htsp_connection_t *htsp, const char *path)
+{
+  struct stat st;
+  int fd = open(path, O_RDONLY);
+  tvhlog(LOG_DEBUG, "HTSP", "Opening file %s -- %s", path, fd < 0 ? strerror(errno) : "OK");
+  if(fd == -1)
+    return htsp_error("Unable to open file");
+
+  htsp_file_t *hf = calloc(1, sizeof(htsp_file_t));
+  hf->hf_fd = fd;
+  hf->hf_id = ++htsp->htsp_file_id;
+  hf->hf_path = strdup(path);
+ LIST_INSERT_HEAD(&htsp->htsp_files, hf, hf_link);
+
+  htsmsg_t *rep = htsmsg_create_map();
+  htsmsg_add_u32(rep, "id", hf->hf_id);
+
+  if(!fstat(hf->hf_fd, &st)) {
+    htsmsg_add_u64(rep, "size", st.st_size);
+    htsmsg_add_u64(rep, "mtime", st.st_mtime);
+  }
+
+  return rep;
+}
+
+
+/**
+ * Open file
+ */
+static htsmsg_t *
+htsp_method_file_open(htsp_connection_t *htsp, htsmsg_t *in)
+{
+  const char *str, *s2;
+  const char *filename = NULL;
+  if((str = htsmsg_get_str(in, "file")) == NULL)
+    return htsp_error("Missing argument 'file'");
+
+  if((s2 = tvh_strbegins(str, "dvr/")) != NULL) {
+    dvr_entry_t *de = dvr_entry_find_by_id(atoi(s2));
+    if(de == NULL)
+      return htsp_error("DVR entry does not exist");
+
+    if(de->de_filename == NULL)
+      return htsp_error("DVR entry does not have a file yet");
+
+    filename = de->de_filename;
+  } else {
+    return htsp_error("Unknown file");
+  }
+
+  return htsp_method_open_path(htsp, filename);
+}
+
+/**
+ *
+ */
+static htsp_file_t *
+file_find(const htsp_connection_t *htsp, htsmsg_t *in)
+{
+  htsp_file_t *hf;
+
+  int id = htsmsg_get_u32_or_default(in, "id", 0);
+
+  LIST_FOREACH(hf, &htsp->htsp_files, hf_link) {
+    if(hf->hf_id == id)
+      return hf;
+  }
+  return NULL;
+}
+
+
+
+/**
+ *
+ */
+static htsmsg_t *
+htsp_method_file_read(htsp_connection_t *htsp, htsmsg_t *in)
+{
+  htsp_file_t *hf = file_find(htsp, in);
+  uint64_t off;
+  uint32_t size;
+
+  if(htsmsg_get_u64(in, "offset", &off))
+    return htsp_error("Missing field 'offset'");
+
+  if(htsmsg_get_u32(in, "size", &size))
+    return htsp_error("Missing field 'size'");
+
+  if(hf == NULL)
+    return htsp_error("Unknown file id");
+
+  void *m = malloc(size);
+  if(m == NULL)
+    return htsp_error("Too big segment");
+
+  int r = pread(hf->hf_fd, m, size, off);
+  if(r < 0) {
+    free(m);
+    return htsp_error("Read error");
+  }
+
+  htsmsg_t *rep = htsmsg_create_map();
+  htsmsg_add_bin(rep, "data", m, r);
+  free(m);
+  return rep;
+}
+
+
+/**
+ *
+ */
+static void
+htsp_file_destroy(htsp_file_t *hf)
+{
+  tvhlog(LOG_DEBUG, "HTSP", "Closed opened file %s", hf->hf_path);
+  free(hf->hf_path);
+  close(hf->hf_fd);
+  LIST_REMOVE(hf, hf_link);
+  free(hf);
+}
+
+/**
+ *
+ */
+static htsmsg_t *
+htsp_method_file_close(htsp_connection_t *htsp, htsmsg_t *in)
+{
+  htsp_file_t *hf = file_find(htsp, in);
+
+  if(hf == NULL)
+    return htsp_error("Unknown file id");
+
+  htsp_file_destroy(hf);
+  return htsmsg_create_map();
+}
+
+
+/**
+ *
+ */
+static htsmsg_t *
+htsp_method_file_stat(htsp_connection_t *htsp, htsmsg_t *in)
+{
+  htsp_file_t *hf = file_find(htsp, in);
+  struct stat st;
+
+  if(hf == NULL)
+    return htsp_error("Unknown file id");
+
+  htsmsg_t *rep = htsmsg_create_map();
+  if(!fstat(hf->hf_fd, &st)) {
+    htsmsg_add_u64(rep, "size", st.st_size);
+    htsmsg_add_u64(rep, "mtime", st.st_mtime);
+  }
+  return rep;
+}
+
+
 /**
  * HTSP methods
  */
@@ -1260,6 +1437,10 @@ struct {
   { "subscribe",                htsp_method_subscribe,      ACCESS_STREAMING},
   { "unsubscribe",              htsp_method_unsubscribe,    ACCESS_STREAMING},
   { "subscriptionChangeWeight", htsp_method_change_weight,  ACCESS_STREAMING},
+  { "openFile",                 htsp_method_file_open,      ACCESS_RECORDER},
+  { "readFile",                 htsp_method_file_read,      ACCESS_RECORDER},
+  { "closeFile",                htsp_method_file_close,     ACCESS_RECORDER},
+  { "statFile",                 htsp_method_file_stat,      ACCESS_RECORDER},
 };
 
 #define NUM_METHODS (sizeof(htsp_methods) / sizeof(htsp_methods[0]))
@@ -1575,9 +1756,13 @@ htsp_serve(int fd, void *opaque, struct sockaddr_in *source,
     }
   }
 
+  htsp_file_t *hf;
+  while((hf = LIST_FIRST(&htsp.htsp_files)) != NULL)
+    htsp_file_destroy(hf);
+
   close(fd);
 }
-  
+
 /**
  *  Fire up HTSP server
  */