diff options
author | thlo <smarttv640@gmail.com> | 2012-12-25 11:36:09 +0100 |
---|---|---|
committer | thlo <t.lohmar@gmx.de> | 2012-12-25 11:36:09 +0100 |
commit | c6513544182b8bbbe62dd9ea9b786964147756b2 (patch) | |
tree | 43fc29789e9eef0e153e7e71ce57c197587cd82c | |
download | vdr-plugin-smarttvweb-c6513544182b8bbbe62dd9ea9b786964147756b2.tar.gz vdr-plugin-smarttvweb-c6513544182b8bbbe62dd9ea9b786964147756b2.tar.bz2 |
Initial Version
-rw-r--r-- | vdr-smarttvweb/Makefile | 109 | ||||
-rw-r--r-- | vdr-smarttvweb/httpresource-hmm.c | 1130 | ||||
-rw-r--r-- | vdr-smarttvweb/httpresource-hmm.h | 94 | ||||
-rw-r--r-- | vdr-smarttvweb/httpresource.c | 2122 | ||||
-rw-r--r-- | vdr-smarttvweb/httpresource.h | 188 | ||||
-rw-r--r-- | vdr-smarttvweb/log.c | 74 | ||||
-rw-r--r-- | vdr-smarttvweb/log.h | 50 | ||||
-rw-r--r-- | vdr-smarttvweb/smarttvfactory.c | 382 | ||||
-rw-r--r-- | vdr-smarttvweb/smarttvfactory.h | 88 | ||||
-rw-r--r-- | vdr-smarttvweb/smarttvweb.c | 152 | ||||
-rw-r--r-- | vdr-smarttvweb/stvw_cfg.c | 165 | ||||
-rw-r--r-- | vdr-smarttvweb/stvw_cfg.h | 86 | ||||
-rw-r--r-- | vdr-smarttvweb/url.c | 302 | ||||
-rw-r--r-- | vdr-smarttvweb/url.h | 49 |
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)) + "&"; + // 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 += "&"; + break; + case 0x27: // '\'': + res += "'"; + break; + case 0x3c: // '<': + res += "<"; + break; + case 0x3e: // '>': + res += ">"; + break; + case 0x22: // '\"': + res += """; + break; + + /* case 0xc4: // Ä + res += "Ä"; + break; + case 0xe4: //'ä': + res += "ä"; + break; + case 0xd6: // Ö + res += "Ö"; + break; + case 0xf6: // 'ö': + res += "ö"; + break; + case 0xdc: //'Ü': + res += "Ü"; + break; + case 0xfc: //'ü': + res += "ü"; + break; + case 0xdf: //'ß': + res += "ß"; + 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)) + "&"; // 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 |