diff --git a/Makefile b/Makefile
index 096628c2..0166cbc0 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \
SRCS += http.c htmlui.c
+SRCS += htsp.c rpc.c
+
SRCS += pvr.c
SRCS += epg.c epg_xmltv.c
diff --git a/htsp.c b/htsp.c
new file mode 100644
index 00000000..d0464671
--- /dev/null
+++ b/htsp.c
@@ -0,0 +1,210 @@
+/*
+ * tvheadend, HTSP interface
+ * Copyright (C) 2007 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tvhead.h"
+#include "channels.h"
+#include "subscriptions.h"
+#include "dispatch.h"
+#include "rpc.h"
+#include "htsp.h"
+#include "htsp_muxer.h"
+#include "tcp.h"
+
+#include
+
+/*
+ *
+ */
+int
+htsp_send_msg(htsp_t *htsp, htsmsg_t *m, int media)
+{
+ tcp_session_t *tcp = &htsp->htsp_tcp_session;
+ tcp_queue_t *tq;
+ void *data;
+ size_t datalen;
+ int max, r = -1;
+
+ tq = media ? &tcp->tcp_q_low : &tcp->tcp_q_hi;
+
+ max = tq->tq_maxdepth - tq->tq_depth; /* max size we are able to enqueue */
+
+ if(htsmsg_binary_serialize(m, &data, &datalen, max) == 0)
+ r = tcp_send_msg(tcp, tq, data, datalen);
+
+ htsmsg_destroy(m);
+ return r;
+}
+
+
+/*
+ *
+ */
+static void
+htsp_input(htsp_t *htsp, const void *buf, int len)
+{
+ htsmsg_t *in, *out;
+ int i;
+ const uint8_t *v = buf;
+
+ printf("Got %d bytes\n", len);
+ for(i =0 ; i < len; i++)
+ printf("%02x.", v[i]);
+ printf("\n");
+
+ if((in = htsmsg_binary_deserialize(buf, len, NULL)) == NULL) {
+ printf("deserialize failed\n");
+ return;
+ }
+ printf("INPUT:\n");
+ htsmsg_print(in);
+
+ out = rpc_dispatch(&htsp->htsp_rpc, in, NULL, htsp);
+
+ htsmsg_destroy(in);
+
+ printf("OUTPUT:\n");
+ htsmsg_print(out);
+
+ htsp_send_msg(htsp, out, 0);
+}
+
+
+/*
+ * data available on socket
+ */
+static void
+htsp_data_input(htsp_t *htsp)
+{
+ int r, l;
+ tcp_session_t *tcp = &htsp->htsp_tcp_session;
+
+ if(htsp->htsp_bufptr < 4) {
+ r = read(tcp->tcp_fd, htsp->htsp_buf + htsp->htsp_bufptr,
+ 4 - htsp->htsp_bufptr);
+ if(r < 1) {
+ tcp_disconnect(tcp, r == 0 ? ECONNRESET : errno);
+ return;
+ }
+
+ htsp->htsp_bufptr += r;
+ if(htsp->htsp_bufptr < 4)
+ return;
+
+ htsp->htsp_msglen = (htsp->htsp_buf[0] << 24) + (htsp->htsp_buf[1] << 16) +
+ (htsp->htsp_buf[2] << 8) + htsp->htsp_buf[3] + 4;
+
+ if(htsp->htsp_msglen < 12 || htsp->htsp_msglen > 16 * 1024 * 1024) {
+ tcp_disconnect(tcp, EBADMSG);
+ return;
+ }
+
+ if(htsp->htsp_bufsize < htsp->htsp_msglen) {
+ htsp->htsp_bufsize = htsp->htsp_msglen;
+ free(htsp->htsp_buf);
+ htsp->htsp_buf = malloc(htsp->htsp_bufsize);
+ }
+ }
+
+ l = htsp->htsp_msglen - htsp->htsp_bufptr;
+
+ r = read(tcp->tcp_fd, htsp->htsp_buf + htsp->htsp_bufptr, l);
+ if(r < 1) {
+ tcp_disconnect(tcp, r == 0 ? ECONNRESET : errno);
+ return;
+ }
+
+ htsp->htsp_bufptr += r;
+
+ if(htsp->htsp_bufptr == htsp->htsp_msglen) {
+ htsp_input(htsp, htsp->htsp_buf + 4, htsp->htsp_msglen - 4);
+ htsp->htsp_bufptr = 0;
+ htsp->htsp_msglen = 0;
+ }
+}
+
+
+
+/*
+ *
+ */
+static void
+htsp_disconnect(htsp_t *htsp)
+{
+ free(htsp->htsp_buf);
+ rpc_deinit(&htsp->htsp_rpc);
+}
+
+
+/*
+ *
+ */
+static void
+htsp_connect(htsp_t *htsp)
+{
+ rpc_init(&htsp->htsp_rpc, "htsp");
+
+ htsp->htsp_bufsize = 1000;
+ htsp->htsp_buf = malloc(htsp->htsp_bufsize);
+}
+
+/*
+ *
+ */
+static void
+htsp_tcp_callback(tcpevent_t event, void *tcpsession)
+{
+ htsp_t *htsp = tcpsession;
+
+ switch(event) {
+ case TCP_CONNECT:
+ htsp_connect(htsp);
+ break;
+
+ case TCP_DISCONNECT:
+ htsp_disconnect(htsp);
+ break;
+
+ case TCP_INPUT:
+ htsp_data_input(htsp);
+ break;
+ }
+}
+
+
+/*
+ * Fire up HTSP server
+ */
+
+void
+htsp_start(int port)
+{
+ tcp_create_server(port, sizeof(htsp_t), "htsp", htsp_tcp_callback);
+}
diff --git a/htsp.h b/htsp.h
new file mode 100644
index 00000000..e00a1480
--- /dev/null
+++ b/htsp.h
@@ -0,0 +1,45 @@
+/*
+ * tvheadend, HTSP interface
+ * Copyright (C) 2007 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef HTSP_H_
+#define HTSP_H_
+
+#include
+
+#include "rpc.h"
+#include "tcp.h"
+
+typedef struct htsp {
+ tcp_session_t htsp_tcp_session; /* Must be first */
+
+ int htsp_bufsize;
+ int htsp_bufptr;
+ int htsp_msglen;
+ uint8_t *htsp_buf;
+
+ rpc_session_t htsp_rpc;
+
+ int htsp_async_init_sent;
+
+} htsp_t;
+
+void htsp_start(int port);
+
+int htsp_send_msg(htsp_t *htsp, htsmsg_t *m, int media);
+
+#endif /* HTSP_H_ */
diff --git a/main.c b/main.c
index 5b5cb52e..8999072f 100644
--- a/main.c
+++ b/main.c
@@ -50,6 +50,7 @@
#include "iptv_output.h"
#include "rtsp.h"
#include "http.h"
+#include "htsp.h"
#include "buffer.h"
#include "htmlui.h"
@@ -205,6 +206,10 @@ main(int argc, char **argv)
if(p)
http_start(p);
+ p = atoi(config_get_str("htsp-server-port", "0"));
+ if(p)
+ htsp_start(p);
+
}
dispatcher();
}
diff --git a/rpc.c b/rpc.c
new file mode 100644
index 00000000..fae09c97
--- /dev/null
+++ b/rpc.c
@@ -0,0 +1,423 @@
+/*
+ * tvheadend, RPC
+ * Copyright (C) 2007 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tvhead.h"
+#include "channels.h"
+#include "subscriptions.h"
+#include "dispatch.h"
+#include "epg.h"
+#include "pvr.h"
+#include "rpc.h"
+
+
+/**
+ * Build a channel information message
+ */
+static htsmsg_t *
+rpc_build_channel_msg(th_channel_t *ch)
+{
+ htsmsg_t *m;
+
+ m = htsmsg_create();
+
+ htsmsg_add_str(m, "name", ch->ch_name);
+ htsmsg_add_u32(m, "tag", ch->ch_tag);
+ if(ch->ch_icon)
+ htsmsg_add_str(m, "icon", refstr_get(ch->ch_icon));
+ return m;
+}
+
+
+
+#if 0
+
+/**
+ * channels_list
+ */
+static void
+htsp_send_all_channels(htsp_connection_t *hc)
+{
+ htsp_msg_t *msg;
+ th_channel_t *ch;
+
+ TAILQ_FOREACH(ch, &channels, ch_global_link) {
+ msg = htsp_build_channel_msg(ch);
+ htsp_add_string(msg, "msgtype", "channelAdd");
+ htsp_send_msg(hc, NULL, msg);
+ htsp_free_msg(msg);
+ }
+}
+
+#endif
+
+
+
+
+/**
+ * Simple function to respond 'ok'
+ */
+htsmsg_t *
+rpc_ok(rpc_session_t *ses)
+{
+ htsmsg_t *r = htsmsg_create();
+ htsmsg_add_u32(r, "seq", ses->rs_seq);
+ return r;
+}
+
+
+
+/**
+ * Simple function to respond with an error
+ */
+htsmsg_t *
+rpc_error(rpc_session_t *ses, const char *err)
+{
+ htsmsg_t *r = htsmsg_create();
+ htsmsg_add_u32(r, "seq", ses->rs_seq);
+ htsmsg_add_str(r, "_error", err);
+ return r;
+}
+
+
+
+/**
+ * Login peer, set auth level and do various other stuff
+ */
+static htsmsg_t *
+rpc_login(rpc_session_t *ses, htsmsg_t *in, void *opaque)
+{
+ const char *user = htsmsg_get_str(in, "username");
+ const char *pass = htsmsg_get_str(in, "password");
+
+ if(user == NULL || pass == NULL) {
+ ses->rs_authlevel = 1;
+ ses->rs_maxweight = 100;
+ } else {
+ /* FIXME: user/password database */
+ ses->rs_authlevel = 1;
+ }
+
+ if(htsmsg_get_u32(in, "async", &ses->rs_is_async) < 0)
+ ses->rs_is_async = 0;
+
+ return rpc_ok(ses);
+}
+
+
+
+
+
+
+
+/*
+ * channelsList method
+ */
+static htsmsg_t *
+rpc_channels_list(rpc_session_t *ses, htsmsg_t *in, void *opaque)
+{
+ htsmsg_t *out;
+ th_channel_t *ch;
+
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "seq", ses->rs_seq);
+
+ TAILQ_FOREACH(ch, &channels, ch_global_link)
+ htsmsg_add_msg(out, "channel", rpc_build_channel_msg(ch));
+
+ return out;
+}
+
+/*
+ * eventInfo method
+ */
+static htsmsg_t *
+rpc_event_info(rpc_session_t *ses, htsmsg_t *in, void *opaque)
+{
+ htsmsg_t *out;
+ th_channel_t *ch;
+ uint32_t u32;
+ const char *s, *errtxt = NULL;
+ event_t *e = NULL, *x;
+ uint32_t tag, prev, next, pvrstatus;
+
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "seq", ses->rs_seq);
+
+ epg_lock();
+
+ if(htsmsg_get_u32(in, "tag", &u32) >= 0) {
+ e = epg_event_find_by_tag(u32);
+ } else if((s = htsmsg_get_str(in, "channel")) != NULL) {
+ if((ch = channel_find(s, 0)) == NULL) {
+ errtxt = "Channel not found";
+ } else {
+ if(htsmsg_get_u32(in, "time", &u32) < 0) {
+ e = epg_event_get_current(ch);
+ } else {
+ e = epg_event_find_by_time(ch, u32);
+ }
+ }
+ } else {
+ errtxt = "Invalid arguments";
+ }
+
+ if(e == NULL) {
+ errtxt = "Event not found";
+ }
+
+ if(errtxt != NULL) {
+ htsmsg_add_str(out, "_error", errtxt);
+ } else {
+ tag = e->e_tag;
+ x = TAILQ_PREV(e, event_queue, e_link);
+ prev = x != NULL ? x->e_tag : 0;
+
+ x = TAILQ_NEXT(e, e_link);
+ next = x != NULL ? x->e_tag : 0;
+
+ htsmsg_add_u32(out, "start", e->e_start);
+ htsmsg_add_u32(out, "stop", e->e_start + e->e_duration);
+
+ if(e->e_title != NULL)
+ htsmsg_add_str(out, "title", e->e_title);
+
+ if(e->e_desc != NULL)
+ htsmsg_add_str(out, "desc", e->e_desc);
+
+ htsmsg_add_u32(out, "tag", tag);
+ if(prev)
+ htsmsg_add_u32(out, "prev", prev);
+ if(next)
+ htsmsg_add_u32(out, "next", next);
+
+ if((pvrstatus = pvr_prog_status(e)) != 0)
+ htsmsg_add_u32(out, "pvrstatus", pvrstatus);
+ }
+
+ epg_unlock();
+
+ return out;
+}
+
+#if 0
+
+
+/*
+ * record method
+ */
+static void
+htsp_record(htsp_connection_t *hc, htsp_msg_t *m)
+{
+ htsp_msg_t *r;
+ uint32_t u32;
+ const char *s, *errtxt = NULL;
+ event_t *e = NULL;
+ recop_t op;
+
+ r = htsp_create_msg();
+ htsp_add_u32(r, "seq", hc->hc_seq);
+
+ epg_lock();
+
+ s = htsp_get_string(m, "command");
+ if(s == NULL) {
+ errtxt = "Missing 'command' field";
+ } else {
+ op = pvr_op2int(s);
+ if(op == -1) {
+ errtxt = "Invalid 'command' field";
+ } else {
+
+ if((u32 = htsp_get_u32(m, "tag")) != 0) {
+ e = epg_event_find_by_tag(u32);
+
+ if(e != NULL) {
+ pvr_event_record_op(e->e_ch, e, op);
+ errtxt = NULL;
+ } else {
+ errtxt = "Event not found";
+ }
+ } else {
+ errtxt = "Missing identifier";
+ }
+ }
+ }
+
+ epg_unlock();
+
+ if(errtxt)
+ htsp_add_string(r, "_error", errtxt);
+
+ htsp_send_msg(hc, NULL, r);
+ htsp_free_msg(r);
+}
+#endif
+
+#if 0
+
+/*
+ * subscribe
+ */
+static void
+htsp_subscribe(htsp_connection_t *hc, htsp_msg_t *m)
+{
+ htsp_msg_t *r;
+ const char *str, *errtxt = NULL;
+ th_channel_t *ch;
+ int weight;
+
+ r = htsp_create_msg();
+ htsp_add_u32(r, "seq", hc->hc_seq);
+
+ if((str = htsp_get_string(m, "channel")) != NULL) {
+ if((ch = channel_find(str, 0)) != NULL) {
+
+ weight = htsp_get_u32(m, "weight") ?: 100;
+ if(weight > hc->hc_maxweight)
+ weight = hc->hc_maxweight;
+
+ htsp_stream_subscribe(hc, ch, weight);
+ } else {
+ errtxt = "Channel not found";
+ }
+ } else {
+ errtxt = "Missing 'channel' field";
+ }
+
+ htsp_send_msg(hc, NULL, r);
+ htsp_free_msg(r);
+}
+
+/*
+ * unsubscribe
+ */
+static void
+htsp_subscribe(htsp_connection_t *hc, htsp_msg_t *m)
+{
+ htsp_msg_t *r;
+ const char *str, *errtxt = NULL;
+ th_channel_t *ch;
+
+ r = htsp_create_msg();
+ htsp_add_u32(r, "seq", hc->hc_seq);
+
+ if((str = htsp_get_string(m, "channel")) != NULL) {
+ if((ch = channel_find(str, 0)) != NULL) {
+ htsp_stream_unsubscribe(hc, ch);
+ } else {
+ errtxt = "Channel not found";
+ }
+ } else {
+ errtxt = "Missing 'channel' field";
+ }
+
+ htsp_send_msg(hc, NULL, r);
+ htsp_free_msg(r);
+}
+#endif
+
+/*
+ * List of all methods and their required auth level
+ */
+
+static rpc_cmd_t common_rpc[] = {
+ { "login", rpc_login, 0 },
+ { "channelsList", rpc_channels_list, 1 },
+ { "eventInfo", rpc_event_info, 1 },
+#if 0
+ { "subscribe", rpc_subscribe, 1 },
+ { "unsubscribe", rpc_unsubscribe, 1 },
+#endif
+ // { "record", rpc_record, 2 },
+ { NULL, NULL, 0 },
+};
+
+
+/*
+ *
+ */
+static htsmsg_t *
+rpc_dispatch_set(rpc_session_t *ses, htsmsg_t *in, rpc_cmd_t *cmds,
+ void *opaque, const char *method)
+{
+ for(; cmds->rc_name; cmds++) {
+ if(strcmp(cmds->rc_name, method))
+ continue;
+ if(cmds->rc_authlevel < ses->rs_authlevel)
+ return rpc_error(ses, "Insufficient privileges");
+ return cmds->rc_func(ses, in, opaque);
+ }
+ return NULL;
+}
+
+
+/*
+ *
+ */
+htsmsg_t *
+rpc_dispatch(rpc_session_t *ses, htsmsg_t *in, rpc_cmd_t *cmds, void *opaque)
+{
+ const char *method;
+ htsmsg_t *out;
+
+ if(htsmsg_get_u32(in, "seq", &ses->rs_seq))
+ ses->rs_seq = 0;
+
+ if((method = htsmsg_get_str(in, "method")) == NULL)
+ return rpc_error(ses, "Method not specified");
+
+ out = rpc_dispatch_set(ses, in, common_rpc, opaque, method);
+ if(out == NULL && cmds != NULL)
+ out = rpc_dispatch_set(ses, in, cmds, opaque, method);
+
+ return out ?: rpc_error(ses, "Method not found");
+}
+
+
+/*
+ *
+ */
+void
+rpc_init(rpc_session_t *ses, const char *logname)
+{
+ memset(ses, 0, sizeof(rpc_session_t));
+ ses->rs_logname = strdup(logname);
+}
+
+/*
+ *
+ */
+void
+rpc_deinit(rpc_session_t *ses)
+{
+ free((void *)ses->rs_logname);
+}
diff --git a/rpc.h b/rpc.h
new file mode 100644
index 00000000..e0fb3b2f
--- /dev/null
+++ b/rpc.h
@@ -0,0 +1,58 @@
+/*
+ * tvheadend, RPC interface
+ * Copyright (C) 2007 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef RPC_H_
+#define RPC_H_
+
+#include
+
+typedef struct rpc_session {
+
+ const char *rs_logname;
+
+ uint32_t rs_seq;
+
+ unsigned int rs_authlevel;
+ unsigned int rs_maxweight;
+ unsigned int rs_is_async;
+
+ struct th_subscription_list rs_subscriptions;
+
+} rpc_session_t;
+
+
+typedef struct rpc_cmd {
+ const char *rc_name;
+ htsmsg_t *(*rc_func)(rpc_session_t *ses, htsmsg_t *in, void *opaque);
+ int rc_authlevel;
+} rpc_cmd_t;
+
+
+
+void rpc_init(rpc_session_t *ses, const char *logname);
+
+void rpc_deinit(rpc_session_t *ses);
+
+htsmsg_t *rpc_dispatch(rpc_session_t *ses, htsmsg_t *in, rpc_cmd_t *cmds,
+ void *opaque);
+
+htsmsg_t *rpc_ok(rpc_session_t *ses);
+htsmsg_t *rpc_error(rpc_session_t *ses, const char *err);
+
+
+#endif /* RPC_H_ */