summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntti Ajanki <antti.ajanki@iki.fi>2013-08-09 21:39:11 +0300
committerAntti Ajanki <antti.ajanki@iki.fi>2013-08-09 21:39:11 +0300
commit451f691d367a46f365101f39cc093ce2feaacd13 (patch)
tree6ac88c6ecf46755c4826719f58a2cf00ac9ecb8c
parent2d4d55cfedccfa80d283592af349e93d0968f58e (diff)
downloadvdr-plugin-webvideo-451f691d367a46f365101f39cc093ce2feaacd13.tar.gz
vdr-plugin-webvideo-451f691d367a46f365101f39cc093ce2feaacd13.tar.bz2
Import vdr-plugin from the master branch
-rw-r--r--src/vdr-plugin/Makefile126
-rw-r--r--src/vdr-plugin/buffer.c84
-rw-r--r--src/vdr-plugin/buffer.h44
-rw-r--r--src/vdr-plugin/common.c261
-rw-r--r--src/vdr-plugin/common.h50
-rw-r--r--src/vdr-plugin/config.c249
-rw-r--r--src/vdr-plugin/config.h72
-rw-r--r--src/vdr-plugin/dictionary.c410
-rw-r--r--src/vdr-plugin/dictionary.h178
-rw-r--r--src/vdr-plugin/download.c302
-rw-r--r--src/vdr-plugin/download.h63
-rw-r--r--src/vdr-plugin/history.c145
-rw-r--r--src/vdr-plugin/history.h62
-rw-r--r--src/vdr-plugin/iniparser.c650
-rw-r--r--src/vdr-plugin/iniparser.h284
-rw-r--r--src/vdr-plugin/menu.c680
-rw-r--r--src/vdr-plugin/menu.h114
-rw-r--r--src/vdr-plugin/menu_timer.c149
-rw-r--r--src/vdr-plugin/menu_timer.h46
-rw-r--r--src/vdr-plugin/menudata.c200
-rw-r--r--src/vdr-plugin/menudata.h102
-rw-r--r--src/vdr-plugin/mime.types4
-rw-r--r--src/vdr-plugin/mimetypes.c98
-rw-r--r--src/vdr-plugin/mimetypes.h35
-rw-r--r--src/vdr-plugin/player.c83
-rw-r--r--src/vdr-plugin/player.h29
-rw-r--r--src/vdr-plugin/po/de_DE.po140
-rw-r--r--src/vdr-plugin/po/fi_FI.po140
-rw-r--r--src/vdr-plugin/po/fr_FR.po159
-rwxr-xr-xsrc/vdr-plugin/po/it_IT.po156
-rw-r--r--src/vdr-plugin/po/uk_UA.po134
-rw-r--r--src/vdr-plugin/request.c530
-rw-r--r--src/vdr-plugin/request.h181
-rw-r--r--src/vdr-plugin/timer.c519
-rw-r--r--src/vdr-plugin/timer.h115
-rw-r--r--src/vdr-plugin/webvideo.c516
36 files changed, 7110 insertions, 0 deletions
diff --git a/src/vdr-plugin/Makefile b/src/vdr-plugin/Makefile
new file mode 100644
index 0000000..0fcaffe
--- /dev/null
+++ b/src/vdr-plugin/Makefile
@@ -0,0 +1,126 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# 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 = webvideo
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g')
+
+### The directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../../../vdr.pc))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+override CFLAGS += $(call PKGCFG,cflags) $(shell xml2-config --cflags)
+override CXXFLAGS += $(call PKGCFG,cxxflags) $(shell xml2-config --cflags)
+override LDFLAGS += $(shell xml2-config --libs) -L../libwebvi -lwebvi
+
+export CFLAGS
+export CXXFLAGS
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I../libwebvi
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o buffer.o common.o config.o download.o history.o menu.o menudata.o mimetypes.o request.o player.o dictionary.o iniparser.o timer.o menu_timer.o
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR = po
+I18Npo = $(wildcard $(PODIR)/*.po)
+I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+ msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+ xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+ msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+ @touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+ install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(SOFILE): $(OBJS)
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LDFLAGS) -o $@
+
+install-lib: $(SOFILE)
+ install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install: install-lib install-i18n
+
+dist: $(I18Npo) clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) --exclude debian --exclude CVS --exclude .svn $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.so.* *.tgz core* *~
diff --git a/src/vdr-plugin/buffer.c b/src/vdr-plugin/buffer.c
new file mode 100644
index 0000000..41b2c38
--- /dev/null
+++ b/src/vdr-plugin/buffer.c
@@ -0,0 +1,84 @@
+/*
+ * buffer.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vdr/tools.h>
+#include "buffer.h"
+
+// --- cMemoryBuffer -------------------------------------------------------
+
+cMemoryBuffer::cMemoryBuffer(size_t prealloc) {
+ capacity = prealloc;
+ buf = (char *)malloc(capacity*sizeof(char));
+ offset = 0;
+ len = 0;
+}
+
+cMemoryBuffer::~cMemoryBuffer() {
+ if (buf)
+ free(buf);
+}
+
+void cMemoryBuffer::Realloc(size_t newsize) {
+ if (newsize > capacity-offset) {
+ if (newsize <= capacity) {
+ // The new buffer fits in the memory if we just move the current
+ // content offset bytes backwards.
+ buf = (char *)memmove(buf, &buf[offset], len);
+ offset = 0;
+ } else {
+ // We need to realloc. Move the content to the beginning of the
+ // buffer while we are at it.
+ capacity += min(capacity, (size_t)10*1024);
+ capacity = max(capacity, newsize);
+ char *newbuf = (char *)malloc(capacity*sizeof(char));
+ if (newbuf) {
+ memcpy(newbuf, &buf[offset], len);
+ offset = 0;
+ free(buf);
+ buf = newbuf;
+ }
+ }
+ }
+}
+
+ssize_t cMemoryBuffer::Put(const char *data, size_t bytes) {
+ if (len+bytes > Free()) {
+ Realloc(len+bytes);
+ }
+
+ if (buf) {
+ memcpy(&buf[offset+len], data, bytes);
+ len += bytes;
+ return bytes;
+ }
+ return -1;
+}
+
+ssize_t cMemoryBuffer::PutFromFile(int fd, size_t bytes) {
+ if (len+bytes > Free()) {
+ Realloc(len+bytes);
+ }
+
+ if (buf) {
+ ssize_t r = safe_read(fd, &buf[offset+len], bytes);
+ if (r > 0)
+ len += r;
+ return r;
+ } else
+ return -1;
+}
+
+void cMemoryBuffer::Pop(size_t bytes) {
+ if (bytes <= len) {
+ offset += bytes;
+ len -= bytes;
+ }
+}
diff --git a/src/vdr-plugin/buffer.h b/src/vdr-plugin/buffer.h
new file mode 100644
index 0000000..0a5ee5c
--- /dev/null
+++ b/src/vdr-plugin/buffer.h
@@ -0,0 +1,44 @@
+/*
+ * buffer.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_BUFFER_H
+#define __WEBVIDEO_BUFFER_H
+
+#include <unistd.h>
+
+// --- cMemoryBuffer -------------------------------------------------------
+
+// FIFO character buffer.
+
+class cMemoryBuffer {
+private:
+ char *buf;
+ size_t offset;
+ size_t len;
+ size_t capacity;
+protected:
+ size_t Free() { return capacity-len-offset; }
+ virtual void Realloc(size_t newsize);
+public:
+ cMemoryBuffer(size_t prealloc = 10*1024);
+ virtual ~cMemoryBuffer();
+
+ // Put data into the end of the buffer
+ virtual ssize_t Put(const char *data, size_t length);
+ // Put data from a file descriptor fd to the buffer
+ virtual ssize_t PutFromFile(int fd, size_t length);
+ // The pointer to the beginning of the buffer. Only valid until the
+ // next Put() or PutFromFile().
+ virtual char *Get() { return &buf[offset]; }
+ // Remove first n bytes from the buffer.
+ void Pop(size_t n);
+ // Returns the current length of the buffer
+ virtual size_t Length() { return len; }
+};
+
+#endif // __WEBVIDEO_BUFFER_H
diff --git a/src/vdr-plugin/common.c b/src/vdr-plugin/common.c
new file mode 100644
index 0000000..2792d24
--- /dev/null
+++ b/src/vdr-plugin/common.c
@@ -0,0 +1,261 @@
+/*
+ * common.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <vdr/tools.h>
+#include "common.h"
+
+char *extensionFromUrl(const char *url) {
+ if (!url)
+ return NULL;
+
+ // Find the possible query ("?query=foo") or fragment ("#bar"). The
+ // extension is located right before them.
+ size_t extendpos = strcspn(url, "?#");
+
+ size_t extstartpos = extendpos-1;
+ while ((extstartpos > 0) && (url[extstartpos] != '.') && (url[extstartpos] != '/'))
+ extstartpos--;
+
+ if ((extstartpos > 0) && (url[extstartpos] == '.')) {
+ // We found the extension. Copy it to a buffer, and return it.
+ char *ext = (char *)malloc(sizeof(char)*(extendpos-extstartpos+1));
+ memcpy(ext, &url[extstartpos], extendpos-extstartpos);
+ ext[extendpos-extstartpos] = '\0';
+
+ return ext;
+ }
+
+ return NULL;
+}
+
+cString parseDomain(const char *url) {
+ const char *schemesep = strstr(url, "://");
+ if (!schemesep)
+ return "";
+
+ const char *domainstart = schemesep+3;
+ const char *domainend = strchr(domainstart, '/');
+
+ int len = domainend-domainstart;
+ char *domain = (char *)malloc((len+1)*sizeof(char));
+ strncpy(domain, domainstart, len);
+ domain[len] = '\0';
+
+ const char *user = strchr(domain, '@');
+ if (user) {
+ len -= user+1 - domain;
+ memmove(domain, user+1, len+1);
+ }
+
+ const char *port = strchr(domain, ':');
+ if (port) {
+ len = port - domain;
+ domain[len] = '\0';
+ }
+
+ strlower(domain);
+
+ return cString(domain, true);
+}
+
+char *validateFileName(const char *filename) {
+ if (!filename)
+ return NULL;
+
+ char *validated = (char *)malloc(strlen(filename)+1);
+ int j=0;
+ for (unsigned int i=0; i<strlen(filename); i++) {
+ if (filename[i] != '/') {
+ validated[j++] = filename[i];
+ }
+ }
+ validated[j] = '\0';
+ return validated;
+}
+
+int moveFile(const char *oldpath, const char *newpath) {
+ if (rename(oldpath, newpath) == 0) {
+ return 0;
+ } else if (errno == EXDEV) {
+ // rename can't move a file between file systems. We have to copy
+ // the file manually.
+ int fdout = open(newpath, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE);
+ if (fdout < 0) {
+ return -1;
+ }
+
+ int fdin = open(oldpath, O_RDONLY);
+ if (fdin < 0) {
+ close(fdout);
+ return -1;
+ }
+
+ const int bufsize = 4096;
+ char buffer[bufsize];
+ bool ok = true;
+ while (true) {
+ ssize_t len = safe_read(fdin, &buffer, bufsize);
+ if (len == 0) {
+ break;
+ } else if (len < 0) {
+ ok = false;
+ break;
+ }
+
+ if (safe_write(fdout, &buffer, len) != len) {
+ ok = false;
+ break;
+ }
+ }
+
+ close(fdin);
+ close(fdout);
+
+ if (ok && (unlink(oldpath) <0)) {
+ return -1;
+ }
+
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+char *URLencode(const char *s) {
+ char reserved_and_unsafe[] =
+ { // reserved characters
+ '$', '&', '+', ',', '/', ':', ';', '=', '?', '@',
+ // unsafe characters
+ ' ', '"', '<', '>', '#', '%', '{', '}',
+ '|', '\\', '^', '~', '[', ']', '`',
+ '\0'
+ };
+
+ if (!s)
+ return NULL;
+
+ char *buf = (char *)malloc((3*strlen(s)+1)*sizeof(char));
+ if (!buf)
+ return NULL;
+
+ unsigned char *out;
+ const unsigned char *in;
+ for (out=(unsigned char *)buf, in=(const unsigned char *)s; *in != '\0'; in++) {
+ if ((*in < 32) // control chracters
+ || (strchr(reserved_and_unsafe, *in)) // reserved and unsafe
+ || (*in > 127)) // non-ASCII
+ {
+ snprintf((char *)out, 4, "%%%02hhX", *in);
+ out += 3;
+ } else {
+ *out = *in;
+ out++;
+ }
+ }
+ *out = '\0';
+
+ return buf;
+}
+
+char *URLdecode(const char *s) {
+ char *res = (char *)malloc(strlen(s)+1);
+ const char *in = s;
+ char *out = res;
+ const char *hex = "0123456789ABCDEF";
+ const char *h1, *h2;
+
+ while (*in) {
+ if ((*in == '%') && (in[1] != '\0') && (in[2] != '\0')) {
+ h1 = strchr(hex, toupper(in[1]));
+ h2 = strchr(hex, toupper(in[2]));
+ if (h1 && h2) {
+ *out = ((h1-hex) << 4) + (h2-hex);
+ in += 3;
+ } else {
+ *out = *in;
+ in++;
+ }
+ } else {
+ *out = *in;
+ in++;
+ }
+ out++;
+ }
+ *out = '\0';
+
+ return res;
+}
+
+char *safeFilename(char *filename, bool vfatnames) {
+ if (filename) {
+ strreplace(filename, '/', '_');
+
+ if (vfatnames) {
+ strreplace(filename, '\\', '_');
+ strreplace(filename, '"', '_');
+ strreplace(filename, '*', '_');
+ strreplace(filename, ':', '_');
+ strreplace(filename, '<', '_');
+ strreplace(filename, '>', '_');
+ strreplace(filename, '?', '_');
+ strreplace(filename, '|', '_');
+ }
+
+ char *p = filename;
+ while ((*p == '.') || isspace(*p)) {
+ p++;
+ }
+
+ if (p != filename) {
+ memmove(filename, p, strlen(p)+1);
+ }
+ }
+
+ return filename;
+}
+
+cString shellEscape(const char *s) {
+ char *buffer = (char *)malloc((4*strlen(s)+3)*sizeof(char));
+ const char *src = s;
+ char *dst = buffer;
+
+ *dst++ = '\'';
+ while (*src) {
+ if (*src == '\'') {
+ *dst++ = '\'';
+ *dst++ = '\\';
+ *dst++ = '\'';
+ *dst++ = '\'';
+ src++;
+ } else {
+ *dst++ = *src++;
+ }
+ }
+ *dst++ = '\'';
+ *dst = '\0';
+
+ return cString(buffer, true);
+}
+
+char *strlower(char *s) {
+ if (!s) return NULL;
+
+ char *p = s;
+ while (*p) {
+ *p = tolower(*p);
+ p++;
+ }
+
+ return s;
+}
diff --git a/src/vdr-plugin/common.h b/src/vdr-plugin/common.h
new file mode 100644
index 0000000..8443081
--- /dev/null
+++ b/src/vdr-plugin/common.h
@@ -0,0 +1,50 @@
+/*
+ * common.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_COMMON_H
+#define __WEBVIDEO_COMMON_H
+
+#ifdef DEBUG
+#define debug(x...) dsyslog("Webvideo: " x);
+#define info(x...) isyslog("Webvideo: " x);
+#define warning(x...) esyslog("Webvideo: Warning: " x);
+#define error(x...) esyslog("Webvideo: " x);
+#else
+#define debug(x...) ;
+#define info(x...) isyslog("Webvideo: " x);
+#define warning(x...) esyslog("Webvideo: Warning: " x);
+#define error(x...) esyslog("Webvideo: " x);
+#endif
+
+// Return the extension of the url or NULL, if the url has no
+// extension. The caller must free the returned string.
+char *extensionFromUrl(const char *url);
+// Return the domain part from url.
+cString parseDomain(const char *url);
+// Returns a "safe" version of filename. Currently just removes / from
+// the name. The caller must free the returned string.
+char *validateFileName(const char *filename);
+int moveFile(const char *oldpath, const char *newpath);
+// Return the URL encoded version of s. The called must free the
+// returned memory.
+char *URLencode(const char *s);
+// Remove URL encoding from s. The called must free the returned
+// memory.
+char *URLdecode(const char *s);
+// Return a "safe" version of filename. Replace '/' with '_' and
+// remove dots from the beginning. If vfatnames is true, replace also
+// other characters which are not allowed on VFAT. The string is
+// modified in-place, i.e. returns the pointer filename that was
+// passed as argument.
+char *safeFilename(char *filename, bool vfatnames);
+// Escape s so that it can be passed as parameter to a shell command.
+cString shellEscape(const char *s);
+// Convert string s to lower case
+char *strlower(char *s);
+
+#endif // __WEBVIDEO_COMMON_H
diff --git a/src/vdr-plugin/config.c b/src/vdr-plugin/config.c
new file mode 100644
index 0000000..1fa62c8
--- /dev/null
+++ b/src/vdr-plugin/config.c
@@ -0,0 +1,249 @@
+/*
+ * config.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "config.h"
+#include "dictionary.h"
+#include "iniparser.h"
+#include "common.h"
+
+// --- cDownloadQuality ---------------------------------------------------
+
+cDownloadQuality::cDownloadQuality(const char *sitename)
+: min(NULL), max(NULL) {
+ site = sitename ? strdup(sitename) : NULL;
+}
+
+cDownloadQuality::~cDownloadQuality() {
+ if (site)
+ free(site);
+ if (min)
+ free(min);
+ if (max)
+ free(max);
+}
+
+void cDownloadQuality::SetMin(const char *val) {
+ if (min)
+ free(min);
+
+ min = val ? strdup(val) : NULL;
+}
+
+void cDownloadQuality::SetMax(const char *val) {
+ if (max)
+ free(max);
+
+ max = val ? strdup(val) : NULL;
+}
+
+const char *cDownloadQuality::GetSite() {
+ return site;
+}
+
+const char *cDownloadQuality::GetMin() {
+ return min;
+}
+
+const char *cDownloadQuality::GetMax() {
+ return max;
+}
+
+// --- cWebvideoConfig -----------------------------------------------------
+
+cWebvideoConfig *webvideoConfig = new cWebvideoConfig();
+
+cWebvideoConfig::cWebvideoConfig() {
+ downloadPath = NULL;
+ templatePath = NULL;
+ preferXine = true;
+ postProcessCmd = NULL;
+ vfatNames = false;
+}
+
+cWebvideoConfig::~cWebvideoConfig() {
+ if (downloadPath)
+ free(downloadPath);
+ if (templatePath)
+ free(templatePath);
+ if (postProcessCmd)
+ free(postProcessCmd);
+}
+
+void cWebvideoConfig::SetDownloadPath(const char *path) {
+ if (downloadPath)
+ free(downloadPath);
+ downloadPath = path ? strdup(path) : NULL;
+}
+
+const char *cWebvideoConfig::GetDownloadPath() {
+ return downloadPath;
+}
+
+void cWebvideoConfig::SetTemplatePath(const char *path) {
+ if (templatePath)
+ free(templatePath);
+ templatePath = path ? strdup(path) : NULL;
+}
+
+const char *cWebvideoConfig::GetTemplatePath() {
+ return templatePath;
+}
+
+void cWebvideoConfig::SetPreferXineliboutput(bool pref) {
+ preferXine = pref;
+}
+
+bool cWebvideoConfig::GetPreferXineliboutput() {
+ return preferXine;
+}
+
+bool cWebvideoConfig::ReadConfigFile(const char *inifile) {
+ dictionary *conf = iniparser_load(inifile);
+
+ if (!conf)
+ return false;
+
+ info("loading config file %s", inifile);
+
+ const char *templatepath = iniparser_getstring(conf, "webvi:templatepath", NULL);
+ if (templatepath) {
+ debug("templatepath = %s (from %s)", templatepath, inifile);
+ SetTemplatePath(templatepath);
+ }
+
+ const char *vfat = iniparser_getstring(conf, "webvi:vfat", NULL);
+ if (vfat) {
+ debug("vfat = %s (from %s)", vfat, inifile);
+
+ if (strcmp(vfat, "1") == 0 ||
+ strcmp(vfat, "true") == 0 ||
+ strcmp(vfat, "yes") == 0 ||
+ strcmp(vfat, "on") == 0)
+ {
+ vfatNames = true;
+ } else if (strcmp(vfat, "0") == 0 ||
+ strcmp(vfat, "false") == 0 ||
+ strcmp(vfat, "no") == 0 ||
+ strcmp(vfat, "off") == 0)
+ {
+ vfatNames = false;
+ } else {
+ warning("Invalid value for config option vfat: %s in %s", vfat, inifile);
+ }
+ }
+
+ for (int i=0; i<iniparser_getnsec(conf); i++) {
+ const char *section = iniparser_getsecname(conf, i);
+
+ if (strcmp(section, "webvi") != 0) {
+ const int maxsectionlen = 100;
+ char key[128];
+ char *keyname;
+ const char *sitename;
+
+ cString domain = parseDomain(section);
+ if (strlen(domain) == 0)
+ sitename = section;
+ else
+ sitename = domain;
+
+ strncpy(key, section, maxsectionlen);
+ key[maxsectionlen] = '\0';
+ strcat(key, ":");
+ keyname = key+strlen(key);
+
+ strcpy(keyname, "download-min-quality");
+ const char *download_min = iniparser_getstring(conf, key, NULL);
+
+ strcpy(keyname, "download-max-quality");
+ const char *download_max = iniparser_getstring(conf, key, NULL);
+
+ strcpy(keyname, "stream-min-quality");
+ const char *stream_min = iniparser_getstring(conf, key, NULL);
+
+ strcpy(keyname, "stream-max-quality");
+ const char *stream_max = iniparser_getstring(conf, key, NULL);
+
+ if (download_min || download_max) {
+ cDownloadQuality *limits = new cDownloadQuality(sitename);
+ limits->SetMin(download_min);
+ limits->SetMax(download_max);
+ downloadLimits.Add(limits);
+
+ debug("download priorities for %s (from %s): min = %s, max = %s",
+ sitename, inifile, download_min, download_max);
+ }
+
+ if (stream_min || stream_max) {
+ cDownloadQuality *limits = new cDownloadQuality(sitename);
+ limits->SetMin(stream_min);
+ limits->SetMax(stream_max);
+ streamLimits.Add(limits);
+
+ debug("streaming priorities for %s (from %s): min = %s, max = %s",
+ sitename, inifile, stream_min, stream_max);
+ }
+ }
+ }
+
+ iniparser_freedict(conf);
+
+ return true;
+}
+
+const char *cWebvideoConfig::GetQuality(const char *site, eRequestType type, int limit) {
+ if (type != REQT_FILE && type != REQT_STREAM)
+ return NULL;
+
+ cList<cDownloadQuality>& priorlist = downloadLimits;
+ if (type == REQT_STREAM)
+ priorlist = streamLimits;
+
+ cDownloadQuality *node = priorlist.First();
+
+ while (node && (strcmp(site, node->GetSite()) != 0)) {
+ node = priorlist.Next(node);
+ }
+
+ if (!node)
+ return NULL;
+
+ if (limit == 0)
+ return node->GetMin();
+ else
+ return node->GetMax();
+}
+
+const char *cWebvideoConfig::GetMinQuality(const char *site, eRequestType type) {
+ return GetQuality(site, type, 0);
+}
+
+const char *cWebvideoConfig::GetMaxQuality(const char *site, eRequestType type) {
+ return GetQuality(site, type, 1);
+}
+
+void cWebvideoConfig::SetPostProcessCmd(const char *cmd) {
+ if (postProcessCmd)
+ free(postProcessCmd);
+ postProcessCmd = cmd ? strdup(cmd) : NULL;
+}
+
+const char *cWebvideoConfig::GetPostProcessCmd() {
+ return postProcessCmd;
+}
+
+void cWebvideoConfig::SetUseVFATNames(bool vfat) {
+ vfatNames = vfat;
+}
+
+bool cWebvideoConfig::GetUseVFATNames() {
+ return vfatNames;
+}
+
diff --git a/src/vdr-plugin/config.h b/src/vdr-plugin/config.h
new file mode 100644
index 0000000..ccda574
--- /dev/null
+++ b/src/vdr-plugin/config.h
@@ -0,0 +1,72 @@
+/*
+ * config.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_CONFIG_H
+#define __WEBVIDEO_CONFIG_H
+
+#include <vdr/tools.h>
+#include "request.h"
+
+class cDownloadQuality : public cListObject {
+private:
+ char *site;
+ char *min;
+ char *max;
+
+public:
+ cDownloadQuality(const char *site);
+ ~cDownloadQuality();
+
+ void SetMin(const char *val);
+ void SetMax(const char *val);
+
+ const char *GetSite();
+ const char *GetMin();
+ const char *GetMax();
+};
+
+class cWebvideoConfig {
+private:
+ char *downloadPath;
+ char *templatePath;
+ char *postProcessCmd;
+ bool preferXine;
+ bool vfatNames;
+ cList<cDownloadQuality> downloadLimits;
+ cList<cDownloadQuality> streamLimits;
+
+ const char *GetQuality(const char *site, eRequestType type, int limit);
+
+public:
+ cWebvideoConfig();
+ ~cWebvideoConfig();
+
+ bool ReadConfigFile(const char *inifile);
+
+ void SetDownloadPath(const char *path);
+ const char *GetDownloadPath();
+
+ void SetTemplatePath(const char *path);
+ const char *GetTemplatePath();
+
+ void SetPreferXineliboutput(bool pref);
+ bool GetPreferXineliboutput();
+
+ void SetUseVFATNames(bool vfat);
+ bool GetUseVFATNames();
+
+ const char *GetMinQuality(const char *site, eRequestType type);
+ const char *GetMaxQuality(const char *site, eRequestType type);
+
+ void SetPostProcessCmd(const char *cmd);
+ const char *GetPostProcessCmd();
+};
+
+extern cWebvideoConfig *webvideoConfig;
+
+#endif
diff --git a/src/vdr-plugin/dictionary.c b/src/vdr-plugin/dictionary.c
new file mode 100644
index 0000000..4c5ae08
--- /dev/null
+++ b/src/vdr-plugin/dictionary.c
@@ -0,0 +1,410 @@
+/*
+ Copyright (c) 2000-2007 by Nicolas Devillard.
+ MIT License, see COPYING for more information.
+*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.c
+ @author N. Devillard
+ @date Sep 2007
+ @version $Revision: 1.27 $
+ @brief Implements a dictionary for string variables.
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+/*
+ $Id: dictionary.c,v 1.27 2007-11-23 21:39:18 ndevilla Exp $
+ $Revision: 1.27 $
+*/
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+#include "dictionary.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/** Maximum value size for integers and doubles. */
+#define MAXVALSZ 1024
+
+/** Minimal allocated number of entries in a dictionary */
+#define DICTMINSZ 128
+
+/** Invalid key token */
+#define DICT_INVALID_KEY ((char*)-1)
+
+/*---------------------------------------------------------------------------
+ Private functions
+ ---------------------------------------------------------------------------*/
+
+/* Doubles the allocated size associated to a pointer */
+/* 'size' is the current allocated size. */
+static void * mem_double(void * ptr, int size)
+{
+ void * newptr ;
+
+ newptr = calloc(2*size, 1);
+ if (newptr==NULL) {
+ return NULL ;
+ }
+ memcpy(newptr, ptr, size);
+ free(ptr);
+ return newptr ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Duplicate a string
+ @param s String to duplicate
+ @return Pointer to a newly allocated string, to be freed with free()
+
+ This is a replacement for strdup(). This implementation is provided
+ for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char * xstrdup(char * s)
+{
+ char * t ;
+ if (!s)
+ return NULL ;
+ t = (char *)malloc(strlen(s)+1) ;
+ if (t) {
+ strcpy(t,s);
+ }
+ return t ;
+}
+
+/*---------------------------------------------------------------------------
+ Function codes
+ ---------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(char * key)
+{
+ int len ;
+ unsigned hash ;
+ int i ;
+
+ len = strlen(key);
+ for (hash=0, i=0 ; i<len ; i++) {
+ hash += (unsigned)key[i] ;
+ hash += (hash<<10);
+ hash ^= (hash>>6) ;
+ }
+ hash += (hash <<3);
+ hash ^= (hash >>11);
+ hash += (hash <<15);
+ return hash ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary objet.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * dictionary_new(int size)
+{
+ dictionary * d ;
+
+ /* If no size was specified, allocate space for DICTMINSZ */
+ if (size<DICTMINSZ) size=DICTMINSZ ;
+
+ if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) {
+ return NULL;
+ }
+ d->size = size ;
+ d->val = (char **)calloc(size, sizeof(char*));
+ d->key = (char **)calloc(size, sizeof(char*));
+ d->hash = (unsigned int *)calloc(size, sizeof(unsigned));
+ return d ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * d)
+{
+ int i ;
+
+ if (d==NULL) return ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]!=NULL)
+ free(d->key[i]);
+ if (d->val[i]!=NULL)
+ free(d->val[i]);
+ }
+ free(d->val);
+ free(d->key);
+ free(d->hash);
+ free(d);
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * dictionary_get(dictionary * d, char * key, char * def)
+{
+ unsigned hash ;
+ int i ;
+
+ hash = dictionary_hash(key);
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ /* Compare hash */
+ if (hash==d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ return d->val[i] ;
+ }
+ }
+ }
+ return def ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * d, char * key, char * val)
+{
+ int i ;
+ unsigned hash ;
+
+ if (d==NULL || key==NULL) return -1 ;
+
+ /* Compute hash for this key */
+ hash = dictionary_hash(key) ;
+ /* Find if value is already in dictionary */
+ if (d->n>0) {
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (hash==d->hash[i]) { /* Same hash value */
+ if (!strcmp(key, d->key[i])) { /* Same key */
+ /* Found a value: modify and return */
+ if (d->val[i]!=NULL)
+ free(d->val[i]);
+ d->val[i] = val ? xstrdup(val) : NULL ;
+ /* Value has been modified: return */
+ return 0 ;
+ }
+ }
+ }
+ }
+ /* Add a new value */
+ /* See if dictionary needs to grow */
+ if (d->n==d->size) {
+
+ /* Reached maximum size: reallocate dictionary */
+ d->val = (char **)mem_double(d->val, d->size * sizeof(char*)) ;
+ d->key = (char **)mem_double(d->key, d->size * sizeof(char*)) ;
+ d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ;
+ if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) {
+ /* Cannot grow dictionary */
+ return -1 ;
+ }
+ /* Double size */
+ d->size *= 2 ;
+ }
+
+ /* Insert key in the first empty slot */
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL) {
+ /* Add key here */
+ break ;
+ }
+ }
+ /* Copy key */
+ d->key[i] = xstrdup(key);
+ d->val[i] = val ? xstrdup(val) : NULL ;
+ d->hash[i] = hash;
+ d->n ++ ;
+ return 0 ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, char * key)
+{
+ unsigned hash ;
+ int i ;
+
+ if (key == NULL) {
+ return;
+ }
+
+ hash = dictionary_hash(key);
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ /* Compare hash */
+ if (hash==d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ /* Found key */
+ break ;
+ }
+ }
+ }
+ if (i>=d->size)
+ /* Key not found */
+ return ;
+
+ free(d->key[i]);
+ d->key[i] = NULL ;
+ if (d->val[i]!=NULL) {
+ free(d->val[i]);
+ d->val[i] = NULL ;
+ }
+ d->hash[i] = 0 ;
+ d->n -- ;
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(dictionary * d, FILE * out)
+{
+ int i ;
+
+ if (d==NULL || out==NULL) return ;
+ if (d->n<1) {
+ fprintf(out, "empty dictionary\n");
+ return ;
+ }
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]) {
+ fprintf(out, "%20s\t[%s]\n",
+ d->key[i],
+ d->val[i] ? d->val[i] : "UNDEF");
+ }
+ }
+ return ;
+}
+
+
+/* Test code */
+#ifdef TESTDIC
+#define NVALS 20000
+int main(int argc, char *argv[])
+{
+ dictionary * d ;
+ char * val ;
+ int i ;
+ char cval[90] ;
+
+ /* Allocate dictionary */
+ printf("allocating...\n");
+ d = dictionary_new(0);
+
+ /* Set values in dictionary */
+ printf("setting %d values...\n", NVALS);
+ for (i=0 ; i<NVALS ; i++) {
+ sprintf(cval, "%04d", i);
+ dictionary_set(d, cval, "salut");
+ }
+ printf("getting %d values...\n", NVALS);
+ for (i=0 ; i<NVALS ; i++) {
+ sprintf(cval, "%04d", i);
+ val = dictionary_get(d, cval, DICT_INVALID_KEY);
+ if (val==DICT_INVALID_KEY) {
+ printf("cannot get value for key [%s]\n", cval);
+ }
+ }
+ printf("unsetting %d values...\n", NVALS);
+ for (i=0 ; i<NVALS ; i++) {
+ sprintf(cval, "%04d", i);
+ dictionary_unset(d, cval);
+ }
+ if (d->n != 0) {
+ printf("error deleting values\n");
+ }
+ printf("deallocating...\n");
+ dictionary_del(d);
+ return 0 ;
+}
+#endif
+/* vim: set ts=4 et sw=4 tw=75 */
diff --git a/src/vdr-plugin/dictionary.h b/src/vdr-plugin/dictionary.h
new file mode 100644
index 0000000..f39493e
--- /dev/null
+++ b/src/vdr-plugin/dictionary.h
@@ -0,0 +1,178 @@
+/*
+ Copyright (c) 2000-2007 by Nicolas Devillard.
+ MIT License, see COPYING for more information.
+*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.h
+ @author N. Devillard
+ @date Sep 2007
+ @version $Revision: 1.12 $
+ @brief Implements a dictionary for string variables.
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+/*
+ $Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp $
+ $Author: ndevilla $
+ $Date: 2007-11-23 21:37:00 $
+ $Revision: 1.12 $
+*/
+
+#ifndef _DICTIONARY_H_
+#define _DICTIONARY_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*---------------------------------------------------------------------------
+ New types
+ ---------------------------------------------------------------------------*/
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dictionary object
+
+ This object contains a list of string/string associations. Each
+ association is identified by a unique string key. Looking up values
+ in the dictionary is speeded up by the use of a (hopefully collision-free)
+ hash function.
+ */
+/*-------------------------------------------------------------------------*/
+typedef struct _dictionary_ {
+ int n ; /** Number of entries in dictionary */
+ int size ; /** Storage size */
+ char ** val ; /** List of string values */
+ char ** key ; /** List of string keys */
+ unsigned * hash ; /** List of hash values for keys */
+} dictionary ;
+
+
+/*---------------------------------------------------------------------------
+ Function prototypes
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(char * key);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary objet.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * dictionary_new(int size);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * vd);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * dictionary_get(dictionary * d, char * key, char * def);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * vd, char * key, char * val);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, char * key);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(dictionary * d, FILE * out);
+
+#endif
diff --git a/src/vdr-plugin/download.c b/src/vdr-plugin/download.c
new file mode 100644
index 0000000..4b7a971
--- /dev/null
+++ b/src/vdr-plugin/download.c
@@ -0,0 +1,302 @@
+/*
+ * download.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <errno.h>
+#include <sys/select.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <vdr/tools.h>
+#include "download.h"
+#include "common.h"
+
+static void diff_timeval(struct timeval *a, struct timeval *b,
+ struct timeval *result) {
+ long usec_diff = a->tv_usec - b->tv_usec;
+ result->tv_sec = a->tv_sec - b->tv_sec;
+
+ while (usec_diff < 0) {
+ usec_diff += 1000000;
+ result->tv_sec -= 1;
+ }
+
+ result->tv_usec = usec_diff;
+}
+
+// --- cWebviThread --------------------------------------------------------
+
+cWebviThread::cWebviThread() {
+ int pipefd[2];
+
+ if (pipe(pipefd) == -1)
+ LOG_ERROR_STR("new request pipe");
+ newreqread = pipefd[0];
+ newreqwrite = pipefd[1];
+ //fcntl(newreqread, F_SETFL, O_NONBLOCK);
+ //fcntl(newreqwrite, F_SETFL, O_NONBLOCK);
+ timerActive = false;
+
+ webvi = webvi_initialize_context();
+ if (webvi != 0) {
+ webvi_set_config(webvi, WEBVI_CONFIG_TIMEOUT_DATA, this);
+ webvi_set_config(webvi, WEBVI_CONFIG_TIMEOUT_CALLBACK, UpdateTimeout);
+ }
+}
+
+cWebviThread::~cWebviThread() {
+ int numactive = activeRequestList.Size();
+ for (int i=0; i<activeRequestList.Size(); i++)
+ delete activeRequestList[i];
+ activeRequestList.Clear();
+
+ for (int i=0; i<finishedRequestList.Size(); i++) {
+ delete finishedRequestList[i];
+ }
+ finishedRequestList.Clear();
+
+ webvi_cleanup_context(webvi);
+
+ if (numactive > 0) {
+ esyslog("%d requests failed to complete", numactive);
+ }
+}
+
+void cWebviThread::UpdateTimeout(long timeout, void *instance) {
+ cWebviThread *self = (cWebviThread *)instance;
+ if (!self)
+ return;
+
+ if (timeout < 0) {
+ self->timerActive = false;
+ } else {
+ struct timeval now;
+ long alrm;
+ gettimeofday(&now, NULL);
+ alrm = timeout + now.tv_usec/1000;
+ self->timer.tv_sec = now.tv_sec + alrm/1000;
+ self->timer.tv_usec = (alrm % 1000) * 1000;
+ self->timerActive = true;
+ }
+}
+
+cWebviThread &cWebviThread::Instance() {
+ static cWebviThread instance;
+
+ return instance;
+}
+
+void cWebviThread::SetTemplatePath(const char *path) {
+ if (webvi != 0 && path)
+ webvi_set_config(webvi, WEBVI_CONFIG_TEMPLATE_PATH, path);
+}
+
+void cWebviThread::MoveToFinishedList(cMenuRequest *req) {
+ // Move the request from the activeList to finishedList.
+ requestMutex.Lock();
+ for (int i=0; i<activeRequestList.Size(); i++) {
+ if (activeRequestList[i] == req) {
+ activeRequestList.Remove(i);
+ break;
+ }
+ }
+ finishedRequestList.Append(req);
+
+ requestMutex.Unlock();
+}
+
+void cWebviThread::ActivateNewRequest() {
+ // Move requests from newRequestList to activeRequestList and start
+ // them.
+ requestMutex.Lock();
+ for (int i=0; i<newRequestList.Size(); i++) {
+ cMenuRequest *req = newRequestList[i];
+ if (req->IsAborted()) {
+ // The request has been aborted even before we got a chance to
+ // start it.
+ MoveToFinishedList(req);
+ } else {
+ if (!req->Start(webvi)) {
+ error("Request failed to start");
+ req->RequestDone(-1, "Request failed to start");
+ MoveToFinishedList(req);
+ } else {
+ activeRequestList.Append(req);
+ }
+ }
+ }
+
+ newRequestList.Clear();
+ requestMutex.Unlock();
+}
+
+void cWebviThread::StopFinishedRequests() {
+ // Check if some requests have finished, and move them to
+ // finishedRequestList.
+ int msg_remaining;
+ WebviMsg *donemsg;
+ cMenuRequest *req;
+
+ do {
+ donemsg = webvi_get_message(webvi, &msg_remaining);
+
+ if (donemsg && donemsg->msg == WEBVIMSG_DONE) {
+ requestMutex.Lock();
+ req = activeRequestList.FindByHandle(donemsg->handle);
+ if (req) {
+ req->RequestDone(donemsg->status_code, donemsg->data);
+ if (req->IsFinished())
+ MoveToFinishedList(req);
+ }
+ requestMutex.Unlock();
+ }
+ } while (msg_remaining > 0);
+}
+
+void cWebviThread::Stop() {
+ // The thread may be sleeping, wake it up first.
+ TEMP_FAILURE_RETRY(write(newreqwrite, "S", 1));
+ Cancel(5);
+}
+
+void cWebviThread::Action(void) {
+ fd_set readfds, writefds, excfds;
+ int maxfd, s;
+ struct timeval timeout, now;
+ long running_handles;
+ bool check_done = false;
+ bool has_request_files = false;
+
+ if (webvi == 0) {
+ error("Failed to get libwebvi context");
+ return;
+ }
+
+ while (Running()) {
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&excfds);
+ webvi_fdset(webvi, &readfds, &writefds, &excfds, &maxfd);
+ FD_SET(newreqread, &readfds);
+ if (newreqread > maxfd)
+ maxfd = newreqread;
+
+ has_request_files = false;
+ requestMutex.Lock();
+ for (int i=0; i<activeRequestList.Size(); i++) {
+ int fd = activeRequestList[i]->File();
+ if (fd != -1) {
+ FD_SET(fd, &readfds);
+ if (fd > maxfd)
+ maxfd = fd;
+ has_request_files = true;
+ }
+ }
+ requestMutex.Unlock();
+
+ if (!timerActive) {
+ timeout.tv_sec = 60;
+ timeout.tv_usec = 0;
+ } else {
+ gettimeofday(&now, NULL);
+ diff_timeval(&timer, &now, &timeout);
+ if (timeout.tv_sec < 0 || timeout.tv_usec < 0) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ }
+ }
+
+ s = TEMP_FAILURE_RETRY(select(maxfd+1, &readfds, &writefds, NULL,
+ &timeout));
+ if (s == -1) {
+ // select error
+ LOG_ERROR_STR("select() error in webvideo downloader thread:");
+ Cancel(-1);
+
+ } else if (s == 0) {
+ // timeout
+ timerActive = false;
+ webvi_perform(webvi, WEBVI_SELECT_TIMEOUT, WEBVI_SELECT_CHECK, &running_handles);
+ check_done = true;
+
+ } else {
+ for (int fd=0; fd<=maxfd; fd++) {
+ if (FD_ISSET(fd, &readfds)) {
+ if (fd == newreqread) {
+ char tmpbuf[8];
+ int n = read(fd, tmpbuf, 8);
+ if (n > 0 && memchr(tmpbuf, 'S', n))
+ Cancel(-1);
+ ActivateNewRequest();
+ } else {
+ cMenuRequest *match = NULL;
+
+ if (has_request_files) {
+ requestMutex.Lock();
+ for (int i=0; i<activeRequestList.Size(); i++) {
+ if (fd == activeRequestList[i]->File()) {
+ match = activeRequestList[i];
+ break;
+ }
+ }
+ requestMutex.Unlock();
+
+ // call Read() after releasing the mutex
+ if (match) {
+ match->Read();
+ if (match->IsFinished())
+ MoveToFinishedList(match);
+ }
+ }
+
+ if (!match) {
+ webvi_perform(webvi, fd, WEBVI_SELECT_READ, &running_handles);
+ check_done = true;
+ }
+ }
+ }
+ if (FD_ISSET(fd, &writefds))
+ webvi_perform(webvi, fd, WEBVI_SELECT_WRITE, &running_handles);
+ if (FD_ISSET(fd, &excfds))
+ webvi_perform(webvi, fd, WEBVI_SELECT_EXCEPTION, &running_handles);
+ }
+ }
+
+ if (check_done) {
+ StopFinishedRequests();
+ check_done = false;
+ }
+ }
+}
+
+void cWebviThread::AddRequest(cMenuRequest *req) {
+ requestMutex.Lock();
+ newRequestList.Append(req);
+ requestMutex.Unlock();
+
+ int s = TEMP_FAILURE_RETRY(write(newreqwrite, "*", 1));
+ if (s == -1)
+ LOG_ERROR_STR("Failed to signal new webvideo request");
+}
+
+cMenuRequest *cWebviThread::GetFinishedRequest() {
+ cMenuRequest *res = NULL;
+ requestMutex.Lock();
+ if (finishedRequestList.Size() > 0) {
+ res = finishedRequestList[finishedRequestList.Size()-1];
+ finishedRequestList.Remove(finishedRequestList.Size()-1);
+ }
+ requestMutex.Unlock();
+
+ return res;
+}
+
+int cWebviThread::GetUnfinishedCount() {
+ if (!Running())
+ return 0;
+ else
+ return activeRequestList.Size();
+}
diff --git a/src/vdr-plugin/download.h b/src/vdr-plugin/download.h
new file mode 100644
index 0000000..14a5c66
--- /dev/null
+++ b/src/vdr-plugin/download.h
@@ -0,0 +1,63 @@
+/*
+ * download.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_DOWNLOAD_H
+#define __WEBVIDEO_DOWNLOAD_H
+
+#include <sys/time.h>
+#include <vdr/thread.h>
+#include <libwebvi.h>
+#include "request.h"
+
+// --- cWebviThread --------------------------------------------------------
+
+class cWebviThread : public cThread {
+private:
+ WebviCtx webvi;
+ cMutex requestMutex;
+ cRequestVector activeRequestList;
+ cRequestVector newRequestList;
+ cRequestVector finishedRequestList;
+ int newreqread, newreqwrite;
+ bool timerActive;
+ struct timeval timer;
+
+ void MoveToFinishedList(cMenuRequest *req);
+ void ActivateNewRequest();
+ void StopFinishedRequests();
+
+protected:
+ void Action(void);
+ static void UpdateTimeout(long timeout, void *data);
+
+public:
+ cWebviThread();
+ ~cWebviThread();
+
+ static cWebviThread &Instance();
+
+ // Stop the thread
+ void Stop();
+ // Set path to the site templates. Should be set before
+ // Start()ing the thread.
+ void SetTemplatePath(const char *path);
+ // Start executing req. The control of req is handed over to the
+ // downloader thread. The main thread should not access req until
+ // the request is handed back to the main thread by
+ // GetFinishedRequest().
+ void AddRequest(cMenuRequest *req);
+ // Return a request that has finished or NULL if no requests are
+ // finished. The ownership of the returned cMenuRequest object
+ // is again assigned to the main thread. The main thread should poll
+ // this function periodically.
+ cMenuRequest *GetFinishedRequest();
+ // Returns the number download requests currectly active
+ int GetUnfinishedCount();
+};
+
+#endif
diff --git a/src/vdr-plugin/history.c b/src/vdr-plugin/history.c
new file mode 100644
index 0000000..fe444ca
--- /dev/null
+++ b/src/vdr-plugin/history.c
@@ -0,0 +1,145 @@
+/*
+ * history.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include "history.h"
+#include "menu.h"
+
+// --- cHistoryObject -----------------------------------------------------
+
+cHistoryObject::cHistoryObject(const char *xml, const char *ref, int ID) {
+ osdxml = strdup(xml);
+ reference = strdup(ref);
+ id = ID;
+ selected = 0;
+}
+
+cHistoryObject::~cHistoryObject() {
+ if (osdxml)
+ free(osdxml);
+ if (reference)
+ free(reference);
+
+ for (int i=0; i < editData.Size(); i++)
+ delete editData[i];
+}
+
+cQueryData *cHistoryObject::GetEditItem(const char *controlName) {
+ for (int i=0; i < editData.Size(); i++) {
+ if (strcmp(editData[i]->GetName(), controlName) == 0) {
+ return editData[i];
+ }
+ }
+
+ return NULL;
+}
+
+int cHistoryObject::QuerySize() const {
+ return editData.Size();
+}
+
+char *cHistoryObject::GetQueryFragment(int i, const char *encoding) const {
+ if (i < 0 && i >= editData.Size())
+ return NULL;
+ else
+ return editData[i]->GetQueryFragment(encoding);
+}
+
+cTextFieldData *cHistoryObject::GetTextFieldData(const char *controlName) {
+ cQueryData *edititem = GetEditItem(controlName);
+ cTextFieldData *tfdata = dynamic_cast<cTextFieldData *>(edititem);
+
+ if (!tfdata) {
+ tfdata = new cTextFieldData(controlName, 256);
+ editData.Append(tfdata);
+ }
+
+ return tfdata;
+}
+
+cItemListData *cHistoryObject::GetItemListData(const char *controlName,
+ cStringList &items,
+ cStringList &values) {
+ int n;
+ char **itemtable, **itemvaluetable;
+ cQueryData *edititem = GetEditItem(controlName);
+ cItemListData *ildata = dynamic_cast<cItemListData *>(edititem);
+
+ if (!ildata) {
+ n = min(items.Size(), values.Size());
+ itemtable = (char **)malloc(n*sizeof(char *));
+ itemvaluetable = (char **)malloc(n*sizeof(char *));
+
+ for (int i=0; i<n; i++) {
+ itemtable[i] = strdup(csc.Convert(items[i]));
+ itemvaluetable[i] = strdup(values[i]);
+ }
+
+ ildata = new cItemListData(controlName,
+ itemtable,
+ itemvaluetable,
+ n);
+
+ editData.Append(ildata);
+ }
+
+ return ildata;
+}
+
+// --- cHistory ------------------------------------------------------------
+
+cHistory::cHistory() {
+ current = NULL;
+}
+
+void cHistory::Clear() {
+ current = NULL;
+ cList<cHistoryObject>::Clear();
+}
+
+void cHistory::TruncateAndAdd(cHistoryObject *page) {
+ cHistoryObject *last = Last();
+ while ((last) && (last != current)) {
+ Del(last);
+ last = Last();
+ }
+
+ Add(page);
+ current = Last();
+}
+
+void cHistory::Reset() {
+ current = NULL;
+}
+
+cHistoryObject *cHistory::Current() {
+ return current;
+}
+
+cHistoryObject *cHistory::Home() {
+ current = First();
+ return current;
+}
+
+cHistoryObject *cHistory::Back() {
+ if (current)
+ current = Prev(current);
+ return current;
+}
+
+cHistoryObject *cHistory::Forward() {
+ cHistoryObject *next;
+ if (current) {
+ next = Next(current);
+ if (next)
+ current = next;
+ } else {
+ current = First();
+ }
+ return current;
+}
diff --git a/src/vdr-plugin/history.h b/src/vdr-plugin/history.h
new file mode 100644
index 0000000..a999098
--- /dev/null
+++ b/src/vdr-plugin/history.h
@@ -0,0 +1,62 @@
+/*
+ * history.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_HISTORY_H
+#define __WEBVIDEO_HISTORY_H
+
+#include <vdr/tools.h>
+#include "menudata.h"
+
+// --- cHistoryObject -----------------------------------------------------
+
+class cHistoryObject : public cListObject {
+private:
+ char *osdxml;
+ int id;
+ int selected;
+ cVector<cQueryData *> editData;
+ char *reference;
+
+ cQueryData *GetEditItem(const char *controlName);
+
+public:
+ cHistoryObject(const char *xml, const char *reference, int ID);
+ ~cHistoryObject();
+
+ int GetID() const { return id; }
+ const char *GetOSD() const { return osdxml; }
+ const char *GetReference() const { return reference; }
+ void RememberSelected(int sel) { selected = sel; }
+ int GetSelected() const { return selected; }
+
+ int QuerySize() const;
+ char *GetQueryFragment(int i, const char *encoding) const;
+ cTextFieldData *GetTextFieldData(const char *controlName);
+ cItemListData *GetItemListData(const char *controlName,
+ cStringList &items,
+ cStringList &itemvalues);
+};
+
+// --- cHistory ------------------------------------------------------------
+
+class cHistory : public cList<cHistoryObject> {
+private:
+ cHistoryObject *current;
+public:
+ cHistory();
+
+ void Clear();
+ void TruncateAndAdd(cHistoryObject *page);
+ void Reset();
+ cHistoryObject *Current();
+ cHistoryObject *Home();
+ cHistoryObject *Back();
+ cHistoryObject *Forward();
+};
+
+#endif // __WEBVIDEO_HISTORY_H
diff --git a/src/vdr-plugin/iniparser.c b/src/vdr-plugin/iniparser.c
new file mode 100644
index 0000000..3990e74
--- /dev/null
+++ b/src/vdr-plugin/iniparser.c
@@ -0,0 +1,650 @@
+/*
+ Copyright (c) 2000-2007 by Nicolas Devillard.
+ MIT License, see COPYING for more information.
+*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.c
+ @author N. Devillard
+ @date Sep 2007
+ @version 3.0
+ @brief Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+/*
+ $Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp $
+ $Revision: 2.18 $
+ $Date: 2008-01-03 18:35:39 $
+*/
+/*---------------------------- Includes ------------------------------------*/
+#include <ctype.h>
+#include "iniparser.h"
+
+/*---------------------------- Defines -------------------------------------*/
+#define ASCIILINESZ (1024)
+#define INI_INVALID_KEY ((char*)-1)
+
+/*---------------------------------------------------------------------------
+ Private to this module
+ ---------------------------------------------------------------------------*/
+/**
+ * This enum stores the status for each parsed line (internal use only).
+ */
+typedef enum _line_status_ {
+ LINE_UNPROCESSED,
+ LINE_ERROR,
+ LINE_EMPTY,
+ LINE_COMMENT,
+ LINE_SECTION,
+ LINE_VALUE
+} line_status ;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Convert a string to lowercase.
+ @param s String to convert.
+ @return ptr to statically allocated string.
+
+ This function returns a pointer to a statically allocated string
+ containing a lowercased version of the input string. Do not free
+ or modify the returned string! Since the returned string is statically
+ allocated, it will be modified at each function call (not re-entrant).
+ */
+/*--------------------------------------------------------------------------*/
+static char * strlwc(const char * s)
+{
+ static char l[ASCIILINESZ+1];
+ int i ;
+
+ if (s==NULL) return NULL ;
+ memset(l, 0, ASCIILINESZ+1);
+ i=0 ;
+ while (s[i] && i<ASCIILINESZ) {
+ l[i] = (char)tolower((int)s[i]);
+ i++ ;
+ }
+ l[ASCIILINESZ]=(char)0;
+ return l ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Remove blanks at the beginning and the end of a string.
+ @param s String to parse.
+ @return ptr to statically allocated string.
+
+ This function returns a pointer to a statically allocated string,
+ which is identical to the input string, except that all blank
+ characters at the end and the beg. of the string have been removed.
+ Do not free or modify the returned string! Since the returned string
+ is statically allocated, it will be modified at each function call
+ (not re-entrant).
+ */
+/*--------------------------------------------------------------------------*/
+static char * strstrip(char * s)
+{
+ static char l[ASCIILINESZ+1];
+ char * last ;
+
+ if (s==NULL) return NULL ;
+
+ while (isspace((int)*s) && *s) s++;
+ memset(l, 0, ASCIILINESZ+1);
+ strcpy(l, s);
+ last = l + strlen(l);
+ while (last > l) {
+ if (!isspace((int)*(last-1)))
+ break ;
+ last -- ;
+ }
+ *last = (char)0;
+ return (char*)l ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getnsec(dictionary * d)
+{
+ int i ;
+ int nsec ;
+
+ if (d==NULL) return -1 ;
+ nsec=0 ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (strchr(d->key[i], ':')==NULL) {
+ nsec ++ ;
+ }
+ }
+ return nsec ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getsecname(dictionary * d, int n)
+{
+ int i ;
+ int foundsec ;
+
+ if (d==NULL || n<0) return NULL ;
+ foundsec=0 ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (strchr(d->key[i], ':')==NULL) {
+ foundsec++ ;
+ if (foundsec>n)
+ break ;
+ }
+ }
+ if (foundsec<=n) {
+ return NULL ;
+ }
+ return d->key[i] ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(dictionary * d, FILE * f)
+{
+ int i ;
+
+ if (d==NULL || f==NULL) return ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (d->val[i]!=NULL) {
+ fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
+ } else {
+ fprintf(f, "[%s]=UNDEF\n", d->key[i]);
+ }
+ }
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump_ini(dictionary * d, FILE * f)
+{
+ int i, j ;
+ char keym[ASCIILINESZ+1];
+ int nsec ;
+ char * secname ;
+ int seclen ;
+
+ if (d==NULL || f==NULL) return ;
+
+ nsec = iniparser_getnsec(d);
+ if (nsec<1) {
+ /* No section in file: dump all keys as they are */
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
+ }
+ return ;
+ }
+ for (i=0 ; i<nsec ; i++) {
+ secname = iniparser_getsecname(d, i) ;
+ seclen = (int)strlen(secname);
+ fprintf(f, "\n[%s]\n", secname);
+ sprintf(keym, "%s:", secname);
+ for (j=0 ; j<d->size ; j++) {
+ if (d->key[j]==NULL)
+ continue ;
+ if (!strncmp(d->key[j], keym, seclen+1)) {
+ fprintf(f,
+ "%-30s = %s\n",
+ d->key[j]+seclen+1,
+ d->val[j] ? d->val[j] : "");
+ }
+ }
+ }
+ fprintf(f, "\n");
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getstring(dictionary * d, const char * key, char * def)
+{
+ char * lc_key ;
+ char * sval ;
+
+ if (d==NULL || key==NULL)
+ return def ;
+
+ lc_key = strlwc(key);
+ sval = dictionary_get(d, lc_key, def);
+ return sval ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ "42" -> 42
+ "042" -> 34 (octal -> decimal)
+ "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(dictionary * d, const char * key, int notfound)
+{
+ char * str ;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str==INI_INVALID_KEY) return notfound ;
+ return (int)strtol(str, NULL, 0);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(dictionary * d, char * key, double notfound)
+{
+ char * str ;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str==INI_INVALID_KEY) return notfound ;
+ return atof(str);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(dictionary * d, const char * key, int notfound)
+{
+ char * c ;
+ int ret ;
+
+ c = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (c==INI_INVALID_KEY) return notfound ;
+ if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
+ ret = 1 ;
+ } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
+ ret = 0 ;
+ } else {
+ ret = notfound ;
+ }
+ return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(
+ dictionary * ini,
+ char * entry
+)
+{
+ int found=0 ;
+ if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
+ found = 1 ;
+ }
+ return found ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, -1 is returned.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary * ini, char * entry, char * val)
+{
+ return dictionary_set(ini, strlwc(entry), val) ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, char * entry)
+{
+ dictionary_unset(ini, strlwc(entry));
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Load a single line from an INI file
+ @param input_line Input line, may be concatenated multi-line input
+ @param section Output space to store section
+ @param key Output space to store key
+ @param value Output space to store value
+ @return line_status value
+ */
+/*--------------------------------------------------------------------------*/
+static line_status iniparser_line(
+ char * input_line,
+ char * section,
+ char * key,
+ char * value)
+{
+ line_status sta ;
+ char line[ASCIILINESZ+1];
+ int len ;
+
+ strcpy(line, strstrip(input_line));
+ len = (int)strlen(line);
+
+ sta = LINE_UNPROCESSED ;
+ if (len<1) {
+ /* Empty line */
+ sta = LINE_EMPTY ;
+ } else if (line[0]=='#') {
+ /* Comment line */
+ sta = LINE_COMMENT ;
+ } else if (line[0]=='[' && line[len-1]==']') {
+ /* Section name */
+ sscanf(line, "[%[^]]", section);
+ strcpy(section, strstrip(section));
+ strcpy(section, strlwc(section));
+ sta = LINE_SECTION ;
+ } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
+ || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
+ || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
+ /* Usual key=value, with or without comments */
+ strcpy(key, strstrip(key));
+ strcpy(key, strlwc(key));
+ strcpy(value, strstrip(value));
+ /*
+ * sscanf cannot handle '' or "" as empty values
+ * this is done here
+ */
+ if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
+ value[0]=0 ;
+ }
+ sta = LINE_VALUE ;
+ } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
+ || sscanf(line, "%[^=] %[=]", key, value) == 2) {
+ /*
+ * Special cases:
+ * key=
+ * key=;
+ * key=#
+ */
+ strcpy(key, strstrip(key));
+ strcpy(key, strlwc(key));
+ value[0]=0 ;
+ sta = LINE_VALUE ;
+ } else {
+ /* Generate syntax error */
+ sta = LINE_ERROR ;
+ }
+ return sta ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame)
+{
+ FILE * in ;
+
+ char line [ASCIILINESZ+1] ;
+ char section [ASCIILINESZ+1] ;
+ char key [ASCIILINESZ+1] ;
+ char tmp [ASCIILINESZ+1] ;
+ char val [ASCIILINESZ+1] ;
+
+ int last=0 ;
+ int len ;
+ int lineno=0 ;
+ int errs=0;
+
+ dictionary * dict ;
+
+ if ((in=fopen(ininame, "r"))==NULL) {
+ fprintf(stderr, "iniparser: cannot open %s\n", ininame);
+ return NULL ;
+ }
+
+ dict = dictionary_new(0) ;
+ if (!dict) {
+ fclose(in);
+ return NULL ;
+ }
+
+ memset(line, 0, ASCIILINESZ);
+ memset(section, 0, ASCIILINESZ);
+ memset(key, 0, ASCIILINESZ);
+ memset(val, 0, ASCIILINESZ);
+ last=0 ;
+
+ while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
+ lineno++ ;
+ len = (int)strlen(line)-1;
+ /* Safety check against buffer overflows */
+ if (line[len]!='\n') {
+ fprintf(stderr,
+ "iniparser: input line too long in %s (%d)\n",
+ ininame,
+ lineno);
+ dictionary_del(dict);
+ fclose(in);
+ return NULL ;
+ }
+ /* Get rid of \n and spaces at end of line */
+ while ((len>=0) &&
+ ((line[len]=='\n') || (isspace(line[len])))) {
+ line[len]=0 ;
+ len-- ;
+ }
+ /* Detect multi-line */
+ if (line[len]=='\\') {
+ /* Multi-line value */
+ last=len ;
+ continue ;
+ } else {
+ last=0 ;
+ }
+ switch (iniparser_line(line, section, key, val)) {
+ case LINE_EMPTY:
+ case LINE_COMMENT:
+ break ;
+
+ case LINE_SECTION:
+ errs = dictionary_set(dict, section, NULL);
+ break ;
+
+ case LINE_VALUE:
+ sprintf(tmp, "%s:%s", section, key);
+ errs = dictionary_set(dict, tmp, val) ;
+ break ;
+
+ case LINE_ERROR:
+ fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
+ ininame,
+ lineno);
+ fprintf(stderr, "-> %s\n", line);
+ errs++ ;
+ break;
+
+ default:
+ break ;
+ }
+ memset(line, 0, ASCIILINESZ);
+ last=0;
+ if (errs<0) {
+ fprintf(stderr, "iniparser: memory allocation failure\n");
+ break ;
+ }
+ }
+ if (errs) {
+ dictionary_del(dict);
+ dict = NULL ;
+ }
+ fclose(in);
+ return dict ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d)
+{
+ dictionary_del(d);
+}
+
+/* vim: set ts=4 et sw=4 tw=75 */
diff --git a/src/vdr-plugin/iniparser.h b/src/vdr-plugin/iniparser.h
new file mode 100644
index 0000000..78bf339
--- /dev/null
+++ b/src/vdr-plugin/iniparser.h
@@ -0,0 +1,284 @@
+/*
+ Copyright (c) 2000-2007 by Nicolas Devillard.
+ MIT License, see COPYING for more information.
+*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.h
+ @author N. Devillard
+ @date Sep 2007
+ @version 3.0
+ @brief Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+
+/*
+ $Id: iniparser.h,v 1.24 2007-11-23 21:38:19 ndevilla Exp $
+ $Revision: 1.24 $
+*/
+
+#ifndef _INIPARSER_H_
+#define _INIPARSER_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The following #include is necessary on many Unixes but not Linux.
+ * It is not needed for Windows platforms.
+ * Uncomment it if needed.
+ */
+/* #include <unistd.h> */
+
+#include "dictionary.h"
+
+/*---------------------------------------------------------------------------
+ Macros
+ ---------------------------------------------------------------------------*/
+/** For backwards compatibility only */
+#define iniparser_getstr(d, k) iniparser_getstring(d, k, NULL)
+#define iniparser_setstr iniparser_setstring
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+int iniparser_getnsec(dictionary * d);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+char * iniparser_getsecname(dictionary * d, int n);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dump_ini(dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getstring(dictionary * d, const char * key, char * def);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ - "42" -> 42
+ - "042" -> 34 (octal -> decimal)
+ - "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(dictionary * d, const char * key, int notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(dictionary * d, char * key, double notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(dictionary * d, const char * key, int notfound);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, -1 is returned.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_setstring(dictionary * ini, char * entry, char * val);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, char * entry);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(dictionary * ini, char * entry) ;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d);
+
+#endif
diff --git a/src/vdr-plugin/menu.c b/src/vdr-plugin/menu.c
new file mode 100644
index 0000000..d03c61d
--- /dev/null
+++ b/src/vdr-plugin/menu.c
@@ -0,0 +1,680 @@
+/*
+ * menu.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <time.h>
+#include <vdr/skins.h>
+#include <vdr/tools.h>
+#include <vdr/i18n.h>
+#include <vdr/osdbase.h>
+#include <vdr/skins.h>
+#include <vdr/font.h>
+#include <vdr/osd.h>
+#include <vdr/interface.h>
+#include "menu.h"
+#include "download.h"
+#include "config.h"
+#include "common.h"
+#include "history.h"
+#include "timer.h"
+#include "menu_timer.h"
+
+cCharSetConv csc = cCharSetConv("UTF-8", cCharSetConv::SystemCharacterTable());
+struct MenuPointers menuPointers;
+
+// --- cXMLMenu --------------------------------------------------
+
+cXMLMenu::cXMLMenu(const char *Title, int c0, int c1, int c2,
+ int c3, int c4)
+: cOsdMenu(Title, c0, c1, c2, c3, c4)
+{
+}
+
+bool cXMLMenu::Deserialize(const char *xml) {
+ xmlDocPtr doc = xmlParseMemory(xml, strlen(xml));
+ if (!doc) {
+ xmlErrorPtr xmlerr = xmlGetLastError();
+ if (xmlerr) {
+ error("libxml error: %s", xmlerr->message);
+ }
+
+ return false;
+ }
+
+ xmlNodePtr node = xmlDocGetRootElement(doc);
+ if (node)
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ if (node->type == XML_ELEMENT_NODE) {
+ if (!CreateItemFromTag(doc, node)) {
+ warning("Failed to parse menu tag: %s", (char *)node->name);
+ }
+ }
+ node = node->next;
+ }
+
+ xmlFreeDoc(doc);
+ return true;
+}
+
+int cXMLMenu::Load(const char *xmlstr) {
+ Clear();
+ Deserialize(xmlstr);
+
+ return 0;
+}
+
+
+// --- cNavigationMenu -----------------------------------------------------
+
+cNavigationMenu::cNavigationMenu(cHistory *History,
+ cProgressVector& dlsummaries)
+ : cXMLMenu("", 25), summaries(dlsummaries)
+{
+ title = NULL;
+ reference = NULL;
+ shortcutMode = 0;
+ history = History;
+ UpdateHelp();
+}
+
+cNavigationMenu::~cNavigationMenu() {
+ menuPointers.navigationMenu = NULL;
+ Clear();
+ if (reference)
+ free(reference);
+}
+
+bool cNavigationMenu::CreateItemFromTag(xmlDocPtr doc, xmlNodePtr node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "link")) {
+ NewLinkItem(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "textfield")) {
+ NewTextField(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "itemlist")) {
+ NewItemList(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "textarea")) {
+ NewTextArea(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "button")) {
+ NewButton(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "title")) {
+ NewTitle(doc, node);
+ return true;
+ }
+
+ return false;
+}
+
+void cNavigationMenu::AddLinkItem(cOsdItem *item,
+ cLinkBase *ref,
+ cLinkBase *streamref) {
+ Add(item);
+
+ if (ref)
+ links.Append(ref);
+ else
+ links.Append(NULL);
+
+ if (streamref)
+ streams.Append(streamref);
+ else
+ streams.Append(NULL);
+}
+
+void cNavigationMenu::NewLinkItem(xmlDocPtr doc, xmlNodePtr node) {
+ // label, ref and object tags
+ xmlChar *itemtitle = NULL, *ref = NULL, *streamref = NULL;
+
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "ref")) {
+ if (ref)
+ xmlFree(ref);
+ ref = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "stream")) {
+ if (streamref)
+ xmlFree(streamref);
+ streamref = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ itemtitle = xmlCharStrdup("???");
+
+ const char *titleconv = csc.Convert((char *)itemtitle);
+ cOsdItem *item = new cOsdItem(titleconv);
+ cSimpleLink *objlinkdata = NULL;
+ cSimpleLink *linkdata = NULL;
+ if (ref)
+ linkdata = new cSimpleLink((char *)ref);
+ if (streamref) {
+ // media object
+ objlinkdata = new cSimpleLink((char *)streamref);
+ } else {
+ // navigation link
+ char *bracketed = (char *)malloc((strlen(titleconv)+3)*sizeof(char));
+ if (bracketed) {
+ bracketed[0] = '\0';
+ strcat(bracketed, "[");
+ strcat(bracketed, titleconv);
+ strcat(bracketed, "]");
+ item->SetText(bracketed, false);
+ }
+ }
+ AddLinkItem(item, linkdata, objlinkdata);
+
+ xmlFree(itemtitle);
+ if (ref)
+ xmlFree(ref);
+ if (streamref)
+ xmlFree(streamref);
+}
+
+void cNavigationMenu::NewTextField(xmlDocPtr doc, xmlNodePtr node) {
+ // name attribute
+ xmlChar *name = xmlGetProp(node, BAD_CAST "name");
+ cHistoryObject *curhistpage = history->Current();
+
+ // label tag
+ xmlChar *text = NULL;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (text)
+ xmlFree(text);
+ text = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!text)
+ text = xmlCharStrdup("???");
+
+ cTextFieldData *data = curhistpage->GetTextFieldData((char *)name);
+ cMenuEditStrItem *item = new cMenuEditStrItem(csc.Convert((char *)text),
+ data->GetValue(),
+ data->GetLength());
+ AddLinkItem(item, NULL, NULL);
+
+ free(text);
+ if (name)
+ xmlFree(name);
+}
+
+void cNavigationMenu::NewItemList(xmlDocPtr doc, xmlNodePtr node) {
+ // name attribute
+ xmlChar *name = xmlGetProp(node, BAD_CAST "name");
+ cHistoryObject *curhistpage = history->Current();
+
+ // label and item tags
+ xmlChar *text = NULL;
+ cStringList items;
+ cStringList itemvalues;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (text)
+ xmlFree(text);
+ text = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "item")) {
+ xmlChar *str = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (!str)
+ str = xmlCharStrdup("???");
+ xmlChar *strvalue = xmlGetProp(node, BAD_CAST "value");
+ if (!strvalue)
+ strvalue = xmlCharStrdup("");
+
+ items.Append(strdup((char *)str));
+ itemvalues.Append(strdup((char *)strvalue));
+
+ xmlFree(str);
+ xmlFree(strvalue);
+ }
+ node = node->next;
+ }
+ if (!text)
+ text = xmlCharStrdup("???");
+
+ cItemListData *data = curhistpage->GetItemListData((const char *)name,
+ items,
+ itemvalues);
+
+ cMenuEditStraItem *item = new cMenuEditStraItem(csc.Convert((char *)text),
+ data->GetValuePtr(),
+ data->GetNumStrings(),
+ data->GetStrings());
+ AddLinkItem(item, NULL, NULL);
+
+ xmlFree(text);
+ if (name)
+ xmlFree(name);
+}
+
+void cNavigationMenu::NewTextArea(xmlDocPtr doc, xmlNodePtr node) {
+ // label tag
+ xmlChar *itemtitle = NULL;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ return;
+
+ const cFont *font = cFont::GetFont(fontOsd);
+ cTextWrapper tw(csc.Convert((char *)itemtitle), font, cOsd::OsdWidth());
+ for (int i=0; i < tw.Lines(); i++) {
+ AddLinkItem(new cOsdItem(tw.GetLine(i), osUnknown, false), NULL, NULL);
+ }
+
+ xmlFree(itemtitle);
+}
+
+void cNavigationMenu::NewButton(xmlDocPtr doc, xmlNodePtr node) {
+ // label and submission tags
+ xmlChar *itemtitle = NULL, *submission = NULL;
+ cHistoryObject *curhistpage = history->Current();
+ xmlChar *encoding = NULL;
+
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "submission")) {
+ if (submission)
+ xmlFree(submission);
+ submission = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+
+ xmlChar *enc = xmlGetProp(node, BAD_CAST "encoding");
+ if (enc) {
+ if (encoding)
+ xmlFree(encoding);
+ encoding = enc;
+ }
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ itemtitle = xmlCharStrdup("???");
+
+ cSubmissionButtonData *data = \
+ new cSubmissionButtonData((char *)submission, curhistpage,
+ (char *)encoding);
+ const char *titleconv = csc.Convert((char *)itemtitle); // do not free
+ char *newtitle = (char *)malloc((strlen(titleconv)+3)*sizeof(char));
+ if (newtitle) {
+ newtitle[0] = '\0';
+ strcat(newtitle, "[");
+ strcat(newtitle, titleconv);
+ strcat(newtitle, "]");
+
+ cOsdItem *item = new cOsdItem(newtitle);
+ AddLinkItem(item, data, NULL);
+ free(newtitle);
+ }
+
+ xmlFree(itemtitle);
+ if (submission)
+ xmlFree(submission);
+ if (encoding)
+ xmlFree(encoding);
+}
+
+void cNavigationMenu::NewTitle(xmlDocPtr doc, xmlNodePtr node) {
+ xmlChar *newtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (newtitle) {
+ const char *conv = csc.Convert((char *)newtitle);
+ SetTitle(conv);
+ if (title)
+ free(title);
+ title = strdup(conv);
+ xmlFree(newtitle);
+ }
+}
+
+eOSState cNavigationMenu::ProcessKey(eKeys Key)
+{
+ cWebviTimer *timer;
+ bool hasStreams;
+ int old = Current();
+ eOSState state = cXMLMenu::ProcessKey(Key);
+ bool validItem = Current() >= 0 && Current() < links.Size();
+
+ if (HasSubMenu())
+ return state;
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kInfo:
+ // The alternative link is active only when object links are
+ // present.
+ if (validItem && streams.At(Current()))
+ state = Select(links.At(Current()), LT_REGULAR);
+ break;
+
+ case kOk:
+ // Primary action: download media object or, if not a media
+ // link, follow the navigation link.
+ if (validItem) {
+ if (streams.At(Current()))
+ state = Select(streams.At(Current()), LT_MEDIA);
+ else
+ state = Select(links.At(Current()), LT_REGULAR);
+ }
+ break;
+
+ case kRed:
+ if (shortcutMode == 0) {
+ state = HistoryBack();
+ } else {
+ menuPointers.statusScreen = new cStatusScreen(summaries);
+ state = AddSubMenu(menuPointers.statusScreen);
+ }
+ break;
+
+ case kGreen:
+ if (shortcutMode == 0) {
+ state = HistoryForward();
+ } else {
+ return AddSubMenu(new cWebviTimerListMenu(cWebviTimerManager::Instance()));
+ }
+ break;
+
+ case kYellow:
+ if (shortcutMode == 0) {
+ hasStreams = false;
+ for (int i=0; i < streams.Size(); i++) {
+ if (streams[i]) {
+ hasStreams = true;
+ break;
+ }
+ }
+
+ if (hasStreams || Interface->Confirm(tr("No streams on this page, create timer anyway?"))) {
+ timer = cWebviTimerManager::Instance().Create(title, reference);
+ if (timer)
+ return AddSubMenu(new cEditWebviTimerMenu(*timer, true, false));
+ }
+
+ state = osContinue;
+ }
+ break;
+
+ case kBlue:
+ if (shortcutMode == 0) {
+ // Secondary action: start streaming if a media object
+ if (validItem && streams.At(Current()))
+ state = Select(streams.At(Current()), LT_STREAMINGMEDIA);
+ }
+ break;
+
+ case k0:
+ shortcutMode = shortcutMode == 0 ? 1 : 0;
+ UpdateHelp();
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ // If the key press caused the selected item to change, we need to
+ // update the help texts.
+ //
+ // In cMenuEditStrItem key == kOk with state == osContinue
+ // indicates leaving the edit mode. We want to update the help
+ // texts in this case also.
+ if ((old != Current()) ||
+ ((Key == kOk) && (state == osContinue))) {
+ UpdateHelp();
+ }
+ }
+
+ return state;
+}
+
+eOSState cNavigationMenu::Select(cLinkBase *link, eLinkType type)
+{
+ if (!link) {
+ return osContinue;
+ }
+ char *ref = link->GetURL();
+ if (!ref) {
+ error("link->GetURL() == NULL in cNavigationMenu::Select");
+ return osContinue;
+ }
+
+ if (type == LT_MEDIA) {
+ cDownloadProgress *progress = summaries.NewDownload();
+ cFileDownloadRequest *req = \
+ new cFileDownloadRequest(history->Current()->GetID(), ref,
+ progress);
+ cWebviThread::Instance().AddRequest(req);
+
+ Skins.Message(mtInfo, tr("Downloading in the background"));
+ } else if (type == LT_STREAMINGMEDIA) {
+ cWebviThread::Instance().AddRequest(new cStreamUrlRequest(history->Current()->GetID(),
+ ref));
+ Skins.Message(mtInfo, tr("Starting player..."));
+ return osEnd;
+ } else {
+ cWebviThread::Instance().AddRequest(new cMenuRequest(history->Current()->GetID(),
+ ref));
+ Skins.Message(mtStatus, tr("Retrieving..."));
+ }
+
+ return osContinue;
+}
+
+void cNavigationMenu::Clear(void) {
+ cXMLMenu::Clear();
+ SetTitle("");
+ if (title)
+ free(title);
+ title = NULL;
+ for (int i=0; i < links.Size(); i++) {
+ if (links[i])
+ delete links[i];
+ if (streams[i])
+ delete streams[i];
+ }
+ links.Clear();
+ streams.Clear();
+}
+
+void cNavigationMenu::Populate(const cHistoryObject *page, const char *statusmsg) {
+ Load(page->GetOSD());
+
+ if (reference)
+ free(reference);
+ reference = strdup(page->GetReference());
+
+ // Make sure that an item is selected (if there is at least
+ // one). The help texts are not updated correctly if no item is
+ // selected.
+
+ SetCurrent(Get(page->GetSelected()));
+ UpdateHelp();
+ SetStatus(statusmsg);
+}
+
+eOSState cNavigationMenu::HistoryBack() {
+ cHistoryObject *cur = history->Current();
+
+ if (cur)
+ cur->RememberSelected(Current());
+
+ cHistoryObject *page = history->Back();
+ if (page) {
+ Populate(page);
+ Display();
+ }
+ return osContinue;
+}
+
+eOSState cNavigationMenu::HistoryForward() {
+ cHistoryObject *before = history->Current();
+ cHistoryObject *after = history->Forward();
+
+ if (before)
+ before->RememberSelected(Current());
+
+ // Update only if the menu really changed
+ if (before != after) {
+ Populate(after);
+ Display();
+ }
+ return osContinue;
+}
+
+void cNavigationMenu::UpdateHelp() {
+ const char *red = NULL;
+ const char *green = NULL;
+ const char *yellow = NULL;
+ const char *blue = NULL;
+
+ if (shortcutMode == 0) {
+ red = (history->Current() != history->First()) ? tr("Back") : NULL;
+ green = (history->Current() != history->Last()) ? tr("Forward") : NULL;
+ yellow = (Current() >= 0) ? tr("Create timer") : NULL;
+ blue = ((Current() >= 0) && (streams.At(Current()))) ? tr("Play") : NULL;
+ } else {
+ red = tr("Status");
+ green = tr("Timers");
+ }
+
+ SetHelp(red, green, yellow, blue);
+}
+
+// --- cStatusScreen -------------------------------------------------------
+
+cStatusScreen::cStatusScreen(cProgressVector& dlsummaries)
+ : cOsdMenu(tr("Unfinished downloads"), 40), summaries(dlsummaries)
+{
+ int charsperline = cOsd::OsdWidth() / cFont::GetFont(fontOsd)->Width('M');
+ SetCols(charsperline-5);
+
+ UpdateHelp();
+ Update();
+}
+
+cStatusScreen::~cStatusScreen() {
+ menuPointers.statusScreen = NULL;
+}
+
+void cStatusScreen::Update() {
+ int c = Current();
+
+ Clear();
+
+ if (summaries.Size() == 0) {
+ SetTitle(tr("No active downloads"));
+ } else {
+
+ for (int i=0; i<summaries.Size(); i++) {
+ cString dltitle;
+ cDownloadProgress *s = summaries[i];
+ dltitle = cString::sprintf("%s\t%s",
+ (const char *)s->GetTitle(),
+ (const char *)s->GetPercentage());
+
+ Add(new cOsdItem(dltitle));
+ }
+
+ if (c >= 0)
+ SetCurrent(Get(c));
+ }
+
+ lastupdate = time(NULL);
+
+ UpdateHelp();
+ Display();
+}
+
+bool cStatusScreen::NeedsUpdate() {
+ return (Count() > 0) && (time(NULL) - lastupdate >= updateInterval);
+}
+
+eOSState cStatusScreen::ProcessKey(eKeys Key) {
+ cFileDownloadRequest *req;
+ int old = Current();
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (HasSubMenu())
+ return state;
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kYellow:
+ if ((Current() >= 0) && (Current() < summaries.Size())) {
+ if (summaries[Current()]->IsFinished()) {
+ delete summaries[Current()];
+ summaries.Remove(Current());
+ Update();
+ } else if ((req = summaries[Current()]->GetRequest()) &&
+ !req->IsFinished()) {
+ req->Abort();
+ Update();
+ }
+ }
+ return osContinue;
+
+ case kOk:
+ case kInfo:
+ if (summaries[Current()]->Error()) {
+ cString msg = cString::sprintf("%s\n%s: %s",
+ (const char *)summaries[Current()]->GetTitle(),
+ tr("Error"),
+ (const char *)summaries[Current()]->GetStatusPharse());
+ return AddSubMenu(new cMenuText(tr("Error details"), msg));
+ } else {
+ cString msg = cString::sprintf("%s (%s)",
+ (const char *)summaries[Current()]->GetTitle(),
+ (const char *)summaries[Current()]->GetPercentage());
+ return AddSubMenu(new cMenuText(tr("Download details"), msg));
+ }
+
+ return osContinue;
+
+ default:
+ break;
+ }
+ } else {
+ // Update help if the key press caused the menu item to change.
+ if (old != Current())
+ UpdateHelp();
+ }
+
+ return state;
+}
+
+void cStatusScreen::UpdateHelp() {
+ bool remove = false;
+ if ((Current() >= 0) && (Current() < summaries.Size())) {
+ if (summaries[Current()]->IsFinished()) {
+ remove = true;
+ }
+ }
+
+ const char *yellow = remove ? tr("Remove") : tr("Abort");
+
+ SetHelp(NULL, NULL, yellow, NULL);
+}
diff --git a/src/vdr-plugin/menu.h b/src/vdr-plugin/menu.h
new file mode 100644
index 0000000..b1e67df
--- /dev/null
+++ b/src/vdr-plugin/menu.h
@@ -0,0 +1,114 @@
+/*
+ * menu.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_MENU_H
+#define __WEBVIDEO_MENU_H
+
+#include <time.h>
+#include <vdr/osdbase.h>
+#include <vdr/menuitems.h>
+#include <vdr/menu.h>
+#include <libxml/parser.h>
+#include "download.h"
+#include "menudata.h"
+
+extern cCharSetConv csc;
+
+// --- cXMLMenu --------------------------------------------------
+
+class cXMLMenu : public cOsdMenu {
+protected:
+ virtual bool Deserialize(const char *xml);
+ virtual bool CreateItemFromTag(xmlDocPtr doc, xmlNodePtr node) = 0;
+public:
+ cXMLMenu(const char *Title, int c0 = 0, int c1 = 0,
+ int c2 = 0, int c3 = 0, int c4 = 0);
+
+ int Load(const char *xmlstr);
+};
+
+// --- cNavigationMenu -----------------------------------------------------
+
+enum eLinkType { LT_REGULAR, LT_MEDIA, LT_STREAMINGMEDIA };
+
+class cHistory;
+class cHistoryObject;
+class cStatusScreen;
+
+class cNavigationMenu : public cXMLMenu {
+private:
+ // links[i] is the navigation link of the i:th item
+ cVector<cLinkBase *> links;
+ // streams[i] is the media stream link of the i:th item
+ cVector<cLinkBase *> streams;
+ cProgressVector& summaries;
+ char *title;
+ char *reference;
+ int shortcutMode;
+
+protected:
+ cHistory *history;
+
+ virtual bool CreateItemFromTag(xmlDocPtr doc, xmlNodePtr node);
+ void AddLinkItem(cOsdItem *item, cLinkBase *ref, cLinkBase *streamref);
+ void NewLinkItem(xmlDocPtr doc, xmlNodePtr node);
+ void NewTextField(xmlDocPtr doc, xmlNodePtr node);
+ void NewItemList(xmlDocPtr doc, xmlNodePtr node);
+ void NewTextArea(xmlDocPtr doc, xmlNodePtr node);
+ void NewButton(xmlDocPtr doc, xmlNodePtr node);
+ void NewTitle(xmlDocPtr doc, xmlNodePtr node);
+ void UpdateHelp();
+
+public:
+ cNavigationMenu(cHistory *History, cProgressVector& dlsummaries);
+ virtual ~cNavigationMenu();
+
+ virtual eOSState ProcessKey(eKeys Key);
+ virtual eOSState Select(cLinkBase *link, eLinkType type);
+ virtual void Clear(void);
+ eOSState HistoryBack();
+ eOSState HistoryForward();
+
+ const char *Reference() const { return reference; }
+ void Populate(const cHistoryObject *page, const char *statusmsg=NULL);
+};
+
+// --- cStatusScreen -------------------------------------------------------
+
+class cStatusScreen : public cOsdMenu {
+public:
+ const static time_t updateInterval = 5; // seconds
+private:
+ cProgressVector& summaries;
+ time_t lastupdate;
+
+protected:
+ void UpdateHelp();
+
+public:
+ cStatusScreen(cProgressVector& dlsummaries);
+ ~cStatusScreen();
+
+ void Update();
+ bool NeedsUpdate();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- MenuPointers --------------------------------------------------------
+
+struct MenuPointers {
+ cNavigationMenu *navigationMenu;
+ cStatusScreen *statusScreen;
+
+ MenuPointers() : navigationMenu(NULL), statusScreen(NULL) {};
+};
+
+extern struct MenuPointers menuPointers;
+
+#endif // __WEBVIDEO_MENU_H
diff --git a/src/vdr-plugin/menu_timer.c b/src/vdr-plugin/menu_timer.c
new file mode 100644
index 0000000..6a79f4e
--- /dev/null
+++ b/src/vdr-plugin/menu_timer.c
@@ -0,0 +1,149 @@
+/*
+ * menu.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <time.h>
+#include <vdr/i18n.h>
+#include <vdr/tools.h>
+#include <vdr/menuitems.h>
+#include <vdr/interface.h>
+#include "menu_timer.h"
+
+#define ARRAYSIZE(a) sizeof(a)/sizeof(a[0])
+
+const char *intervalNames[] = {NULL, NULL, NULL};
+const int intervalValues[] = {24*60*60, 7*24*60*60, 30*24*60*60};
+
+// --- cEditWebviTimerMenu -------------------------------------------------
+
+cEditWebviTimerMenu::cEditWebviTimerMenu(cWebviTimer &timer,
+ bool refreshWhenDone,
+ bool execButton)
+ : cOsdMenu(tr("Edit timer"), 20), timer(timer), interval(1),
+ refresh(refreshWhenDone)
+{
+ // title
+ strn0cpy(title, timer.GetTitle(), maxTitleLen);
+ Add(new cMenuEditStrItem(tr("Title"), title, maxTitleLen));
+
+ // interval
+ for (unsigned i=0; i<ARRAYSIZE(intervalValues); i++) {
+ if (timer.GetInterval() == intervalValues[i]) {
+ interval = i;
+ break;
+ }
+ }
+
+ if (!intervalNames[0]) {
+ // Initialize manually to make the translations work
+ intervalNames[0] = tr("Once per day");
+ intervalNames[1] = tr("Once per week");
+ intervalNames[2] = tr("Once per month");
+ }
+
+ Add(new cMenuEditStraItem(tr("Update interval"), &interval,
+ ARRAYSIZE(intervalNames), intervalNames));
+
+ // "execute now" button
+ if (execButton)
+ Add(new cOsdItem(tr("Execute now"), osUser1, true));
+
+ // last update time
+ char lastTime[25];
+ if (timer.LastUpdate() == 0) {
+ // TRANSLATORS: at most 24 chars
+ strcpy(lastTime, tr("Never"));
+ } else {
+ time_t updateTime = timer.LastUpdate();
+ strftime(lastTime, 25, "%x %X", localtime(&updateTime));
+ }
+
+ const char *active ="";
+ if (timer.Running())
+ active = " (active)";
+
+ cString lastUpdated = cString::sprintf("%s\t%s%s", tr("Last fetched:"), lastTime, active);
+ Add(new cOsdItem(lastUpdated, osUnknown, false));
+
+ // error
+ if (!timer.Success()) {
+ Add(new cOsdItem(tr("Error on last refresh!"), osUnknown, false));
+ Add(new cOsdItem(timer.LastError(), osUnknown, false));
+ }
+}
+
+cEditWebviTimerMenu::~cEditWebviTimerMenu() {
+ if (refresh)
+ timer.Execute();
+}
+
+eOSState cEditWebviTimerMenu::ProcessKey(eKeys Key) {
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osContinue) {
+ timer.SetTitle(title);
+ timer.SetInterval(intervalValues[interval]);
+ } else if (state == osUser1) {
+ timer.Execute();
+ Skins.Message(mtInfo, tr("Downloading in the background"));
+ }
+
+ return state;
+}
+
+// --- cWebviTimerListMenu -------------------------------------------------
+
+cWebviTimerListMenu::cWebviTimerListMenu(cWebviTimerManager &timers)
+ : cOsdMenu(tr("Timers")), timers(timers)
+{
+ cWebviTimer *t = timers.First();
+ while (t) {
+ Add(new cOsdItem(t->GetTitle(), osUnknown, true));
+ t = timers.Next(t);
+ }
+
+ SetHelp(NULL, NULL, tr("Remove"), NULL);
+}
+
+eOSState cWebviTimerListMenu::ProcessKey(eKeys Key) {
+ cWebviTimer *t;
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (HasSubMenu())
+ return state;
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk:
+ t = timers.GetLinear(Current());
+ if (t)
+ return AddSubMenu(new cEditWebviTimerMenu(*t));
+ break;
+
+ case kYellow:
+ t = timers.GetLinear(Current());
+ if (t) {
+ if (t->Running()) {
+ // FIXME: ask if the user wants to cancel the downloads
+ Skins.Message(mtInfo, tr("Timer running, can't remove"));
+ } else if (Interface->Confirm(tr("Remove timer?"))) {
+ timers.Remove(t);
+ Del(Current());
+ Display();
+ }
+
+ return osContinue;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return state;
+}
diff --git a/src/vdr-plugin/menu_timer.h b/src/vdr-plugin/menu_timer.h
new file mode 100644
index 0000000..192c062
--- /dev/null
+++ b/src/vdr-plugin/menu_timer.h
@@ -0,0 +1,46 @@
+/*
+ * menu_timer.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_MENU_TIMER_H
+#define __WEBVIDEO_MENU_TIMER_H
+
+#include <vdr/osdbase.h>
+#include "timer.h"
+
+// --- cEditWebviTimerMenu -------------------------------------------------
+
+class cEditWebviTimerMenu : public cOsdMenu {
+private:
+ static const int maxTitleLen = 128;
+
+ cWebviTimer &timer;
+ char title[maxTitleLen];
+ int interval;
+ bool refresh;
+
+public:
+ cEditWebviTimerMenu(cWebviTimer &timer, bool refreshWhenDone=false,
+ bool execButton=true);
+ ~cEditWebviTimerMenu();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- cWebviTimerListMenu -------------------------------------------------
+
+class cWebviTimerListMenu : public cOsdMenu {
+private:
+ cWebviTimerManager& timers;
+
+public:
+ cWebviTimerListMenu(cWebviTimerManager &timers);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+#endif
diff --git a/src/vdr-plugin/menudata.c b/src/vdr-plugin/menudata.c
new file mode 100644
index 0000000..6fc899c
--- /dev/null
+++ b/src/vdr-plugin/menudata.c
@@ -0,0 +1,200 @@
+/*
+ * menudata.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <vdr/tools.h>
+#include "menudata.h"
+#include "common.h"
+#include "history.h"
+
+// --- cQueryData ----------------------------------------------------------
+
+cQueryData::cQueryData(const char *Name) {
+ name = Name ? strdup(Name) : NULL;
+}
+
+cQueryData::~cQueryData() {
+ if (name)
+ free(name);
+}
+
+// --- cSimpleLink ---------------------------------------------------------
+
+cSimpleLink::cSimpleLink(const char *reference) {
+ ref = reference ? strdup(reference) : NULL;
+}
+
+cSimpleLink::~cSimpleLink() {
+ if (ref) {
+ free(ref);
+ }
+}
+
+char *cSimpleLink::GetURL() {
+ return ref;
+}
+
+// --- cTextFieldData ------------------------------------------------------
+
+cTextFieldData::cTextFieldData(const char *Name, int Length)
+: cQueryData(Name)
+{
+ valuebufferlength = Length;
+ valuebuffer = (char *)malloc(Length*sizeof(char));
+ *valuebuffer = '\0';
+}
+
+cTextFieldData::~cTextFieldData() {
+ if(valuebuffer)
+ free(valuebuffer);
+}
+
+char *cTextFieldData::GetQueryFragment(const char *encoding) {
+ const char *name = GetName();
+ char *val;
+
+ if (name && *name && valuebuffer) {
+ if (encoding) {
+ cCharSetConv charsetconv = cCharSetConv("UTF-8", encoding);
+ val = URLencode(charsetconv.Convert(valuebuffer));
+ } else {
+ val = URLencode(valuebuffer);
+ }
+
+ cString tmp = cString::sprintf("%s,%s", name, val);
+ free(val);
+ return strdup(tmp);
+ }
+
+ return NULL;
+}
+
+char *cTextFieldData::GetValue() {
+ return valuebuffer;
+}
+
+int cTextFieldData::GetLength() {
+ return valuebufferlength;
+}
+
+// --- cItemListData -------------------------------------------------------
+
+cItemListData::cItemListData(const char *Name, char **Strings, char **StringValues, int NumStrings)
+: cQueryData(Name)
+{
+ strings = Strings;
+ stringvalues = StringValues;
+ numstrings = NumStrings;
+ value = 0;
+}
+
+cItemListData::~cItemListData() {
+ for (int i=0; i < numstrings; i++) {
+ free(strings[i]);
+ free(stringvalues[i]);
+ }
+ if (strings)
+ free(strings);
+ if (stringvalues)
+ free(stringvalues);
+}
+
+char *cItemListData::GetQueryFragment(const char *encoding) {
+ const char *name = GetName();
+ char *val;
+
+ if (name && *name) {
+ if (encoding) {
+ cCharSetConv charsetconv = cCharSetConv("UTF-8", encoding);
+ val = URLencode(charsetconv.Convert(stringvalues[value]));
+ } else {
+ val = URLencode(stringvalues[value]);
+ }
+
+ cString tmp = cString::sprintf("%s,%s", name, val);
+ free(val);
+ return strdup(tmp);
+ }
+
+ return NULL;
+}
+
+char **cItemListData::GetStrings() {
+ return strings;
+}
+
+char **cItemListData::GetStringValues() {
+ return stringvalues;
+}
+
+int cItemListData::GetNumStrings() {
+ return numstrings;
+}
+
+int *cItemListData::GetValuePtr() {
+ return &value;
+}
+
+// --- cSubmissionButtonData -----------------------------------------------
+
+cSubmissionButtonData::cSubmissionButtonData(
+ const char *queryUrl, const cHistoryObject *currentPage,
+ const char *enc)
+{
+ querybase = queryUrl ? strdup(queryUrl) : NULL;
+ page = currentPage;
+ encoding = enc ? strdup(enc) : NULL;
+}
+
+cSubmissionButtonData::~cSubmissionButtonData() {
+ if (querybase)
+ free(querybase);
+ if (encoding)
+ free(encoding);
+ // do not free page
+}
+
+char *cSubmissionButtonData::GetURL() {
+ if (!querybase)
+ return NULL;
+
+ char *querystr = (char *)malloc(sizeof(char)*(strlen(querybase)+2));
+ strcpy(querystr, querybase);
+
+ if (!page)
+ return querystr;
+
+ if (strchr(querystr, '?'))
+ strcat(querystr, "&");
+ else
+ strcat(querystr, "?");
+
+ int numparameters = 0;
+ for (int i=0; i<page->QuerySize(); i++) {
+ char *parameter = page->GetQueryFragment(i, encoding);
+ if (parameter) {
+ size_t len = strlen(querystr) + strlen(parameter) + 8;
+ querystr = (char *)realloc(querystr, len*sizeof(char));
+ if (i > 0)
+ strcat(querystr, "&");
+ strcat(querystr, "subst=");
+ strcat(querystr, parameter);
+ numparameters++;
+
+ free(parameter);
+ }
+ }
+
+ if (numparameters == 0) {
+ // remove the '?' or '&' because no parameters were added to the url
+ querystr[strlen(querystr)-1] = '\0';
+ }
+
+ return querystr;
+}
diff --git a/src/vdr-plugin/menudata.h b/src/vdr-plugin/menudata.h
new file mode 100644
index 0000000..98e5915
--- /dev/null
+++ b/src/vdr-plugin/menudata.h
@@ -0,0 +1,102 @@
+/*
+ * menudata.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_MENUDATA_H
+#define __WEBVIDEO_MENUDATA_H
+
+// --- cLinkBase -----------------------------------------------------------
+
+class cLinkBase {
+public:
+ virtual ~cLinkBase() {}; // avoids "virtual functions but
+ // non-virtual destructor" warning
+
+ virtual char *GetURL() = 0;
+};
+
+// --- cQueryData ----------------------------------------------------------
+
+class cQueryData {
+private:
+ char *name;
+
+public:
+ cQueryData(const char *Name);
+ virtual ~cQueryData();
+
+ const char *GetName() { return name; }
+ virtual char *GetQueryFragment(const char *encoding) = 0;
+};
+
+// --- cSimpleLink ---------------------------------------------------------
+
+class cSimpleLink : public cLinkBase {
+private:
+ char *ref;
+public:
+ cSimpleLink(const char *ref);
+ virtual ~cSimpleLink();
+
+ virtual char *GetURL();
+};
+
+// --- cTextFieldData ------------------------------------------------------
+
+class cTextFieldData : public cQueryData {
+private:
+ char *name;
+ char *valuebuffer;
+ int valuebufferlength;
+public:
+ cTextFieldData(const char *Name, int Length);
+ virtual ~cTextFieldData();
+
+ virtual char *GetQueryFragment(const char *encoding);
+ char *GetValue();
+ int GetLength();
+};
+
+// --- cItemListData -------------------------------------------------------
+
+class cItemListData : public cQueryData {
+private:
+ char *name;
+ int value;
+ int numstrings;
+ char **strings;
+ char **stringvalues;
+public:
+ cItemListData(const char *Name, char **Strings, char **StringValues, int NumStrings);
+ virtual ~cItemListData();
+
+ virtual char *GetQueryFragment(const char *encoding);
+ char **GetStrings();
+ char **GetStringValues();
+ int GetNumStrings();
+ int *GetValuePtr();
+};
+
+// --- cSubmissionButtonData -----------------------------------------------
+
+class cHistoryObject;
+
+class cSubmissionButtonData : public cLinkBase {
+private:
+ char *querybase;
+ const cHistoryObject *page;
+ char *encoding;
+public:
+ cSubmissionButtonData(const char *queryUrl,
+ const cHistoryObject *currentPage,
+ const char *encoding);
+ virtual ~cSubmissionButtonData();
+
+ virtual char *GetURL();
+};
+
+#endif
diff --git a/src/vdr-plugin/mime.types b/src/vdr-plugin/mime.types
new file mode 100644
index 0000000..beefdc3
--- /dev/null
+++ b/src/vdr-plugin/mime.types
@@ -0,0 +1,4 @@
+# Some non-standard, but common, MIME types
+
+video/flv flv
+video/x-flv flv
diff --git a/src/vdr-plugin/mimetypes.c b/src/vdr-plugin/mimetypes.c
new file mode 100644
index 0000000..17c29e6
--- /dev/null
+++ b/src/vdr-plugin/mimetypes.c
@@ -0,0 +1,98 @@
+/*
+ * mimetypes.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <vdr/tools.h>
+#include "mimetypes.h"
+#include "common.h"
+
+// --- cMimeListObject -----------------------------------------------------
+
+cMimeListObject::cMimeListObject(const char *mimetype, const char *extension) {
+ type = strdup(mimetype);
+ ext = strdup(extension);
+}
+
+cMimeListObject::~cMimeListObject() {
+ free(type);
+ free(ext);
+}
+
+// --- cMimeTypes ----------------------------------------------------------
+
+cMimeTypes::cMimeTypes(const char **mimetypefiles) {
+ for (const char **filename=mimetypefiles; *filename; filename++) {
+ FILE *f = fopen(*filename, "r");
+ if (!f) {
+ LOG_ERROR_STR((const char *)cString::sprintf("failed to open mime type file %s", *filename));
+ continue;
+ }
+
+ cReadLine rl;
+ char *line = rl.Read(f);
+ while (line) {
+ // Comment lines starting with '#' and empty lines are skipped
+ // Expected format for the lines:
+ // mime/type ext
+ if (*line && (*line != '#')) {
+ char *ptr = line;
+ while ((*ptr != '\0') && (!isspace(*ptr)))
+ ptr++;
+
+ if (ptr == line) {
+ // empty line, ignore
+ line = rl.Read(f);
+ continue;
+ }
+
+ char *mimetype = (char *)malloc(ptr-line+1);
+ strncpy(mimetype, line, ptr-line);
+ mimetype[ptr-line] = '\0';
+
+ while (*ptr && isspace(*ptr))
+ ptr++;
+ char *eptr = ptr;
+ while (*ptr && !isspace(*ptr))
+ ptr++;
+
+ if (ptr == eptr) {
+ // no extension, ignore
+ free(mimetype);
+ line = rl.Read(f);
+ continue;
+ }
+
+ char *extension = (char *)malloc(ptr-eptr+1);
+ strncpy(extension, eptr, ptr-eptr);
+ extension[ptr-eptr] = '\0';
+
+ types.Add(new cMimeListObject(mimetype, extension));
+ free(extension);
+ free(mimetype);
+ }
+ line = rl.Read(f);
+ }
+
+ fclose(f);
+ }
+}
+
+char *cMimeTypes::ExtensionFromMimeType(const char *mimetype) {
+ if (!mimetype)
+ return NULL;
+
+ for (cMimeListObject *m = types.First(); m; m = types.Next(m))
+ if (strcmp(m->GetType(), mimetype) == 0) {
+ return strdup(m->GetExtension());
+ }
+
+ return NULL;
+}
diff --git a/src/vdr-plugin/mimetypes.h b/src/vdr-plugin/mimetypes.h
new file mode 100644
index 0000000..76e735b
--- /dev/null
+++ b/src/vdr-plugin/mimetypes.h
@@ -0,0 +1,35 @@
+/*
+ * mimetypes.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_MIMETYPES_H
+#define __WEBVIDEO_MIMETYPES_H
+
+class cMimeListObject : public cListObject {
+private:
+ char *type;
+ char *ext;
+public:
+ cMimeListObject(const char *mimetype, const char *extension);
+ ~cMimeListObject();
+
+ char *GetType() { return type; };
+ char *GetExtension() { return ext; };
+};
+
+class cMimeTypes {
+private:
+ cList<cMimeListObject> types;
+public:
+ cMimeTypes(const char **filenames);
+
+ char *ExtensionFromMimeType(const char *mimetype);
+};
+
+extern cMimeTypes *MimeTypes;
+
+#endif
diff --git a/src/vdr-plugin/player.c b/src/vdr-plugin/player.c
new file mode 100644
index 0000000..5e76948
--- /dev/null
+++ b/src/vdr-plugin/player.c
@@ -0,0 +1,83 @@
+/*
+ * player.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <vdr/plugin.h>
+#include "player.h"
+#include "common.h"
+
+bool cXineliboutputPlayer::Launch(const char *url) {
+ debug("launching xinelib player, url = %s", url);
+
+ char *url2;
+ if (strncmp(url, "file://", 7) == 0) {
+ url2 = (char *)malloc(strlen(url)+1);
+ strcpy(url2, "fifo://");
+ strcat(url2, url+7);
+ } else {
+ url2 = strdup(url);
+ }
+
+ /*
+ * xineliboutput plugin insists on percent encoding (certain
+ * characters in) the URL. A properly encoded URL will get broken if
+ * we let xineliboutput to encode it the second time. For example,
+ * current (Feb 2009) Youtube URLs are affected by this. We will
+ * decode the URL before passing it to xineliboutput to fix Youtube
+ *
+ * On the other hand, some URLs will get broken if the encoding is
+ * removed here. There simply isn't a way to make all URLs work
+ * because of the way xineliboutput handles the encoding.
+ */
+ char *decoded = URLdecode(url2);
+ debug("decoded = %s", decoded);
+ bool ret = cPluginManager::CallFirstService("MediaPlayer-1.0", (void *)decoded);
+ free(decoded);
+ free(url2);
+ return ret;
+}
+
+bool cMPlayerPlayer::Launch(const char *url) {
+ /*
+ * This code for launching mplayer plugin is just for testing, and
+ * most likely does not work.
+ */
+
+ debug("launching MPlayer");
+ warning("Support for MPlayer is experimental. Don't expect this to work!");
+
+ struct MPlayerServiceData
+ {
+ int result;
+ union
+ {
+ const char *filename;
+ } data;
+ };
+
+ const char* const tmpPlayListFileName = "/tmp/webvideo.m3u";
+ FILE *f = fopen(tmpPlayListFileName, "w");
+ fwrite(url, strlen(url), 1, f);
+ fclose(f);
+
+ MPlayerServiceData mplayerdata;
+ mplayerdata.data.filename = tmpPlayListFileName;
+
+ if (!cPluginManager::CallFirstService("MPlayer-Play-v1", &mplayerdata)) {
+ debug("Failed to locate Mplayer service");
+ return false;
+ }
+
+ if (!mplayerdata.result) {
+ debug("Mplayer service failed");
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/vdr-plugin/player.h b/src/vdr-plugin/player.h
new file mode 100644
index 0000000..dbaf448
--- /dev/null
+++ b/src/vdr-plugin/player.h
@@ -0,0 +1,29 @@
+/*
+ * menu.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_PLAYER_H
+#define __WEBVIDEO_PLAYER_H
+
+class cMediaPlayer {
+public:
+ virtual ~cMediaPlayer() {};
+ virtual bool Launch(const char *url) = 0;
+};
+
+class cXineliboutputPlayer : public cMediaPlayer {
+public:
+ bool Launch(const char *url);
+};
+
+class cMPlayerPlayer : public cMediaPlayer {
+public:
+ bool Launch(const char *url);
+};
+
+
+#endif
diff --git a/src/vdr-plugin/po/de_DE.po b/src/vdr-plugin/po/de_DE.po
new file mode 100644
index 0000000..a33d3e3
--- /dev/null
+++ b/src/vdr-plugin/po/de_DE.po
@@ -0,0 +1,140 @@
+# German translations for webvideo package.
+# Copyright (C) 2009 THE webvideo'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the webvideo package.
+# Antti Ajanki <antti.ajanki@iki.fi>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: webvideo 0.1.1\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2010-11-18 20:19+0200\n"
+"PO-Revision-Date: 2009-02-18 20:04+0200\n"
+"Last-Translator: <cnc@gmx.de>\n"
+"Language-Team: German\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "No streams on this page, create timer anyway?"
+msgstr ""
+
+msgid "Downloading in the background"
+msgstr "Download im Hintergrund"
+
+msgid "Starting player..."
+msgstr "Player wird gestartet..."
+
+msgid "Retrieving..."
+msgstr "Abrufen..."
+
+msgid "Back"
+msgstr "Zurück"
+
+msgid "Forward"
+msgstr "Vor"
+
+msgid "Create timer"
+msgstr ""
+
+msgid "Play"
+msgstr "Play"
+
+msgid "Status"
+msgstr "Status"
+
+msgid "Timers"
+msgstr ""
+
+msgid "Unfinished downloads"
+msgstr "Nicht beendete Downloads"
+
+msgid "No active downloads"
+msgstr "Kein aktiver Download"
+
+#. TRANSLATORS: at most 5 characters
+msgid "Error"
+msgstr ""
+
+msgid "Error details"
+msgstr ""
+
+msgid "Download details"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Abort"
+msgstr "Abbruch"
+
+msgid "Edit timer"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Once per day"
+msgstr ""
+
+msgid "Once per week"
+msgstr ""
+
+msgid "Once per month"
+msgstr ""
+
+msgid "Update interval"
+msgstr ""
+
+msgid "Execute now"
+msgstr ""
+
+#. TRANSLATORS: at most 24 chars
+msgid "Never"
+msgstr ""
+
+msgid "Last fetched:"
+msgstr ""
+
+msgid "Error on last refresh!"
+msgstr ""
+
+msgid "Timer running, can't remove"
+msgstr ""
+
+msgid "Remove timer?"
+msgstr ""
+
+#, fuzzy
+msgid "Aborted"
+msgstr "Abbruch"
+
+msgid "Download video files from the web"
+msgstr "Download Video Files aus dem Web"
+
+msgid "Streaming failed: no URL"
+msgstr "Streaming fehlgeschlagen: Keine URL"
+
+msgid "Streaming not supported, try downloading"
+msgstr ""
+
+msgid "Failed to launch media player"
+msgstr "Media Player konnte nicht gestartet werden"
+
+msgid "timer"
+msgstr ""
+
+#, c-format
+msgid "One download completed, %d remains%s"
+msgstr "Ein Download komplett, %d verbleibend%s"
+
+msgid "Download aborted"
+msgstr ""
+
+#, c-format
+msgid "Download failed (error = %d)"
+msgstr "Download fehlgeschlagen (Error = %d)"
+
+#, c-format
+msgid "%d downloads not finished"
+msgstr "%d laufende Downloads"
diff --git a/src/vdr-plugin/po/fi_FI.po b/src/vdr-plugin/po/fi_FI.po
new file mode 100644
index 0000000..351929d
--- /dev/null
+++ b/src/vdr-plugin/po/fi_FI.po
@@ -0,0 +1,140 @@
+# Finnish translations for webvideo package.
+# Copyright (C) 2008,2009 THE webvideo'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the webvideo package.
+# Antti Ajanki <antti.ajanki@iki.fi>, 2008,2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: webvideo 0.1.1\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2010-11-15 19:23+0200\n"
+"PO-Revision-Date: 2008-06-07 18:03+0300\n"
+"Last-Translator: Antti Ajanki <antti.ajanki@iki.fi>\n"
+"Language-Team: Finnish\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "No streams on this page, create timer anyway?"
+msgstr "Luo ajastin vaikka tällä sivulla ei videoita?"
+
+msgid "Downloading in the background"
+msgstr "Ladataan taustalla"
+
+msgid "Starting player..."
+msgstr "Käynnistetään toistin..."
+
+msgid "Retrieving..."
+msgstr "Ladataan..."
+
+msgid "Back"
+msgstr "Peruuta"
+
+msgid "Forward"
+msgstr "Eteenpäin"
+
+msgid "Create timer"
+msgstr "Luo ajastin"
+
+msgid "Play"
+msgstr "Toista"
+
+msgid "Status"
+msgstr "Tila"
+
+msgid "Timers"
+msgstr "Ajastimet"
+
+msgid "Unfinished downloads"
+msgstr "Ladattavat tiedostot"
+
+msgid "No active downloads"
+msgstr "Ei keskeneräisia latauksia"
+
+#. TRANSLATORS: at most 5 characters
+msgid "Error"
+msgstr "Virhe"
+
+msgid "Error details"
+msgstr "Virhe"
+
+msgid "Download details"
+msgstr "Latauksen tiedot"
+
+msgid "Remove"
+msgstr "Poista"
+
+msgid "Abort"
+msgstr "Keskeytä"
+
+msgid "Edit timer"
+msgstr "Muokkaa ajastinta"
+
+msgid "Title"
+msgstr "Nimi"
+
+msgid "Once per day"
+msgstr "Kerran päivässä"
+
+msgid "Once per week"
+msgstr "Kerran viikossa"
+
+msgid "Once per month"
+msgstr "Kerran kuussa"
+
+msgid "Update interval"
+msgstr "Päivitystahti"
+
+msgid "Execute now"
+msgstr "Suorita nyt"
+
+#. TRANSLATORS: at most 24 chars
+msgid "Never"
+msgstr "Ei koskaan"
+
+msgid "Last fetched:"
+msgstr "Viimeisin päivitys"
+
+msgid "Error on last refresh!"
+msgstr "Virhe edellisessä päivityksessä"
+
+msgid "Timer running, can't remove"
+msgstr "Poisto ei onnistu, koska ajastin on käynnissä"
+
+msgid "Remove timer?"
+msgstr "Poista ajastin?"
+
+msgid "Aborted"
+msgstr "Keskeytetty"
+
+msgid "Download video files from the web"
+msgstr "Lataa videotiedostoja Internetistä"
+
+msgid "Streaming failed: no URL"
+msgstr "Toisto epäonnistui: ei URLia"
+
+msgid "Streaming not supported, try downloading"
+msgstr "Toisto ei tuettu, kokeile lataamista"
+
+msgid "Failed to launch media player"
+msgstr "Toistimen käynnistäminen epäonnistui"
+
+msgid "timer"
+msgstr "ajastin"
+
+#, c-format
+msgid "One download completed, %d remains%s"
+msgstr "Yksi tiedosto ladattu, %d jäljellä%s"
+
+msgid "Download aborted"
+msgstr "Lataaminen keskeytetty"
+
+#, c-format
+msgid "Download failed (error = %d)"
+msgstr "Lataus epäonnistui (virhe = %d)"
+
+#, c-format
+msgid "%d downloads not finished"
+msgstr "%d tiedostoa lataamatta"
diff --git a/src/vdr-plugin/po/fr_FR.po b/src/vdr-plugin/po/fr_FR.po
new file mode 100644
index 0000000..c4176a6
--- /dev/null
+++ b/src/vdr-plugin/po/fr_FR.po
@@ -0,0 +1,159 @@
+# French translations for webvideo package.
+# Copyright (C) 2008 THE webvideo'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the webvideo package.
+# Bruno ROUSSEL <bruno.roussel@free.fr>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: webvideo 0.0.5\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2010-11-18 20:19+0200\n"
+"PO-Revision-Date: 2008-09-08 20:34+0100\n"
+"Last-Translator: Bruno ROUSSEL <bruno.roussel@free.fr>\n"
+"Language-Team: French\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "No streams on this page, create timer anyway?"
+msgstr ""
+
+msgid "Downloading in the background"
+msgstr "Téléchargement en tâche de fond"
+
+msgid "Starting player..."
+msgstr ""
+
+msgid "Retrieving..."
+msgstr "Récupération..."
+
+msgid "Back"
+msgstr "Arrière"
+
+msgid "Forward"
+msgstr "Avant"
+
+msgid "Create timer"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Status"
+msgstr "Status"
+
+msgid "Timers"
+msgstr ""
+
+msgid "Unfinished downloads"
+msgstr ""
+
+msgid "No active downloads"
+msgstr ""
+
+#. TRANSLATORS: at most 5 characters
+msgid "Error"
+msgstr ""
+
+msgid "Error details"
+msgstr ""
+
+#, fuzzy
+msgid "Download details"
+msgstr "Status du téléchargement"
+
+msgid "Remove"
+msgstr ""
+
+msgid "Abort"
+msgstr ""
+
+msgid "Edit timer"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Once per day"
+msgstr ""
+
+msgid "Once per week"
+msgstr ""
+
+msgid "Once per month"
+msgstr ""
+
+msgid "Update interval"
+msgstr ""
+
+msgid "Execute now"
+msgstr ""
+
+#. TRANSLATORS: at most 24 chars
+msgid "Never"
+msgstr ""
+
+msgid "Last fetched:"
+msgstr ""
+
+msgid "Error on last refresh!"
+msgstr ""
+
+msgid "Timer running, can't remove"
+msgstr ""
+
+msgid "Remove timer?"
+msgstr ""
+
+msgid "Aborted"
+msgstr ""
+
+msgid "Download video files from the web"
+msgstr "Téléchargement du fichier vidéo depuis le web"
+
+msgid "Streaming failed: no URL"
+msgstr ""
+
+msgid "Streaming not supported, try downloading"
+msgstr ""
+
+msgid "Failed to launch media player"
+msgstr ""
+
+msgid "timer"
+msgstr ""
+
+#, c-format
+msgid "One download completed, %d remains%s"
+msgstr "Un téléchargement terminé, il en reste %d%s"
+
+msgid "Download aborted"
+msgstr ""
+
+#, c-format
+msgid "Download failed (error = %d)"
+msgstr "Erreur de téléchargement (Erreur = %d)"
+
+#, c-format
+msgid "%d downloads not finished"
+msgstr "%d téléchargement(s) non terminé(s)."
+
+#~ msgid "<No title>"
+#~ msgstr "<Pas de titre>"
+
+#~ msgid "Can't download web page!"
+#~ msgstr "Impossible de télécharger la page web !"
+
+#~ msgid "XSLT transformation produced no URL!"
+#~ msgstr "La conversion XSLT n'a pas généré d'URL !"
+
+#~ msgid "XSLT transformation failed."
+#~ msgstr "Erreur de conversion XSLT."
+
+#~ msgid "Unknown error!"
+#~ msgstr "Erreur inconnue !"
+
+#~ msgid "Select video source"
+#~ msgstr "Sélectionner la source vidéo"
diff --git a/src/vdr-plugin/po/it_IT.po b/src/vdr-plugin/po/it_IT.po
new file mode 100755
index 0000000..809fc2f
--- /dev/null
+++ b/src/vdr-plugin/po/it_IT.po
@@ -0,0 +1,156 @@
+# Italian translations for webvideo package.
+# Copyright (C) 2008 THE webvideo'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the webvideo package.
+# Diego Pierotto <vdr-italian@tiscali.it>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: webvideo 0.0.1\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2010-11-18 20:19+0200\n"
+"PO-Revision-Date: 2010-12-30 22:00+0100\n"
+"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
+"Language-Team: Italian\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Italian\n"
+"X-Poedit-Country: ITALY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+msgid "No streams on this page, create timer anyway?"
+msgstr "Nessun flusso in questa pagina, create comunque il timer?"
+
+msgid "Downloading in the background"
+msgstr "Scaricamento in sottofondo"
+
+msgid "Starting player..."
+msgstr "Avvio lettore..."
+
+msgid "Retrieving..."
+msgstr "Recupero..."
+
+msgid "Back"
+msgstr "Indietro"
+
+msgid "Forward"
+msgstr "Avanti"
+
+msgid "Create timer"
+msgstr "Crea nuovo timer"
+
+msgid "Play"
+msgstr "Riproduci"
+
+msgid "Status"
+msgstr "Stato"
+
+msgid "Timers"
+msgstr "Timer"
+
+msgid "Unfinished downloads"
+msgstr "Scaricamenti non completati"
+
+msgid "No active downloads"
+msgstr "Nessun scaricamento attivo"
+
+#. TRANSLATORS: at most 5 characters
+msgid "Error"
+msgstr "Errore"
+
+msgid "Error details"
+msgstr "Dettagli errore"
+
+msgid "Download details"
+msgstr "Dettagli scaricamento"
+
+msgid "Remove"
+msgstr "Elimina"
+
+msgid "Abort"
+msgstr "Annulla"
+
+msgid "Edit timer"
+msgstr "Modifica timer"
+
+msgid "Title"
+msgstr "Titolo"
+
+msgid "Once per day"
+msgstr "Una volta al giorno"
+
+msgid "Once per week"
+msgstr "Una volta alla settimana"
+
+msgid "Once per month"
+msgstr "Una volta al mese"
+
+msgid "Update interval"
+msgstr "Intervallo di aggiornamento"
+
+msgid "Execute now"
+msgstr "Riproduci ora"
+
+#. TRANSLATORS: at most 24 chars
+msgid "Never"
+msgstr "Mai"
+
+msgid "Last fetched:"
+msgstr "Ultima analisi:"
+
+msgid "Error on last refresh!"
+msgstr "Ultimo aggiornamento fallito!"
+
+msgid "Timer running, can't remove"
+msgstr "Timer in esecuzione, impossibile eliminare"
+
+msgid "Remove timer?"
+msgstr "Eliminare il timer?"
+
+msgid "Aborted"
+msgstr "Annullato"
+
+msgid "Download video files from the web"
+msgstr "Scarica file video dal web"
+
+msgid "Streaming failed: no URL"
+msgstr "Trasmissione fallita: nessun URL"
+
+msgid "Streaming not supported, try downloading"
+msgstr "Trasmissione non supportata, provare scaricando"
+
+msgid "Failed to launch media player"
+msgstr "Impossibile avviare il lettore multimediale"
+
+msgid "timer"
+msgstr "timer"
+
+#, c-format
+msgid "One download completed, %d remains%s"
+msgstr "Scaricamento completato, %d rimanente/i%s"
+
+msgid "Download aborted"
+msgstr "Scaricamento annullato"
+
+#, c-format
+msgid "Download failed (error = %d)"
+msgstr "Scaricamento fallito (errore = %d)"
+
+#, c-format
+msgid "%d downloads not finished"
+msgstr "%d scaricamenti non conclusi"
+
+#~ msgid "<No title>"
+#~ msgstr "<Senza titolo>"
+#~ msgid "Can't download web page!"
+#~ msgstr "Impossibile scaricare la pagina web!"
+#~ msgid "XSLT transformation produced no URL!"
+#~ msgstr "La conversione XSLT non ha generato alcun URL!"
+#~ msgid "XSLT transformation failed."
+#~ msgstr "Conversione XSLT fallita."
+#~ msgid "Unknown error!"
+#~ msgstr "Errore sconosciuto!"
+#~ msgid "Select video source"
+#~ msgstr "Seleziona fonte video"
+
diff --git a/src/vdr-plugin/po/uk_UA.po b/src/vdr-plugin/po/uk_UA.po
new file mode 100644
index 0000000..a2f4db4
--- /dev/null
+++ b/src/vdr-plugin/po/uk_UA.po
@@ -0,0 +1,134 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: webvideo 0.5.0\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2013-05-19 00:52+0200\n"
+"PO-Revision-Date: 2013-05-19 00:42+0200\n"
+"Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
+"Language-Team: <vdr@linuxtv.org>\n"
+"Language: Ukrainian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "No streams on this page, create timer anyway?"
+msgstr "Немає потоку на цій сторінці, створити таймер все одно?"
+
+msgid "Downloading in the background"
+msgstr "Звантаження в тлі"
+
+msgid "Starting player..."
+msgstr "Запуск програвача..."
+
+msgid "Retrieving..."
+msgstr "Отримання..."
+
+msgid "Back"
+msgstr "Назад"
+
+msgid "Forward"
+msgstr "Вперед"
+
+msgid "Create timer"
+msgstr "Ств. таймер"
+
+msgid "Play"
+msgstr "Програвання"
+
+msgid "Status"
+msgstr "Статус"
+
+msgid "Timers"
+msgstr "Таймери"
+
+msgid "Unfinished downloads"
+msgstr "Незавершені звантаження"
+
+msgid "No active downloads"
+msgstr "Немає активних звантажень"
+
+#. TRANSLATORS: at most 5 characters
+msgid "Error"
+msgstr "Помилка"
+
+msgid "Error details"
+msgstr "Деталі помилки"
+
+msgid "Download details"
+msgstr "Деталі звантаження"
+
+msgid "Remove"
+msgstr "Вилучення"
+
+msgid "Abort"
+msgstr "Перервати"
+
+msgid "Edit timer"
+msgstr "Редагувати таймер"
+
+msgid "Title"
+msgstr "Назва"
+
+msgid "Once per day"
+msgstr "Раз на день"
+
+msgid "Once per week"
+msgstr "Раз на тиждень"
+
+msgid "Once per month"
+msgstr "Раз на місяць"
+
+msgid "Update interval"
+msgstr "Інтервал оновлення"
+
+msgid "Execute now"
+msgstr "Запустити вже"
+
+#. TRANSLATORS: at most 24 chars
+msgid "Never"
+msgstr "Ніколи"
+
+msgid "Last fetched:"
+msgstr "Востаннє отримані:"
+
+msgid "Error on last refresh!"
+msgstr "Помилка при останньому оновленні!"
+
+msgid "Timer running, can't remove"
+msgstr "Запущено таймер, неможливо вилучити!"
+
+msgid "Remove timer?"
+msgstr "Вилучити таймер?"
+
+msgid "Aborted"
+msgstr "Перервано"
+
+msgid "Download video files from the web"
+msgstr "Звантаження відео-файлів з мережі"
+
+msgid "Streaming failed: no URL"
+msgstr "Програвання потоку не вдалося: немає посилання"
+
+msgid "Streaming not supported, try downloading"
+msgstr "Програвання потоку не підтримується, спробуйте звантажити"
+
+msgid "Failed to launch media player"
+msgstr "Помилка запуску програвача медіа"
+
+msgid "timer"
+msgstr "таймер"
+
+#, c-format
+msgid "One download completed, %d remains%s"
+msgstr "Одне звантаження закінчено, %d залишилося%s"
+
+msgid "Download aborted"
+msgstr "Звантаження перервано"
+
+#, c-format
+msgid "Download failed (error = %d)"
+msgstr "Звантаження не вдалося (помилка = %d)"
+
+#, c-format
+msgid "%d downloads not finished"
+msgstr "%d звантажень не завершено"
diff --git a/src/vdr-plugin/request.c b/src/vdr-plugin/request.c
new file mode 100644
index 0000000..0d1abf1
--- /dev/null
+++ b/src/vdr-plugin/request.c
@@ -0,0 +1,530 @@
+/*
+ * request.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <vdr/tools.h>
+#include <vdr/i18n.h>
+#include "request.h"
+#include "common.h"
+#include "mimetypes.h"
+#include "config.h"
+#include "timer.h"
+
+// --- cDownloadProgress ---------------------------------------------------
+
+cDownloadProgress::cDownloadProgress() {
+ strcpy(name, "???");
+ downloaded = -1;
+ total = -1;
+ statusCode = -1;
+ req = NULL;
+}
+
+void cDownloadProgress::AssociateWith(cFileDownloadRequest *request) {
+ req = request;
+}
+
+void cDownloadProgress::SetContentLength(long bytes) {
+ total = bytes;
+}
+
+void cDownloadProgress::SetTitle(const char *title) {
+ cMutexLock lock(&mutex);
+
+ strncpy(name, title, NAME_LEN-1);
+ name[NAME_LEN-1] = '\0';
+}
+
+void cDownloadProgress::Progress(long downloadedbytes) {
+ // Atomic operation, no mutex needed
+ downloaded = downloadedbytes;
+}
+
+void cDownloadProgress::MarkDone(int errorcode, cString pharse) {
+ cMutexLock lock(&mutex);
+
+ statusCode = errorcode;
+ statusPharse = pharse;
+}
+
+bool cDownloadProgress::IsFinished() {
+ return statusCode != -1;
+}
+
+cString cDownloadProgress::GetTitle() {
+ cMutexLock lock(&mutex);
+
+ if (req && req->IsAborted())
+ return cString::sprintf("[%s] %s", tr("Aborted"), name);
+ else
+ return cString(name);
+}
+
+cString cDownloadProgress::GetPercentage() {
+ cMutexLock lock(&mutex);
+
+ if ((const char*)statusPharse != NULL && statusCode != 0)
+ // TRANSLATORS: at most 5 characters
+ return cString(tr("Error"));
+ else if ((downloaded < 0) || (total < 0))
+ return cString("???");
+ else
+ return cString::sprintf("%3d%%", (int) (100*(float)downloaded/total + 0.5));
+}
+
+cString cDownloadProgress::GetStatusPharse() {
+ cMutexLock lock(&mutex);
+
+ return statusPharse;
+}
+
+bool cDownloadProgress::Error() {
+ return (const char *)statusPharse != NULL;
+}
+
+// --- cProgressVector -----------------------------------------------------
+
+cDownloadProgress *cProgressVector::NewDownload() {
+ cDownloadProgress *progress = new cDownloadProgress();
+ Append(progress);
+ return progress;
+}
+
+// --- cMenuRequest --------------------------------------------------------
+
+cMenuRequest::cMenuRequest(int ID, const char *wvtreference)
+: reqID(ID), aborted(false), finished(false), status(0), webvi(-1),
+ handle(-1), timer(NULL)
+{
+ wvtref = strdup(wvtreference);
+}
+
+cMenuRequest::~cMenuRequest() {
+ if (handle != -1) {
+ if (!finished)
+ Abort();
+ webvi_delete_handle(webvi, handle);
+ }
+
+ // do not delete timer
+}
+
+ssize_t cMenuRequest::WriteCallback(const char *ptr, size_t len, void *request) {
+ cMenuRequest *instance = (cMenuRequest *)request;
+ if (instance)
+ return instance->WriteData(ptr, len);
+ else
+ return len;
+}
+
+ssize_t cMenuRequest::WriteData(const char *ptr, size_t len) {
+ return inBuffer.Put(ptr, len);
+}
+
+char *cMenuRequest::ExtractSiteName(const char *ref) {
+ if (strncmp(ref, "wvt:///", 7) != 0)
+ return NULL;
+
+ const char *first = ref+7;
+ const char *last = strchr(first, '/');
+ if (!last)
+ last = first+strlen(first);
+
+ return strndup(first, last-first);
+}
+
+void cMenuRequest::AppendQualityParamsToRef() {
+ if (!wvtref)
+ return;
+
+ char *site = ExtractSiteName(wvtref);
+ if (site) {
+ const char *min = webvideoConfig->GetMinQuality(site, GetType());
+ const char *max = webvideoConfig->GetMaxQuality(site, GetType());
+ free(site);
+
+ if (min && !max) {
+ cString newref = cString::sprintf("%s&minquality=%s", wvtref, min);
+ free(wvtref);
+ wvtref = strdup((const char *)newref);
+
+ } else if (!min && max) {
+ cString newref = cString::sprintf("%s&maxquality=%s", wvtref, max);
+ free(wvtref);
+ wvtref = strdup((const char *)newref);
+
+ } else if (min && max) {
+ cString newref = cString::sprintf("%s&minquality=%s&maxquality=%s", wvtref, min, max);
+ free(wvtref);
+ wvtref = strdup((const char *)newref);
+ }
+ }
+}
+
+WebviHandle cMenuRequest::PrepareHandle() {
+ if (handle == -1) {
+ handle = webvi_new_request(webvi, wvtref, WEBVIREQ_MENU);
+
+ if (handle != -1) {
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback);
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this);
+ }
+ }
+
+ return handle;
+}
+
+bool cMenuRequest::Start(WebviCtx webvictx) {
+ debug("starting request %d", reqID);
+
+ webvi = webvictx;
+ if ((PrepareHandle() != -1) && (webvi_start_handle(webvi, handle) == WEBVIERR_OK)) {
+ finished = false;
+ return true;
+ } else
+ return false;
+}
+
+void cMenuRequest::RequestDone(int errorcode, cString pharse) {
+ debug("RequestDone %d %s", errorcode, (const char *)pharse);
+
+ finished = true;
+ status = errorcode;
+ statusPharse = pharse;
+}
+
+void cMenuRequest::Abort() {
+ if (aborted || finished || handle == -1)
+ return;
+
+ aborted = true;
+ webvi_stop_handle(webvi, handle);
+};
+
+bool cMenuRequest::Success() {
+ return status == 0;
+}
+
+cString cMenuRequest::GetStatusPharse() {
+ return statusPharse;
+}
+
+cString cMenuRequest::GetResponse() {
+ size_t len = inBuffer.Length();
+ const char *src = inBuffer.Get();
+ char *buf = (char *)malloc((len+1)*sizeof(char));
+ strncpy(buf, src, len);
+ buf[len] = '\0';
+ return cString(buf, true);
+}
+
+// --- cFileDownloadRequest ------------------------------------------------
+
+cFileDownloadRequest::cFileDownloadRequest(int ID, const char *streamref,
+ cDownloadProgress *progress)
+: cMenuRequest(ID, streamref), title(NULL), bytesDownloaded(0),
+ contentLength(-1), destfile(NULL), destfilename(NULL),
+ progressUpdater(progress), state(STATE_WEBVI)
+{
+ if (progressUpdater)
+ progressUpdater->AssociateWith(this);
+
+ AppendQualityParamsToRef();
+}
+
+cFileDownloadRequest::~cFileDownloadRequest() {
+ if (destfile) {
+ destfile->Close();
+ delete destfile;
+ }
+ if (destfilename)
+ free(destfilename);
+ if (title)
+ free(title);
+ // do not delete progressUpdater
+}
+
+WebviHandle cFileDownloadRequest::PrepareHandle() {
+ if (handle == -1) {
+ handle = webvi_new_request(webvi, wvtref, WEBVIREQ_FILE);
+
+ if (handle != -1) {
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback);
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this);
+ }
+ }
+
+ return handle;
+}
+
+ssize_t cFileDownloadRequest::WriteData(const char *ptr, size_t len) {
+ if (!destfile) {
+ if (!OpenDestFile())
+ return -1;
+ }
+
+ bytesDownloaded += len;
+ if (progressUpdater)
+ progressUpdater->Progress(bytesDownloaded);
+
+ return destfile->Write(ptr, len);
+}
+
+bool cFileDownloadRequest::OpenDestFile() {
+ char *contentType;
+ char *url;
+ char *ext;
+ cString filename;
+ int fd, i;
+
+ if (handle == -1) {
+ error("handle == -1 while trying to open destination file");
+ return false;
+ }
+
+ if (destfile)
+ delete destfile;
+
+ destfile = new cUnbufferedFile;
+
+ webvi_get_info(webvi, handle, WEBVIINFO_URL, &url);
+ webvi_get_info(webvi, handle, WEBVIINFO_STREAM_TITLE, &title);
+ webvi_get_info(webvi, handle, WEBVIINFO_CONTENT_TYPE, &contentType);
+ webvi_get_info(webvi, handle, WEBVIINFO_CONTENT_LENGTH, &contentLength);
+
+ if (!contentType || !url) {
+ if(contentType)
+ free(contentType);
+ if (url)
+ free(url);
+
+ error("no content type or url, can't infer extension");
+ return false;
+ }
+
+ ext = GetExtension(contentType, url);
+
+ free(url);
+ free(contentType);
+
+ const char *destdir = webvideoConfig->GetDownloadPath();
+ char *basename = strdup(title ? title : "???");
+ basename = safeFilename(basename, webvideoConfig->GetUseVFATNames());
+
+ i = 1;
+ filename = cString::sprintf("%s/%s%s", destdir, basename, ext);
+ while (true) {
+ debug("trying to open %s", (const char *)filename);
+
+ fd = destfile->Open(filename, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE);
+
+ if (fd == -1 && errno == EEXIST)
+ filename = cString::sprintf("%s/%s-%d%s", destdir, basename, i++, ext);
+ else
+ break;
+ };
+
+ free(basename);
+ free(ext);
+
+ if (fd < 0) {
+ error("Failed to open file %s: %m", (const char *)filename);
+ delete destfile;
+ destfile = NULL;
+ return false;
+ }
+
+ if (destfilename)
+ free(destfilename);
+ destfilename = strdup(filename);
+ info("Saving to %s", destfilename);
+
+ if (progressUpdater) {
+ progressUpdater->SetTitle(title);
+ progressUpdater->SetContentLength(contentLength);
+ }
+
+ return true;
+}
+
+char *cFileDownloadRequest::GetExtension(const char *contentType, const char *url) {
+ // Get extension from Content-Type
+ char *ext = NULL;
+ char *ext2 = MimeTypes->ExtensionFromMimeType(contentType);
+
+ // Workaround for buggy servers: If the server claims that the mime
+ // type is text/plain, ignore the server and fall back to extracting
+ // the extension from the URL. This function should be called only
+ // for video, audio or ASX files and therefore text/plain is clearly
+ // incorrect.
+ if (ext2 && contentType && !strcasecmp(contentType, "text/plain")) {
+ debug("Ignoring content type text/plain, getting extension from url.");
+ free(ext2);
+ ext2 = NULL;
+ }
+
+ if (ext2) {
+ // Append dot in the start of the extension
+ ext = (char *)malloc(strlen(ext2)+2);
+ ext[0] = '.';
+ ext[1] = '\0';
+ strcat(ext, ext2);
+ free(ext2);
+ return ext;
+ }
+
+ // Get extension from URL
+ ext = extensionFromUrl(url);
+ if (ext)
+ return ext;
+
+ // No extension!
+ return strdup("");
+}
+
+void cFileDownloadRequest::RequestDone(int errorcode, cString pharse) {
+ if (state == STATE_WEBVI) {
+ if (destfile)
+ destfile->Close();
+
+ if (errorcode == 0)
+ StartPostProcessing();
+ else
+ state = STATE_FINISHED;
+
+ } else if (state == STATE_POSTPROCESS) {
+ postProcessPipe.Close();
+ state = STATE_FINISHED;
+ }
+
+ if (state == STATE_FINISHED) {
+ cMenuRequest::RequestDone(errorcode, pharse);
+ if (progressUpdater)
+ progressUpdater->MarkDone(errorcode, pharse);
+ }
+}
+
+void cFileDownloadRequest::Abort() {
+ if (state == STATE_POSTPROCESS)
+ postProcessPipe.Close();
+
+ cMenuRequest::Abort();
+}
+
+void cFileDownloadRequest::StartPostProcessing() {
+ state = STATE_POSTPROCESS;
+
+ const char *script = webvideoConfig->GetPostProcessCmd();
+ if (!script || !destfilename) {
+ state = STATE_FINISHED;
+ return;
+ }
+
+ info("post-processing %s", destfilename);
+
+ cString cmd = cString::sprintf("%s %s",
+ (const char *)shellEscape(script),
+ (const char *)shellEscape(destfilename));
+ debug("executing %s", (const char *)cmd);
+
+ if (!postProcessPipe.Open(cmd, "r")) {
+ state = STATE_FINISHED;
+ return;
+ }
+
+ int flags = fcntl(fileno(postProcessPipe), F_GETFL, 0);
+ flags |= O_NONBLOCK;
+ fcntl(fileno(postProcessPipe), F_SETFL, flags);
+}
+
+int cFileDownloadRequest::File() {
+ FILE *f = postProcessPipe;
+
+ if (f)
+ return fileno(f);
+ else
+ return -1;
+}
+
+bool cFileDownloadRequest::Read() {
+ const size_t BUF_LEN = 512;
+ char buf[BUF_LEN];
+
+ if (!(FILE *)postProcessPipe)
+ return false;
+
+ while (true) {
+ ssize_t nbytes = read(fileno(postProcessPipe), buf, BUF_LEN);
+
+ if (nbytes < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ LOG_ERROR_STR("post process pipe");
+ return false;
+ }
+ } else if (nbytes == 0) {
+ info("post-processing of %s finished", destfilename);
+
+ if (IsAborted())
+ RequestDone(-2, "Aborted");
+ else
+ RequestDone(0, "");
+
+ return true;
+ } else {
+ debug("pp: %.*s", nbytes, buf);
+
+ if (nbytes < (ssize_t)BUF_LEN)
+ return true;
+ }
+ }
+
+ return true;
+}
+
+// --- cStreamUrlRequest ---------------------------------------------------
+
+cStreamUrlRequest::cStreamUrlRequest(int ID, const char *ref)
+: cMenuRequest(ID, ref) {
+ AppendQualityParamsToRef();
+}
+
+WebviHandle cStreamUrlRequest::PrepareHandle() {
+ if (handle == -1) {
+ handle = webvi_new_request(webvi, wvtref, WEBVIREQ_STREAMURL);
+
+ if (handle != -1) {
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback);
+ webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this);
+ }
+ }
+
+ return handle;
+}
+
+// --- cTimerRequest -------------------------------------------------------
+
+cTimerRequest::cTimerRequest(int ID, const char *ref)
+: cMenuRequest(ID, ref)
+{
+}
+
+// --- cRequestVector ------------------------------------------------------
+
+cMenuRequest *cRequestVector::FindByHandle(WebviHandle handle) {
+ for (int i=0; i<Size(); i++)
+ if (At(i)->GetHandle() == handle)
+ return At(i);
+
+ return NULL;
+}
diff --git a/src/vdr-plugin/request.h b/src/vdr-plugin/request.h
new file mode 100644
index 0000000..4a37741
--- /dev/null
+++ b/src/vdr-plugin/request.h
@@ -0,0 +1,181 @@
+/*
+ * request.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_REQUEST_H
+#define __WEBVIDEO_REQUEST_H
+
+#include <vdr/tools.h>
+#include <vdr/thread.h>
+#include <libwebvi.h>
+#include "buffer.h"
+
+enum eRequestType { REQT_NONE, REQT_MENU, REQT_FILE, REQT_STREAM, REQT_TIMER };
+
+class cFileDownloadRequest;
+class cWebviTimer;
+
+// --- cDownloadProgress ---------------------------------------------------
+
+class cDownloadProgress {
+private:
+ const static int NAME_LEN = 128;
+
+ char name[NAME_LEN];
+ long downloaded;
+ long total;
+ int statusCode;
+ cString statusPharse;
+ cFileDownloadRequest *req;
+ cMutex mutex;
+public:
+ cDownloadProgress();
+
+ void AssociateWith(cFileDownloadRequest *request);
+ void SetContentLength(long bytes);
+ void SetTitle(const char *title);
+ void Progress(long downloadedbytes);
+ void MarkDone(int errorcode, cString pharse);
+ bool IsFinished();
+
+ cString GetTitle();
+ cString GetPercentage();
+ cString GetStatusPharse();
+ bool Error();
+ cFileDownloadRequest *GetRequest() { return req; }
+};
+
+// --- cProgressVector -----------------------------------------------------
+
+class cProgressVector : public cVector<cDownloadProgress *> {
+public:
+ cDownloadProgress *NewDownload();
+};
+
+// --- cMenuRequest ----------------------------------------------------
+
+class cMenuRequest {
+private:
+ int reqID;
+ bool aborted;
+ bool finished;
+ int status;
+ cString statusPharse;
+
+protected:
+ WebviCtx webvi;
+ WebviHandle handle;
+ char *wvtref;
+ cMemoryBuffer inBuffer;
+ cWebviTimer *timer;
+
+ virtual ssize_t WriteData(const char *ptr, size_t len);
+ virtual WebviHandle PrepareHandle();
+ static ssize_t WriteCallback(const char *ptr, size_t len, void *request);
+
+ char *ExtractSiteName(const char *ref);
+ void AppendQualityParamsToRef();
+
+public:
+ cMenuRequest(int ID, const char *wvtreference);
+ virtual ~cMenuRequest();
+
+ int GetID() { return reqID; }
+ WebviHandle GetHandle() { return handle; }
+ const char *GetReference() { return wvtref; }
+
+ bool Start(WebviCtx webvictx);
+ virtual void RequestDone(int errorcode, cString pharse);
+ bool IsFinished() { return finished; }
+ virtual void Abort();
+ bool IsAborted() { return aborted; }
+
+ // Return true if the lastest status code indicates success.
+ bool Success();
+ // Return the status code
+ int GetStatusCode() { return status; }
+ // Return the response pharse
+ cString GetStatusPharse();
+
+ virtual eRequestType GetType() { return REQT_MENU; }
+
+ // Return the content of the response message
+ virtual cString GetResponse();
+
+ void SetTimer(cWebviTimer *t) { timer = t; }
+ cWebviTimer *GetTimer() { return timer; }
+
+ virtual int File() { return -1; }
+ virtual bool Read() { return true; }
+};
+
+// --- cFileDownloadRequest ------------------------------------------------
+
+class cFileDownloadRequest : public cMenuRequest {
+private:
+ enum eDownloadState { STATE_WEBVI, STATE_POSTPROCESS, STATE_FINISHED };
+
+ char *title;
+ long bytesDownloaded;
+ long contentLength;
+ cUnbufferedFile *destfile;
+ char *destfilename;
+ cDownloadProgress *progressUpdater;
+ cPipe postProcessPipe;
+ eDownloadState state;
+
+protected:
+ virtual WebviHandle PrepareHandle();
+ virtual ssize_t WriteData(const char *ptr, size_t len);
+ bool OpenDestFile();
+ char *GetExtension(const char *contentType, const char *url);
+ void StartPostProcessing();
+
+public:
+ cFileDownloadRequest(int ID, const char *streamref,
+ cDownloadProgress *progress);
+ virtual ~cFileDownloadRequest();
+
+ eRequestType GetType() { return REQT_FILE; }
+ void RequestDone(int errorcode, cString pharse);
+ void Abort();
+
+ int File();
+ bool Read();
+};
+
+// --- cStreamUrlRequest ---------------------------------------------------
+
+class cStreamUrlRequest : public cMenuRequest {
+protected:
+ virtual WebviHandle PrepareHandle();
+
+public:
+ cStreamUrlRequest(int ID, const char *ref);
+
+ eRequestType GetType() { return REQT_STREAM; }
+};
+
+// --- cTimerRequest -------------------------------------------------------
+
+class cTimerRequest : public cMenuRequest {
+public:
+ cTimerRequest(int ID, const char *ref);
+
+ eRequestType GetType() { return REQT_TIMER; }
+};
+
+// --- cRequestVector ------------------------------------------------------
+
+class cRequestVector : public cVector<cMenuRequest *> {
+public:
+ cRequestVector(int Allocated = 10) : cVector<cMenuRequest *>(Allocated) {}
+
+ cMenuRequest *FindByHandle(WebviHandle handle);
+};
+
+#endif
diff --git a/src/vdr-plugin/timer.c b/src/vdr-plugin/timer.c
new file mode 100644
index 0000000..6346763
--- /dev/null
+++ b/src/vdr-plugin/timer.c
@@ -0,0 +1,519 @@
+/*
+ * timer.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <libxml/parser.h>
+#include "timer.h"
+#include "request.h"
+#include "common.h"
+#include "download.h"
+#include "config.h"
+
+// --- cWebviTimer -----------------------------------------------
+
+cWebviTimer::cWebviTimer(int ID, const char *title,
+ const char *ref, cWebviTimerManager *manager,
+ time_t last, int interval, bool success,
+ const char *errmsg)
+ : id(ID), title(title ? strdup(title) : strdup("???")),
+ reference(ref ? strdup(ref) : NULL), lastUpdate(last),
+ interval(interval), running(false), lastSucceeded(success),
+ lastError(errmsg ? strdup(errmsg) : NULL),
+ parent(manager)
+{
+}
+
+cWebviTimer::~cWebviTimer() {
+ if(title)
+ free(title);
+ if (reference)
+ free(reference);
+ if (lastError)
+ free(lastError);
+}
+
+void cWebviTimer::SetTitle(const char *newTitle) {
+ if (title)
+ free(title);
+ title = newTitle ? strdup(newTitle) : strdup("???");
+
+ parent->SetModified();
+}
+
+void cWebviTimer::SetInterval(int interval) {
+ if (interval < MIN_TIMER_INTERVAL)
+ this->interval = MIN_TIMER_INTERVAL;
+ else
+ this->interval = interval;
+
+ parent->SetModified();
+}
+
+int cWebviTimer::GetInterval() const {
+ return interval;
+}
+
+time_t cWebviTimer::NextUpdate() const {
+ int delta = interval;
+
+ // Retry again soon if the last try failed
+ if (!lastSucceeded && delta > RETRY_TIMER_INTERVAL)
+ delta = RETRY_TIMER_INTERVAL;
+
+ return lastUpdate + delta;
+}
+
+void cWebviTimer::Execute() {
+ if (running) {
+ debug("previous instance of this timer is still running");
+ return;
+ }
+
+ info("Executing timer \"%s\"", title);
+
+ running = true;
+ cTimerRequest *req = new cTimerRequest(id, reference);
+ req->SetTimer(this);
+ cWebviThread::Instance().AddRequest(req);
+
+ lastUpdate = time(NULL);
+ SetError(NULL);
+ parent->SetModified();
+
+ activeStreams.Clear();
+}
+
+void cWebviTimer::SetError(const char *errmsg) {
+ bool oldSuccess = lastSucceeded;
+
+ if (lastError)
+ free(lastError);
+ lastError = NULL;
+
+ if (errmsg) {
+ lastSucceeded = false;
+ lastError = strdup(errmsg);
+ } else {
+ lastSucceeded = true;
+ }
+
+ if (oldSuccess != lastSucceeded)
+ parent->SetModified();
+}
+
+const char *cWebviTimer::LastError() const {
+ return lastError ? lastError : "";
+}
+
+void cWebviTimer::DownloadStreams(const char *menuxml, cProgressVector& summaries) {
+ if (!menuxml) {
+ SetError("xml == NULL");
+ return;
+ }
+
+ xmlDocPtr doc = xmlParseMemory(menuxml, strlen(menuxml));
+ if (!doc) {
+ xmlErrorPtr xmlerr = xmlGetLastError();
+ if (xmlerr)
+ error("libxml error: %s", xmlerr->message);
+ SetError(xmlerr->message);
+ return;
+ }
+
+ xmlNodePtr node = xmlDocGetRootElement(doc);
+ if (node)
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "link")) {
+ xmlNodePtr node2 = node->children;
+
+ while(node2) {
+ if (!xmlStrcmp(node2->name, BAD_CAST "stream")) {
+ xmlChar *streamref = xmlNodeListGetString(doc, node2->xmlChildrenNode, 1);
+ const char *ref = (const char *)streamref;
+
+ if (parent->AlreadyDownloaded(ref)) {
+ debug("timer: %s has already been downloaded", ref);
+ } else if (*ref) {
+ info("timer: downloading %s", ref);
+
+ activeStreams.Append(strdup(ref));
+ cFileDownloadRequest *req = \
+ new cFileDownloadRequest(REQ_ID_TIMER, ref,
+ summaries.NewDownload());
+ req->SetTimer(this);
+ cWebviThread::Instance().AddRequest(req);
+ }
+
+ xmlFree(streamref);
+ }
+
+ node2 = node2->next;
+ }
+ }
+
+ node = node->next;
+ }
+
+ xmlFreeDoc(doc);
+
+ if (activeStreams.Size() == 0) {
+ running = false;
+ }
+}
+
+void cWebviTimer::CheckFailed(const char *errmsg) {
+ SetError(errmsg);
+ running = false;
+}
+
+void cWebviTimer::RequestFinished(const char *ref, const char *errmsg) {
+ if (errmsg && !lastError)
+ SetError(errmsg);
+
+ if (ref) {
+ if (!errmsg && parent)
+ parent->MarkDownloaded(ref);
+
+ int i = activeStreams.Find(ref);
+ if (i != -1) {
+ free(activeStreams[i]);
+ activeStreams.Remove(i);
+ }
+ }
+
+ if (activeStreams.Size() == 0) {
+ info("timer \"%s\" done", title);
+ running = false;
+ } else {
+ debug("timer %s is still downloading %d streams", reference, activeStreams.Size());
+ }
+}
+
+// --- cWebviTimerManager ----------------------------------------
+
+cWebviTimerManager::cWebviTimerManager()
+: nextID(1), modified(false), disableSaving(false), convertTemplatePaths(false)
+{
+}
+
+cWebviTimerManager &cWebviTimerManager::Instance() {
+ static cWebviTimerManager instance;
+
+ return instance;
+}
+
+void cWebviTimerManager::LoadTimers(FILE *f) {
+ cReadLine rl;
+ long lastRefresh;
+ int interval;
+ int success;
+ char *ref;
+ const char *ver;
+ const char *title;
+ const char *errmsg;
+ int n, i;
+
+ ver = rl.Read(f);
+ if (strcmp(ver, "# WVTIMER1") != 0 &&
+ strncmp(ver, "# WVTIMER1/", 11) != 0) {
+ error("Can't load timers. Unknown format: %s", ver);
+ disableSaving = true;
+ return;
+ }
+
+ convertTemplatePaths = (strcmp(ver, "# WVTIMER1") == 0);
+
+ i = 1;
+ while (true) {
+ n = fscanf(f, "%ld %d %d %ms", &lastRefresh, &interval, &success, &ref);
+ if (n != 4) {
+ if (n != EOF) {
+ error("Error while reading webvi timers file");
+ } else if (ferror(f)) {
+ LOG_ERROR_STR("webvi timers file");
+ }
+
+ break;
+ }
+
+ if (convertTemplatePaths) {
+ char *newref = UpgradedTemplatePath(ref);
+ if (newref) {
+ free(ref);
+ ref = newref;
+ }
+ }
+
+ title = rl.Read(f);
+ title = title ? skipspace(title) : "???";
+ errmsg = success ? NULL : "";
+
+ info("timer %d: title %s", i++, title);
+ debug(" ref %s, lastRefresh %ld, interval %d", ref, lastRefresh, interval);
+
+ timers.Add(new cWebviTimer(nextID++, title, ref, this,
+ (time_t)lastRefresh, interval,
+ success, errmsg));
+
+ free(ref);
+ }
+}
+
+void cWebviTimerManager::LoadHistory(FILE *f) {
+ cReadLine rl;
+ char *line;
+
+ while ((line = rl.Read(f)))
+ refHistory.Append(strdup(line));
+
+ debug("loaded history: len = %d", refHistory.Size());
+}
+
+void cWebviTimerManager::SaveTimers(FILE *f, const char *version) {
+ // Format: space separated field in this order:
+ // lastUpdate interval lastSucceeded reference title
+
+ fprintf(f, "# WVTIMER1/%s\n", version);
+
+ cWebviTimer *t = timers.First();
+ while (t) {
+ if (fprintf(f, "%ld %d %d %s %s\n",
+ t->LastUpdate(), t->GetInterval(), t->Success(),
+ t->GetReference(), t->GetTitle()) < 0) {
+ error("Failed to save timer data!");
+ }
+
+ t = timers.Next(t);
+ }
+}
+
+void cWebviTimerManager::SaveHistory(FILE *f) {
+ int size = refHistory.Size();
+ int first;
+
+ if (size <= MAX_TIMER_HISTORY_SIZE)
+ first = 0;
+ else
+ first = size - MAX_TIMER_HISTORY_SIZE;
+
+ for (int i=first; i<size; i++) {
+ const char *ref = refHistory[i];
+ if (fwrite(ref, strlen(ref), 1, f) != 1 ||
+ fwrite("\n", 1, 1, f) != 1) {
+ error("Error while writing timer history");
+ break;
+ }
+ }
+}
+
+char *cWebviTimerManager::UpgradedTemplatePath(char *ref) {
+ // template names changed in 0.4.0
+ const char *templateNameMap[10][2] = \
+ {{"wvt:///youtube/", "wvt:///www.youtube.com/"},
+ {"wvt:///svtplay/", "wvt:///svtplay.se/"},
+ {"wvt:///moontv/", "wvt:///moontv.fi/"},
+ {"wvt:///metacafe/", "wvt:///www.metacafe.com/"},
+ {"wvt:///vimeo/", "wvt:///www.vimeo.com/"},
+ {"wvt:///katsomo/", "wvt:///www.katsomo.fi/"},
+ {"wvt:///ruutufi/", "wvt:///www.ruutu.fi/"},
+ {"wvt:///google/", "wvt:///video.google.com/"},
+ {"wvt:///yleareena/", "wvt:///areena.yle.fi/"}};
+
+ for (int i=0; i<10; i++) {
+ int oldlen = strlen(templateNameMap[i][0]);
+ if (strncmp(ref, templateNameMap[i][0], oldlen) == 0) {
+ int newlen = strlen(templateNameMap[i][1]) + strlen(ref);
+ char *newref = (char *)malloc((newlen+1)*sizeof(char));
+ strcpy(newref, templateNameMap[i][1]);
+ strcat(newref, ref+oldlen);
+ return newref;
+ }
+ }
+
+ return NULL;
+}
+
+void cWebviTimerManager::ConvertTimerHistoryTemplates() {
+ for (int i=0; i<refHistory.Size(); i++) {
+ char *oldref = refHistory[i];
+ char *newref = UpgradedTemplatePath(oldref);
+ if (!newref)
+ continue;
+
+ refHistory[i] = newref;
+ free(oldref);
+ }
+
+ modified = true;
+}
+
+bool cWebviTimerManager::Load(const char *path) {
+ FILE *f;
+ bool ok = true;
+
+ cString timersname = AddDirectory(path, "timers.dat");
+ f = fopen(timersname, "r");
+ if (f) {
+ debug("loading webvi timers from %s", (const char *)timersname);
+ LoadTimers(f);
+ fclose(f);
+ } else {
+ if (errno != ENOENT)
+ LOG_ERROR_STR("Can't load webvi timers");
+ ok = false;
+ }
+
+ cString historyname = AddDirectory(path, "timers.hst");
+ f = fopen(historyname, "r");
+ if (f) {
+ debug("loading webvi history from %s", (const char *)historyname);
+ LoadHistory(f);
+ fclose(f);
+ } else {
+ if (errno != ENOENT)
+ LOG_ERROR_STR("Can't load webvi timer history");
+ ok = false;
+ }
+
+ if (convertTemplatePaths)
+ ConvertTimerHistoryTemplates();
+
+ return ok;
+}
+
+bool cWebviTimerManager::Save(const char *path, const char *version) {
+ FILE *f;
+ bool ok = true;
+
+ if (!modified)
+ return true;
+ if (disableSaving) {
+ error("Not saving timers because the file format is unknown.");
+ return false;
+ }
+
+ cString timersname = AddDirectory(path, "timers.dat");
+ f = fopen(timersname, "w");
+ if (f) {
+ debug("saving webvi timers to %s", (const char *)timersname);
+ SaveTimers(f, version);
+ fclose(f);
+ } else {
+ LOG_ERROR_STR("Can't save webvi timers");
+ ok = false;
+ }
+
+ cString historyname = AddDirectory(path, "timers.hst");
+ f = fopen(historyname, "w");
+ if (f) {
+ debug("saving webvi timer history to %s", (const char *)historyname);
+ SaveHistory(f);
+ fclose(f);
+ } else {
+ LOG_ERROR_STR("Can't save webvi timer history");
+ ok = false;
+ }
+
+ modified = !ok;
+
+ return ok;
+}
+
+void cWebviTimerManager::Update() {
+ cWebviTimer *timer = timers.First();
+ if (!timer)
+ return;
+
+ time_t now = time(NULL);
+
+#ifdef DEBUG
+ char timestr[25];
+
+ strftime(timestr, 25, "%x %X", localtime(&now));
+ debug("Running webvi timers update at %s", timestr);
+#endif
+
+ while (timer) {
+ if (timer->NextUpdate() < now) {
+ debug("%d. %s: launching now",
+ timer->GetID(), timer->GetTitle());
+ timer->Execute();
+ } else {
+#ifdef DEBUG
+ time_t next = timer->NextUpdate();
+ strftime(timestr, 25, "%x %X", localtime(&next));
+ debug("%d. %s: next update at %s",
+ timer->GetID(), timer->GetTitle(), timestr);
+#endif
+ }
+
+ timer = timers.Next(timer);
+ }
+}
+
+cWebviTimer *cWebviTimerManager::GetByID(int id) const {
+ cWebviTimer *timer = timers.First();
+
+ while (timer) {
+ if (timer->GetID() == id)
+ return timer;
+
+ timer = timers.Next(timer);
+ }
+
+ return NULL;
+}
+
+cWebviTimer *cWebviTimerManager::Create(const char *title,
+ const char *ref,
+ bool getExisting) {
+ cWebviTimer *t;
+
+ if (!ref)
+ return NULL;
+
+ if (getExisting) {
+ t = timers.First();
+ while (t) {
+ if (strcmp(t->GetReference(), ref) == 0) {
+ return t;
+ }
+
+ t = timers.Next(t);
+ }
+ }
+
+ t = new cWebviTimer(nextID++, title, ref, this);
+ timers.Add(t);
+
+ modified = true;
+
+ return t;
+}
+
+void cWebviTimerManager::Remove(cWebviTimer *timer) {
+ timers.Del(timer);
+ modified = true;
+}
+
+void cWebviTimerManager::MarkDownloaded(const char *ref) {
+ if (!ref)
+ return;
+
+ if (refHistory.Find(ref) == -1) {
+ refHistory.Append(strdup(ref));
+ modified = true;
+ }
+}
+
+bool cWebviTimerManager::AlreadyDownloaded(const char *ref) {
+ return refHistory.Find(ref) != -1;
+}
diff --git a/src/vdr-plugin/timer.h b/src/vdr-plugin/timer.h
new file mode 100644
index 0000000..369fbce
--- /dev/null
+++ b/src/vdr-plugin/timer.h
@@ -0,0 +1,115 @@
+/*
+ * timer.h: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#ifndef __WEBVIDEO_TIMER_H
+#define __WEBVIDEO_TIMER_H
+
+#include <time.h>
+#include <stdio.h>
+#include <vdr/tools.h>
+#include "request.h"
+
+#define REQ_ID_TIMER -2
+#define DEFAULT_TIMER_INTERVAL 7*24*60*60
+#define RETRY_TIMER_INTERVAL 60*60
+#define MIN_TIMER_INTERVAL 10*60
+#define MAX_TIMER_HISTORY_SIZE 2000
+
+class cWebviTimerManager;
+
+// --- cWebviTimer -----------------------------------------------
+
+class cWebviTimer : public cListObject {
+private:
+ int id;
+ char *title;
+ char *reference;
+
+ time_t lastUpdate;
+ int interval;
+
+ bool running;
+ cStringList activeStreams;
+ bool lastSucceeded;
+ char *lastError;
+
+ cWebviTimerManager *parent;
+
+public:
+ cWebviTimer(int ID, const char *title, const char *ref,
+ cWebviTimerManager *manager,
+ time_t last=0, int interval=DEFAULT_TIMER_INTERVAL,
+ bool success=true, const char *errmsg=NULL);
+ ~cWebviTimer();
+
+ int GetID() const { return id; }
+ void SetTitle(const char *newTitle);
+ const char *GetTitle() const { return title; }
+ void SetInterval(int interval);
+ int GetInterval() const;
+ const char *GetReference() const { return reference; }
+
+ time_t LastUpdate() const { return lastUpdate; }
+ time_t NextUpdate() const;
+
+ void SetError(const char *errmsg);
+ bool Success() const { return lastSucceeded; }
+ const char *LastError() const;
+
+ void Execute();
+ bool Running() { return running; }
+ void DownloadStreams(const char *menuxml, cProgressVector& summaries);
+ void CheckFailed(const char *errmsg);
+ void RequestFinished(const char *ref, const char *errmsg);
+};
+
+// --- cWebviTimerManager ----------------------------------------
+
+class cWebviTimerManager {
+private:
+ cList<cWebviTimer> timers;
+ int nextID;
+ cStringList refHistory;
+ bool modified;
+ bool disableSaving;
+ bool convertTemplatePaths;
+
+ cWebviTimerManager();
+ ~cWebviTimerManager() {};
+ cWebviTimerManager(const cWebviTimerManager &); // intentionally undefined
+ cWebviTimerManager &operator=(const cWebviTimerManager &); // intentionally undefined
+
+ void LoadTimers(FILE *f);
+ void LoadHistory(FILE *f);
+ void SaveTimers(FILE *f, const char *version);
+ void SaveHistory(FILE *f);
+
+ char *UpgradedTemplatePath(char *ref);
+ void ConvertTimerHistoryTemplates();
+
+public:
+ static cWebviTimerManager &Instance();
+
+ bool Load(const char *path);
+ bool Save(const char *path, const char *version);
+
+ cWebviTimer *Create(const char *title, const char *reference,
+ bool getExisting=true);
+ void Remove(cWebviTimer *timer);
+ cWebviTimer *First() const { return timers.First(); }
+ cWebviTimer *Next(const cWebviTimer *cur) const { return timers.Next(cur); }
+ cWebviTimer *GetLinear(int idx) const { return timers.Get(idx); }
+ cWebviTimer *GetByID(int id) const;
+ void SetModified() { modified = true; }
+
+ void Update();
+ void MarkDownloaded(const char *ref);
+ bool AlreadyDownloaded(const char *ref);
+};
+
+#endif
diff --git a/src/vdr-plugin/webvideo.c b/src/vdr-plugin/webvideo.c
new file mode 100644
index 0000000..4f3cf68
--- /dev/null
+++ b/src/vdr-plugin/webvideo.c
@@ -0,0 +1,516 @@
+/*
+ * webvideo.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <getopt.h>
+#include <time.h>
+#include <vdr/plugin.h>
+#include <vdr/tools.h>
+#include <vdr/videodir.h>
+#include <vdr/i18n.h>
+#include <vdr/skins.h>
+#include <libwebvi.h>
+#include "menu.h"
+#include "history.h"
+#include "download.h"
+#include "request.h"
+#include "mimetypes.h"
+#include "config.h"
+#include "player.h"
+#include "common.h"
+#include "timer.h"
+
+const char *VERSION = "0.5.0";
+static const char *DESCRIPTION = trNOOP("Download video files from the web");
+static const char *MAINMENUENTRY = "Webvideo";
+cMimeTypes *MimeTypes = NULL;
+
+class cPluginWebvideo : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+ cHistory history;
+ cProgressVector summaries;
+ cString templatedir;
+ cString destdir;
+ cString conffile;
+ cString postprocesscmd;
+ bool prefermplayer;
+ bool vfatnames;
+
+ static int nextMenuID;
+
+ void UpdateOSDFromHistory(const char *statusmsg=NULL);
+ void UpdateStatusMenu(bool force=false);
+ bool StartStreaming(const cString &streamurl);
+ void ExecuteTimers(void);
+ void HandleFinishedRequests(void);
+ cString CreateWvtRef(const char *url);
+
+public:
+ cPluginWebvideo(void);
+ virtual ~cPluginWebvideo();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return tr(DESCRIPTION); }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+ };
+
+int cPluginWebvideo::nextMenuID = 1;
+
+cPluginWebvideo::cPluginWebvideo(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!
+ prefermplayer = false;
+ vfatnames = false;
+}
+
+cPluginWebvideo::~cPluginWebvideo()
+{
+ // Clean up after yourself!
+ webvi_cleanup(0);
+}
+
+const char *cPluginWebvideo::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return " -d DIR, --downloaddir=DIR Save downloaded files to DIR\n" \
+ " -t DIR, --templatedir=DIR Read video site templates from DIR\n" \
+ " -c FILE, --conf=FILE Load settings from FILE\n" \
+ " -p CMD, --postprocess=CMD Execute CMD after downloading\n" \
+ " --vfat Generate Windows compatible filenames\n" \
+ " -m, --prefermplayer Prefer mplayer over xineliboutput when streaming\n";
+}
+
+bool cPluginWebvideo::ProcessArgs(int argc, char *argv[])
+{
+ // Implement command line argument processing here if applicable.
+ static struct option long_options[] = {
+ { "downloaddir", required_argument, NULL, 'd' },
+ { "templatedir", required_argument, NULL, 't' },
+ { "conf", required_argument, NULL, 'c' },
+ { "postprocess", required_argument, NULL, 'p' },
+ { "prefermplayer", no_argument, NULL, 'm' },
+ { "vfat", no_argument, NULL, 'v' },
+ { NULL }
+ };
+
+ int c;
+ while ((c = getopt_long(argc, argv, "d:t:c:p:mv", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ destdir = cString(optarg);
+ break;
+ case 't':
+ templatedir = cString(optarg);
+ break;
+ case 'c':
+ conffile = cString(optarg);
+ break;
+ case 'p':
+ postprocesscmd = cString(optarg);
+ break;
+ case 'm':
+ prefermplayer = true;
+ break;
+ case 'v':
+ vfatnames = true;
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cPluginWebvideo::Initialize(void)
+{
+ // Initialize any background activities the plugin shall perform.
+
+ // Test that run-time and compile-time libxml versions are compatible
+ LIBXML_TEST_VERSION;
+
+ // default values if not given on the command line
+ if ((const char *)destdir == NULL)
+ webvideoConfig->SetDownloadPath(cString(VideoDirectory));
+ if ((const char *)conffile == NULL)
+ conffile = AddDirectory(ConfigDirectory(Name()), "webvi.plugin.conf");
+
+ webvideoConfig->ReadConfigFile(conffile);
+
+ if ((const char *)destdir)
+ webvideoConfig->SetDownloadPath(destdir);
+ if ((const char *)templatedir)
+ webvideoConfig->SetTemplatePath(templatedir);
+ if ((const char *)postprocesscmd)
+ webvideoConfig->SetPostProcessCmd(postprocesscmd);
+ if (prefermplayer)
+ webvideoConfig->SetPreferXineliboutput(false);
+ if (vfatnames)
+ webvideoConfig->SetUseVFATNames(vfatnames);
+
+ cString mymimetypes = AddDirectory(ConfigDirectory(Name()), "mime.types");
+ const char *mimefiles [] = {"/etc/mime.types", (const char *)mymimetypes, NULL};
+ MimeTypes = new cMimeTypes(mimefiles);
+
+ if (webvi_global_init() != 0) {
+ error("Failed to initialize libwebvi");
+ return false;
+ }
+
+ cWebviTimerManager::Instance().Load(ConfigDirectory(Name()));
+
+ cWebviThread::Instance().SetTemplatePath(webvideoConfig->GetTemplatePath());
+
+ return true;
+}
+
+bool cPluginWebvideo::Start(void)
+{
+ // Start any background activities the plugin shall perform.
+ cWebviThread::Instance().Start();
+
+ return true;
+}
+
+void cPluginWebvideo::Stop(void)
+{
+ // Stop any background activities the plugin shall perform.
+ cWebviThread::Instance().Stop();
+ delete MimeTypes;
+
+ cWebviTimerManager::Instance().Save(ConfigDirectory(Name()), Version());
+
+ xmlCleanupParser();
+}
+
+void cPluginWebvideo::Housekeeping(void)
+{
+ // Perform any cleanup or other regular tasks.
+
+ cWebviTimerManager::Instance().Save(ConfigDirectory(Name()), Version());
+}
+
+void cPluginWebvideo::MainThreadHook(void)
+{
+ // Perform actions in the context of the main program thread.
+ // WARNING: Use with great care - see PLUGINS.html!
+ ExecuteTimers();
+
+ HandleFinishedRequests();
+}
+
+void cPluginWebvideo::ExecuteTimers(void)
+{
+ static int counter = 0;
+
+ // don't do this too often
+ if (counter++ > 1800) {
+ cWebviTimerManager::Instance().Update();
+ counter = 0;
+ }
+}
+
+void cPluginWebvideo::HandleFinishedRequests(void)
+{
+ bool forceStatusUpdate = false;
+ cMenuRequest *req;
+ cFileDownloadRequest *dlreq;
+ cString streamurl;
+ cWebviTimer *timer;
+ cString timermsg;
+
+ while ((req = cWebviThread::Instance().GetFinishedRequest())) {
+ int cid = -1;
+ int code = req->GetStatusCode();
+ if (history.Current()) {
+ cid = history.Current()->GetID();
+ }
+
+ debug("Finished request: %d (current: %d), type = %d, status = %d",
+ req->GetID(), cid, req->GetType(), code);
+
+ if (req->Success()) {
+ switch (req->GetType()) {
+ case REQT_MENU:
+ // Only change the menu if the request was launched from the
+ // current menu.
+ if (req->GetID() == cid) {
+ if (cid == 0) {
+ // Special case: replace the placeholder menu
+ history.Clear();
+ }
+
+ if (history.Current())
+ history.Current()->RememberSelected(menuPointers.navigationMenu->Current());
+ history.TruncateAndAdd(new cHistoryObject(req->GetResponse(),
+ req->GetReference(),
+ nextMenuID++));
+ UpdateOSDFromHistory();
+ }
+ break;
+
+ case REQT_STREAM:
+ streamurl = req->GetResponse();
+ if (streamurl[0] == '\0')
+ Skins.Message(mtError, tr("Streaming failed: no URL"));
+ else if (strncmp(streamurl, "wvt://", 6) == 0)
+ Skins.Message(mtError, tr("Streaming not supported, try downloading"));
+ else if (!StartStreaming(streamurl))
+ Skins.Message(mtError, tr("Failed to launch media player"));
+ break;
+
+ case REQT_FILE:
+ dlreq = dynamic_cast<cFileDownloadRequest *>(req);
+
+ if (dlreq) {
+ for (int i=0; i<summaries.Size(); i++) {
+ if (summaries[i]->GetRequest() == dlreq) {
+ delete summaries[i];
+ summaries.Remove(i);
+ break;
+ }
+ }
+ }
+
+ timermsg = cString("");
+ if (req->GetTimer()) {
+ req->GetTimer()->RequestFinished(req->GetReference(), NULL);
+
+ timermsg = cString::sprintf(" (%s)", tr("timer"));
+ }
+
+ Skins.Message(mtInfo, cString::sprintf(tr("One download completed, %d remains%s"),
+ cWebviThread::Instance().GetUnfinishedCount(),
+ (const char *)timermsg));
+ forceStatusUpdate = true;
+ break;
+
+ case REQT_TIMER:
+ timer = req->GetTimer();
+ if (timer)
+ timer->DownloadStreams(req->GetResponse(), summaries);
+ break;
+
+ default:
+ break;
+ }
+ } else { // failed request
+ if (req->GetType() == REQT_TIMER) {
+ warning("timer request failed (%d: %s)",
+ code, (const char*)req->GetStatusPharse());
+
+ timer = req->GetTimer();
+ if (timer)
+ timer->CheckFailed(req->GetStatusPharse());
+ } else {
+ warning("request failed (%d: %s)",
+ code, (const char*)req->GetStatusPharse());
+
+ if (code == -2 || code == 402)
+ Skins.Message(mtError, tr("Download aborted"));
+ else
+ Skins.Message(mtError, cString::sprintf(tr("Download failed (error = %d)"), code));
+
+ dlreq = dynamic_cast<cFileDownloadRequest *>(req);
+ if (dlreq) {
+ for (int i=0; i<summaries.Size(); i++) {
+ if (summaries[i]->GetRequest() == dlreq) {
+ summaries[i]->AssociateWith(NULL);
+ break;
+ }
+ }
+ }
+
+ if (req->GetTimer())
+ req->GetTimer()->RequestFinished(req->GetReference(),
+ (const char*)req->GetStatusPharse());
+
+ forceStatusUpdate = true;
+ }
+ }
+
+ delete req;
+ }
+
+ UpdateStatusMenu(forceStatusUpdate);
+}
+
+cString cPluginWebvideo::Active(void)
+{
+ // Return a message string if shutdown should be postponed
+ int c = cWebviThread::Instance().GetUnfinishedCount();
+ if (c > 0)
+ return cString::sprintf(tr("%d downloads not finished"), c);
+ else
+ return NULL;
+}
+
+cOsdObject *cPluginWebvideo::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ const char *mainMenuReference = "wvt:///?srcurl=mainmenu";
+ const char *placeholderMenu = "<wvmenu><title>Webvideo</title></wvmenu>";
+ const char *statusmsg = NULL;
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100*1000*1000; // 100 ms
+
+ menuPointers.navigationMenu = new cNavigationMenu(&history, summaries);
+
+ cHistoryObject *hist = history.Home();
+ if (!hist) {
+ cWebviThread::Instance().AddRequest(new cMenuRequest(0, mainMenuReference));
+ cHistoryObject *placeholder = new cHistoryObject(placeholderMenu, mainMenuReference, 0);
+ history.TruncateAndAdd(placeholder);
+
+ // The main menu response should come right away. Try to update
+ // the menu here without having to wait for the next
+ // MainThreadHook call by VDR main loop.
+ for (int i=0; i<4; i++) {
+ nanosleep(&ts, NULL);
+ HandleFinishedRequests();
+ if (history.Current() != placeholder) {
+ return menuPointers.navigationMenu;
+ }
+ };
+
+ statusmsg = tr("Retrieving...");
+ }
+
+ UpdateOSDFromHistory(statusmsg);
+ return menuPointers.navigationMenu;
+}
+
+cMenuSetupPage *cPluginWebvideo::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return NULL;
+}
+
+bool cPluginWebvideo::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+bool cPluginWebvideo::Service(const char *Id, void *Data)
+{
+ // Handle custom service requests from other plugins
+ return false;
+}
+
+const char **cPluginWebvideo::SVDRPHelpPages(void)
+{
+ static const char *HelpPages[] = {
+ "PLAY <url>\n"
+ " Stream a media file embedded on web page at <url>.",
+ "DWLD <url>\n"
+ " Download a media file embedded on web page at <url>.",
+ NULL
+ };
+ return HelpPages;
+}
+
+cString cPluginWebvideo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+ if(strcasecmp(Command, "PLAY") == 0 || strcasecmp(Command, "DWLD") == 0) {
+ if(*Option) {
+ debug("SVDRP(%s, %s)", Command, Option);
+ cString twvtref = CreateWvtRef(Option);
+ if (strcmp(twvtref, "") != 0) {
+ cMenuRequest *req;
+ if (strcasecmp(Command, "PLAY") == 0)
+ req = new cStreamUrlRequest(0, twvtref);
+ else
+ req = new cFileDownloadRequest(0, twvtref, summaries.NewDownload());
+ cWebviThread::Instance().AddRequest(req);
+ ReplyCode = 250; // Ok
+ return cString("Downloading video file");
+ } else {
+ ReplyCode = 550; // Requested action not taken
+ return cString("Unable to parse URL");
+ }
+ } else {
+ ReplyCode = 550; // Requested action not taken
+ return cString("File name missing");
+ }
+ }
+
+ return NULL;
+}
+
+cString cPluginWebvideo::CreateWvtRef(const char *url) {
+ cString domain = parseDomain(url);
+ if (strcmp(domain, "") == 0)
+ return "";
+
+ char *encoded = URLencode(url);
+ cString res = cString::sprintf("wvt:///%s/videopage.xsl?srcurl=%s",
+ (const char *)domain, encoded);
+ free(encoded);
+ return res;
+}
+
+void cPluginWebvideo::UpdateOSDFromHistory(const char *statusmsg) {
+ if (menuPointers.navigationMenu) {
+ cHistoryObject *hist = history.Current();
+ menuPointers.navigationMenu->Populate(hist, statusmsg);
+ menuPointers.navigationMenu->Display();
+ } else {
+ debug("OSD is not ours.");
+ }
+}
+
+void cPluginWebvideo::UpdateStatusMenu(bool force) {
+ if (menuPointers.statusScreen &&
+ (force || menuPointers.statusScreen->NeedsUpdate())) {
+ menuPointers.statusScreen->Update();
+ }
+}
+
+bool cPluginWebvideo::StartStreaming(const cString &streamurl) {
+ cMediaPlayer *players[2];
+
+ if (webvideoConfig->GetPreferXineliboutput()) {
+ players[0] = new cXineliboutputPlayer();
+ players[1] = new cMPlayerPlayer();
+ } else {
+ players[0] = new cMPlayerPlayer();
+ players[1] = new cXineliboutputPlayer();
+ }
+
+ bool ret = false;
+ for (int i=0; i<2; i++) {
+ if (players[i]->Launch(streamurl)) {
+ ret = true;
+ break;
+ }
+ }
+
+ for (int i=0; i<2 ; i++) {
+ delete players[i];
+ }
+
+ return ret;
+}
+
+VDRPLUGINCREATOR(cPluginWebvideo); // Don't touch this!