summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthlo <t.lohmar@gmx.de>2012-12-25 11:36:09 +0100
committerthlo <t.lohmar@gmx.de>2012-12-25 11:36:09 +0100
commit6399355d20ee87365e19ef94aa8798c602f697e6 (patch)
tree43fc29789e9eef0e153e7e71ce57c197587cd82c
downloadvdr-plugin-smarttvweb-6399355d20ee87365e19ef94aa8798c602f697e6.tar.gz
vdr-plugin-smarttvweb-6399355d20ee87365e19ef94aa8798c602f697e6.tar.bz2
Initial Version
-rw-r--r--vdr-smarttvweb/Makefile109
-rw-r--r--vdr-smarttvweb/httpresource-hmm.c1130
-rw-r--r--vdr-smarttvweb/httpresource-hmm.h94
-rw-r--r--vdr-smarttvweb/httpresource.c2122
-rw-r--r--vdr-smarttvweb/httpresource.h188
-rw-r--r--vdr-smarttvweb/log.c74
-rw-r--r--vdr-smarttvweb/log.h50
-rw-r--r--vdr-smarttvweb/smarttvfactory.c382
-rw-r--r--vdr-smarttvweb/smarttvfactory.h88
-rw-r--r--vdr-smarttvweb/smarttvweb.c152
-rw-r--r--vdr-smarttvweb/stvw_cfg.c165
-rw-r--r--vdr-smarttvweb/stvw_cfg.h86
-rw-r--r--vdr-smarttvweb/url.c302
-rw-r--r--vdr-smarttvweb/url.h49
14 files changed, 4991 insertions, 0 deletions
diff --git a/vdr-smarttvweb/Makefile b/vdr-smarttvweb/Makefile
new file mode 100644
index 0000000..b04d143
--- /dev/null
+++ b/vdr-smarttvweb/Makefile
@@ -0,0 +1,109 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+#
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+#
+PLUGIN = smarttvweb
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+
+CXX ?= g++
+ifdef DEBUG
+CXXFLAGS ?= -g -O0 -fPIC -Wall -Woverloaded-virtual #-Werror
+else
+CXXFLAGS ?= -fPIC -Wall -Woverloaded-virtual #-Werror
+#CXXFLAGS ?= -O2 -fPIC -Wall -Woverloaded-virtual #-Werror
+endif
+
+### The directory environment:
+
+#VDRDIR = ../../..
+VDRDIR = /usr/include/vdr
+LIBDIR = .
+#LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### Allow user defined options to overwrite defaults:
+
+#-include $(VDRDIR)/Make.config
+
+### read standlone settings if there
+-include .standalone
+
+### The version number of VDR (taken from VDR's "config.h"):
+
+VDRVERSION = $(shell grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o smarttvfactory.o httpresource.o log.o url.o stvw_cfg.o
+
+OBJS2 =
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+# Dependencies:
+
+MAKEDEP = g++ -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Targets:
+
+all: allbase libvdr-$(PLUGIN).so
+standalone: standalonebase smarttvweb-standalone
+
+objectsstandalone: $(OBJS)
+objects: $(OBJS) $(OBJS2)
+
+allbase:
+ ( if [ -f .standalone ] ; then ( rm -f .standalone; make clean ; make objects ) ; else exit 0 ;fi )
+standalonebase:
+ ( if [ ! -f .standalone ] ; then ( make clean; echo "DEFINES+=-DSTANDALONE" > .standalone; echo "DEFINES+=-D_FILE_OFFSET_BITS=64" >> .standalone; make objectsstandalone ) ; else exit 0 ;fi )
+
+libvdr-$(PLUGIN).so: objects
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) $(OBJS2) -o $@
+ @cp $@ $(LIBDIR)/$@.$(APIVERSION)
+
+smarttvweb-standalone: objectsstandalone
+ $(CXX) $(CXXFLAGS) $(OBJS) -lpthread -o $@
+ chmod u+x $@
+
+dist: clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ rm -f $(OBJS) $(OBJS2) $(DEPFILE) libvdr*.so.* *.tgz core* *~ .standalone smarttvweb-standalone
+
diff --git a/vdr-smarttvweb/httpresource-hmm.c b/vdr-smarttvweb/httpresource-hmm.c
new file mode 100644
index 0000000..1c2117f
--- /dev/null
+++ b/vdr-smarttvweb/httpresource-hmm.c
@@ -0,0 +1,1130 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <vector>
+#include "httpresource.h"
+
+#ifndef VOMPSTANDALONE
+#include <vdr/recording.h>
+#include <vdr/videodir.h>
+#endif
+
+#define SERVER "SmartTvWeb/0.1"
+#define PROTOCOL "HTTP/1.1"
+#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"
+
+#define MAXLEN 4096
+#define OKAY 0
+#define ERROR (-1)
+#define DEBUG_REGHEADERS
+#define DEBUGPREFIX "mReqId= " << mReqId << " fd= " << mFd
+#define DEBUGHDR " " << __PRETTY_FUNCTION__ << " (" << __LINE__ << ") "
+
+using namespace std;
+
+struct sVdrFileEntry {
+ unsigned long long sSize;
+ unsigned long long sCumSize;
+ int sIdx;
+
+ sVdrFileEntry () {};
+ sVdrFileEntry (off_t s, off_t t, int i)
+ : sSize(s), sCumSize(t), sIdx(i) {};
+};
+
+void HttpResourceStartThread(void* arg) {
+ cHttpResource* m = (cHttpResource*)arg;
+ m->threadLoop();
+ delete m;
+ pthread_exit(NULL);
+}
+
+// An HTTP resource has two states: Either Read data or write data.
+// In read data (until the header and the full body is received), there is no data to write
+// in Write data, there is no data to read
+// The only think for both states is to watch the socket close state.
+cHttpResource::cHttpResource(int f, int id,string addr, int port): mServerAddr(addr), mServerPort(port), mFd(f), mReqId(id), mConnected(true), mConnState(WAITING),
+ mMethod(), mDataBuffer(NULL), mBlkData(false), mBlkPos(0), mBlkLen(0), mPath(), mVersion(), protocol(),
+ mAcceptRanges(true), rangeHdr(), mFileSize(-1), mRemLength(0) {
+
+ mLog = Log::getInstance();
+
+ *(mLog->log()) << DEBUGPREFIX
+ << " ------- Hello ------- "
+ << DEBUGHDR<< endl;
+
+ pthread_mutex_init(&mSendLock, NULL);
+ mDataBuffer = new char[MAXLEN];
+}
+
+
+cHttpResource::~cHttpResource() {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ------- Bye ----- "
+ << " mConnState= " << getConnStateName()
+ << DEBUGHDR << endl;
+ delete mDataBuffer;
+}
+
+void cHttpResource::setNonBlocking() {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Set Socket to non-blocking"
+ << DEBUGHDR << endl;
+ int oldflags = fcntl(mFd, F_GETFL, 0);
+ oldflags |= O_NONBLOCK;
+ fcntl(mFd, F_SETFL, oldflags);
+}
+
+int cHttpResource::run() {
+ if (pthread_create(&mThreadId, NULL, (void*(*)(void*))HttpResourceStartThread, (void *)this) == -1)
+ return 0;
+ return 1;
+}
+
+void cHttpResource::threadLoop() {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Thread Started"
+ << DEBUGHDR << endl;
+
+ // The default is to read one HTTP request and then close...
+
+ fd_set read_state;
+ int maxfd;
+ struct timeval waitd;
+ FD_ZERO(&read_state);
+ FD_SET(mFd, &read_state);
+ maxfd = mFd;
+
+ for (;;) {
+ fd_set readfds;
+ int ret;
+
+ waitd.tv_sec = 1; // Make select wait up to 1 second for data
+ waitd.tv_usec = 0; // and 0 milliseconds.
+
+ readfds = read_state;
+ ret = select(maxfd + 1, &readfds, NULL, NULL, &waitd);
+ if ((ret < 0) && (errno == EINTR)) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Error: " << strerror(errno)
+ << DEBUGHDR << endl;
+ continue;
+ }
+
+ // Check for data on already accepted connections
+ if (FD_ISSET(mFd, &readfds)) {
+ *(mLog->log())<< endl;
+ *(mLog->log())<< DEBUGPREFIX
+ << " Request Received from Client fd= "<< mFd
+ << DEBUGHDR << endl;
+
+ ret = readFromClient();
+ if (ret <0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Closing Connection"
+ << DEBUGHDR
+ << endl;
+ close(mFd);
+ break;
+ }
+ }
+ } // for (;;)
+
+ *(mLog->log())<< DEBUGPREFIX
+ << " Left loop to terminate fd= " << mFd
+ << DEBUGHDR << endl;
+}
+
+int cHttpResource::readFromClient() {
+ // int ret =0;
+ struct stat statbuf;
+ int ret = OKAY;
+
+ *(mLog->log())<< DEBUGPREFIX
+ << DEBUGHDR << endl;
+
+ if (mConnState != WAITING) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: State is not WAITING"
+ << DEBUGHDR << endl;
+ return OKAY;
+ }
+
+ processHttpHeaderNew();
+ *(mLog->log())<< " mPath= " << mPath << endl;
+ // << " mPath(utf8)= " << iso8859ToUtf8(mPath)
+
+ setNonBlocking();
+ mConnState = SERVING;
+
+ // done - look for range header
+ if (strcasecmp(mMethod.c_str(), "GET") != 0){
+ sendError(501, "Not supported", NULL, "Method is not supported.");
+ return ERROR;
+ }
+
+ if (mPath.compare("/recordings.html") == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << "generating /recordings.html"
+ << DEBUGHDR << endl;
+
+ ret = sendRecordingsHtml( &statbuf);
+ return ERROR;
+ }
+ if (mPath.compare("/recordings.xml") == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << "generating /recordings.xml"
+ << DEBUGHDR << endl;
+
+ ret = sendRecordingsXml( &statbuf);
+ return ERROR;
+ }
+
+ if (stat(mPath.c_str(), &statbuf) < 0) {
+ sendError(404, "Not Found", NULL, "File not found.");
+ return ERROR;
+ }
+ else {
+ if (S_ISDIR(statbuf.st_mode)) {
+ if (mPath.find(".rec") != string::npos) {
+ ret = sendVdrDir( &statbuf);
+ }
+ else {
+ sendDir( &statbuf);
+ }
+ }
+ else {
+ printf ("file send\n");
+ mFileSize = statbuf.st_size;
+ ret = sendFirstChunk(&statbuf);
+ }
+ }
+ if (mRemLength <=0)
+ ret = ERROR;
+ if (mConnState == TOCLOSE)
+ ret = ERROR;
+
+ return ret;
+}
+
+void cHttpResource::sendError(int status, const char *title, const char *extra, const char *text) {
+ char f[400];
+
+ mConnState = TOCLOSE;
+ string hdr = "";
+ sendHeaders(status, title, extra, "text/html", -1, -1);
+ snprintf(f, sizeof(f), "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\r\n", status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "<BODY><H4>%d %s</H4>\r\n", status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "%s\r\n", text);
+ hdr += f;
+ snprintf(f, sizeof(f), "</BODY></HTML>\r\n");
+ hdr += f;
+
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return;
+ }
+}
+
+
+int cHttpResource::sendDir(struct stat *statbuf) {
+ char pathbuf[4096];
+ char f[400];
+ int len;
+
+ mConnState = TOCLOSE;
+ *(mLog->log())<< "sendDir:" << endl;
+ *(mLog->log())<< "path= " << mPath << endl;
+ len = mPath.length();
+ int ret = OKAY;
+
+ if (len == 0 || mPath[len - 1] != '/') {
+ snprintf(pathbuf, sizeof(pathbuf), "Location: %s/", mPath.c_str());
+ sendError(302, "Found", pathbuf, "Directories must end with a slash.");
+ ret = ERROR;
+ }
+ else {
+ snprintf(pathbuf, sizeof(pathbuf), "%sindex.html", mPath.c_str());
+ if (stat(pathbuf, statbuf) >= 0) {
+ mPath = pathbuf;
+ sendFirstChunk(statbuf);
+ // sendFile(pathbuf, statbuf); // found an index.html file in the directory
+ }
+ else {
+ DIR *dir;
+ struct dirent *de;
+
+ sendHeaders(200, "OK", NULL, "text/html", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ snprintf(f, sizeof(f), "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD>\r\n<BODY>", mPath.c_str());
+ hdr += f;
+ snprintf(f, sizeof(f), "<H4>Index of %s</H4>\r\n<PRE>\n", mPath.c_str());
+ hdr += f;
+ snprintf(f, sizeof(f), "Name Last Modified Size\r\n");
+ hdr += f;
+ snprintf(f, sizeof(f), "<HR>\r\n");
+ hdr += f;
+
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+
+ if (len > 1) {
+ snprintf(f, sizeof(f), "<A HREF=\"..\">..</A>\r\n");
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+ }
+
+ dir = opendir(mPath.c_str());
+ while ((de = readdir(dir)) != NULL) {
+ char timebuf[32];
+ struct tm *tm;
+ strcpy(pathbuf, mPath.c_str());
+ printf (" -Entry: %s\n", de->d_name);
+ strcat(pathbuf, de->d_name);
+
+ stat(pathbuf, statbuf);
+ tm = gmtime(&(statbuf->st_mtime));
+ strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tm);
+
+ hdr = "";
+ snprintf(f, sizeof(f), "<A HREF=\"%s%s\">", de->d_name, S_ISDIR(statbuf->st_mode) ? "/" : "");
+ hdr += f;
+
+ snprintf(f, sizeof(f), "%s%s", de->d_name, S_ISDIR(statbuf->st_mode) ? "/</A>" : "</A> ");
+ hdr += f;
+
+ if (strlen(de->d_name) < 32) {
+ snprintf(f, sizeof(f), "%*s", 32 - strlen(de->d_name), "");
+ hdr += f;
+ }
+ if (S_ISDIR(statbuf->st_mode)) {
+ snprintf(f, sizeof(f), "%s\r\n", timebuf);
+ hdr += f;
+ }
+ else {
+ snprintf(f, sizeof(f), "%s\r\n", timebuf);
+ hdr += f;
+ }
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+ }
+ closedir(dir);
+ *(mLog->log())<< DEBUGPREFIX
+ << "Done"
+ << DEBUGHDR << endl;
+
+ snprintf(f, sizeof(f), "</PRE>\r\n<HR>\r\n<ADDRESS>%s</ADDRESS>\r\n</BODY></HTML>\r\n", SERVER);
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+ }
+ }
+ if (mRemLength != 0)
+ *(mLog->log())<< " WARNING: mRemLength not zero" << endl;
+ mRemLength = 0;
+ return ret;
+}
+
+string cHttpResource::removeEtChar(string line) {
+ bool done = false;
+ size_t cur_pos = 0;
+ size_t pos = 0;
+ string res = "";
+
+ int end_after_done = 0;
+
+ while (!done) {
+ pos = line.find('&', cur_pos);
+ if (pos == string::npos) {
+ done = true;
+ res += line.substr(cur_pos);
+ break;
+ }
+ if (pos >= 0) {
+ res += line.substr(cur_pos, (pos-cur_pos)) + "&#38;";
+ // cur_pos = cur_pos+ pos +1;
+ cur_pos = pos +1;
+ end_after_done ++;
+ }
+ }
+ if (end_after_done != 0) {
+ *(mLog->log())<< "removeEtChar" << " line= " << line;
+ *(mLog->log())<< " res= " << res << " occurances= " << end_after_done << endl;
+ }
+ return res;
+}
+
+int cHttpResource::sendRecordingsXml(struct stat *statbuf) {
+ sendHeaders(200, "OK", NULL, "application/xml", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
+ // hdr += "<?xml version=\"1.0\"?>\n";
+ hdr += "<rss version=\"2.0\">\n";
+ hdr+= "<channel>\n";
+
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+
+ char buff[20];
+ char f[400];
+ cRecordings Recordings;
+ Recordings.Load();
+
+ int count = 0;
+ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
+ hdr = "";
+ if (recording->HierarchyLevels() == 0) {
+ if (++count != 11)
+ continue;
+ hdr += "<item>\n";
+ strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&recording->start));
+ hdr += "<title>";
+ snprintf(f, sizeof(f), "%s - %s", buff, removeEtChar(recording->Name()).c_str());
+ hdr += f;
+ hdr += "</title>\n";
+ hdr += "<link>";
+ snprintf(f, sizeof(f), "http://%s:%d%s/", mServerAddr.c_str(), mServerPort, removeEtChar(recording->FileName()).c_str());
+ hdr += f;
+ hdr += "</link>\n";
+ hdr += "<description>";
+ // TODO Title
+ const cRecordingInfo* info = recording->Info();
+ // snprintf(f, sizeof(f), "title= %s\n", info->Title());
+ // hdr += f;
+ hdr += "Hallo";
+ hdr += "HexDump title= \n" + hexDump(recording->Name());
+ hdr += "\nHexDump link= \n" + hexDump(recording->FileName()) + "\n";
+ *(mLog->log())<< DEBUGPREFIX
+ << " *** "
+ << " HexDump title= \n" << hexDump(recording->Name()) << endl
+ << " HexDump link= \n" << hexDump(recording->FileName()) << endl
+ << DEBUGHDR
+ << endl;
+
+ // snprintf(f, sizeof(f), "short= %s\n", info->ShortText());
+ // hdr += f;
+ // snprintf(f, sizeof(f), "desc= %s\n", info->Description());
+ // hdr += f;
+
+ hdr += "</description>\n";
+ hdr += "</item>\n";
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+ }
+ // start is time_t
+ }
+
+ hdr += "</channel>\n";
+ hdr += "</rss>\n";
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+
+ return ERROR;
+}
+int cHttpResource::sendRecordingsHtml(struct stat *statbuf) {
+ sendHeaders(200, "OK", NULL, "text/html", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<HTML><HEAD><TITLE>Recordings</TITLE></HEAD>\r\n";
+ hdr += "<meta http-equiv=\"content-type\" content=\"text/html;charset=ISO-8859-15\"/>\r\n";
+ hdr += "<BODY>";
+ hdr += "<H4>Recordings</H4>\r\n<PRE>\n";
+ hdr += "<HR>\r\n";
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+
+ char buff[20];
+ char f[400];
+ cRecordings Recordings;
+ Recordings.Load();
+ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
+ hdr = "";
+ strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&recording->start));
+ snprintf(f, sizeof(f), "%s - %d <A HREF=\"%s/\">%s</A>\r\n", buff, recording->HierarchyLevels(), recording->FileName(), recording->Name());
+ hdr += f;
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return ERROR;
+ }
+ // start is time_t
+ }
+ return ERROR;
+}
+
+int cHttpResource::sendVdrDir(struct stat *statbuf) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " *** "
+ << DEBUGHDR
+ << endl;
+ char pathbuf[4096];
+ char f[400];
+ int vdr_idx = 0;
+ off_t total_file_size = 0;
+ int ret = OKAY;
+
+ string vdr_dir = mPath;
+ vector<sVdrFileEntry> file_sizes;
+ bool more_to_go = true;
+
+ while (more_to_go) {
+ vdr_idx ++;
+ snprintf(pathbuf, sizeof(pathbuf), "%s%03d.vdr", mPath.c_str(), vdr_idx);
+ if (stat(pathbuf, statbuf) >= 0) {
+ *(mLog->log())<< " found for " << pathbuf << endl;
+ file_sizes.push_back(sVdrFileEntry(statbuf->st_size, total_file_size, vdr_idx));
+ total_file_size += statbuf->st_size;
+ }
+ else {
+ more_to_go = false;
+ }
+ }
+ if (file_sizes.size() < 1) {
+ // There seems to be vdr video file in the directory
+ *(mLog->log())<< DEBUGPREFIX
+ << " No video file in the directory"
+ << DEBUGHDR << endl;
+ sendError(404, "Not Found", NULL, "File not found.");
+ ret = ERROR;
+ return ret;
+ }
+ *(mLog->log())<< DEBUGPREFIX
+ << " vdr filesize list "
+ << DEBUGHDR << endl;
+
+ for (uint i = 0; i < file_sizes.size(); i++)
+ *(mLog->log())<< " i= " << i << " size= " << file_sizes[i].sSize << " tSize= " << file_sizes[i].sCumSize << endl;
+ // *(mLog->log())<< endl;
+ *(mLog->log())<< " total_file_size= " << total_file_size << endl;
+
+ uint cur_idx = 0;
+
+ if (!rangeHdr.isRangeRequest) {
+ snprintf(pathbuf, sizeof(pathbuf), "%s%03d.vdr", mPath.c_str(), file_sizes[cur_idx].sIdx);
+
+ if (openFile(pathbuf) != OKAY)
+ return ERROR;
+
+ mRemLength = total_file_size;
+ sendHeaders(200, "OK", NULL, "video/mpeg", total_file_size, statbuf->st_mtime);
+ }
+ else { // Range request
+ // idenify the first file
+ *(mLog->log())<< DEBUGPREFIX
+ << " Range Request Handling"
+ << DEBUGHDR << endl;
+
+ cur_idx = file_sizes.size() -1;
+ for (uint i = 0; i < file_sizes.size(); i++) {
+ if (file_sizes[i].sCumSize > rangeHdr.begin) {
+ cur_idx = i -1;
+ break;
+ }
+ }
+ *(mLog->log())<< " Identified Record i= " << cur_idx << " file_sizes[i].sCumSize= "
+ << file_sizes[cur_idx].sCumSize << " rangeHdr.begin= " << rangeHdr.begin
+ << " vdr_no= " << file_sizes[cur_idx].sIdx << endl;
+ snprintf(pathbuf, sizeof(pathbuf), "%s%03d.vdr", mPath.c_str(), file_sizes[cur_idx].sIdx);
+ *(mLog->log())<< " file identified= " << pathbuf << endl;
+ if (openFile(pathbuf) != OKAY)
+ return ERROR;
+
+ mPath = pathbuf;
+ *(mLog->log())<< " Seeking into file= " << (rangeHdr.begin - file_sizes[cur_idx].sCumSize)
+ << " cur_idx= " << cur_idx
+ << " file_sizes[cur_idx].sCumSize= " << file_sizes[cur_idx].sCumSize
+ << endl;
+
+ fseek(mFile, (rangeHdr.begin - file_sizes[cur_idx].sCumSize), SEEK_SET);
+ if (rangeHdr.end == 0)
+ rangeHdr.end = total_file_size;
+ mRemLength = (rangeHdr.end-rangeHdr.begin);
+ snprintf(f, sizeof(f), "Content-Range: bytes %lld-%lld/%lld", rangeHdr.begin, (rangeHdr.end -1), total_file_size);
+ sendHeaders(206, "Partial Content", f, "video/mpeg", total_file_size, statbuf->st_mtime);
+ }
+
+ *(mLog->log())<< " ***** Yes, vdr dir found ***** mPath= " << mPath<< endl;
+
+ while (cur_idx < file_sizes.size()) {
+ ret = sendDataChunk();
+ fclose(mFile);
+
+ if (ret == ERROR) {
+ /* *(mLog->log())<< DEBUGPREFIX
+ << " Error, returning"
+ << DEBUGHDR << endl;
+ */
+ return ERROR;
+ break;
+ }
+ cur_idx ++;
+ if (cur_idx == file_sizes.size()) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Done "
+ << DEBUGHDR << endl;
+ break;
+ }
+ if (ret == OKAY) {
+ snprintf(pathbuf, sizeof(pathbuf), "%s%03d.vdr", mPath.c_str(), file_sizes[cur_idx].sIdx);
+ *(mLog->log())<< " Next File pathbuf= " << pathbuf << endl;
+ if (openFile(pathbuf) != OKAY)
+ return ERROR;
+ }
+ }
+ return ret;
+}
+
+void cHttpResource::sendHeaders(int status, const char *title, const char *extra, const char *mime,
+ off_t length, time_t date) {
+
+ time_t now;
+ char timebuf[128];
+ char f[400];
+
+ string hdr = "";
+ snprintf(f, sizeof(f), "%s %d %s\r\n", PROTOCOL, status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "Server: %s\r\n", SERVER);
+ hdr += f;
+ now = time(NULL);
+ strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));
+ snprintf(f, sizeof(f), "Date: %s\r\n", timebuf);
+ hdr += f;
+ if (extra) {
+ snprintf(f, sizeof(f), "%s\r\n", extra);
+ hdr += f;
+ }
+ if (mime) {
+ snprintf(f, sizeof(f), "Content-Type: %s\r\n", mime);
+ hdr += f;
+ }
+ if (length >= 0) {
+ snprintf(f, sizeof(f), "Content-Length: %lld\r\n", length);
+ *(mLog->log())<< DEBUGPREFIX
+ << " length= " << length
+ << DEBUGHDR << endl;
+ hdr += f;
+ }
+ if (date != -1) {
+ strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&date));
+ snprintf(f, sizeof(f), "Last-Modified: %s\r\n", timebuf);
+ hdr += f;
+ }
+ if (mAcceptRanges) {
+ snprintf(f, sizeof(f), "Accept-Ranges: bytes\r\n");
+ hdr += f;
+ }
+ snprintf(f, sizeof(f), "Connection: close\r\n");
+ hdr += f;
+
+ snprintf(f, sizeof(f), "\r\n");
+ hdr += f;
+ if ( writeToClient(hdr.c_str(), hdr.size()) == ERROR) {
+ return;
+ }
+
+}
+
+int cHttpResource::sendFirstChunk(struct stat *statbuf) {
+ // Send the First Datachunk, incl all headers
+
+ *(mLog->log())<< "fd= " << mFd << " mReqId= "<< mReqId << " mPath= " << mPath
+ << DEBUGHDR
+ << endl;
+
+ char f[400];
+
+ if (openFile(mPath.c_str()) == ERROR)
+ return ERROR;
+ mFile = fopen(mPath.c_str(), "r");
+ int ret = OKAY;
+
+ if (!mFile) {
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ ret = ERROR;
+ }
+ else {
+ mFileSize = S_ISREG(statbuf->st_mode) ? statbuf->st_size : -1;
+
+ if (!rangeHdr.isRangeRequest) {
+ mRemLength = mFileSize;
+ sendHeaders(200, "OK", NULL, getMimeType(mPath.c_str()), mFileSize, statbuf->st_mtime);
+ }
+ else { // Range request
+ fseek(mFile, rangeHdr.begin, SEEK_SET);
+ if (rangeHdr.end == 0)
+ rangeHdr.end = mFileSize;
+ mRemLength = (rangeHdr.end-rangeHdr.begin);
+ snprintf(f, sizeof(f), "Content-Range: bytes %lld-%lld/%lld", rangeHdr.begin, (rangeHdr.end -1), mFileSize);
+ sendHeaders(206, "Partial Content", f, getMimeType(mPath.c_str()), (rangeHdr.end-rangeHdr.begin), statbuf->st_mtime);
+ }
+
+ *(mLog->log())<< "fd= " << mFd << " mReqId= "<< mReqId
+ << ": Done mRemLength= "<< mRemLength << " ret= " << ret
+ << DEBUGHDR << endl;
+
+ ret = sendDataChunk();
+ *(mLog->log())<< " Close File" << endl;
+ fclose(mFile);
+ }
+ return ret;
+
+}
+
+int cHttpResource::sendDataChunk() {
+ // the the paload of an open file
+ *(mLog->log())<< DEBUGPREFIX
+ << " mConnState= " << getConnStateName() << " mRemLength= "<< mRemLength
+ << DEBUGHDR << endl;
+
+ int n;
+ int chunk_no =0;
+ int bytes_to_send = 0;
+
+ int ret = 0;
+
+ char buf[MAXLEN];
+ int buflen = sizeof(buf);
+
+ if (!mConnected)
+ return ERROR;
+
+ if (mConnState == WAITING) {
+ *(mLog->log())<< "fd= " << mFd
+ << " Something wrong: Should not be here"
+ << DEBUGHDR << endl;
+ return OKAY;
+ }
+ if (mRemLength ==0) {
+ *(mLog->log())<< " closing connection"
+ << DEBUGHDR << endl;
+ mConnected = false;
+ close (mFd);
+ return ERROR;
+ }
+ if (mConnState == TOCLOSE) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " closing connection"
+ << DEBUGHDR << endl;
+ mConnected = false;
+ close (mFd);
+ return ERROR;
+ }
+
+ bool done = false;
+ while (!done) {
+ // Check whethere there is no other data to send (from last time)
+ if (mRemLength == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " mRemLength == 0 --> closing connection"
+ << DEBUGHDR << endl;
+ mConnected = false;
+ close (mFd);
+ return ERROR;
+
+ }
+ if (mRemLength >= buflen) {
+ bytes_to_send = buflen;
+ }
+ else
+ bytes_to_send = mRemLength;
+
+ n = fread(buf, 1, bytes_to_send, mFile);
+ if (n != bytes_to_send) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " -- Something wrong here - n= " << n << " bytes_to_send= " << bytes_to_send
+ << DEBUGHDR << endl;
+ done = true;
+ }
+
+ ret = writeToClient( buf, bytes_to_send);
+ if (ret == ERROR) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Stopping - Client closed connection "
+ << DEBUGHDR << endl;
+
+ // socket had blocket. wait until select comes back.
+ done = true;
+ // fclose(mFile);
+ ret = ERROR;
+ break;
+ }
+
+ mRemLength -= bytes_to_send;
+ chunk_no ++;
+ // *(mLog->log())<< " chunk_no= " << chunk_no << endl;
+ }
+
+ return ret;
+}
+
+int cHttpResource::writeToClient(const char *buf, size_t buflen) {
+ // *(mLog->log())<< __PRETTY_FUNCTION__ << " (" << __LINE__ << "): "
+ // << "fd= " << mFd << " mReqId=" << mReqId << " mConnected= " << ((mConnected)? "true":"false")
+ // << " buflen= " << buflen << " mRemLength= "<< mRemLength << endl;
+
+ unsigned int bytes_written = 0;
+ int this_write;
+ int retries = 0;
+
+ struct timeval timeout;
+
+ int ret = 0;
+ fd_set write_set;
+
+ FD_ZERO(&write_set);
+
+ pthread_mutex_lock(&mSendLock);
+
+ if (!mConnected) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " not connected anymore"
+ DEBUGHDR << endl;
+ pthread_mutex_unlock(&mSendLock);
+ return ERROR;
+ }
+
+ if (mConnState == WAITING) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Should not be in WAITING state"
+ << DEBUGHDR << endl;
+ pthread_mutex_unlock(&mSendLock);
+ return OKAY;
+ }
+
+ bool done = false;
+ while (!done) {
+ FD_ZERO(&write_set);
+ FD_SET(mFd, &write_set);
+ timeout.tv_sec = 10;
+ timeout.tv_usec = 0;
+ ret = select(mFd + 1, NULL, &write_set, NULL, NULL);
+ if (ret < 1) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Select returned error -- Closing connection"
+ << DEBUGHDR << endl;
+ mConnected = false;
+ pthread_mutex_unlock(&mSendLock);
+ close(mFd);
+ return ERROR; // error, or timeout
+ }
+ if (ret == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Select returned ZERO -- Closing connection"
+ << DEBUGHDR << endl;
+ mConnected = false;
+ pthread_mutex_unlock(&mSendLock);
+ close(mFd);
+ return ERROR; // error, or timeout
+ }
+ this_write = write(mFd, &buf[bytes_written], buflen - bytes_written);
+ if (this_write <=0) {
+ /* *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: Stopped (Client terminated Connection)"
+ << DEBUGHDR << endl;
+ */
+ mConnected = false;
+ close(mFd);
+ pthread_mutex_unlock(&mSendLock);
+ return ERROR;
+ }
+ bytes_written += this_write;
+
+ if (bytes_written == buflen) {
+ done = true;
+ break;
+ }
+ else {
+ if (++retries == 100) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: Too many retries "
+ << DEBUGHDR << endl;
+ mConnected = false;
+ close(mFd);
+ pthread_mutex_unlock(&mSendLock);
+ return ERROR;
+ }
+ }
+ }
+ // *(mLog->log())<< __PRETTY_FUNCTION__ << " (" << __LINE__ << "): "
+ // << " done "<< endl;
+ // Done with writing
+ pthread_mutex_unlock(&mSendLock);
+
+ return OKAY;
+}
+
+
+
+
+char *cHttpResource::getMimeType(const char *name) {
+ char *ext = strrchr((char*)name, '.');
+ if (!ext)
+ return NULL;
+ // if (ext.compare(".html") || ext.compare(".htm")) return "text/html";
+ if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) return "text/html";
+ if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
+ if (strcmp(ext, ".gif") == 0) return "image/gif";
+ if (strcmp(ext, ".png") == 0) return "image/png";
+ if (strcmp(ext, ".css") == 0) return "text/css";
+ if (strcmp(ext, ".au") == 0) return "audio/basic";
+ if (strcmp(ext, ".wav") == 0) return "audio/wav";
+ if (strcmp(ext, ".avi") == 0) return "video/x-msvideo";
+ if (strcmp(ext, ".mp4") == 0) return "video/mp4";
+ if (strcmp(ext, ".vdr") == 0) return "video/mpeg";
+ if (strcmp(ext, ".mpeg") == 0 || strcmp(ext, ".mpg") == 0) return "video/mpeg";
+ if (strcmp(ext, ".mp3") == 0) return "audio/mpeg";
+ return NULL;
+}
+
+int cHttpResource::processHttpHeaderNew() {
+ *(mLog->log())<< DEBUGPREFIX
+ << " processHttpHeaderNew "
+ << DEBUGHDR << endl;
+
+ char buf[MAXLEN];
+ int buflen = sizeof(buf);
+
+ int line_count = 0;
+ bool hdr_end_found = false;
+ bool is_req = true;
+ // block until the entire request header is read
+ string rem_hdr = "";
+
+ while (!hdr_end_found) {
+ int count = 0;
+ while ((buflen = read(mFd, buf, sizeof(buf))) == -1)
+ count ++;
+ if (count != 0)
+ *(mLog->log())<< " Blocked for " << count << " Iterations " << endl;
+ //FIXME. Better return and wait.
+ if (buflen == -1) {
+ *(mLog->log())<< " Some Error" << endl;
+ return 2; // Nothing to read
+ }
+ #ifdef DEBUG_REQHEADERS
+ *(mLog->log())<< " Read " << buflen << " Bytes from " << fd << endl;
+ #endif
+ string req_line = rem_hdr + buf;
+ if (rem_hdr.size() != 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " rem_hdr.size() = " << rem_hdr.size()
+ << DEBUGHDR << endl;
+ }
+ buflen += rem_hdr.size();
+
+ size_t last_pos = 0;
+ while (true) {
+ line_count ++;
+ size_t pos = req_line.find ("\r\n", last_pos);
+ if (pos > buflen) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Pos (" << pos << ") outside of read buffer"
+ << DEBUGHDR << endl;
+ rem_hdr = req_line.substr(last_pos, buflen - last_pos);
+ *(mLog->log())<< DEBUGPREFIX
+ << " No HdrEnd Found, read more data. rem_hdr= " << rem_hdr.size()
+ << DEBUGHDR << endl;
+ break;
+ }
+ if ((last_pos - pos) == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Header End Found"
+ << DEBUGHDR << endl;
+ hdr_end_found = true;
+ break;
+ }
+
+ if (pos == string::npos){
+ // not found
+ rem_hdr = req_line.substr(last_pos, buflen - last_pos);
+ *(mLog->log())<< DEBUGPREFIX
+ << " No HdrEnd Found, read more data. rem_hdr= " << rem_hdr.size()
+ << DEBUGHDR << endl;
+ break;
+ }
+
+ string line = req_line.substr(last_pos, (pos-last_pos));
+
+#ifdef DEBUG_REQHEADERS
+ *(mLog->log())<< " Line= " << line << endl;
+#endif
+ last_pos = pos +2;
+ if (is_req) {
+ is_req = false;
+ // Parse the request line
+ mMethod = line.substr(0, line.find_first_of(" "));
+ mPath = line.substr(line.find_first_of(" ") +1, (line.find_last_of(" ") - line.find_first_of(" ") -1));
+ mVersion = line.substr(line.find_last_of(" ") +1);
+
+ *(mLog->log())<< DEBUGPREFIX
+ << " ReqLine= " << line << endl;
+ *(mLog->log())<< DEBUGPREFIX
+ << " mMethod= " << mMethod
+ << " mPath= " << mPath
+ // << " mPath(utf8)= " << iso8859ToUtf8(mPath)
+ << " mVer= " << mVersion
+ << " HexDump= " << endl << hexDump(mPath)
+ << DEBUGHDR << endl;
+ }
+ else {
+ string hdr_name = line.substr(0, line.find_first_of(":"));
+ string hdr_val = line.substr(line.find_first_of(":") +2);
+ if (hdr_name.compare("Range") == 0) {
+ parseRangeHeaderValue(hdr_val);
+ *(mLog->log())<< " Range: Begin= " << rangeHdr.begin
+ << " End= " << rangeHdr.end
+ << endl;
+ }
+ if (hdr_name.compare("User-Agent") == 0) {
+ // *(mLog->log())<< " ***" << hdr_name << endl;
+ *(mLog->log())<< " User-Agent: " << hdr_val
+ << endl;
+ }
+
+ }
+ // *(mLog->log())<< " update last_pos= " << last_pos << endl;;
+ }
+ }
+ // exit(0);
+ return OKAY;
+}
+
+string cHttpResource::getConnStateName() {
+ string state_string;
+ switch (mConnState) {
+ case WAITING:
+ state_string = "WAITING";
+ break;
+ case SERVING:
+ state_string = "SERVING";
+ break;
+ case TOCLOSE:
+ state_string = "TOCLOSE";
+ break;
+ default:
+ state_string = "UNKNOWN";
+ break;
+ }
+ return state_string;
+}
+
+int cHttpResource::parseRangeHeaderValue(string val) {
+ rangeHdr.isRangeRequest = true;
+ size_t pos_equal = val.find_first_of('=');
+ size_t pos_minus = val.find_first_of('-');
+
+ string range_type = val.substr(0, pos_equal);
+ string first_val= val.substr(pos_equal+1, pos_minus -(pos_equal+1));
+ rangeHdr.begin = atoll(first_val.c_str());
+
+ string sec_val = "";
+ if ((pos_minus +1)< val.size()){
+ sec_val = val.substr(pos_minus+1);
+ rangeHdr.end = atoll(sec_val.c_str());
+ }
+ // *(mLog->log())<< " RangeType= (" << range_type
+ // << ") Begin= " << rangeHdr.begin
+ // << " End= " << rangeHdr.end
+ // << endl;
+ // *(mLog->log())<< " equal= " << equal << " minus= " << minus << " size= " << hdr_val.size() << endl;
+
+}
+
+int cHttpResource::openFile(const char *name) {
+ mFile = fopen(name, "r");
+ if (!mFile) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " fopen failed pathbuf= " << name
+ << DEBUGHDR << endl;
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ return ERROR;
+ }
+ return OKAY;
+}
+
+string cHttpResource::hexDump(string in) {
+ string res = "";
+ string ascii = "";
+ char buf[10];
+
+ int line_count = 0;
+ for (uint i = 0; i < in.size(); i++) {
+ unsigned char num = in[i];
+ sprintf (buf, "%02hhX", num);
+ if ((num >= 32) && (num < 127)) {
+ ascii += char(num);
+ }
+ else
+ ascii += '.';
+ res += buf;
+
+ line_count++;
+ switch (line_count) {
+ case 8:
+ res += " ";
+ ascii += " ";
+ break;
+ case 16:
+ res += " " + ascii;
+ res += "\r\n";
+ ascii = "";
+ line_count = 0;
+ break;
+ default:
+ res += " ";
+ break;
+ }
+ }
+ if (line_count != 0) {
+ for (int i = 0; i < ((16 - line_count) * 3 ); i++)
+ res += " ";
+ if (line_count >= 8)
+ res += " ";
+ res += ascii;
+ }
+ return res;
+}
+
+
+string iso8859ToUtf8 (string input) {
+ string res = "";
+
+ /* for (uint i = 0; i < input.size(); i++) {
+ unsigned char num = input[i];
+ if (num < 128)
+ res += char(num);
+ else {
+ // res += char(0xc2 + (num > 0xbf));
+ // res += char((num & 0x3f) +0x80);
+ }
+
+ }*/
+
+ return res;
+ // unsigned char *in, *out;
+ // while (*in)
+ // if (*in<128) *out++=*in++;
+ // else *out++=0xc2+(*in>0xbf), *out++=(*in++&0x3f)+0x80;
+
+ }
diff --git a/vdr-smarttvweb/httpresource-hmm.h b/vdr-smarttvweb/httpresource-hmm.h
new file mode 100644
index 0000000..543377d
--- /dev/null
+++ b/vdr-smarttvweb/httpresource-hmm.h
@@ -0,0 +1,94 @@
+
+#ifndef __HTTPREQUEST_H__
+#define __HTTPREQUEST_H__
+
+#include <string>
+#include <cstring>
+#include <pthread.h>
+#include "log.h"
+
+using namespace std;
+
+struct cRange {
+cRange(): isRangeRequest(false), begin(0), end(0) {};
+ bool isRangeRequest;
+ unsigned long long begin;
+ unsigned long long end;
+};
+
+enum eConnState {
+ WAITING,
+ SERVING,
+ TOCLOSE
+};
+
+class cHttpResource {
+
+ public:
+ cHttpResource(int, int, string, int);
+ virtual ~cHttpResource();
+
+ int readFromClient();
+ // int sendNextChunk();
+ void threadLoop();
+ int run();
+
+ private:
+ Log* mLog;
+ pthread_t mThreadId;
+ pthread_mutex_t mSendLock;
+ string mServerAddr;
+ int mServerPort;
+ int mFd;
+ int mReqId;
+
+ bool mConnected;
+ eConnState mConnState;
+ string mMethod;
+ char *mDataBuffer;
+ bool mBlkData;
+ int mBlkPos;
+ int mBlkLen;
+
+ // string path;
+ string mPath;
+ string mVersion;
+ string protocol;
+
+ bool mAcceptRanges;
+ cRange rangeHdr;
+ unsigned long long mFileSize;
+ uint mRemLength;
+ FILE *mFile;
+
+
+ // int tcpServerWrite(const char buf[], int buflen);
+ int writeToClient(const char *buf, size_t buflen);
+ int sendDataChunk();
+
+ void setNonBlocking();
+
+ int processHttpHeaderNew();
+ // int processHttpHeader();
+ void sendError(int status, const char *title, const char *extra, const char *text);
+ int sendDir(struct stat *statbuf);
+ int sendVdrDir(struct stat *statbuf);
+ int sendRecordingsHtml (struct stat *statbuf);
+ int sendRecordingsXml (struct stat *statbuf);
+ string removeEtChar(string line);
+
+ void sendHeaders(int status, const char *title, const char *extra, const char *mime,
+ off_t length, time_t date);
+
+ int sendFirstChunk(struct stat *statbuf);
+
+ // Helper Functions
+ char *getMimeType(const char *name);
+ string getConnStateName();
+ int parseRangeHeaderValue(string);
+ int openFile(const char *name);
+ string hexDump(string in);
+ string iso8859ToUtf8 (string);
+
+};
+#endif
diff --git a/vdr-smarttvweb/httpresource.c b/vdr-smarttvweb/httpresource.c
new file mode 100644
index 0000000..c10721b
--- /dev/null
+++ b/vdr-smarttvweb/httpresource.c
@@ -0,0 +1,2122 @@
+/*
+ * httpresource.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <vector>
+#include "httpresource.h"
+#include "smarttvfactory.h"
+#include "stvw_cfg.h"
+
+#include "url.h"
+
+#ifndef STANDALONE
+#include <vdr/recording.h>
+#include <vdr/channels.h>
+#include <vdr/timers.h>
+#include <vdr/videodir.h>
+#include <vdr/epg.h>
+#endif
+
+#define SERVER "SmartTvWeb/0.1"
+#define PROTOCOL "HTTP/1.1"
+#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"
+
+#define MAXLEN 4096
+#define OKAY 0
+#define ERROR (-1)
+#define DEBUG_REGHEADERS
+#define DEBUGPREFIX "mReqId= " << mReqId << " fd= " << mFd
+#define DEBUGHDR " " << __PRETTY_FUNCTION__ << " (" << __LINE__ << ") "
+
+#define DEBUG
+
+#define SEGMENT_DURATION 10
+
+using namespace std;
+
+
+struct sVdrFileEntry {
+ unsigned long long sSize;
+ unsigned long long sFirstOffset;
+ int sIdx;
+
+ sVdrFileEntry () {};
+ sVdrFileEntry (off_t s, off_t t, int i)
+ : sSize(s), sFirstOffset(t), sIdx(i) {};
+};
+
+
+struct sTimerEntry {
+ string name;
+ time_t startTime;
+ int duration;
+sTimerEntry(string t, time_t s, int d) : name(t), startTime(s), duration(d) {};
+};
+
+// 8 Byte per entry
+struct tIndexTs {
+ uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
+ int reserved:7; // reserved for future use
+ int independent:1; // marks frames that can be displayed by themselves (for trick modes)
+ uint16_t number:16; // up to 64K files per recording
+ };
+
+union tIndexRead {
+ struct tIndexTs in;
+ char buf[8];
+};
+
+
+cHttpResource::cHttpResource(int f, int id,string addr, int port, SmartTvServer* factory): mFactory(factory), mLog(), mServerAddr(addr),
+ mServerPort(port), mFd(f), mReqId(id), mConnTime(0), mHandleReadCount(0),
+ mConnected(true), mConnState(WAITING), mContentType(NYD), mReadBuffer(),
+ mMethod(), mResponseMessagePos(0), mBlkData(NULL), mBlkPos(0), mBlkLen(0),
+ mPath(), mVersion(), protocol(), mReqContentLength(0),
+ mPayload(), mUserAgent(),
+ mAcceptRanges(true), rangeHdr(), mFileSize(-1), mRemLength(0), mFile(NULL), mVdrIdx(1), mFileStructure(),
+ mIsRecording(false), mRecProgress(0.0) {
+
+ mLog = Log::getInstance();
+ mPath = "";
+ mConnTime = time(NULL);
+ setNonBlocking();
+ mBlkData = new char[MAXLEN];
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " cHttpResource created" << endl;
+#endif
+}
+
+
+cHttpResource::~cHttpResource() {
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " Destructor of cHttpResource called"
+ << endl;
+#endif
+ delete[] mBlkData;
+ if (mFile != NULL) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: mFile still open. Closing now..." << endl;
+ fclose(mFile);
+ mFile = NULL;
+ }
+
+}
+
+int cHttpResource::checkStatus() {
+ time_t now = time(NULL);
+
+ switch (mConnState) {
+ case WAITING:
+ case READHDR:
+ case READPAYLOAD:
+ if (now - mConnTime > 2) {
+ *(mLog->log()) << DEBUGPREFIX
+ << " checkStatus: no activity for 2sec "
+ << "mmConnState= " << getConnStateName()
+ << endl;
+ return ERROR;
+ }
+ break;
+ case TOCLOSE:
+ return ERROR;
+ break;
+ }
+
+ // check for how much time the
+ return OKAY;
+}
+
+void cHttpResource::setNonBlocking() {
+ int oldflags = fcntl(mFd, F_GETFL, 0);
+ oldflags |= O_NONBLOCK;
+ fcntl(mFd, F_SETFL, oldflags);
+}
+
+
+int cHttpResource::handleRead() {
+ mHandleReadCount ++;
+ if (mConnState == SERVING) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " handleRead() in wrong state= " << getConnStateName()
+ << endl;
+ return OKAY;
+ }
+ if (mConnState == TOCLOSE) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " handleRead() in wrong state= " << getConnStateName()
+ << endl;
+ return ERROR;
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " handleRead() state= " << getConnStateName()
+ << endl;
+#endif
+
+ char buf[MAXLEN];
+ int buflen = sizeof(buf);
+
+ int line_count = 0;
+ bool is_req = true;
+ string rem_hdr = "";
+
+ buflen = read(mFd, buf, sizeof(buf));
+
+ if (buflen == -1) {
+ *(mLog->log())<< " Some Error, no data received" << endl;
+ return ERROR; // Nothing to read
+ }
+
+ // if (( mConnState == WAITING) and ((time(NULL) - mConnTime) > 1)) {
+ if (( mConnState == WAITING) and (mHandleReadCount > 1000)) {
+ *(mLog->log()) << DEBUGPREFIX << " hmm, handleRead() no data since 1sec -> closing. mHandleReadCount= " << mHandleReadCount << endl;
+ return ERROR; // Nothing to read
+ }
+
+ if ( ((mConnState == READHDR) or (mConnState == READPAYLOAD)) and (mHandleReadCount > 5000)) {
+ *(mLog->log()) << DEBUGPREFIX << " ERROR Still not finished after mHandleReadCount= " << mHandleReadCount << endl;
+ return ERROR; // Nothing to read
+ }
+
+ if (buflen == 0) {
+ return OKAY; // Nothing to read
+ }
+
+ if (mConnState == READPAYLOAD) {
+ mPayload += string(buf, buflen);
+ if (mPayload.size() == mReqContentLength) {
+ //Done
+ mConnState = SERVING;
+ return processRequest();
+ }
+ }
+
+ if (mConnState == WAITING) {
+ mConnState = READHDR;
+ }
+
+ string req_line = mReadBuffer + string(buf, buflen);
+ buflen += rem_hdr.size();
+
+ size_t last_pos = 0;
+ // Parse http header lines
+ while (true) {
+ line_count ++;
+ size_t pos = req_line.find ("\r\n", last_pos);
+
+ if ((pos > buflen) or (pos == string::npos)) {
+ mReadBuffer = req_line.substr(last_pos, buflen - last_pos);
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX << " No HdrEnd Found, read more data. rem_hdr= " << mReadBuffer.size()
+ << " buflen= " << buflen
+ << DEBUGHDR << endl;
+ *(mLog->log())<< cUrlEncode::hexDump(mReadBuffer) << endl;
+#endif
+ return OKAY;
+ }
+
+ if ((last_pos - pos) == 0) {
+ // Header End
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX << " ---- Header End Found" << DEBUGHDR << endl;
+#endif
+
+ if (mReqContentLength != 0) {
+ mConnState = READPAYLOAD;
+ mPayload = req_line.substr(pos +2, buflen - (pos +2));
+ if (mPayload.size() != mReqContentLength)
+ return OKAY;
+ else {
+ mConnState = SERVING;
+ return processRequest();
+ }
+ } // if(content_length != 0)
+ else {
+ mConnState = SERVING;
+ return processRequest();
+ }
+ } // if (header end)
+
+ string line = req_line.substr(last_pos, (pos-last_pos));
+#ifdef DEBUG_REQHEADERS
+ *(mLog->log())<< " Line= " << line << endl;
+#endif
+ last_pos = pos +2;
+ if (mPath.size() == 0) {
+#ifndef DEBUG
+ *(mLog->log())<< " parsing Request Line= " << line << endl;
+#endif
+ is_req = false;
+ // Parse the request line
+ if (parseHttpRequestLine(line) != OKAY) {
+ return ERROR;
+ };
+ }
+ else {
+ parseHttpHeaderLine (line);
+ }
+ }
+ return OKAY;
+}
+
+int cHttpResource::processRequest() {
+ // do stuff based on the request and the query
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX << " processRequest for mPath= " << mPath << DEBUGHDR << endl;
+#endif
+ struct stat statbuf;
+ int ret = OKAY;
+
+ if (mMethod.compare("POST")==0) {
+ return handlePost();
+ }
+
+ if (strcasecmp(mMethod.c_str(), "GET") != 0){
+ sendError(501, "Not supported", NULL, "Method is not supported.");
+ return ERROR;
+ }
+
+#ifndef STANDALONE
+ if (mPath.compare("/recordings.html") == 0) {
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " generating /recordings.html"
+ << DEBUGHDR << endl;
+#endif
+ ret = sendRecordingsHtml( &statbuf);
+ return OKAY;
+ }
+ if (mPath.compare("/recordings.xml") == 0) {
+
+ ret = sendRecordingsXml( &statbuf);
+ return OKAY;
+ }
+
+ if (mPath.compare("/channels.xml") == 0) {
+ ret = sendChannelsXml( &statbuf);
+ return OKAY;
+ }
+
+ if (mPath.compare("/epg.xml") == 0) {
+ ret = sendEpgXml( &statbuf);
+ return OKAY;
+ }
+#endif
+ if (mPath.compare("/media.xml") == 0) {
+ ret = sendMediaXml( &statbuf);
+ return OKAY;
+ }
+
+ if (mPath.compare(mPath.size() -8, 8, "-seg.mpd") == 0) {
+ ret = sendManifest( &statbuf, false);
+ return OKAY;
+ }
+
+ if (mPath.compare(mPath.size() -9, 9, "-seg.m3u8") == 0) {
+ ret = sendManifest( &statbuf, true);
+ return OKAY;
+ }
+
+ if (mPath.compare(mPath.size() -7, 7, "-seg.ts") == 0) {
+ ret = sendMediaSegment( &statbuf);
+ return OKAY;
+ }
+
+ if (mPath.compare("/widget.conf") == 0) {
+ mPath = mFactory->getConfigDir() + "/widget.conf";
+ }
+
+ if (stat(mPath.c_str(), &statbuf) < 0) {
+ sendError(404, "Not Found", NULL, "File not found.");
+ return ERROR;
+ }
+
+ if (S_ISDIR(statbuf.st_mode)) {
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " processRequest: isDir - mPath: " << mPath.c_str() << endl;
+#endif
+
+
+ if (mPath.size() >4) {
+ if (mPath.compare(mPath.size() - 4, 4, ".rec") == 0) {
+ mContentType = VDRDIR;
+ return sendVdrDir( &statbuf);
+ }
+ }
+ // else {
+ sendDir( &statbuf);
+ mContentType = MEMBLOCK;
+ return OKAY;
+ // }
+ }
+ else {
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " processRequest: file send\n";
+#endif
+
+ mFileSize = statbuf.st_size;
+
+ mContentType = SINGLEFILE;
+
+ return sendFile(&statbuf);
+ }
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " processRequest: Not Handled SHOULD not be here\n";
+#endif
+ return ERROR;
+}
+
+
+int cHttpResource::handleWrite() {
+
+ if (mConnState == TOCLOSE) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " handleWrite() in wrong state= " << getConnStateName()
+ << endl;
+ return ERROR;
+ }
+
+ if (mConnState != SERVING) {
+ return OKAY;
+ }
+
+ if (mBlkLen == mBlkPos) {
+ // note the mBlk may be filled with header info first.
+ if (fillDataBlk() != OKAY) {
+ return ERROR;
+ }
+ }
+
+ int this_write = write(mFd, &mBlkData[mBlkPos], mBlkLen - mBlkPos);
+ if (this_write <=0) {
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR after write: Stopped (Client terminated Connection)"
+ << " mBlkPos= " << mBlkPos << " mBlkLen= " << mBlkLen
+ << DEBUGHDR << endl;
+#endif
+ mConnState = TOCLOSE;
+ mConnected = false;
+ return ERROR;
+ }
+ mBlkPos += this_write;
+
+ return OKAY;
+}
+
+
+int cHttpResource::fillDataBlk() {
+ char pathbuf[4096];
+
+ mBlkPos = 0;
+ int to_read = 0;
+
+ switch(mContentType) {
+ case NYD:
+
+ break;
+ case VDRDIR:
+ // Range requests are assumed to be all open
+ if (mFile == NULL) {
+ *(mLog->log()) << DEBUGPREFIX << " no open file anymore "
+ << "--> Done " << endl;
+ return ERROR;
+ }
+ if (mRemLength == 0) {
+ *(mLog->log()) << DEBUGPREFIX << " mRemLength is zero "
+ << "--> Done " << endl;
+ fclose(mFile);
+ mFile = NULL;
+ return ERROR;
+ }
+ to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength);
+
+ mBlkLen = fread(mBlkData, 1, to_read, mFile);
+ mRemLength -= mBlkLen;
+
+ if (mRemLength == 0) {
+ *(mLog->log()) << DEBUGPREFIX << " last Block read "
+ << "--> Almost Done " << endl;
+ return OKAY;
+ }
+
+ if (mBlkLen != MAXLEN) {
+ fclose(mFile);
+ mFile = NULL;
+ mVdrIdx ++;
+
+ snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
+ mPath = pathbuf;
+ if (openFile(pathbuf) != OKAY) {
+ *(mLog->log())<< DEBUGPREFIX << " Failed to open file= " << pathbuf << " mRemLength= " << mRemLength<< endl;
+ mFile = NULL;
+ if (mBlkLen == 0) {
+ *(mLog->log()) << DEBUGPREFIX << " mBlkLen is zero --> Done " << endl;
+ return ERROR;
+ }
+ else
+ *(mLog->log()) << DEBUGPREFIX << " Still data to send mBlkLen= " << mBlkLen <<" --> continue " << endl;
+ return OKAY;
+ }
+
+ if (mBlkLen == 0) {
+ to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength);
+ mBlkLen = fread(mBlkData, 1, to_read, mFile);
+ }
+ mRemLength -= mBlkLen;
+ }
+ break;
+ case SINGLEFILE:
+ to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength);
+ mBlkLen = fread(mBlkData, 1, to_read, mFile);
+ mRemLength -= mBlkLen;
+ if (mBlkLen == 0) {
+
+ // read until EOF
+ fclose(mFile);
+ mFile = NULL;
+ return ERROR;
+ }
+ break;
+ case MEMBLOCK:
+ int rem_len = mResponseMessage->size() - mResponseMessagePos;
+ if (rem_len == 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " fillDataBlock: MEMBLOCK done" << endl;
+ delete mResponseMessage;
+ mResponseMessagePos = 0;
+ mConnState = TOCLOSE;
+ return ERROR;
+ }
+ if (rem_len > MAXLEN)
+ rem_len = MAXLEN;
+
+ string sub_msg = mResponseMessage->substr(mResponseMessagePos, rem_len);
+ mResponseMessagePos += rem_len;
+ mBlkLen = sub_msg.size();
+ memcpy(mBlkData, sub_msg.c_str(), rem_len);
+ break;
+ }
+
+ return OKAY;
+}
+
+
+int cHttpResource::parseResume(cResumeEntry &entry, string &id) {
+ bool done = false;
+ size_t cur_pos = 0;
+
+ bool have_devid = false;
+ bool have_title = false;
+ bool have_start = false;
+ bool have_resume = false;
+
+ while (!done) {
+ size_t pos = mPayload.find('\n', cur_pos);
+ if (pos == string::npos) {
+ done = true;
+ continue;
+ }
+ size_t pos_col = mPayload.find(':', cur_pos);
+ string attr= mPayload.substr(cur_pos, (pos_col- cur_pos));
+ string val = mPayload.substr(pos_col +1, (pos - pos_col-1));
+
+ if (attr== "devid") {
+ have_devid = true;
+ id = val;
+ }
+ else if (attr == "title") {
+ have_title = true;
+ entry.mTitle = val;
+ }
+ else if (attr == "start") {
+ have_start = true;
+ entry.mStartTime = atoi(val.c_str());
+ }
+ else if (attr == "resume") {
+ have_resume = true;
+ entry.mResume = atoi(val.c_str());
+ }
+ else {
+ *(mLog->log())<< DEBUGPREFIX
+ << " parseResume: ERROR: Unknown attr= " << attr
+ << " with val= " << val
+ << endl;
+ }
+ cur_pos = pos +1;
+ if (cur_pos >= mPayload.size())
+ done= true;
+ }
+ if (have_resume && have_start && have_title && have_devid)
+ return OKAY;
+ else
+ return ERROR;
+}
+
+int cHttpResource::handlePost() {
+ mConnState = SERVING;
+ mContentType = MEMBLOCK;
+
+ // sent an empty response message with just the OK header
+ mResponseMessage = new string();
+ *mResponseMessage = "";
+ mResponseMessagePos = 0;
+
+ /*
+ Resume support:
+ Key for recordings: Title plus start time. Value: Current PlayTime in Sec
+ Structure: Either, the plugin reads the list from file when the first client connects
+ Question: How to get files into /var/lib/vdr/plugins
+
+ First: Create a list of resumes (use vdr cList ).
+ Write the list to file
+ Read the list from file
+
+ */
+
+ if (mPath.compare("/log") == 0) {
+ *(mLog->log())<< mPayload
+ << endl;
+ }
+
+ if (mPath.compare("/resume") == 0) {
+
+ string dev_id;
+ cResumeEntry entry;
+ if (parseResume(entry, dev_id) == ERROR) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR parsing resume"
+ << endl;
+ }
+ *(mLog->log())<< DEBUGPREFIX
+ << " Resume: id= " << dev_id
+ << " resume= " << entry << endl;
+ }
+
+ sendHeaders(200, "OK", NULL, NULL, -1, -1);
+
+ return OKAY;
+}
+
+
+void cHttpResource::sendError(int status, const char *title, const char *extra, const char *text) {
+ char f[400];
+
+ mConnState = SERVING;
+ mContentType = MEMBLOCK;
+ mResponseMessage = new string();
+ *mResponseMessage = "";
+ mResponseMessagePos = 0;
+
+ string hdr = "";
+ sendHeaders(status, title, extra, "text/html", -1, -1);
+
+ snprintf(f, sizeof(f), "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\r\n", status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "<BODY><H4>%d %s</H4>\r\n", status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "%s\r\n", text);
+ hdr += f;
+ snprintf(f, sizeof(f), "</BODY></HTML>\r\n");
+ hdr += f;
+
+
+ strcpy(&(mBlkData[mBlkLen]), hdr.c_str());
+ mBlkLen += hdr.size();
+
+}
+
+
+int cHttpResource::sendDir(struct stat *statbuf) {
+ char pathbuf[4096];
+ char f[400];
+ int len;
+
+ mConnState = SERVING;
+
+#ifndef DEBUG
+ *(mLog->log()) << DEBUGPREFIX << " sendDir: mPath= " << mPath << endl;
+#endif
+ len = mPath.length();
+ // int ret = OKAY;
+
+ if (len == 0 || mPath[len - 1] != '/') {
+ snprintf(pathbuf, sizeof(pathbuf), "Location: %s/", mPath.c_str());
+ sendError(302, "Found", pathbuf, "Directories must end with a slash.");
+ return OKAY;
+ }
+
+ snprintf(pathbuf, sizeof(pathbuf), "%sindex.html", mPath.c_str());
+ if (stat(pathbuf, statbuf) >= 0) {
+ mPath = pathbuf;
+ sendFile(statbuf);
+ }
+ else {
+#ifndef DEBUG
+ *(mLog->log()) << DEBUGPREFIX << " sendDir: create index.html " << endl;
+#endif
+ DIR *dir;
+ struct dirent *de;
+
+ sendHeaders(200, "OK", NULL, "text/html", -1, statbuf->st_mtime);
+ mResponseMessage = new string();
+ mResponseMessagePos = 0;
+ *mResponseMessage = "";
+
+ string hdr = "";
+ snprintf(f, sizeof(f), "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD>\r\n<BODY>", mPath.c_str());
+ hdr += f;
+ snprintf(f, sizeof(f), "<H4>Index of %s</H4>\r\n<PRE>\n", mPath.c_str());
+ hdr += f;
+ snprintf(f, sizeof(f), "Name Last Modified Size\r\n");
+ hdr += f;
+ snprintf(f, sizeof(f), "<HR>\r\n");
+ hdr += f;
+
+ *mResponseMessage += hdr;
+ hdr = "";
+
+ if (len > 1) {
+ snprintf(f, sizeof(f), "<A HREF=\"..\">..</A>\r\n");
+ hdr += f;
+ }
+ *mResponseMessage += hdr;
+
+ dir = opendir(mPath.c_str());
+ while ((de = readdir(dir)) != NULL) {
+ char timebuf[32];
+ struct tm *tm;
+ strcpy(pathbuf, mPath.c_str());
+ // printf (" -Entry: %s\n", de->d_name);
+ strcat(pathbuf, de->d_name);
+
+ stat(pathbuf, statbuf);
+ tm = gmtime(&(statbuf->st_mtime));
+ strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tm);
+
+ hdr = "";
+ snprintf(f, sizeof(f), "<A HREF=\"%s%s\">", de->d_name, S_ISDIR(statbuf->st_mode) ? "/" : "");
+ hdr += f;
+
+ snprintf(f, sizeof(f), "%s%s", de->d_name, S_ISDIR(statbuf->st_mode) ? "/</A>" : "</A> ");
+ hdr += f;
+
+ if (strlen(de->d_name) < 32) {
+ snprintf(f, sizeof(f), "%*s", 32 - strlen(de->d_name), "");
+ hdr += f;
+ }
+ if (S_ISDIR(statbuf->st_mode)) {
+ snprintf(f, sizeof(f), "%s\r\n", timebuf);
+ hdr += f;
+ }
+ else {
+ snprintf(f, sizeof(f), "%s\r\n", timebuf);
+ hdr += f;
+ }
+
+ *mResponseMessage += hdr;
+ }
+ closedir(dir);
+ snprintf(f, sizeof(f), "</PRE>\r\n<HR>\r\n<ADDRESS>%s</ADDRESS>\r\n</BODY></HTML>\r\n", SERVER);
+ *mResponseMessage += f;
+
+ }
+
+ mRemLength = 0;
+ return OKAY;
+}
+
+
+int cHttpResource::writeXmlItem(string name, string link, string programme, string desc, string guid, time_t start, int dur) {
+ string hdr = "";
+ char f[400];
+
+ hdr += "<item>\n";
+ // snprintf(f, sizeof(f), "%s - %s", );
+ hdr += "<title>" + name +"</title>\n";
+ hdr += "<link>" +link + "</link>\n";
+ hdr += "<guid>" + guid + "</guid>\n";
+
+ hdr += "<programme>" + programme +"</programme>\n";
+ hdr += "<description>" + desc + "</description>\n";
+
+ snprintf(f, sizeof(f), "%ld", start);
+ hdr += "<start>";
+ hdr += f;
+ hdr += "</start>\n";
+
+ hdr += "<startstr>";
+ if (start != 0) {
+ strftime(f, sizeof(f), "%y%m%d %H:%M", localtime(&start));
+ hdr += f;
+ }
+ else
+ hdr += "0 0";
+ hdr += "</startstr>\n";
+
+ snprintf(f, sizeof(f), "%d", dur);
+ hdr += "<duration>";
+ hdr += f;
+ hdr += "</duration>\n";
+
+ hdr += "</item>\n";
+
+ *mResponseMessage += hdr;
+
+ // return writeToClient(hdr.c_str(), hdr.size());
+ return OKAY;
+}
+
+int cHttpResource::parseQueryLine (vector<sQueryAVP> *avps) {
+ bool done = false;
+ size_t cur_pos = 0;
+ while (!done) {
+ size_t end_pos = mQuery.find('&', cur_pos);
+ size_t pos_eq = mQuery.find('=', cur_pos);
+
+ if (pos_eq != cur_pos) {
+ avps->push_back(sQueryAVP(mQuery.substr(cur_pos, (pos_eq -cur_pos)), mQuery.substr(pos_eq+1, (end_pos -pos_eq-1)) ));
+ }
+ if (end_pos == string::npos)
+ done = true;
+ else
+ cur_pos = end_pos +1;
+ }
+
+ return OKAY;
+}
+
+int cHttpResource::getQueryAttributeValue(vector<sQueryAVP> *avps, string attr, string &val) {
+ int found = ERROR;
+ for (uint i = 0; i < avps->size(); i++) {
+ if ((*avps)[i].attribute == attr) {
+ val = (*avps)[i].value;
+ found = OKAY;
+ }
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " a= "
+ << (*avps)[i].attribute
+ << " v= " << (*avps)[i].value
+ << endl;
+#endif
+ }
+ return found;
+}
+
+int cHttpResource::parseFiles(vector<sFileEntry> *entries, string prefix, string dir_base, string dir_name, struct stat *statbuf) {
+ char pathbuf[4096];
+ string link;
+ // char f[400];
+ // int len;
+
+ DIR *dir;
+ struct dirent *de;
+ string dir_comp;
+ dir_comp = dir_base + dir_name + "/";
+
+ *(mLog->log()) << DEBUGPREFIX
+ << " parseFiles: Prefix= " << prefix
+ << " base= " << dir_base
+ << " dir= " << dir_name
+ << " comp= " << dir_comp
+ << endl;
+
+ dir = opendir(dir_comp.c_str());
+ while ((de = readdir(dir)) != NULL) {
+ if ((strcmp(de->d_name, ".") == 0) or (strcmp(de->d_name, "..") == 0)) {
+ continue;
+ }
+
+ // *(mLog->log()) << DEBUGPREFIX << " " << de->d_name << endl;
+ strcpy(pathbuf, dir_comp.c_str());
+ strcat(pathbuf, de->d_name);
+
+ stat(pathbuf, statbuf);
+
+ if (S_ISDIR(statbuf->st_mode)) {
+ if (strcmp(&(pathbuf[strlen(pathbuf)-4]), ".rec") == 0) {
+ time_t now = time(NULL);
+ struct tm tm_r;
+ struct tm t = *localtime_r(&now, &tm_r);
+ t.tm_isdst = -1;
+ // char [20] rest;
+ int start = -1;
+ sscanf(de->d_name, "%4d-%02d-%02d.%02d%.%02d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min);
+ t.tm_year -= 1900;
+ t.tm_mon--;
+ t.tm_sec = 0;
+ start = mktime(&t);
+
+ *(mLog->log()) << DEBUGPREFIX
+ << " Vdr Folder Found: " << pathbuf << " start= " << start << endl;
+
+ entries->push_back(sFileEntry(dir_name, pathbuf, start));
+ }
+ else {
+ parseFiles(entries, prefix + de->d_name + "~", dir_comp, de->d_name, statbuf);
+ }
+ }
+ else {
+ entries->push_back(sFileEntry(prefix+de->d_name, pathbuf, 1));
+ }
+ }
+ closedir(dir);
+ return OKAY;
+}
+
+int cHttpResource::sendManifest (struct stat *statbuf, bool is_hls) {
+#ifndef STANDALONE
+
+ size_t pos = mPath.find_last_of ("/");
+
+ mDir = mPath.substr(0, pos);
+ string mpd_name = mPath.substr(pos+1);
+
+ float seg_dur = mFactory->getSegmentDuration() *1.0;
+ // float seg_dur = SEGMENT_DURATION *1.0;
+
+
+ cRecordings* recordings = &Recordings;
+ cRecording* rec = recordings->GetByName(mDir.c_str());
+ float duration = rec->NumFrames() / rec->FramesPerSecond();
+ time_t now = time(NULL);
+
+ if (rec->Info() != NULL){
+ if (rec->Info()->GetEvent() != NULL) {
+ if (rec->Info()->GetEvent()->EndTime() > now) {
+
+ float corr = (now - rec->Info()->GetEvent()->StartTime()) - duration;
+ duration = rec->Info()->GetEvent()->Duration() -int(corr);
+ *(mLog->log()) << DEBUGPREFIX
+ << " is Recording: Duration= " << duration << " sec"
+ << " correction: " << int(corr)
+ << endl;
+ }
+ }
+ else
+ *(mLog->log()) << DEBUGPREFIX << " WARNING: rec-Info()->GetEvent() is NULL " << endl;
+ }
+ else
+ *(mLog->log()) << DEBUGPREFIX << " WARNING: rec-Info() is NULL " << endl;
+
+ // duration is now either the actual duration of the asset or the target duration of the asset
+ int end_seg = int (duration / seg_dur) +1;
+ // FIXME: Test Only
+ /* if (rec->FramesPerSecond() > 40)
+ end_seg = int (duration *2 / seg_dur) +1;
+*/
+ *(mLog->log()) << DEBUGPREFIX
+ << " m3u8 for mDir= " << mDir
+ << " duration= " << duration
+ << " seg_dur= " << seg_dur
+ << " end_seg= " << end_seg
+ << endl;
+
+
+
+ if (is_hls) {
+ writeM3U8(duration, seg_dur, end_seg);
+ }
+ else {
+ writeMPD(duration, seg_dur, end_seg);
+ }
+
+#endif
+ return OKAY;
+}
+
+void cHttpResource::writeM3U8(float duration, float seg_dur, int end_seg) {
+ mResponseMessage = new string();
+ mResponseMessagePos = 0;
+ *mResponseMessage = "";
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+ char buf[30];
+
+ string hdr = "";
+
+ sendHeaders(200, "OK", NULL, "application/x-mpegURL", -1, -1);
+
+ *mResponseMessage += "#EXTM3U\n";
+ // snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", (seg_dur-1));
+ snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", int(seg_dur));
+ hdr = buf;
+ *mResponseMessage += hdr;
+
+ *mResponseMessage += "#EXT-X-MEDIA-SEQUENCE:1\n";
+ *mResponseMessage += "#EXT-X-KEY:METHOD=NONE\n";
+
+ for (int i = 1; i < end_seg; i++){
+ // snprintf(buf, sizeof(buf), "#EXTINF:%.1f,\n", (seg_dur-0.5));
+ snprintf(buf, sizeof(buf), "#EXTINF:%.2f,\n", seg_dur);
+ hdr = buf;
+ *mResponseMessage += hdr;
+
+ snprintf(buf, sizeof(buf), "%d-seg.ts\n", i);
+ hdr = buf;
+ *mResponseMessage += hdr;
+ }
+ *mResponseMessage += "#EXT-X-ENDLIST\n";
+
+}
+
+void cHttpResource::writeMPD(float duration, float seg_dur, int end_seg) {
+ mResponseMessage = new string();
+ mResponseMessagePos = 0;
+ *mResponseMessage = "";
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+ char buf[30];
+ char line[400];
+
+ string hdr = "";
+
+ // sendHeaders(200, "OK", NULL, "application/xml", -1, -1);
+
+ *mResponseMessage += "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+
+ snprintf(line, sizeof(line), "<MPD type=\"OnDemand\" minBufferTime=\"PT%dS\" mediaPresentationDuration=\"PT%.1fS\"",
+ mFactory->getHasMinBufferTime(), duration);
+ *mResponseMessage = *mResponseMessage + line;
+
+ // *mResponseMessage += "<MPD type=\"OnDemand\" minBufferTime=\"PT30S\" mediaPresentationDuration=";
+
+ // snprintf(buf, sizeof(buf), "\"PT%.1fS\"", duration);
+ // hdr = buf;
+
+ // *mResponseMessage += hdr + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\" ";
+ *mResponseMessage += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\" ";
+ *mResponseMessage += "xsi:schemaLocation=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\">\n";
+ *mResponseMessage += "<ProgramInformation>\n";
+ *mResponseMessage += "<ChapterDataURL/>\n";
+ *mResponseMessage += "</ProgramInformation>\n";
+ *mResponseMessage += "<Period start=\"PT0S\" segmentAlignmentFlag=\"True\">\n";
+
+ snprintf(line, sizeof(line), "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"%d\" startWithRAP=\"True\" width=\"1280\" height=\"720\" group=\"0\">\n", mFactory->getHasBitrate());
+ *mResponseMessage = *mResponseMessage + line;
+ // *mResponseMessage += "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"15000000\" startWithRAP=\"True\" width=\"1280\" height=\"720\" group=\"0\">\n";
+
+ // *mResponseMessage += "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"5000000\" startWithRAP=\"True\" width=\"720\" height=\"576\" group=\"0\">\n";
+
+ hdr = "<SegmentInfo duration=";
+ snprintf(buf, sizeof(buf), "\"PT%.1fS\"", (seg_dur*1.0));
+ hdr = hdr + buf + " >\n";
+ *mResponseMessage += hdr;
+
+ snprintf(buf, sizeof(buf), "\"%d\"", end_seg);
+ *mResponseMessage += "<UrlTemplate sourceURL=\"$Index$-seg.ts\" startIndex=\"1\" endIndex=";
+ hdr = buf ;
+ *mResponseMessage += hdr + " />\n";
+
+ *mResponseMessage += "</SegmentInfo>\n";
+ *mResponseMessage += "</Representation>\n";
+ *mResponseMessage += "</Period>\n";
+ *mResponseMessage += "</MPD>";
+
+ sendHeaders(200, "OK", NULL, "application/xml", mResponseMessage->size(), -1);
+}
+
+int cHttpResource::sendMediaSegment (struct stat *statbuf) {
+#ifndef STANDALONE
+
+ *(mLog->log()) << DEBUGPREFIX << " sendMediaSegment " << mPath << endl;
+ size_t pos = mPath.find_last_of ("/");
+
+ mDir = mPath.substr(0, pos);
+ string seg_name = mPath.substr(pos+1);
+ int seg_number;
+
+ int seg_dur = mFactory->getSegmentDuration();
+// int seg_dur = SEGMENT_DURATION;
+ int frames_per_seg = 0;
+
+ sscanf(seg_name.c_str(), "%d-seg.ts", &seg_number);
+
+ //FIXME: Do some consistency checks on the seg_number
+ //* Does the segment exist
+
+ cRecordings* recordings = &Recordings;
+ cRecording* rec = recordings->GetByName(mDir.c_str());
+ if (rec != NULL) {
+ frames_per_seg = seg_dur * rec->FramesPerSecond();
+ }
+ else {
+ *(mLog->log()) << DEBUGPREFIX << " ERROR: Ooops, rec is NULL, assuming 25 fps " << endl;
+ frames_per_seg = seg_dur * 25;
+ }
+ //FIXME: HD Fix
+ // frames_per_seg = seg_dur * 25;
+
+ *(mLog->log()) << DEBUGPREFIX
+ << " mDir= " << mDir
+ << " seg_name= " << seg_name
+ << " seg_number= "<< seg_number
+ << " fps= " << rec->FramesPerSecond()
+ << " frames_per_seg= " << frames_per_seg
+ << endl;
+ int start_frame_count = (seg_number -1) * frames_per_seg;
+
+ FILE* idx_file = fopen((mDir +"/index").c_str(), "r");
+ if (idx_file == NULL){
+ *(mLog->log()) << DEBUGPREFIX
+ << " failed to open idx file = "<< (mDir +"/index").c_str()
+ << endl;
+ sendError(404, "Not Found", NULL, "Failed to open Index file");
+ return OKAY;
+ }
+
+ char *index_buf = new char[(frames_per_seg +3) *8];
+
+ // fseek to start_frame_count * sizeof(in_read)
+ fseek(idx_file, start_frame_count * 8, SEEK_SET);
+
+ // read to (seg_number * frames_per_seg +1) * sizeof(in_read)
+ // buffersize is frames_per_seg * seg_number * sizeof(in_read)
+ int buffered_indexes = fread(index_buf, 8, (frames_per_seg +2), idx_file);
+
+ fclose(idx_file);
+
+ if(buffered_indexes <= 0 ) {
+ *(mLog->log())<<DEBUGPREFIX
+ << " issue while reading" << endl;
+ sendError(404, "Not Found", NULL, "Failed to read Index file");
+ return OKAY;
+ }
+
+ // Reading the segment
+ mFileStructure = "%s/%05d.ts";
+ int start_offset = -1;
+ int start_idx = -1;
+
+ tIndexTs in_read_ts;
+ memcpy (&in_read_ts, &(index_buf[0]), 8);
+
+ start_offset = in_read_ts.offset;
+ start_idx = in_read_ts.number;
+
+ char seg_fn[200];
+ mVdrIdx = start_idx;
+
+ snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
+ mPath = seg_fn;
+
+ /*
+ * Now we determine the end of the segment
+ */
+ memcpy (&in_read_ts, &(index_buf[(frames_per_seg)*8]), 8);
+
+ int end_idx = in_read_ts.number;
+ int end_offset = in_read_ts.offset;
+
+ /*#ifndef DEBUG*/
+ *(mLog->log()) << DEBUGPREFIX
+ << " GenSegment: start (no/idx)= " << start_idx << " / " << start_offset
+ << " to (no/idx)= " << end_idx << " / " << end_offset
+ << endl;
+ /*#endif*/
+
+ delete[] index_buf;
+
+ int rem_len = 0;
+ bool error = false;
+ if (start_idx == end_idx){
+ mRemLength = (end_offset - start_offset);
+
+#ifndef DEBUG
+ *(mLog->log()) << DEBUGPREFIX
+ << " start_idx == end_idx: mRemLength= " <<mRemLength
+ << endl;
+#endif
+ }
+ else {
+ *(mLog->log()) << DEBUGPREFIX
+ << " start_idx < end_idx "
+ << endl;
+ snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
+ if (stat(seg_fn, statbuf) < 0) {
+ *(mLog->log()) << DEBUGPREFIX
+ << " file= " <<seg_fn << " does not exist"
+ << endl;
+ error= true;
+ // issue:
+ }
+ rem_len = statbuf->st_size - start_offset; // remaining length of the first segment
+ for (int idx = (start_idx+1); idx < end_idx; idx ++) {
+ snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), idx);
+ if (stat(seg_fn, statbuf) < 0) {
+ *(mLog->log()) << DEBUGPREFIX
+ << " for loop file= " <<seg_fn << " does not exist"
+ << endl;
+ error = true;
+ break;
+ // issue:
+ }
+ rem_len += statbuf->st_size; // remaining length of the first segment
+ }
+ rem_len += end_offset; //
+ mRemLength = rem_len;
+ *(mLog->log()) << DEBUGPREFIX
+ << " start_idx= " << start_idx << " != end_idx= "<< end_idx <<": mRemLength= " <<mRemLength
+ << endl;
+ }
+
+ if (error){
+ sendError(404, "Not Found", NULL, "Not all inputs exists");
+ return OKAY;
+ }
+
+ mContentType = VDRDIR;
+ if (openFile(seg_fn) != OKAY) {
+ *(mLog->log())<< DEBUGPREFIX << " Failed to open file= " << seg_fn
+ << " mRemLength= " << mRemLength<< endl;
+ sendError(404, "Not Found", NULL, "File not found.");
+ return OKAY;
+ }
+ fseek(mFile, start_offset, SEEK_SET);
+
+ sendHeaders(200, "OK", NULL, "video/mpeg", mRemLength, -1);
+
+#endif
+ return OKAY;
+}
+
+int cHttpResource::sendMediaXml (struct stat *statbuf) {
+ char pathbuf[4096];
+ string link;
+ string media_folder = "/hd2/mpeg";
+
+ mResponseMessage = new string();
+ mResponseMessagePos = 0;
+ *mResponseMessage = "";
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+
+ *(mLog->log()) << DEBUGPREFIX << " sendMedia " << endl;
+
+ vector<sFileEntry> entries;
+ parseFiles(&entries, "", media_folder, "", statbuf);
+
+ sendHeaders(200, "OK", NULL, "application/xml", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ hdr += "<rss version=\"2.0\">\n";
+ hdr+= "<channel>\n";
+
+ *mResponseMessage += hdr;
+
+ hdr = "";
+
+ for (uint i=0; i < entries.size(); i++) {
+
+ snprintf(pathbuf, sizeof(pathbuf), "http://%s:%d%s", mServerAddr.c_str(), mServerPort,
+ cUrlEncode::doUrlSaveEncode(entries[i].sPath).c_str());
+ if (writeXmlItem(cUrlEncode::doXmlSaveEncode(entries[i].sName), pathbuf, "NA", "NA", "-",
+ entries[i].sStart, -1) == ERROR)
+ return ERROR;
+
+ }
+
+ hdr = "</channel>\n";
+ hdr += "</rss>\n";
+ *mResponseMessage += hdr;
+
+ return OKAY;
+}
+
+int cHttpResource::sendEpgXml (struct stat *statbuf) {
+#ifndef STANDALONE
+ char f[400];
+ mResponseMessage = new string();
+ *mResponseMessage = "";
+ mResponseMessagePos = 0;
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " generating /epg.xml"
+ << DEBUGHDR << endl;
+#endif
+ vector<sQueryAVP> avps;
+ parseQueryLine(&avps);
+ string id = "S19.2E-1-1107-17500";
+ if (getQueryAttributeValue(&avps, "id", id) == ERROR){
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: id not found"
+ << DEBUGHDR << endl;
+ sendError(400, "Bad Request", NULL, "no id in query line");
+ return OKAY;
+ }
+
+
+ /* for (int i = 0; i < avps.size(); i++) {
+ if (avps[i].attribute == "id")
+ id = avps[i].value;
+ *(mLog->log())<< DEBUGPREFIX
+ << " a= "
+ << avps[i].attribute
+ << " v= " << avps[i].value
+ << endl;
+ }*/
+ tChannelID chan_id = tChannelID::FromString (id.c_str());
+ if ( chan_id == tChannelID::InvalidID) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: Not possible to get the ChannelId from the string"
+ << DEBUGHDR << endl;
+ // ERROR
+ // FIXME: Should send a proper error code
+ return ERROR;
+ }
+
+ cSchedulesLock * lock = new cSchedulesLock(false, 500);
+ const cSchedules *schedules = cSchedules::Schedules(*lock);
+
+ const cSchedule *schedule = schedules->GetSchedule(chan_id);
+ const cEvent * ev = schedule->GetPresentEvent();
+
+ delete lock;
+
+ sendHeaders(200, "OK", NULL, "application/xml", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ hdr += "<tv version=\"2.0\">\n";
+ hdr+= "<programme>\n";
+
+ *mResponseMessage += hdr;
+ // Payload here
+
+ string title = cUrlEncode::doXmlSaveEncode(ev->Title());
+ hdr = "<title>" + title +"</title>\n";
+ hdr += "<desc>";
+ hdr += cUrlEncode::doXmlSaveEncode(ev->Description());
+ hdr += "</desc>\n";
+
+ snprintf(f, sizeof(f), "<start>%ld</start>\n", ev->StartTime());
+ hdr += f ;
+
+ snprintf(f, sizeof(f), "<end>%ld</end>\n", ev->EndTime());
+ hdr += f;
+ *mResponseMessage += hdr;
+
+ hdr = "</programme>\n";
+ hdr += "</tv>\n";
+
+ *mResponseMessage += hdr;
+
+
+#endif
+ return OKAY;
+}
+
+int cHttpResource::sendChannelsXml (struct stat *statbuf) {
+#ifndef STANDALONE
+ char f[400];
+ mResponseMessage = new string();
+ *mResponseMessage = "";
+ mResponseMessagePos = 0;
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " generating /channels.xml"
+ << DEBUGHDR << endl;
+#endif
+
+ vector<sQueryAVP> avps;
+ parseQueryLine(&avps);
+ string mode = "";
+ bool add_desc = true;
+ if (getQueryAttributeValue(&avps, "mode", mode) == OKAY){
+ if (mode == "nodesc") {
+ add_desc = false;
+ *(mLog->log())<< DEBUGPREFIX
+ << " Mode: No Description"
+ << endl;
+ }
+ else {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Mode: Unknown"
+ << endl;
+ }
+ }
+
+ sendHeaders(200, "OK", NULL, "application/xml", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ hdr += "<rss version=\"2.0\">\n";
+ hdr+= "<channel>\n";
+
+ *mResponseMessage += hdr;
+
+ int count = mFactory->getLiveChannels();
+ cSchedulesLock * lock = new cSchedulesLock(false, 500);
+ const cSchedules *schedules = cSchedules::Schedules(*lock);
+
+ for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
+ if (channel->GroupSep())
+ continue;
+ if (--count == 0) {
+ break;
+ }
+
+ snprintf(f, sizeof(f), "http://%s:3000/%s.ts", mServerAddr.c_str(), *(channel->GetChannelID()).ToString());
+ string link = f;
+
+ const cSchedule *schedule = schedules->GetSchedule(channel->GetChannelID());
+ string desc = "No description available";
+ string title = "Not available";
+ time_t start_time = 0;
+ int duration = 0;
+
+ if (schedule != NULL) {
+ const cEvent *ev = schedule->GetPresentEvent();
+ if (ev != NULL) {
+ if ((ev->Description() != NULL) && add_desc)
+ desc = cUrlEncode::doXmlSaveEncode(ev->Description());
+
+ if ((ev->Title() != NULL) && add_desc)
+ title = cUrlEncode::doXmlSaveEncode(ev->Title());
+ start_time = ev->StartTime();
+ duration = ev->Duration();
+ }
+ else {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Event Info is Zero for Count= "
+ << count
+ << " Name= " << channel->Name() << endl;
+ }
+ }
+ else {
+ *(mLog->log())<< DEBUGPREFIX
+ << " Schedule is Zero for Count= "
+ << count
+ << " Name= " << channel->Name() << endl;
+ }
+
+ if (writeXmlItem(channel->Name(), link, title, desc, *(channel->GetChannelID()).ToString(), start_time, duration) == ERROR)
+ return ERROR;
+
+ }
+
+ hdr = "</channel>\n";
+ hdr += "</rss>\n";
+
+ *mResponseMessage += hdr;
+ delete lock;
+
+#endif
+ return OKAY;
+}
+
+int cHttpResource::sendRecordingsXml(struct stat *statbuf) {
+#ifndef STANDALONE
+ mResponseMessage = new string();
+ *mResponseMessage = "";
+ mResponseMessagePos = 0;
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+
+ vector<sQueryAVP> avps;
+ parseQueryLine(&avps);
+ string model = "";
+ string link_ext = "";
+ string type = "";
+ string has_4_hd_str = "";
+ bool has_4_hd = true;
+
+ if (getQueryAttributeValue(&avps, "model", model) == OKAY){
+ *(mLog->log())<< DEBUGPREFIX
+ << " Found a Model Parameter: " << model
+ << endl;
+ if (model == "samsung")
+ link_ext = "/manifest-seg.m3u8|COMPONENT=HLS";
+ }
+
+ if (getQueryAttributeValue(&avps, "type", type) == OKAY){
+ *(mLog->log())<< DEBUGPREFIX
+ << " Found a Type Parameter: " << type
+ << endl;
+ if (type == "hls") {
+ if (model == "samsung")
+ link_ext = "/manifest-seg.m3u8|COMPONENT=HLS";
+ else
+ link_ext = "/manifest-seg.m3u8";
+ }
+ if (type == "has") {
+ if (model == "samsung")
+ link_ext = "/manifest-seg.mpd|COMPONENT=HAS";
+ else
+ link_ext = "/manifest-seg.mpd";
+ }
+ }
+
+ if (getQueryAttributeValue(&avps, "has4hd", has_4_hd_str) == OKAY){
+ *(mLog->log())<< DEBUGPREFIX
+ << " Found a Has4Hd Parameter: " << has_4_hd_str
+ << endl;
+ if (has_4_hd_str == "false")
+ has_4_hd = false;
+ }
+
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " generating /recordings.xml"
+ << DEBUGHDR << endl;
+#endif
+ sendHeaders(200, "OK", NULL, "application/xml", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ hdr += "<rss version=\"2.0\">\n";
+ hdr+= "<channel>\n";
+
+ *mResponseMessage += hdr;
+
+ if (writeXmlItem("HAS - Big Bugs Bunny", "http://192.168.1.122/sm/BBB-DASH/HAS_BigBuckTS.xml|COMPONENT=HAS", "NA", "Big Bucks Bunny - HAS",
+ "-", 0, 0) == ERROR)
+ return ERROR;
+
+ if (writeXmlItem("HLS - Big Bugs Bunny", "http://192.168.1.122/sm/BBB-DASH/HLS_BigBuckTS.m3u8|COMPONENT=HLS", "NA", "Big Bucks Bunny - HLS",
+ "-", 0, 0) == ERROR)
+ return ERROR;
+
+ if (writeXmlItem("HAS - The Town", "http://192.168.1.122/sm/TheTown/manifest-seg.mpd|COMPONENT=HAS", "NA", "HD Test",
+ "-", 0, 0) == ERROR)
+ return ERROR;
+
+
+ //--------------------
+ cRecordings* recordings = &Recordings;
+ // char buff[20];
+ char f[600];
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " recordings->Count()= " << recordings->Count()
+ << DEBUGHDR << endl;
+#endif
+
+ // List of recording timer
+ time_t now = time(NULL);
+
+ vector<sTimerEntry> act_rec;
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " checking avtive timer"
+ << endl;
+#endif
+ for (cTimer * ti = Timers.First(); ti; ti = Timers.Next(ti)){
+ ti->Matches();
+
+ if (ti->HasFlags(tfRecording) ) {
+ if (ti->Event() == NULL) {
+ *(mLog->log()) << DEBUGPREFIX
+ << " WARNING: Active recording for " << ti->File()
+ << " is skipped (No Event()" << endl;
+ continue;
+ }
+ *(mLog->log()) << DEBUGPREFIX
+ << " Active Timer: " << ti->File()
+ << " Start= " << ti->Event()->StartTime()
+ << " Duration= " << ti->Event()->Duration()
+ << endl;
+ act_rec.push_back(sTimerEntry(ti->File(), ti->Event()->StartTime(), ti->Event()->Duration()));
+ }
+ }
+
+ *(mLog->log())<< DEBUGPREFIX
+ << " Found " << act_rec.size()
+ << " running timers"
+ << endl;
+
+ int rec_dur = 0;
+ for (cRecording *recording = recordings->First(); recording; recording = recordings->Next(recording)) {
+ hdr = "";
+
+ // if (recording->IsPesRecording() or ((recording->FramesPerSecond() > 30.0) and !(mFactory->getConfig()->useHasForHd())))
+ if (recording->IsPesRecording() or ((recording->FramesPerSecond() > 30.0) and !has_4_hd ))
+ snprintf(f, sizeof(f), "http://%s:%d%s", mServerAddr.c_str(), mServerPort,
+ cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str());
+ else
+ snprintf(f, sizeof(f), "http://%s:%d%s%s", mServerAddr.c_str(), mServerPort,
+ cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(), link_ext.c_str());
+
+ string link = f;
+ string desc = "No description available";
+ rec_dur = recording->LengthInSeconds();
+
+ string name = recording->Name();
+
+ for (uint x = 0; x < act_rec.size(); x++) {
+ if (act_rec[x].name == name) {
+
+ *(mLog->log())<< DEBUGPREFIX
+ << " !!!!! Found active Recording !!! "
+ << endl;
+ // link += "/hm-seg.m3u8";
+ rec_dur += (act_rec[x].startTime + act_rec[x].duration - now);
+
+
+ }
+ } // for
+
+ if (recording->Info() != NULL) {
+ if (recording->Info()->Description() != NULL) {
+ desc = cUrlEncode::doXmlSaveEncode(recording->Info()->Description());
+ }
+
+ }
+
+ if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", desc, "-",
+ recording->start, rec_dur) == ERROR)
+ return ERROR;
+
+ }
+
+ hdr = "</channel>\n";
+ hdr += "</rss>\n";
+
+ *mResponseMessage += hdr;
+
+
+#endif
+ return OKAY;
+}
+
+int cHttpResource::sendRecordingsHtml(struct stat *statbuf) {
+#ifndef STANDALONE
+ mResponseMessage = new string();
+ mResponseMessagePos = 0;
+ *mResponseMessage = "";
+ mContentType = MEMBLOCK;
+
+ mConnState = SERVING;
+
+ sendHeaders(200, "OK", NULL, "text/html", -1, statbuf->st_mtime);
+
+ string hdr = "";
+ hdr += "<HTML><HEAD><TITLE>Recordings</TITLE></HEAD>\r\n";
+ hdr += "<meta http-equiv=\"content-type\" content=\"text/html;charset=UTF-8\"/>\r\n";
+ hdr += "<BODY>";
+ hdr += "<H4>Recordings</H4>\r\n<PRE>\n";
+ hdr += "<HR>\r\n";
+
+ *mResponseMessage += hdr;
+
+ char buff[20];
+ char f[400];
+ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
+ hdr = "";
+ strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&recording->start));
+ snprintf(f, sizeof(f), "%s - %d <A HREF=\"%s\">%s</A>\r\n", buff, recording->HierarchyLevels(), recording->FileName(), recording->Name());
+ hdr += f;
+ *mResponseMessage += hdr;
+ // start is time_t
+ }
+#endif
+ return OKAY;
+}
+
+
+int cHttpResource::sendVdrDir(struct stat *statbuf) {
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX << " *** sendVdrDir mPath= " << mPath << endl;
+#endif
+ char pathbuf[4096];
+ char f[400];
+ int vdr_idx = 0;
+ off_t total_file_size = 0;
+ // int ret = OKAY;
+ string vdr_dir = mPath;
+ vector<sVdrFileEntry> file_sizes;
+ bool more_to_go = true;
+
+ checkRecording();
+
+ mVdrIdx = 1;
+ mFileStructure = "%s/%03d.vdr";
+
+ if (mPath.compare(mPath.size() - 9, 9, "99.99.rec") != 0) {
+ mFileStructure = "%s/%05d.ts";
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX << " using dir format: " << mFileStructure.c_str() << endl;
+#endif
+ }
+
+ // --- looup all vdr files in the dir ---
+ while (more_to_go) {
+ vdr_idx ++;
+ snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mPath.c_str(), vdr_idx);
+ if (stat(pathbuf, statbuf) >= 0) {
+#ifndef DEBUG
+ *(mLog->log())<< " found for " << pathbuf << endl;
+#endif
+ file_sizes.push_back(sVdrFileEntry(statbuf->st_size, total_file_size, vdr_idx));
+ total_file_size += statbuf->st_size;
+ }
+ else {
+ more_to_go = false;
+ }
+ }
+ if (file_sizes.size() < 1) {
+ // There seems to be vdr video file in the directory
+ *(mLog->log())<< DEBUGPREFIX
+ << " No video file in the directory"
+ << DEBUGHDR << endl;
+ sendError(404, "Not Found", NULL, "File not found.");
+ return OKAY;
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " vdr filesize list "
+ << DEBUGHDR << endl;
+ for (uint i = 0; i < file_sizes.size(); i++)
+ *(mLog->log())<< " i= " << i << " size= " << file_sizes[i].sSize << " firstOffset= " << file_sizes[i].sFirstOffset << endl;
+ *(mLog->log())<< " total_file_size= " << total_file_size << endl << endl;
+#endif
+
+ // total_file_size (on disk)
+
+ // ---------------- file sizes ---------------------
+
+ uint cur_idx = 0;
+
+#ifndef DEBUG
+ if (mIsRecording) {
+ snprintf(f, sizeof(f), " CurFileSize= %lld mRecProgress= %f ExtFileSize= %lld", total_file_size, mRecProgress, (long long int)(mRecProgress * total_file_size));
+ *(mLog->log()) << DEBUGPREFIX
+ << endl << " isRecording: " << f
+ << endl;
+ }
+#endif
+
+ if (!rangeHdr.isRangeRequest) {
+ snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mPath.c_str(), file_sizes[cur_idx].sIdx);
+
+ if (openFile(pathbuf) != OKAY) {
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ return OKAY;
+ }
+
+ mRemLength = total_file_size;
+
+ sendHeaders(200, "OK", NULL, "video/mpeg", ((mIsRecording) ? (mRecProgress * total_file_size): total_file_size ), statbuf->st_mtime);
+ }
+ else { // Range request
+ // idenify the first file
+#ifndef DEBUG
+ *(mLog->log()) << DEBUGPREFIX
+ << endl <<" --- Range Request Handling ---"
+ << DEBUGHDR << endl;
+#endif
+ if (mIsRecording && (rangeHdr.begin > total_file_size)) {
+ *(mLog->log()) << DEBUGPREFIX
+ << " ERROR: Not yet available" << endl;
+ sendError(404, "Not Found", NULL, "File not found.");
+ return OKAY;
+ }
+ cur_idx = file_sizes.size() -1;
+ for (uint i = 1; i < file_sizes.size(); i++) {
+ if (rangeHdr.begin < file_sizes[i].sFirstOffset ) {
+ cur_idx = i -1;
+ break;
+ }
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< " Identified Record i= " << cur_idx << " file_sizes[i].sFirstOffset= "
+ << file_sizes[cur_idx].sFirstOffset << " rangeHdr.begin= " << rangeHdr.begin
+ << " vdr_no= " << file_sizes[cur_idx].sIdx << endl;
+#endif
+
+ mVdrIdx = file_sizes[cur_idx].sIdx;
+ snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mPath.c_str(), file_sizes[cur_idx].sIdx);
+#ifndef DEBUG
+ *(mLog->log())<< " file identified= " << pathbuf << endl;
+#endif
+ if (openFile(pathbuf) != OKAY) {
+ *(mLog->log())<< "----- fopen failed dump ----------" << endl;
+ *(mLog->log())<< DEBUGPREFIX
+ << " vdr filesize list "
+ << DEBUGHDR << endl;
+ for (uint i = 0; i < file_sizes.size(); i++)
+ *(mLog->log())<< " i= " << i << " size= " << file_sizes[i].sSize << " firstOffset= " << file_sizes[i].sFirstOffset << endl;
+ *(mLog->log())<< " total_file_size= " << total_file_size << endl << endl;
+
+ *(mLog->log())<< " Identified Record i= " << cur_idx << " file_sizes[i].sFirstOffset= "
+ << file_sizes[cur_idx].sFirstOffset << " rangeHdr.begin= " << rangeHdr.begin
+ << " vdr_no= " << file_sizes[cur_idx].sIdx << endl;
+ *(mLog->log())<< "---------------" << endl;
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ return OKAY;
+ }
+ mDir = mPath;
+ mPath = pathbuf;
+#ifndef DEBUG
+ *(mLog->log())<< " Seeking into file= " << (rangeHdr.begin - file_sizes[cur_idx].sFirstOffset)
+ << " cur_idx= " << cur_idx
+ << " file_sizes[cur_idx].sFirstOffset= " << file_sizes[cur_idx].sFirstOffset
+ << endl;
+#endif
+ fseek(mFile, (rangeHdr.begin - file_sizes[cur_idx].sFirstOffset), SEEK_SET);
+ if (rangeHdr.end == 0)
+ rangeHdr.end = ((mIsRecording) ? (mRecProgress * total_file_size): total_file_size);
+
+ mRemLength = (rangeHdr.end-rangeHdr.begin);
+
+ snprintf(f, sizeof(f), "Content-Range: bytes %lld-%lld/%lld", rangeHdr.begin, (rangeHdr.end -1),
+ ((mIsRecording) ? (long long int)(mRecProgress * total_file_size): total_file_size));
+
+ sendHeaders(206, "Partial Content", f, "video/mpeg", (rangeHdr.end-rangeHdr.begin), statbuf->st_mtime);
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< " ***** Yes, vdr dir found ***** mPath= " << mPath<< endl;
+#endif
+
+ return OKAY; // handleRead() done
+}
+
+void cHttpResource::sendHeaders(int status, const char *title, const char *extra, const char *mime,
+ off_t length, time_t date) {
+
+ time_t now;
+ char timebuf[128];
+ char f[400];
+
+ string hdr = "";
+ snprintf(f, sizeof(f), "%s %d %s\r\n", PROTOCOL, status, title);
+ hdr += f;
+ snprintf(f, sizeof(f), "Server: %s\r\n", SERVER);
+ hdr += f;
+ now = time(NULL);
+ strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));
+ snprintf(f, sizeof(f), "Date: %s\r\n", timebuf);
+ hdr += f;
+ if (extra) {
+ snprintf(f, sizeof(f), "%s\r\n", extra);
+ *(mLog->log())<< DEBUGPREFIX << " " << f;
+ hdr += f;
+ }
+ if (mime) {
+ snprintf(f, sizeof(f), "Content-Type: %s\r\n", mime);
+ hdr += f;
+ }
+ if (length >= 0) {
+ snprintf(f, sizeof(f), "Content-Length: %lld\r\n", length);
+ *(mLog->log())<< DEBUGPREFIX << " " << f << endl;
+ hdr += f;
+ }
+ if (date != -1) {
+ strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&date));
+ snprintf(f, sizeof(f), "Last-Modified: %s\r\n", timebuf);
+ hdr += f;
+ }
+ if (mAcceptRanges) {
+ snprintf(f, sizeof(f), "Accept-Ranges: bytes\r\n");
+ hdr += f;
+ }
+ snprintf(f, sizeof(f), "Connection: close\r\n");
+ hdr += f;
+
+ snprintf(f, sizeof(f), "\r\n");
+ hdr += f;
+
+
+ if (mBlkLen != 0) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR in SendHeader: mBlkLen != 0!!! --> Overwriting" << endl;
+ }
+ mBlkLen = hdr.size();
+ strcpy(mBlkData, hdr.c_str());
+
+}
+
+int cHttpResource::sendFile(struct stat *statbuf) {
+ // Send the First Datachunk, incl all headers
+
+ *(mLog->log())<< "fd= " << mFd << " mReqId= "<< mReqId << " mPath= " << mPath
+ << DEBUGHDR
+ << endl;
+
+ char f[400];
+
+ if (openFile(mPath.c_str()) == ERROR) {
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ return OKAY;
+ }
+ mFile = fopen(mPath.c_str(), "r");
+ int ret = OKAY;
+
+ if (!mFile) {
+ sendError(403, "Forbidden", NULL, "Access denied.");
+ ret = ERROR;
+ }
+ else {
+ mFileSize = S_ISREG(statbuf->st_mode) ? statbuf->st_size : -1;
+
+ if (!rangeHdr.isRangeRequest) {
+ mRemLength = mFileSize;
+ sendHeaders(200, "OK", NULL, getMimeType(mPath.c_str()), mFileSize, statbuf->st_mtime);
+ }
+ else { // Range request
+ fseek(mFile, rangeHdr.begin, SEEK_SET);
+ if (rangeHdr.end == 0)
+ rangeHdr.end = mFileSize;
+ mRemLength = (rangeHdr.end-rangeHdr.begin);
+ snprintf(f, sizeof(f), "Content-Range: bytes %lld-%lld/%lld", rangeHdr.begin, (rangeHdr.end -1), mFileSize);
+ sendHeaders(206, "Partial Content", f, getMimeType(mPath.c_str()), (rangeHdr.end-rangeHdr.begin), statbuf->st_mtime);
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< "fd= " << mFd << " mReqId= "<< mReqId
+ << ": Done mRemLength= "<< mRemLength << " ret= " << ret
+ << DEBUGHDR << endl;
+#endif
+ mConnState = SERVING;
+
+ }
+ return ret;
+
+}
+
+const char *cHttpResource::getMimeType(const char *name) {
+ char *ext = strrchr((char*)name, '.');
+ if (!ext)
+ return NULL;
+ // if (ext.compare(".html") || ext.compare(".htm")) return "text/html";
+ if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) return "text/html";
+ if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
+ if (strcmp(ext, ".gif") == 0) return "image/gif";
+ if (strcmp(ext, ".png") == 0) return "image/png";
+ if (strcmp(ext, ".css") == 0) return "text/css";
+ if (strcmp(ext, ".au") == 0) return "audio/basic";
+ if (strcmp(ext, ".wav") == 0) return "audio/wav";
+ if (strcmp(ext, ".avi") == 0) return "video/x-msvideo";
+ if (strcmp(ext, ".mp4") == 0) return "video/mp4";
+ if (strcmp(ext, ".vdr") == 0) return "video/mpeg";
+ if (strcmp(ext, ".ts") == 0) return "video/mpeg";
+ if (strcmp(ext, ".mpd") == 0) return "application/dash+xml";
+ if (strcmp(ext, ".xml") == 0) return "application/xml";
+ if (strcmp(ext, ".mpeg") == 0 || strcmp(ext, ".mpg") == 0) return "video/mpeg";
+ if (strcmp(ext, ".mp3") == 0) return "audio/mpeg";
+ return NULL;
+}
+
+
+string cHttpResource::getConnStateName() {
+ string state_string;
+ switch (mConnState) {
+ case WAITING:
+ state_string = "WAITING";
+ break;
+ case READHDR:
+ state_string = "READ Req HDR";
+ break;
+ case READPAYLOAD:
+ state_string = "READ Req Payload";
+ break;
+ case SERVING:
+ state_string = "SERVING";
+ break;
+ case TOCLOSE:
+ state_string = "TOCLOSE";
+ break;
+ default:
+ state_string = "UNKNOWN";
+ break;
+ }
+ return state_string;
+}
+
+int cHttpResource::parseHttpRequestLine(string line) {
+ mMethod = line.substr(0, line.find_first_of(" "));
+ mRequest = line.substr(line.find_first_of(" ") +1, (line.find_last_of(" ") - line.find_first_of(" ") -1));
+ mVersion = line.substr(line.find_last_of(" ") +1);
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " ReqLine= " << line << endl;
+#endif
+ if (mVersion.compare(0, 4, "HTTP") != 0) {
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " ERROR: No HTTP request -> Closing Connection" << line << endl;
+#endif
+ return ERROR;
+ }
+
+ size_t pos = mRequest.find('?');
+ if (pos != string::npos)
+ mQuery = mRequest.substr (pos+1, string::npos);
+ mPath = cUrlEncode::doUrlSaveDecode(mRequest.substr(0, mRequest.find('?')));
+ *(mLog->log())<< DEBUGPREFIX
+ << " mMethod= " << mMethod
+ << " mPath= " << mPath
+ << " mVer= " << mVersion
+ << " mQuery= " << mQuery
+ // << " HexDump= " << endl << cUrlEncode::hexDump(mPath) << endl
+ << endl;
+ return OKAY;
+}
+
+int cHttpResource::parseHttpHeaderLine (string line) {
+ string hdr_name = line.substr(0, line.find_first_of(":"));
+ string hdr_val = line.substr(line.find_first_of(":") +2);
+
+ if (hdr_name.compare("Range") == 0) {
+ parseRangeHeaderValue(hdr_val);
+ *(mLog->log()) << DEBUGPREFIX
+ << " Range: Begin= " << rangeHdr.begin
+ << " End= " << rangeHdr.end
+ << endl;
+ }
+ if (hdr_name.compare("User-Agent") == 0) {
+ mUserAgent = hdr_val;
+ *(mLog->log())<< " User-Agent: " << hdr_val
+ << endl;
+ }
+ if (hdr_name.compare("Content-Length") == 0) {
+ mReqContentLength = atoll(hdr_val.c_str());
+#ifndef DEBUG
+ *(mLog->log())<< " Content-Length: " << mReqContentLength
+ << endl;
+#endif
+ }
+ return 0;
+}
+
+void cHttpResource::checkRecording() {
+ // sets mIsRecording to true when the recording is still on-going
+ mIsRecording = false;
+ time_t now = time(NULL);
+
+#ifndef STANDALONE
+
+ // cRecordings* recordings = mFactory->getRecordings();
+ cRecordings* recordings = &Recordings;
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " GetByName(" <<mPath.c_str() << ")"
+ << endl;
+#endif
+ cRecording* rec = recordings->GetByName(mPath.c_str());
+ if (rec != NULL) {
+ const cEvent *ev = rec->Info()->GetEvent();
+ if (ev != NULL) {
+ if (now < ev->EndTime()) {
+ // still recording
+ mIsRecording = true;
+
+ // mRecProgress * curFileSize = estimated File Size
+ mRecProgress = (ev->EndTime() - ev->StartTime()) *1.1 / (rec->NumFrames() / rec->FramesPerSecond());
+ // mRecProgress = (ev->EndTime() - ev->StartTime()) *1.0/ (now - ev->StartTime());
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " **** is still recording for mIsRecording= "
+ << mIsRecording
+ << " mRecProgress= " << mRecProgress << endl;
+#endif
+ }
+ }
+
+#ifndef DEBUG
+ *(mLog->log())<< DEBUGPREFIX
+ << " checking, whether recording is on-going"
+ << " now= " << now << " start= " << rec->Start()
+ << " curDur= " << rec->LengthInSeconds()
+ << endl;
+#endif
+ }
+#ifndef DEBUG
+ else {
+ *(mLog->log())<< DEBUGPREFIX
+ << " **** Recording Entry Not found **** " << endl;
+ }
+#endif
+#endif
+}
+
+int cHttpResource::parseRangeHeaderValue(string val) {
+ rangeHdr.isRangeRequest = true;
+ size_t pos_equal = val.find_first_of('=');
+ size_t pos_minus = val.find_first_of('-');
+
+ string range_type = val.substr(0, pos_equal);
+ string first_val= val.substr(pos_equal+1, pos_minus -(pos_equal+1));
+ rangeHdr.begin = atoll(first_val.c_str());
+
+ string sec_val = "";
+ if ((pos_minus +1)< val.size()){
+ sec_val = val.substr(pos_minus+1);
+ rangeHdr.end = atoll(sec_val.c_str());
+ }
+ return 0;
+}
+
+int cHttpResource::openFile(const char *name) {
+ mFile = fopen(name, "r");
+ if (!mFile) {
+ *(mLog->log())<< DEBUGPREFIX
+ << " fopen failed pathbuf= " << name
+ << endl;
+ // sendError(403, "Forbidden", NULL, "Access denied.");
+ return ERROR;
+ }
+ return OKAY;
+}
+
diff --git a/vdr-smarttvweb/httpresource.h b/vdr-smarttvweb/httpresource.h
new file mode 100644
index 0000000..22e44c4
--- /dev/null
+++ b/vdr-smarttvweb/httpresource.h
@@ -0,0 +1,188 @@
+/*
+ * httpresource.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+#ifndef __HTTPREQUEST_H__
+#define __HTTPREQUEST_H__
+
+#include <string>
+#include <cstring>
+#include <pthread.h>
+#include "log.h"
+
+using namespace std;
+
+struct cRange {
+cRange(): isRangeRequest(false), begin(0), end(0) {};
+ bool isRangeRequest;
+ unsigned long long begin;
+ unsigned long long end;
+};
+
+struct sQueryAVP {
+ string attribute;
+ string value;
+sQueryAVP(string a, string v) : attribute (a), value(v) {};
+};
+
+
+enum eConnState {
+ WAITING,
+ READHDR,
+ READPAYLOAD,
+ SERVING,
+ TOCLOSE
+};
+
+enum eContentType {
+ NYD, // Not Yet Defined
+ VDRDIR,
+ SINGLEFILE,
+ MEMBLOCK
+};
+
+struct sFileEntry {
+ string sName;
+ string sPath;
+ int sStart;
+
+sFileEntry(string n, string l, int s) : sName(n), sPath(l), sStart(s) {
+ };
+};
+
+class SmartTvServer;
+class cResumeEntry;
+
+class cHttpResource {
+
+ public:
+ cHttpResource(int, int, string, int, SmartTvServer*);
+ virtual ~cHttpResource();
+
+ int handleRead();
+ int handleWrite();
+
+ int checkStatus();
+
+ int readFromClient();
+ void threadLoop();
+ int run();
+
+ private:
+ SmartTvServer* mFactory;
+ Log* mLog;
+
+ string mServerAddr;
+ int mServerPort;
+ int mFd;
+ int mReqId;
+
+ time_t mConnTime;
+ int mHandleReadCount;
+ bool mConnected;
+ eConnState mConnState;
+ eContentType mContentType;
+
+ string mReadBuffer;
+ string mMethod;
+
+ string *mResponseMessage;
+ int mResponseMessagePos;
+ char* mBlkData;
+ int mBlkPos;
+ int mBlkLen;
+
+ // string path;
+ string mRequest;
+ string mQuery;
+ string mPath;
+ string mDir;
+ string mVersion;
+ string protocol;
+ unsigned long long mReqContentLength;
+ string mPayload;
+ string mUserAgent;
+
+ bool mAcceptRanges;
+ cRange rangeHdr;
+ unsigned long long mFileSize;
+ uint mRemLength;
+ FILE *mFile;
+ int mVdrIdx;
+ string mFileStructure;
+ bool mIsRecording;
+ float mRecProgress;
+
+ // int writeToClient(const char *buf, size_t buflen);
+ // int sendDataChunk();
+
+ void setNonBlocking();
+ int fillDataBlk();
+
+ int handlePost();
+ int processRequest();
+ int processHttpHeaderNew();
+
+ int readRequestPayload();
+ // int processHttpHeader();
+ void sendError(int status, const char *title, const char *extra, const char *text);
+ int sendDir(struct stat *statbuf);
+ int sendVdrDir(struct stat *statbuf);
+ int sendRecordingsHtml (struct stat *statbuf);
+ int sendRecordingsXml (struct stat *statbuf);
+ int sendChannelsXml (struct stat *statbuf);
+ int sendEpgXml (struct stat *statbuf);
+ int sendMediaXml (struct stat *statbuf);
+
+ // int sendMPD (struct stat *statbuf);
+ int sendManifest (struct stat *statbuf, bool is_hls = true);
+ void writeM3U8(float duration, float seg_dur, int end_seg);
+ void writeMPD(float duration, float seg_dur, int end_seg);
+
+
+ int sendMediaSegment (struct stat *statbuf);
+
+ void sendHeaders(int status, const char *title, const char *extra, const char *mime,
+ off_t length, time_t date);
+
+ int sendFile(struct stat *statbuf);
+
+ // Helper Functions
+ const char *getMimeType(const char *name);
+ string getConnStateName();
+ void checkRecording();
+ int parseRangeHeaderValue(string);
+ int parseHttpRequestLine(string);
+ int parseHttpHeaderLine (string);
+ int parseQueryLine (vector<sQueryAVP> *avps);
+ int parseResume(cResumeEntry &entry, string &id);
+
+ int parseFiles(vector<sFileEntry> *entries, string prefix, string dir_base, string dir_name, struct stat *statbuf);
+
+ int getQueryAttributeValue(vector<sQueryAVP> *avps, string id, string &val);
+ int openFile(const char *name);
+ int writeXmlItem(string title, string link, string programme, string desc, string guid, time_t start, int dur);
+ // string removeEtChar(string line);
+ // string hexDump(string input);
+ // string convertUrl(string input);
+ // string convertBack(string input);
+};
+#endif
diff --git a/vdr-smarttvweb/log.c b/vdr-smarttvweb/log.c
new file mode 100644
index 0000000..08a9ed5
--- /dev/null
+++ b/vdr-smarttvweb/log.c
@@ -0,0 +1,74 @@
+/*
+ * log.c: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#include "log.h"
+#include <time.h>
+
+Log* Log::instance = NULL;
+
+Log::Log() {
+ if (instance)
+ return;
+ instance = this;
+ mLogFile = NULL;
+}
+
+Log::~Log() {
+ instance = NULL;
+}
+
+Log* Log::getInstance() {
+ return instance;
+}
+int Log::init(string fileName) {
+ char timebuf[128];
+ time_t now = time(NULL);
+ strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
+
+ mLogFile = new ofstream();
+ mLogFile->open(fileName.c_str(), ios::out );
+ *mLogFile << "Log Created: " << timebuf << endl;
+ return 0;
+}
+
+int Log::init(char* fileName) {
+ char timebuf[128];
+ time_t now = time(NULL);
+ strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
+
+ mLogFile = new ofstream();
+ mLogFile->open(fileName, ios::out );
+ *mLogFile << "Log Created: " << timebuf << endl;
+ return 0;
+}
+
+int Log::shutdown() {
+ if (mLogFile)
+ mLogFile->close();
+ return 1;
+}
+
+ofstream* Log::log() {
+ return mLogFile;
+}
+
diff --git a/vdr-smarttvweb/log.h b/vdr-smarttvweb/log.h
new file mode 100644
index 0000000..8a09fe3
--- /dev/null
+++ b/vdr-smarttvweb/log.h
@@ -0,0 +1,50 @@
+/*
+ * log.h.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+class Log
+{
+ public:
+ Log();
+ ~Log();
+ static Log* getInstance();
+
+ int init(char* fileName);
+ int init(string fileName);
+ int shutdown();
+ ofstream* log();
+
+ private:
+ static Log* instance;
+
+ ofstream *mLogFile;
+};
+
+#endif
diff --git a/vdr-smarttvweb/smarttvfactory.c b/vdr-smarttvweb/smarttvfactory.c
new file mode 100644
index 0000000..12a24bc
--- /dev/null
+++ b/vdr-smarttvweb/smarttvfactory.c
@@ -0,0 +1,382 @@
+/*
+ * smarttvfactory.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+#ifndef STANDALONE
+#include <vdr/recording.h>
+#include <vdr/videodir.h>
+#endif
+
+#include <iostream>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include <iostream>
+#include <fstream>
+
+
+#include "smarttvfactory.h"
+
+#ifndef STANDALONE
+#define PORT 8000
+#else
+#define PORT 9000
+#endif
+
+#define OKAY 0
+#define ERROR (-1)
+
+#define DEBUG
+
+
+using namespace std;
+
+void SmartTvServerStartThread(void* arg) {
+ SmartTvServer* m = (SmartTvServer*)arg;
+ m->threadLoop();
+ delete m;
+ pthread_exit(NULL);
+}
+
+SmartTvServer::SmartTvServer(): mRequestCount(0), isInited(false), serverPort(PORT), mServerFd(-1),
+ mSegmentDuration(10), mHasMinBufferTime(40), mHasBitrate(6000000), mLiveChannels(20),
+ clientList(), mActiveSessions(0), mConfig(NULL) {
+}
+
+
+SmartTvServer::~SmartTvServer() {
+ if (mConfig != NULL)
+ delete mConfig;
+}
+
+void SmartTvServer::cleanUp() {
+ mLog.shutdown();
+}
+
+int SmartTvServer::runAsThread() {
+ int res = pthread_create(&mThreadId, NULL, (void*(*)(void*))SmartTvServerStartThread, (void *)this);
+ if (res != 0) {
+ *(mLog.log()) << " Error creating thread. res= " << res
+ << endl;
+ return 0;
+ }
+ return 1;
+}
+
+void SmartTvServer::threadLoop() {
+ *(mLog.log()) << " SmartTvServer Thread Started " << endl;
+
+ loop();
+
+ *(mLog.log()) << " SmartTvServer Thread Stopped " << endl;
+}
+
+
+void SmartTvServer::loop() {
+ socklen_t addr_size = 0;
+ int rfd;
+ sockaddr_in sadr;
+ int req_id = 0;
+ int ret = 0;
+ struct timeval timeout;
+
+ int maxfd;
+
+ fd_set read_set;
+ fd_set write_set;
+
+ FD_ZERO(&read_set);
+ FD_ZERO(&write_set);
+
+ FD_ZERO(&mReadState);
+ FD_ZERO(&mWriteState);
+
+ FD_SET(mServerFd, &mReadState);
+ maxfd = mServerFd;
+
+ struct ifreq ifr;
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, "eth0", IFNAMSIZ-1);
+ ioctl(mServerFd, SIOCGIFADDR, &ifr);
+ string own_ip = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
+
+ *(mLog.log()) << "mServerFd= " << mServerFd << endl;
+
+ int handeled_fds = 0;
+
+ for (;;) {
+ FD_ZERO(&read_set);
+ FD_ZERO(&write_set);
+ read_set = mReadState;
+ write_set = mWriteState;
+
+ if (ret != handeled_fds) {
+ *(mLog.log()) << "ERROR: Select-ret= " << ret
+ << " != handeled_fds= " << handeled_fds << endl;
+ /* FD_ZERO(&mReadState);
+ FD_ZERO(&mWriteState);
+ FD_SET(mServerFd, &mReadState);
+ maxfd = mServerFd;
+
+ read_set = mReadState;
+ write_set = mWriteState;
+ for (uint idx= 0; idx < clientList.size(); idx++) {
+ if (clientList[idx] != NULL) {
+ close(idx);
+ delete clientList[idx];
+ clientList[idx] = NULL;
+ }
+ }
+ mActiveSessions = 0;
+*/
+ }
+
+ handeled_fds = 0;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+
+ ret = select(maxfd + 1, &read_set, &write_set, NULL, &timeout);
+
+ if (ret == 0) {
+ for (uint idx= 0; idx < clientList.size(); idx++) {
+ if (clientList[idx] != NULL)
+ if (clientList[idx]->checkStatus() == ERROR) {
+ close(idx);
+ delete clientList[idx];
+ clientList[idx] = NULL;
+ mActiveSessions--;
+ FD_CLR(idx, &mReadState); /* dead client */
+ FD_CLR(idx, &mWriteState);
+ *(mLog.log()) << "WARNING: Timeout - Dead Client fd=" << idx << endl;
+ }
+ }
+ continue;
+ } // timeout
+
+ if (ret < 0){
+ *(mLog.log()) << "ERROR: select error " << errno << endl;
+ continue;
+ } // Error
+
+ // new accept
+ if (FD_ISSET(mServerFd, &read_set)) {
+ if((rfd = accept(mServerFd, (sockaddr*)&sadr, &addr_size))!= -1){
+ req_id ++;
+ handeled_fds ++;
+
+#ifndef DEBUG
+ *(mLog.log()) << "fd= " << rfd
+ << " --------------------- Received connection ---------------------" << endl;
+#endif
+
+ FD_SET(rfd, &mReadState); /* neuen Client fd dazu */
+ FD_SET(rfd, &mWriteState); /* neuen Client fd dazu */
+
+ if (rfd > maxfd) {
+ maxfd = rfd;
+ }
+
+ if (clientList.size() < (rfd+1)) {
+ clientList.resize(rfd+1, NULL); // Check.
+ }
+ clientList[rfd] = new cHttpResource(rfd, req_id, own_ip, serverPort, this);
+ mActiveSessions ++;
+
+ }
+ else{
+ *(mLog.log()) << "Error accepting " << errno << endl;
+ }
+ }
+
+ // Check for data on already accepted connections
+ // for (rfd = mServerFd + 1; rfd <= maxfd; ++rfd) {
+ // for (rfd = 0; rfd <= maxfd; ++rfd) {
+ for (rfd = 0; rfd < clientList.size(); rfd++) {
+ if (clientList[rfd] == NULL)
+ continue;
+ if (FD_ISSET(rfd, &read_set)) {
+ handeled_fds ++;
+ // HandleRead
+ if (clientList[rfd] == NULL) {
+ *(mLog.log()) << "ERROR in Check Read: oops - no cHttpResource anymore fd= " << rfd << endl;
+ close(rfd);
+ FD_CLR(rfd, &mReadState); /* remove dead client */
+ FD_CLR(rfd, &mWriteState);
+ continue;
+ }
+ if ( clientList[rfd]->handleRead() < 0){
+#ifndef DEBUG
+ *(mLog.log()) << "fd= " << rfd << " --------------------- Check Read: Closing ---------------------" << endl;
+#endif
+ close(rfd);
+ delete clientList[rfd];
+ clientList[rfd] = NULL;
+ mActiveSessions--;
+ FD_CLR(rfd, &mReadState); /* dead client */
+ FD_CLR(rfd, &mWriteState);
+ }
+ }
+ }
+
+ // Check for write
+ // for (rfd = mServerFd + 1; rfd <= maxfd; ++rfd) {
+ // for (rfd = 0; rfd <= maxfd; ++rfd) {
+ for (rfd = 0; rfd < clientList.size(); rfd++) {
+ if (clientList[rfd] == NULL)
+ continue;
+ if (FD_ISSET(rfd, &write_set)) {
+ handeled_fds++;
+ // HandleWrite
+ if (clientList[rfd] == NULL) {
+ close(rfd);
+ FD_CLR(rfd, &mReadState);
+ FD_CLR(rfd, &mWriteState);
+ continue;
+ }
+ if ( clientList[rfd]->handleWrite() < 0){
+ *(mLog.log()) << "fd= " << rfd << " --------------------- Check Write: Closing ---------------------" << endl;
+ close(rfd);
+ delete clientList[rfd];
+ clientList[rfd] = NULL;
+ mActiveSessions--;
+ FD_CLR(rfd, &mReadState);
+ FD_CLR(rfd, &mWriteState);
+ }
+ }
+ }
+
+ // selfcheck
+ /* *(mLog.log()) << "Select Summary: ret= " << ret
+ << " handeled_fds=" << handeled_fds
+ << " mActiveSessions= " << mActiveSessions
+ << " clientList.size()= " << clientList.size()
+ << endl;
+*/
+ //Check for active sessions
+ /*
+ *(mLog.log()) << "checking number of active sessions clientList.size()= " << clientList.size() << endl;
+
+ int act_ses = 0;
+ for (uint idx= 0; idx < clientList.size(); idx++) {
+ if (clientList[idx] != NULL)
+ act_ses++;
+ }
+ if (act_ses != mActiveSessions) {
+ *(mLog.log()) << "ERROR: Lost somewhere a session: "
+ << "mActiveSessions= " << mActiveSessions
+ << "act_ses= " << act_ses
+ << endl;
+ mActiveSessions = act_ses;
+ }
+ *(mLog.log()) << "checking number of active sessions - done mActiveSessions= " << mActiveSessions << endl;
+*/
+ } // for (;;)
+} // org bracket
+
+int SmartTvServer::isServing() {
+ return (mActiveSessions != 0 ? true : false);
+}
+
+void SmartTvServer::initServer(string dir) {
+ /* This function initialtes the listening socket for the server
+ * and sets isInited to true
+ */
+ mConfigDir = dir;
+ int ret;
+ struct sockaddr_in sock;
+ int yes = 1;
+
+
+#ifndef STANDALONE
+ mConfig = new cSmartTvConfig(dir);
+ mLog.init(mConfig->getLogFile());
+ // mLog.init("/multimedia/video/smartvvweblog.txt");
+ esyslog("SmartTvWeb: Logfile created");
+
+ *(mLog.log()) << mConfig->getLogFile() << endl;
+
+#else
+ mConfig = new cSmartTvConfig(".");
+ mLog.init(mConfig->getLogFile());
+ // mLog.init("/tmp/smartvvweblog-standalone.txt");
+ cout << "SmartTvWeb: Logfile created" << endl;
+ cout << "SmartTvWeb: Listening on port= " << PORT << endl;
+
+#endif
+
+ mSegmentDuration= mConfig->getSegmentDuration();
+ mHasMinBufferTime= mConfig->getHasMinBufferTime();
+ mHasBitrate = mConfig->getHasBitrate();
+ mLiveChannels = mConfig->getLiveChannels();
+
+ *(mLog.log()) <<"HTTP server listening on port " << serverPort << endl;
+
+ mServerFd = socket(PF_INET, SOCK_STREAM, 0);
+ if (mServerFd <0) {
+ *(mLog.log()) << "Error: Cannot create serving socket, exit" << endl;
+ exit(1);
+ }
+
+ ret = setsockopt(mServerFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
+ if (ret <0) {
+ *(mLog.log()) << "Error: Cannot set sockopts on serving socket, exit" << endl;
+ exit(1);
+ }
+
+ memset((char *) &sock, 0, sizeof(sock));
+ sock.sin_family = AF_INET;
+ sock.sin_addr.s_addr = htonl(INADDR_ANY);
+ sock.sin_port = htons(serverPort);
+
+ ret = bind(mServerFd, (struct sockaddr *) &sock, sizeof(sock));
+ if (ret !=0) {
+ *(mLog.log()) << "Error: Cannot bind serving socket, exit" << endl;
+ exit(1);
+ }
+
+ ret = listen(mServerFd, 5);
+ if (ret <0) {
+ *(mLog.log()) << "Error: Cannot set listening on serving socket, exit" << endl;
+ exit(1);
+ }
+
+ isInited = true;
+}
+
+
+
+
diff --git a/vdr-smarttvweb/smarttvfactory.h b/vdr-smarttvweb/smarttvfactory.h
new file mode 100644
index 0000000..e2936bf
--- /dev/null
+++ b/vdr-smarttvweb/smarttvfactory.h
@@ -0,0 +1,88 @@
+/*
+ * smarttvfactory.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#ifndef __SMARTTVSERVER_H__
+#define __SMARTTVSERVER_H__
+
+#include <string>
+#include <cstring>
+#include <vector>
+#include <list>
+#include "httpresource.h"
+#include "log.h"
+#include "stvw_cfg.h"
+
+#ifndef STANDALONE
+#include <vdr/recording.h>
+#endif
+
+using namespace std;
+
+class SmartTvServer {
+ public:
+ SmartTvServer();
+ virtual ~SmartTvServer();
+
+ void initServer(string c_dir);
+ void loop();
+ void cleanUp();
+ int runAsThread();
+ void threadLoop();
+
+ Log mLog;
+
+ void readRecordings();
+ int isServing();
+
+ string getConfigDir() { return mConfigDir; };
+ unsigned int getSegmentDuration() { return mSegmentDuration; };
+ int getHasMinBufferTime() { return mHasMinBufferTime; };
+ unsigned int getHasBitrate() { return mHasBitrate; };
+ int getLiveChannels() { return mLiveChannels; };
+
+ cSmartTvConfig* getConfig() { return mConfig; };
+
+ private:
+ pthread_t mThreadId;
+ int mRequestCount;
+ bool isInited;
+ int serverPort;
+ int mServerFd;
+ unsigned int mSegmentDuration;
+ int mHasMinBufferTime;
+ unsigned int mHasBitrate;
+ int mLiveChannels;
+
+ vector<cHttpResource*> clientList;
+ // list<int>mFdList;
+ int mActiveSessions;
+ string mConfigDir;
+ cSmartTvConfig *mConfig;
+
+ int mMaxFd;
+ fd_set mReadState;
+ fd_set mWriteState;
+};
+
+
+#endif
diff --git a/vdr-smarttvweb/smarttvweb.c b/vdr-smarttvweb/smarttvweb.c
new file mode 100644
index 0000000..b7c907a
--- /dev/null
+++ b/vdr-smarttvweb/smarttvweb.c
@@ -0,0 +1,152 @@
+/*
+ * smarttvweb.c: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#ifndef STANDALONE
+#include <vdr/plugin.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <errno.h>
+
+#include <dirent.h>
+
+#include "smarttvfactory.h"
+
+
+static const char *VERSION = "0.9.0";
+static const char *DESCRIPTION = "SmartTV Web Server";
+
+
+using namespace std;
+
+#ifndef STANDALONE
+class cPluginSmartTvWeb : public cPlugin
+{
+public:
+ cPluginSmartTvWeb(void);
+ virtual ~cPluginSmartTvWeb();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+#if VDRVERSNUM > 10300
+ virtual cString Active(void);
+#endif
+
+private:
+ SmartTvServer mServer;
+ string mConfigDir;
+};
+
+cPluginSmartTvWeb::cPluginSmartTvWeb(void) {
+ // Initialize any member variables here.
+ // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+ // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+ mConfigDir = "";
+}
+
+bool cPluginSmartTvWeb::Start(void) {
+ // Start any background activities the plugin shall perform.
+
+ if (mConfigDir.compare("") == 0) {
+ const char* dir_name = cPlugin::ConfigDirectory(Name());
+ if (!dir_name) {
+ dsyslog("SmartTvWeb: Could not get config dir from VDR");
+ }
+ else
+ mConfigDir = string(dir_name);
+ }
+
+ mServer.initServer(mConfigDir);
+ int success = mServer.runAsThread();
+
+ esyslog("SmartTvWeb: started %s", (success == 1) ? "sucessfully" : "failed!!!!");
+
+ return ((success == 1) ? true: false);
+}
+
+cPluginSmartTvWeb::~cPluginSmartTvWeb() {
+ // Clean up after yourself!
+ mServer.cleanUp();
+}
+
+const char *cPluginSmartTvWeb::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return " \n";
+}
+
+bool cPluginSmartTvWeb::ProcessArgs(int argc, char *argv[]) {
+ // Implement command line argument processing here if applicable.
+ return true;
+}
+
+bool cPluginSmartTvWeb::Initialize(void) {
+ // Initialize any background activities the plugin shall perform.
+ esyslog("SmartTvWeb: Initialize called");
+
+ return true;
+}
+
+bool cPluginSmartTvWeb::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+#if VDRVERSNUM > 10300
+
+cString cPluginSmartTvWeb::Active(void) {
+ esyslog("SmartTvWeb: Active called Checkme");
+ if (mServer.isServing())
+ return tr("SmartTV client(s) serving");
+ else
+ return NULL;
+}
+
+#endif
+
+VDRPLUGINCREATOR(cPluginSmartTvWeb); // Don't touch this!
+
+#else //VOMPSTANDALONE
+
+
+int main(int argc, char *argv[]) {
+ printf ("Starting up\n");
+
+ SmartTvServer server;
+ server.initServer(".");
+ server.loop();
+}
+#endif
diff --git a/vdr-smarttvweb/stvw_cfg.c b/vdr-smarttvweb/stvw_cfg.c
new file mode 100644
index 0000000..bc0c4f6
--- /dev/null
+++ b/vdr-smarttvweb/stvw_cfg.c
@@ -0,0 +1,165 @@
+/*
+ * stvw_cfg.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+#include "stvw_cfg.h"
+
+#ifndef STANDALONE
+#include <vdr/plugin.h>
+#endif
+
+#include <iostream>
+#include <fstream>
+#include <cstdio>
+#include <cstdlib>
+
+cSmartTvConfig::cSmartTvConfig(string d): mConfigDir(d), mLog(NULL), mCfgFile(NULL),
+ mLogFile(), mMediaFolder(), mSegmentDuration(), mHasMinBufferTime(), mHasBitrate(),
+ mLiveChannels() {
+
+#ifndef STANDALONE
+ mLogFile= "/multimedia/video/smartvvweblog.txt";
+#else
+ mLogFile= "/tmp/smartvvweblog-standalone.txt";
+#endif
+
+ // Defaults
+ mMediaFolder= "/hd2/mpeg";
+ mSegmentDuration = 10;
+ mHasMinBufferTime = 40;
+ mHasBitrate = 6000000;
+ mLiveChannels = 30;
+ mLog = Log::getInstance();
+
+ readConfig();
+}
+
+cSmartTvConfig::~cSmartTvConfig() {
+ fclose(mCfgFile);
+}
+
+void cSmartTvConfig::readConfig() {
+ string line;
+ char attr[200];
+ char value[200];
+
+ ifstream myfile ((mConfigDir +"/smarttvweb.conf").c_str());
+
+ if (!myfile.is_open()) {
+#ifndef STANDALONE
+ esyslog ("ERROR in SmartTvWeb: Cannot open config file. Expecting %s", (mConfigDir +"/smarttvweb.conf").c_str() );
+#else
+ cout << "ERROR: Cannot open config file. Expecting "<< (mConfigDir +"/smarttvweb.conf") << endl;
+#endif
+ return;
+ }
+
+ while ( myfile.good() ) {
+ getline (myfile, line);
+
+ if ((line == "") or (line[0] == '#'))
+ continue;
+
+ sscanf(line.c_str(), "%s %s", attr, value);
+
+ if (strcmp(attr, "LogFile")==0) {
+ mLogFile = string(value);
+ // cout << " Found mLogFile= " << mLogFile << endl;
+ continue;
+ }
+
+ if (strcmp(attr, "MediaFolder") == 0) {
+ mMediaFolder = string (value);
+ // cout << " Found mMediaFolder= " << mMediaFolder << endl;
+ continue;
+ }
+ if (strcmp(attr, "SegmentDuration") == 0) {
+ mSegmentDuration = atoi(value);
+ // cout << " Found mSegmentDuration= " << mSegmentDuration << endl;
+ continue;
+ }
+
+ if (strcmp(attr, "HasMinBufferTime") == 0) {
+ mHasMinBufferTime = atoi(value);
+ // cout << " Found mHasMinBufferTime= " << mHasMinBufferTime << endl;
+ continue;
+ }
+ if (strcmp(attr, "HasBitrate") == 0) {
+ mHasBitrate = atoi(value);
+ // cout << " Found mHasBitrate= " <<mHasBitrate << endl;
+ continue;
+ }
+ if (strcmp(attr, "LiveChannels") == 0) {
+ mLiveChannels = atoi(value);
+ // cout << " Found mLiveChannels= " <<mLiveChannels << endl;
+ continue;
+ }
+
+
+#ifndef STANDALONE
+ esyslog("WARNING in SmartTvWeb: Attribute= %s with value= %s was not processed, thus ignored.", attr, value);
+#else
+ cout << "WARNING: Attribute= "<< attr << " with value= " << value << " was not processed, thus ignored." << endl;
+#endif
+ }
+ myfile.close();
+}
+
+cResumes* cSmartTvConfig::readConfig(string f) {
+
+
+ /*
+ string line;
+ ifstream myfile ("example.txt");
+
+ if (myfile.is_open()) {
+ while ( myfile.good() ) {
+ getline (myfile,line);
+ *(mLog->log()) << " readConfig: " << line << endl;
+ if (line == "")
+ continue;
+
+ string t;
+ time_t st;
+ int r;
+ time_t lv;
+ int count = scanf (line.c_str(), "%s %lld %d %lld", &t, &st, &r, &lv);
+ if (count == 4) {
+ *(mLog->log()) << " read: " << t << " st= " << st << " r= " << r << " lv= " << lv << endl;
+ }
+ // first title
+
+
+ }
+ myfile.close();
+ }
+
+ else {
+ *(mLog->log()) << " readConfig: Cannot open file " << f << endl;
+ return NULL;
+ }
+
+*/
+
+ // open the file
+ // read the lines
+ return NULL;
+}
diff --git a/vdr-smarttvweb/stvw_cfg.h b/vdr-smarttvweb/stvw_cfg.h
new file mode 100644
index 0000000..4d19953
--- /dev/null
+++ b/vdr-smarttvweb/stvw_cfg.h
@@ -0,0 +1,86 @@
+/*
+ * stvw_cfg.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#ifndef __SMARTTV_CONFIG_H__
+#define __SMARTTV_CONFIG_H__
+
+#include <string>
+#include <cstring>
+#include <vector>
+#include <ctime>
+#include "log.h"
+
+using namespace std;
+
+class cResumeEntry {
+ public:
+ string mTitle;
+ time_t mStartTime; // title is not unique
+ int mResume;
+ time_t mLastViewed;
+
+ friend ostream& operator<<(ostream& out, const cResumeEntry& o) {
+ out << "mTitle= " << o.mTitle << " mStartTime= " << o.mStartTime << " mResume= " << o.mResume << endl;
+ return out;
+ };
+};
+
+class cResumes {
+ public:
+ cResumes(string t) : mDevice(t) {};
+
+ vector<cResumeEntry> mResumes;
+
+ string mDevice;
+};
+
+class cSmartTvConfig {
+ private:
+ string mConfigDir;
+ Log* mLog;
+ FILE *mCfgFile;
+
+ string mLogFile;
+ string mMediaFolder;
+ unsigned int mSegmentDuration;
+ int mHasMinBufferTime;
+ unsigned int mHasBitrate;
+ int mLiveChannels;
+
+ public:
+ cSmartTvConfig(string dir);
+ ~cSmartTvConfig();
+
+ void readConfig();
+
+ cResumes* readConfig(string);
+
+ string getLogFile() { return mLogFile; };
+ string getMediaFolder() { return mMediaFolder; };
+ unsigned int getSegmentDuration() {return mSegmentDuration; };
+ int getHasMinBufferTime() { return mHasMinBufferTime; };
+ unsigned int getHasBitrate() {return mHasBitrate; };
+ int getLiveChannels() {return mLiveChannels; };
+};
+
+#endif
diff --git a/vdr-smarttvweb/url.c b/vdr-smarttvweb/url.c
new file mode 100644
index 0000000..75b5817
--- /dev/null
+++ b/vdr-smarttvweb/url.c
@@ -0,0 +1,302 @@
+/*
+ * url.c: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+#include <cstdio>
+#include"url.h"
+
+
+
+string cUrlEncode::doUrlSaveEncode(string in) {
+ string res = "";
+ unsigned char num = 0;
+ char buf[5];
+
+ bool done = false;
+ unsigned int idx = 0;
+ while (!done) {
+ if (idx == in.size()) {
+ done = true;
+ continue;
+ }
+ num = in[idx];
+ switch (num) {
+ case '&':
+ res += "%26";
+ break;
+ case '%':
+ res += "%25";
+ break;
+ case '#':
+ // break;
+ if (in.compare(idx, 3, "#3F") == 0) {
+ res += "%3F";
+ idx +=3;
+ continue;
+ }
+ if (in.compare(idx, 3, "#3A") == 0) {
+ res += "%3A";
+ idx += 3;
+ continue;
+ }
+ if (in.compare(idx, 3, "#2F") == 0) {
+ res += "%2F";
+ idx += 3;
+ continue;
+ }
+ res += "%23"; // just a '#' char
+ break;
+ default:
+ // Copy the normal chars
+ if (num < 128)
+ res += char(num);
+ else {
+ sprintf (buf, "%02hhX", num);
+ res += "%";
+ res += buf;
+ }
+ break;
+ } // switch
+
+ idx ++;
+ }
+ return res;
+}
+
+
+string cUrlEncode::doUrlSaveDecode(string input) {
+ string res = "";
+ unsigned int idx = 0;
+ int x;
+ while (idx < input.size()){
+ if (input[idx] == '%') {
+ string num = input.substr(idx+1, 2);
+ sscanf(num.c_str(), "%X", &x);
+ idx+= 3;
+ switch (x) {
+ case 0x23: // '#'
+ res += "#";
+ break;
+ case 0x25: // '%'
+ res += "%";
+ break;
+ case 0x2f: // '/'
+ res += "#2F";
+ break;
+ case 0x3a: // ':'
+ res += "#3A";
+ break;
+ case 63: // '?'
+ res += "#3F";
+ break;
+ default:
+ res += char(x);
+ break;
+ }
+ }
+ else {
+ res += input[idx];
+ idx ++;
+ }
+ }
+ return res;
+}
+
+
+string cUrlEncode::doXmlSaveEncode(string in) {
+ string res = "";
+ unsigned char num = 0;
+ char buf[5];
+
+ bool done = false;
+ unsigned int idx = 0;
+ while (!done) {
+ if (idx == in.size()) {
+ done = true;
+ continue;
+ }
+ num = in[idx];
+ switch (num) {
+ case 0x26: // '&':
+ res += "&#38;";
+ break;
+ case 0x27: // '\'':
+ res += "&apos;";
+ break;
+ case 0x3c: // '<':
+ res += "&lt;";
+ break;
+ case 0x3e: // '>':
+ res += "&gt;";
+ break;
+ case 0x22: // '\"':
+ res += "&quot;";
+ break;
+
+ /* case 0xc4: // Ä
+ res += "&#196;";
+ break;
+ case 0xe4: //'ä':
+ res += "&#228;";
+ break;
+ case 0xd6: // Ö
+ res += "&#214;";
+ break;
+ case 0xf6: // 'ö':
+ res += "&#246;";
+ break;
+ case 0xdc: //'Ü':
+ res += "&#220;";
+ break;
+ case 0xfc: //'ü':
+ res += "&#252;";
+ break;
+ case 0xdf: //'ß':
+ res += "&#223;";
+ break;
+*/
+ default:
+ // Copy the normal chars
+ res += char(num);
+ break;
+ } // switch
+
+ idx ++;
+ }
+ return res;
+}
+
+
+string cUrlEncode::removeEtChar(string line, bool xml) {
+ bool done = false;
+ size_t cur_pos = 0;
+ size_t pos = 0;
+ string res = "";
+
+ int end_after_done = 0;
+
+ while (!done) {
+ pos = line.find('&', cur_pos);
+ if (pos == string::npos) {
+ done = true;
+ res += line.substr(cur_pos);
+ break;
+ }
+ if (pos >= 0) {
+ if (xml)
+ res += line.substr(cur_pos, (pos-cur_pos)) + "&#38;"; // xml save encoding
+ else
+ res += line.substr(cur_pos, (pos-cur_pos)) + "%26"; // url save encoding
+ // cur_pos = cur_pos+ pos +1;
+ cur_pos = pos +1;
+ end_after_done ++;
+ }
+ }
+ return res;
+}
+
+string cUrlEncode::hexDump(char *line, int line_len) {
+ string res = "";
+ string ascii = "";
+ char buf[10];
+
+ int line_count = 0;
+ for (unsigned int i = 0; i < line_len; i++) {
+ unsigned char num = line[i];
+ sprintf (buf, "%02hhX", num);
+ res += buf;
+ if ((num >= 32) && (num < 127)) {
+ ascii += char(num);
+ }
+ else
+ ascii += '.';
+
+ line_count++;
+ switch (line_count) {
+ case 8:
+ res += " ";
+ ascii += " ";
+ break;
+ case 17:
+ res += " " + ascii;
+ res += "\r\n";
+ ascii = "";
+ line_count = 0;
+ break;
+ default:
+ res += " ";
+ break;
+ }
+ }
+ if (line_count != 0) {
+ for (int i = 0; i < ((17 - line_count) * 3 ); i++)
+ res += " ";
+ if (line_count >= 8)
+ res += " ";
+
+ res += " ";
+ res += ascii;
+ }
+ return res;
+}
+
+string cUrlEncode::hexDump(string in) {
+ string res = "";
+ string ascii = "";
+ char buf[10];
+
+ int line_count = 0;
+ for (unsigned int i = 0; i < in.size(); i++) {
+ unsigned char num = in[i];
+ sprintf (buf, "%02hhX", num);
+ res += buf;
+ if ((num >= 32) && (num < 127)) {
+ ascii += char(num);
+ }
+ else
+ ascii += '.';
+
+ line_count++;
+ switch (line_count) {
+ case 8:
+ res += " ";
+ ascii += " ";
+ break;
+ case 16:
+ res += " " + ascii;
+ res += "\r\n";
+ ascii = "";
+ line_count = 0;
+ break;
+ default:
+ res += " ";
+ break;
+ }
+ }
+ if (line_count != 0) {
+ for (int i = 0; i < ((16 - line_count) * 3 ); i++)
+ res += " ";
+ if (line_count >= 8)
+ res += " ";
+ res += ascii;
+ }
+ return res;
+}
diff --git a/vdr-smarttvweb/url.h b/vdr-smarttvweb/url.h
new file mode 100644
index 0000000..4695f8b
--- /dev/null
+++ b/vdr-smarttvweb/url.h
@@ -0,0 +1,49 @@
+/*
+ * url.h: VDR on Smart TV plugin
+ *
+ * Copyright (C) 2012 Thorsten Lohmar
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ */
+
+
+#ifndef __URL_H__
+#define __URL_H__
+
+#include <string>
+
+using namespace std;
+
+class cUrlEncode {
+
+ public:
+ cUrlEncode() {};
+ cUrlEncode(string &) {};
+
+ static string doUrlSaveEncode (string);
+ static string doUrlSaveDecode (string);
+
+ static string doXmlSaveEncode (string);
+ static string removeEtChar(string line, bool xml=true);
+ static string hexDump(char *line, int line_len);
+ static string hexDump(string);
+
+ private:
+
+};
+
+#endif