summaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorlouis <louis.braun@gmx.de>2016-01-26 18:32:38 +0100
committerlouis <louis.braun@gmx.de>2016-01-26 18:32:38 +0100
commit809fbda03c5014ba9cd361f5113d1d717cd41ea6 (patch)
tree264bbc5640375f1bcb165fc7f4a3e595adcc26ca /extensions
parent196dd7eb9965a405bb16b51dc870fbbb31aeef87 (diff)
downloadvdr-plugin-skindesigner-809fbda03c5014ba9cd361f5113d1d717cd41ea6.tar.gz
vdr-plugin-skindesigner-809fbda03c5014ba9cd361f5113d1d717cd41ea6.tar.bz2
Version 0.8.0 beta
Diffstat (limited to 'extensions')
-rw-r--r--extensions/cairoimage.c100
-rw-r--r--extensions/cairoimage.h27
-rw-r--r--extensions/curlfuncs.c237
-rw-r--r--extensions/curlfuncs.h45
-rw-r--r--extensions/extrecinfo.c96
-rw-r--r--extensions/extrecinfo.h36
-rw-r--r--extensions/fontmanager.c181
-rw-r--r--extensions/fontmanager.h35
-rw-r--r--extensions/helpers.c240
-rw-r--r--extensions/helpers.h50
-rw-r--r--extensions/imagecache.c579
-rw-r--r--extensions/imagecache.h79
-rw-r--r--extensions/imageloader.c507
-rw-r--r--extensions/imageloader.h115
-rw-r--r--extensions/libxmlwrapper.c189
-rw-r--r--extensions/libxmlwrapper.h46
-rw-r--r--extensions/pluginmanager.c301
-rw-r--r--extensions/pluginmanager.h71
-rw-r--r--extensions/recfolderinfo.c219
-rw-r--r--extensions/recfolderinfo.h43
-rw-r--r--extensions/scrapmanager.c359
-rw-r--r--extensions/scrapmanager.h28
-rw-r--r--extensions/skinrepo.c375
-rw-r--r--extensions/skinrepo.h98
-rw-r--r--extensions/skinsetup.c377
-rw-r--r--extensions/skinsetup.h102
-rw-r--r--extensions/timers.c121
-rw-r--r--extensions/timers.h24
28 files changed, 4680 insertions, 0 deletions
diff --git a/extensions/cairoimage.c b/extensions/cairoimage.c
new file mode 100644
index 0000000..0fc6879
--- /dev/null
+++ b/extensions/cairoimage.c
@@ -0,0 +1,100 @@
+#include "cairoimage.h"
+
+cCairoImage::cCairoImage(void) {
+ surface = NULL;
+ cr = NULL;
+}
+
+cCairoImage::~cCairoImage() {
+ if (cr)
+ cairo_destroy (cr);
+ if (surface)
+ cairo_surface_destroy (surface);
+}
+
+void cCairoImage::InitCairoImage(int width, int height) {
+ this->width = width;
+ this->height = height;
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ cr = cairo_create(surface);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_SUBPIXEL);
+}
+
+void cCairoImage::DrawTextVertical(string text, tColor color, string font, int size, int direction) {
+
+ int imgHeight = GetTextWidth(text, font, size);
+ InitCairoImage(size * 1.2, imgHeight);
+
+ SetColor(color);
+ cairo_font_weight_t fontWeight = CAIRO_FONT_WEIGHT_NORMAL;
+ cairo_font_slant_t fontSlant = CAIRO_FONT_SLANT_NORMAL;
+ cairo_select_font_face (cr, font.c_str(), fontSlant, fontWeight);
+ cairo_set_font_size (cr, size);
+ int x = size;
+ int y = imgHeight;
+ double rotate = 3*M_PI/2;
+ if (direction == (int)eDirection::topdown) {
+ rotate = M_PI/2;
+ x = size*0.3;
+ y = 0;
+ }
+ cairo_move_to (cr, x, y);
+ cairo_rotate(cr, rotate);
+ cairo_show_text (cr, text.c_str());
+}
+
+cImage *cCairoImage::GetImage(void) {
+ if (!cr || !surface)
+ return NULL;
+
+ unsigned char *data = cairo_image_surface_get_data(surface);
+ cImage *image = new cImage(cSize(width, height), (tColor*)data);
+ return image;
+}
+
+/**********************************************************************************
+* Private Functions
+**********************************************************************************/
+
+int cCairoImage::GetTextWidth(string text, string font, int size) {
+ cairo_surface_t *tmpSurface;
+ cairo_t *tmpCr;
+
+ double width = 300;
+ double height = (double)size *1.3;
+
+ cairo_font_weight_t fontWeight = CAIRO_FONT_WEIGHT_NORMAL;
+ cairo_font_slant_t fontSlant = CAIRO_FONT_SLANT_NORMAL;
+
+ tmpSurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ tmpCr = cairo_create (tmpSurface);
+
+ cairo_select_font_face (tmpCr, font.c_str(), fontSlant, fontWeight);
+ cairo_set_font_size (tmpCr, size);
+
+ cairo_text_extents_t te;
+ cairo_text_extents (tmpCr, text.c_str(), &te);
+ int textWidth = te.width;
+
+ cairo_destroy (tmpCr);
+ cairo_surface_destroy (tmpSurface);
+
+ return (double)textWidth * 1.1;
+}
+
+void cCairoImage::SetColor(tColor color) {
+ if (!cr || !surface)
+ return;
+ tIndex tAlpha = (color & 0xFF000000) >> 24;
+ tIndex tRed = (color & 0x00FF0000) >> 16;
+ tIndex tGreen = (color & 0x0000FF00) >> 8;
+ tIndex tBlue = (color & 0x000000FF);
+
+ double a = (int)tAlpha / (double)255;
+ double r = (int)tRed / (double)255;
+ double g = (int)tGreen / (double)255;
+ double b = (int)tBlue / (double)255;
+
+ cairo_set_source_rgba(cr, r, g, b, a);
+}
diff --git a/extensions/cairoimage.h b/extensions/cairoimage.h
new file mode 100644
index 0000000..4d4788d
--- /dev/null
+++ b/extensions/cairoimage.h
@@ -0,0 +1,27 @@
+#ifndef __CAIROIMAGE_H
+#define __CAIROIMAGE_H
+
+#include <cairo.h>
+#include <vdr/osd.h>
+#include <string>
+#include <sstream>
+#include "../coreengine/definitions.h"
+
+using namespace std;
+
+class cCairoImage {
+private:
+ int width;
+ int height;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ void SetColor(tColor color);
+ int GetTextWidth(string text, string font, int size);
+public:
+ cCairoImage(void);
+ virtual ~cCairoImage();
+ void InitCairoImage(int width, int height);
+ void DrawTextVertical(string text, tColor color, string font, int size, int direction);
+ cImage *GetImage(void);
+};
+#endif //__CAIROIMAGE_H \ No newline at end of file
diff --git a/extensions/curlfuncs.c b/extensions/curlfuncs.c
new file mode 100644
index 0000000..c6dfee5
--- /dev/null
+++ b/extensions/curlfuncs.c
@@ -0,0 +1,237 @@
+/*
+Copyright (c) 2002, Mayukh Bose
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Mayukh Bose nor the names of other
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ Change History:
+ 11/23/2004 - Removed the #include <unistd.h> line because I didn't
+ need it. Wonder why I had it there in the first place :).
+ 10/20/2004 - Publicly released this code.
+*/
+#include <string>
+#include <cstdio>
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <vdr/tools.h>
+#include "curlfuncs.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+using namespace std;
+
+// Local function prototypes
+int CurlDoPost(const char *url, string *sOutput, const string &sReferer,
+ struct curl_httppost *formpost, struct curl_slist *headerlist);
+
+namespace curlfuncs {
+ string sBuf;
+ bool bInitialized = false;
+ CURL *curl = NULL;
+}
+
+size_t collect_data(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ string sTmp;
+ register size_t actualsize = size * nmemb;
+ if ((FILE *)stream == NULL) {
+ sTmp.assign((char *)ptr, actualsize);
+ curlfuncs::sBuf += sTmp;
+ }
+ else {
+ fwrite(ptr, size, nmemb, (FILE *)stream);
+ }
+ return actualsize;
+}
+
+inline void InitCurlLibraryIfNeeded()
+{
+ if (!curlfuncs::bInitialized) {
+ curl_global_init(CURL_GLOBAL_ALL);
+ curlfuncs::curl = curl_easy_init();
+ if (!curlfuncs::curl)
+ throw string("Could not create new curl instance");
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_NOPROGRESS, 1); // Do not show progress
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEFUNCTION, collect_data);
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_FOLLOWLOCATION, TRUE);
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Mayukh's libcurl wrapper http://www.mayukhbose.com/)");
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_TIMEOUT, 5L);
+ curlfuncs::bInitialized = true;
+ }
+}
+
+int CurlGetUrl(const char *url, string *sOutput, const string &sReferer)
+{
+ InitCurlLibraryIfNeeded();
+
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url); // Set the URL to get
+ if (sReferer != "")
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str());
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPGET, TRUE);
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ curlfuncs::sBuf = "";
+ if (curl_easy_perform(curlfuncs::curl) == 0)
+ *sOutput = curlfuncs::sBuf;
+ else {
+ // We have an error here mate!
+ *sOutput = "";
+ return 0;
+ }
+
+ return 1;
+}
+
+int CurlGetUrlFile(const char *url, const char *filename, const string &sReferer)
+{
+ int nRet = 0;
+ InitCurlLibraryIfNeeded();
+
+ // Point the output to a file
+ FILE *fp;
+ if ((fp = fopen(filename, "w")) == NULL)
+ return 0;
+
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, fp); // Set option to write to file
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url); // Set the URL to get
+ if (sReferer != "")
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str());
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPGET, TRUE);
+ if (curl_easy_perform(curlfuncs::curl) == 0)
+ nRet = 1;
+ else
+ nRet = 0;
+
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, NULL); // Set option back to default (string)
+ fclose(fp);
+ return nRet;
+}
+
+int CurlPostUrl(const char *url, const string &sPost, string *sOutput, const string &sReferer)
+{
+ InitCurlLibraryIfNeeded();
+
+ int retval = 1;
+ string::size_type nStart = 0, nEnd, nPos;
+ string sTmp, sName, sValue;
+ struct curl_httppost *formpost=NULL;
+ struct curl_httppost *lastptr=NULL;
+ struct curl_slist *headerlist=NULL;
+
+ // Add the POST variables here
+ while ((nEnd = sPost.find("##", nStart)) != string::npos) {
+ sTmp = sPost.substr(nStart, nEnd - nStart);
+ if ((nPos = sTmp.find("=")) == string::npos)
+ return 0;
+ sName = sTmp.substr(0, nPos);
+ sValue = sTmp.substr(nPos+1);
+ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, sName.c_str(), CURLFORM_COPYCONTENTS, sValue.c_str(), CURLFORM_END);
+ nStart = nEnd + 2;
+ }
+ sTmp = sPost.substr(nStart);
+ if ((nPos = sTmp.find("=")) == string::npos)
+ return 0;
+ sName = sTmp.substr(0, nPos);
+ sValue = sTmp.substr(nPos+1);
+ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, sName.c_str(), CURLFORM_COPYCONTENTS, sValue.c_str(), CURLFORM_END);
+
+ retval = CurlDoPost(url, sOutput, sReferer, formpost, headerlist);
+
+ curl_formfree(formpost);
+ curl_slist_free_all(headerlist);
+ return retval;
+}
+
+int CurlPostRaw(const char *url, const string &sPost, string *sOutput, const string &sReferer)
+{
+ InitCurlLibraryIfNeeded();
+
+ int retval;
+ struct curl_httppost *formpost=NULL;
+ struct curl_slist *headerlist=NULL;
+
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_POSTFIELDS, sPost.c_str());
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_POSTFIELDSIZE, 0); //FIXME: Should this be the size instead, in case this is binary string?
+
+ retval = CurlDoPost(url, sOutput, sReferer, formpost, headerlist);
+
+ curl_formfree(formpost);
+ curl_slist_free_all(headerlist);
+ return retval;
+}
+
+int CurlDoPost(const char *url, string *sOutput, const string &sReferer,
+ struct curl_httppost *formpost, struct curl_slist *headerlist)
+{
+ headerlist = curl_slist_append(headerlist, "Expect:");
+
+ // Now do the form post
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url);
+ if (sReferer != "")
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str());
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPPOST, formpost);
+
+ curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ curlfuncs::sBuf = "";
+ if (curl_easy_perform(curlfuncs::curl) == 0) {
+ *sOutput = curlfuncs::sBuf;
+ return 1;
+ }
+ else {
+ // We have an error here mate!
+ *sOutput = "";
+ return 0;
+ }
+}
+
+void FreeCurlLibrary(void)
+{
+ if (curlfuncs::curl)
+ curl_easy_cleanup(curlfuncs::curl);
+ curl_global_cleanup();
+ curlfuncs::bInitialized = false;
+}
+
+int CurlSetCookieFile(char *filename)
+{
+ InitCurlLibraryIfNeeded();
+ if (curl_easy_setopt(curlfuncs::curl, CURLOPT_COOKIEFILE, filename) != 0)
+ return 0;
+ if (curl_easy_setopt(curlfuncs::curl, CURLOPT_COOKIEJAR, filename) != 0)
+ return 0;
+ return 1;
+}
+
+char *CurlEscape(const char *url) {
+ InitCurlLibraryIfNeeded();
+ return curl_escape(url , strlen(url));
+}
diff --git a/extensions/curlfuncs.h b/extensions/curlfuncs.h
new file mode 100644
index 0000000..e0f76cf
--- /dev/null
+++ b/extensions/curlfuncs.h
@@ -0,0 +1,45 @@
+/*
+Copyright (c) 2002, Mayukh Bose
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Mayukh Bose nor the names of other
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __CURLFUNCS_H_20020513__
+#define __CURLFUNCS_H_20020513__
+#include <string>
+ using namespace std;
+
+int CurlGetUrl(const char *url, string *sOutput, const string &sReferer="");
+int CurlGetUrlFile(const char *url, const char *filename, const string &sReferer="");
+void FreeCurlLibrary(void);
+int CurlSetCookieFile(char *filename);
+int CurlPostUrl(const char *url, const string &sPost, string *sOutput, const string &sReferer = "");
+int CurlPostRaw(const char *url, const string &sPost, string *sOutput, const string &sReferer = "");
+char *CurlEscape(const char *url);
+#endif
diff --git a/extensions/extrecinfo.c b/extensions/extrecinfo.c
new file mode 100644
index 0000000..435d43b
--- /dev/null
+++ b/extensions/extrecinfo.c
@@ -0,0 +1,96 @@
+#include "../config.h"
+#include "helpers.h"
+#include "extrecinfo.h"
+
+cExtRecInfo::cExtRecInfo(const char *xml) {
+ this->xml = xml;
+}
+
+cExtRecInfo::~cExtRecInfo(void) {
+
+}
+
+bool cExtRecInfo::Parse(void) {
+ //read media info
+ string mediaInfoXml;
+ StripXmlTag(xml, mediaInfoXml, "mediainfo");
+ if (mediaInfoXml.size() == 0) {
+ return false;
+ }
+ StripXmlTag(mediaInfoXml, resWidth, "res_width");
+ StripXmlTag(mediaInfoXml, resHeight, "res_height");
+ resString = GetScreenResolutionString(resWidth, resHeight, &isHD);
+ StripXmlTag(mediaInfoXml, aspectratio, "aspectratio");
+ isWideScreen = !aspectratio.compare("16:9");
+ StripXmlTag(mediaInfoXml, codec, "codec");
+ StripXmlTag(mediaInfoXml, format, "format");
+ StripXmlTag(mediaInfoXml, framerate, "framerate");
+ StripXmlTag(mediaInfoXml, interlace, "interlace");
+
+ size_t found = 0;
+ isDolby = false;
+ do {
+ string track;
+ found = StripXmlTag(mediaInfoXml, track, "track", found);
+ if (found == string::npos)
+ break;
+ tAudioTrack sTrack;
+ StripXmlTag(track, sTrack.codec, "codec");
+ StripXmlTag(track, sTrack.bitrate, "bitrate");
+ StripXmlTag(track, sTrack.language, "language");
+ if (!sTrack.codec.compare("AC-3"))
+ isDolby = true;
+ tracks.push_back(sTrack);
+ } while (found != string::npos);
+
+ return true;
+}
+
+//get content of <tag> ... </tag> inside xml
+size_t cExtRecInfo::StripXmlTag(string &xmlstring, string &content, const char *tag, int start) {
+ // set the search strings
+ stringstream strStart, strStop;
+ strStart << "<" << tag << ">";
+ strStop << "</" << tag << ">";
+ // find the strings
+ size_t locStart = xmlstring.find(strStart.str(), start);
+ size_t locStop = xmlstring.find(strStop.str(), start);
+ if (locStart == string::npos || locStop == string::npos)
+ return string::npos;
+ // extract relevant text
+ int pos = locStart + strStart.str().size();
+ int len = locStop - pos;
+
+ content = (len < 0) ? "" : xmlstring.substr(pos, len);
+ return locStop + strStop.str().size();
+}
+
+size_t cExtRecInfo::StripXmlTag(string &xmlstring, int &content, const char *tag, int start) {
+ // set the search strings
+ stringstream strStart, strStop;
+ strStart << "<" << tag << ">";
+ strStop << "</" << tag << ">";
+ // find the strings
+ size_t locStart = xmlstring.find(strStart.str(), start);
+ size_t locStop = xmlstring.find(strStop.str(), start);
+ if (locStart == string::npos || locStop == string::npos)
+ return string::npos;
+ // extract relevant text
+ int pos = locStart + strStart.str().size();
+ int len = locStop - pos;
+
+ string value = (len < 0) ? "" : xmlstring.substr(pos, len);
+ content = atoi(value.c_str());
+ return locStop + strStop.str().size();
+}
+
+void cExtRecInfo::Debug(void) {
+ dsyslog("skindesigner: extRecInfo xml: %s", xml.c_str());
+ dsyslog("skindesigner: : res_width %d, res_height %d, res %s, aspectratio %s, codec %s, format %s, framerate %s, interlace %s, hd %s, widescreen %s",
+ resWidth, resHeight, resString.c_str(), aspectratio.c_str(), codec.c_str(), format.c_str(), framerate.c_str(), interlace.c_str(),
+ isHD ? "true": "false", isWideScreen ? "true" : "false");
+ int numTrack = 1;
+ for (vector<tAudioTrack>::iterator it = tracks.begin(); it != tracks.end(); it++) {
+ dsyslog("skindesigner: audio track %d, codec %s, bitrate %s, language: %s", numTrack++, (*it).codec.c_str(), (*it).bitrate.c_str(), (*it).language.c_str());
+ }
+}
diff --git a/extensions/extrecinfo.h b/extensions/extrecinfo.h
new file mode 100644
index 0000000..9fb5bab
--- /dev/null
+++ b/extensions/extrecinfo.h
@@ -0,0 +1,36 @@
+#ifndef __EXTRECINFO_H
+#define __EXTRECINFO_H
+
+#include <vdr/recording.h>
+
+struct tAudioTrack {
+ string codec;
+ string bitrate;
+ string language;
+};
+
+class cExtRecInfo {
+private:
+ string xml;
+ size_t StripXmlTag(string &xmlstring, string &content, const char *tag, int start = 0);
+ size_t StripXmlTag(string &xmlstring, int &content, const char *tag, int start = 0);
+public:
+ cExtRecInfo(const char *xml);
+ ~cExtRecInfo(void);
+ bool Parse(void);
+ void Debug(void);
+ int resWidth;
+ int resHeight;
+ string resString;
+ bool isHD;
+ string aspectratio;
+ bool isWideScreen;
+ string codec;
+ string format;
+ string framerate;
+ string interlace;
+ bool isDolby;
+ vector< tAudioTrack > tracks;
+};
+
+#endif // __EXTRECINFO_H \ No newline at end of file
diff --git a/extensions/fontmanager.c b/extensions/fontmanager.c
new file mode 100644
index 0000000..47e2885
--- /dev/null
+++ b/extensions/fontmanager.c
@@ -0,0 +1,181 @@
+#include "fontmanager.h"
+#include "../config.h"
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+using namespace std;
+
+cMutex cFontManager::mutex;
+
+cFontManager::cFontManager(void) {
+}
+
+cFontManager::~cFontManager(void) {
+ DeleteFonts();
+}
+
+void cFontManager::Debug(void) {
+ dsyslog("skindesigner: fontmanager fonts available:");
+ for (map < string, map< int, cFont* > >::iterator fts = fonts.begin(); fts != fonts.end(); fts++) {
+ dsyslog("skindesigner: FontName %s", fts->first.c_str());
+ for (map<int, cFont*>::iterator ftSizes = (fts->second).begin(); ftSizes != (fts->second).end(); ftSizes++) {
+ int confHeight = ftSizes->first;
+ int realHeight = (ftSizes->second)->Height();
+ dsyslog("skindesigner: fontSize %d, fontHeight %d, ratio %f", confHeight, realHeight, (double)confHeight / (double)realHeight);
+ }
+ }
+}
+
+void cFontManager::ListAvailableFonts(void) {
+ cStringList availableFonts;
+ cFont::GetAvailableFontNames(&availableFonts);
+ int numFonts = availableFonts.Size();
+ esyslog("skindesigner: %d Fonts available:", numFonts);
+ for (int i=0; i<numFonts; i++) {
+ esyslog("skindesigner: font %d: %s", i, availableFonts[i]);
+ }
+}
+
+void cFontManager::DeleteFonts() {
+ cMutexLock MutexLock(&mutex);
+ for(map<string, map<int,cFont*> >::iterator it = fonts.begin(); it != fonts.end(); it++) {
+ for(map<int,cFont*>::iterator it2 = (it->second).begin(); it2 != (it->second).end(); it2++) {
+ delete it2->second;
+ }
+ }
+ fonts.clear();
+}
+
+int cFontManager::Width(string fontName, int fontSize, const char *text) {
+ cMutexLock MutexLock(&mutex);
+ if (!text)
+ return 0;
+ cFont *font = GetFont(fontName, fontSize);
+ //if not already cached, load it new
+ if (!font)
+ InsertFont(fontName, fontSize);
+ font = GetFont(fontName, fontSize);
+ if (!font)
+ return 0;
+ int width = font->Width(text);
+ return width;
+}
+
+int cFontManager::Height(string fontName, int fontSize) {
+ cMutexLock MutexLock(&mutex);
+ cFont *font = GetFont(fontName, fontSize);
+ //if not already cached, load it new
+ if (!font)
+ InsertFont(fontName, fontSize);
+ font = GetFont(fontName, fontSize);
+ if (!font)
+ return 0;
+ return font->Height();
+}
+
+cFont *cFontManager::Font(string fontName, int fontSize) {
+ cMutexLock MutexLock(&mutex);
+ cFont *font = GetFont(fontName, fontSize);
+ //if not already cached, load it new
+ if (!font)
+ InsertFont(fontName, fontSize);
+ font = GetFont(fontName, fontSize);
+ return font;
+}
+
+cFont *cFontManager::FontUncached(string fontName, int fontSize) {
+ cMutexLock MutexLock(&mutex);
+ cFont *font = CreateFont(fontName, fontSize);
+ return font;
+}
+
+/********************************************************************************
+* Private Functions
+********************************************************************************/
+
+cFont *cFontManager::CreateFont(string name, int size) {
+ cMutexLock MutexLock(&mutex);
+ cFont *fontTmp = cFont::CreateFont(name.c_str(), size);
+ if (!fontTmp)
+ fontTmp = cFont::CreateFont(Setup.FontOsd, size);
+ int realHeight = fontTmp->Height();
+ delete fontTmp;
+ cFont *font = cFont::CreateFont(name.c_str(), (double)size / (double)realHeight * (double)size);
+ if (!font)
+ font = cFont::CreateFont(Setup.FontOsd, (double)size / (double)realHeight * (double)size);
+ return font;
+}
+
+void cFontManager::InsertFont(string name, int size) {
+ if (FontExists(name, size))
+ return;
+ cFont *newFont = CreateFont(name, size);
+ if (!newFont)
+ return;
+ map < string, map< int, cFont* > >::iterator hit = fonts.find(name);
+ if (hit != fonts.end()) {
+ (hit->second).insert(pair<int, cFont*>(size, newFont));
+ } else {
+ map<int, cFont*> fontsizes;
+ fontsizes.insert(pair<int, cFont*>(size, newFont));
+ fonts.insert(pair<string, map<int, cFont*> >(name, fontsizes));
+ }
+}
+
+bool cFontManager::FontExists(string name, int size) {
+ map < string, map< int, cFont* > >::iterator hit = fonts.find(name);
+ if (hit == fonts.end())
+ return false;
+ map< int, cFont* >::iterator hit2 = (hit->second).find(size);
+ if (hit2 == (hit->second).end())
+ return false;
+ return true;
+}
+
+cFont *cFontManager::GetFont(string name, int size) {
+ map< string, map<int,cFont*> >::iterator hitName = fonts.find(name);
+ if (hitName == fonts.end())
+ return NULL;
+ map<int,cFont*>::iterator hitSize = (hitName->second).find(size);
+ if (hitSize == (hitName->second).end())
+ return NULL;
+ return hitSize->second;
+}
+
+int cFontManager::GetFontHeight(const char *name, int height, int charWidth) {
+ FT_Library library;
+ FT_Face face;
+ cString fontFileName = cFont::GetFontFileName(name);
+
+ int descender = 0;
+ int y_ppem = 0;
+ int error = FT_Init_FreeType(&library);
+ if (error) return 0;
+ error = FT_New_Face(library, fontFileName, 0, &face);
+ if (error) return 0;
+ error = FT_Set_Char_Size(face, charWidth * 64, height * 64, 0, 0);
+ if (error) return 0;
+
+ descender = face->size->metrics.descender/64;
+ y_ppem = face->size->metrics.y_ppem;
+ int realHeight = y_ppem + descender;
+
+ FT_Done_Face(face);
+ FT_Done_FreeType(library);
+
+ return realHeight;
+}
+
+bool cFontManager::FontInstalled(string fontName) {
+ cStringList availableFonts;
+ cFont::GetAvailableFontNames(&availableFonts);
+ int numFonts = availableFonts.Size();
+ string compare = fontName + ":";
+ for (int i=0; i<numFonts; i++) {
+ string currentFont = availableFonts[i];
+ if (currentFont.find(compare) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/extensions/fontmanager.h b/extensions/fontmanager.h
new file mode 100644
index 0000000..c71d465
--- /dev/null
+++ b/extensions/fontmanager.h
@@ -0,0 +1,35 @@
+#ifndef __FONTMANAGER_H
+#define __FONTMANAGER_H
+
+#include <string>
+#include <map>
+#include <vector>
+#include <vdr/skins.h>
+
+using namespace std;
+
+class cFontManager {
+private:
+ static cMutex mutex;
+ map < string, map< int, cFont* > > fonts;
+ cFont *CreateFont(string name, int size);
+ void InsertFont(string name, int size);
+ bool FontExists(string name, int size);
+ cFont *GetFont(string name, int size);
+ int GetFontHeight(const char *name, int height, int charWidth = 0);
+public:
+ cFontManager(void);
+ ~cFontManager(void);
+ void Lock(void) { mutex.Lock(); };
+ void Unlock(void) { mutex.Unlock(); };
+ void DeleteFonts(void);
+ int Width(string fontName, int fontSize, const char *text);
+ int Height(string fontName, int fontSize);
+ cFont *Font(string fontName, int fontSize);
+ cFont *FontUncached(string fontName, int fontSize);
+ void Debug(void);
+ void ListAvailableFonts(void);
+ bool FontInstalled(string fontName);
+};
+
+#endif //__FONTMANAGER_H \ No newline at end of file
diff --git a/extensions/helpers.c b/extensions/helpers.c
new file mode 100644
index 0000000..00c13d2
--- /dev/null
+++ b/extensions/helpers.c
@@ -0,0 +1,240 @@
+#include <string>
+#include <sstream>
+#include <vector>
+#include <stdlib.h>
+#include <fstream>
+#include <iostream>
+#include "helpers.h"
+#include <vdr/skins.h>
+
+cPlugin *GetScraperPlugin(void) {
+ static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+ if( !pScraper ) // if it doesn't exit, try tvscraper
+ pScraper = cPluginManager::GetPlugin("tvscraper");
+ return pScraper;
+}
+
+cSize ScaleToFit(int widthMax, int heightMax, int widthOriginal, int heightOriginal) {
+ int width = 1;
+ int height = 1;
+
+ if ((widthMax == 0)||(heightMax==0)||(widthOriginal==0)||(heightOriginal==0))
+ return cSize(width, height);
+
+ if ((widthOriginal <= widthMax) && (heightOriginal <= heightMax)) {
+ width = widthOriginal;
+ height = heightOriginal;
+ } else if ((widthOriginal > widthMax) && (heightOriginal <= heightMax)) {
+ width = widthMax;
+ height = (double)width/(double)widthOriginal * heightOriginal;
+ } else if ((widthOriginal <= widthMax) && (heightOriginal > heightMax)) {
+ height = heightMax;
+ width = (double)height/(double)heightOriginal * widthOriginal;
+ } else {
+ width = widthMax;
+ height = (double)width/(double)widthOriginal * heightOriginal;
+ if (height > heightMax) {
+ height = heightMax;
+ width = (double)height/(double)heightOriginal * widthOriginal;
+ }
+ }
+ return cSize(width, height);
+}
+
+int Minimum(int a, int b, int c, int d, int e, int f) {
+ int min = a;
+ if (b < min) min = b;
+ if (c < min) min = c;
+ if (d < min) min = d;
+ if (e < min) min = e;
+ if (f < min) min = f;
+ return min;
+}
+
+string StrToLowerCase(string str) {
+ string lowerCase = str;
+ const int length = lowerCase.length();
+ for(int i=0; i < length; ++i) {
+ lowerCase[i] = std::tolower(lowerCase[i]);
+ }
+ return lowerCase;
+}
+
+bool isNumber(const string& s) {
+ string::const_iterator it = s.begin();
+ while (it != s.end() && std::isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
+bool IsToken(const string& token) {
+ if ((token.find("{") == 0) && (token.find("}") == (token.size()-1)))
+ return true;
+ return false;
+}
+
+bool FileExists(const string &fullpath) {
+ struct stat buffer;
+ if (stat (fullpath.c_str(), &buffer) == 0) {
+ return true;
+ }
+ if (config.debugImageLoading) {
+ dsyslog("skindesigner: did not find %s", fullpath.c_str());
+ }
+ return false;
+}
+
+bool FileExists(const string &path, const string &name, const string &ext) {
+ stringstream fileName;
+ fileName << path << name << "." << ext;
+ struct stat buffer;
+ if (stat (fileName.str().c_str(), &buffer) == 0) {
+ return true;
+ }
+ if (config.debugImageLoading) {
+ dsyslog("skindesigner: did not find %s", fileName.str().c_str());
+ }
+ return false;
+}
+
+bool FolderExists(const string &path) {
+ struct stat buffer;
+ return stat(path.c_str(), &buffer) == 0 && S_ISDIR(buffer.st_mode);
+}
+
+bool FirstFileInFolder(string &path, string &extension, string &fileName) {
+ DIR *folder = NULL;
+ struct dirent *file;
+ folder = opendir(path.c_str());
+ if (!folder)
+ return false;
+ while (file = readdir(folder)) {
+ if (endswith(file->d_name, extension.c_str())) {
+ string currentFileName = file->d_name;
+ int strlength = currentFileName.size();
+ if (strlength < 8)
+ continue;
+ fileName = currentFileName;
+ return true;
+ }
+ }
+ return false;
+}
+
+void CreateFolder(string &path) {
+ cString command = cString::sprintf("mkdir -p %s", path.c_str());
+ int ok = system(*command);
+ if (!ok) {}
+}
+
+// trim from start
+string &ltrim(string &s) {
+ s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun<int, int>(isspace))));
+ return s;
+}
+
+// trim from end
+string &rtrim(string &s) {
+ s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun<int, int>(isspace))).base(), s.end());
+ return s;
+}
+
+// trim from both ends
+string &trim(string &s) {
+ return ltrim(rtrim(s));
+}
+
+// split: receives a char delimiter; returns a vector of strings
+// By default ignores repeated delimiters, unless argument rep == 1.
+vector<string>& splitstring::split(char delim, int rep) {
+ if (!flds.empty()) flds.clear(); // empty vector if necessary
+ string work = data();
+ string buf = "";
+ int i = 0;
+ while (i < (int)work.length()) {
+ if (work[i] != delim)
+ buf += work[i];
+ else if (rep == 1) {
+ flds.push_back(buf);
+ buf = "";
+ } else if (buf.length() > 0) {
+ flds.push_back(buf);
+ buf = "";
+ }
+ i++;
+ }
+ if (!buf.empty())
+ flds.push_back(buf);
+ return flds;
+}
+
+cStopWatch::cStopWatch(const char* message) {
+ start = cTimeMs::Now();
+ last = start;
+ if (message) {
+ dsyslog("skindesigner: Starting StopWatch %s", message);
+ }
+}
+
+void cStopWatch::Report(const char* message) {
+ dsyslog("skindesigner: %s - needed %d ms", message, (int)(cTimeMs::Now() - last));
+ last = cTimeMs::Now();
+}
+
+void cStopWatch::Stop(const char* message) {
+ dsyslog("skindesigner: %s - needed %d ms", message, (int)(cTimeMs::Now() - start));
+}
+
+string GetTimeString(int seconds) {
+ time_t sec(seconds);
+ tm *p = gmtime(&sec);
+ int hours = p->tm_hour;
+ int mins = p->tm_min;
+ int secs = p->tm_sec;
+ if (hours > 0) {
+ return *cString::sprintf("%d:%02d:%02d", hours, mins, secs);
+ }
+ return *cString::sprintf("%02d:%02d", mins, secs);;
+}
+
+
+//View Helpers
+string GetScreenResolutionString(int width, int height, bool *isHD) {
+ string name = "";
+ switch (width) {
+ case 1920:
+ case 1440:
+ name = "hd1080i";
+ *isHD = true;
+ break;
+ case 1280:
+ if (height == 720)
+ name = "hd720p";
+ else
+ name = "hd1080i";
+ *isHD = true;
+ break;
+ case 720:
+ name = "sd576i";
+ break;
+ default:
+ name = "sd576i";
+ break;
+ }
+ return name;
+}
+
+string GetScreenAspectString(double aspect, bool *isWideScreen) {
+ string name = "";
+ *isWideScreen = false;
+ if (aspect == 4.0/3.0) {
+ name = "4:3";
+ *isWideScreen = false;
+ } else if (aspect == 16.0/9.0) {
+ name = "16:9";
+ *isWideScreen = true;
+ } else if (aspect == 2.21) {
+ name = "21:9";
+ *isWideScreen = true;
+ }
+ return name;
+}
diff --git a/extensions/helpers.h b/extensions/helpers.h
new file mode 100644
index 0000000..b059a16
--- /dev/null
+++ b/extensions/helpers.h
@@ -0,0 +1,50 @@
+#ifndef __HELPERS_H
+#define __HELPERS_H
+
+#include <string>
+#include <algorithm>
+#include <vdr/osd.h>
+#include <vdr/plugin.h>
+#include "../config.h"
+
+cPlugin *GetScraperPlugin(void);
+
+cSize ScaleToFit(int widthMax, int heightMax, int widthOriginal, int heightOriginal);
+int Minimum(int a, int b, int c, int d, int e, int f);
+std::string StrToLowerCase(string str);
+bool isNumber(const string& s);
+bool IsToken(const string& token);
+bool FileExists(const string &fullpath);
+bool FileExists(const string &path, const string &name, const string &ext);
+bool FolderExists(const string &path);
+bool FirstFileInFolder(string &path, string &extension, string &fileName);
+void CreateFolder(string &path);
+
+string &ltrim(string &s);
+string &rtrim(string &s);
+string &trim(string &s);
+
+class splitstring : public std::string {
+ std::vector<std::string> flds;
+public:
+ splitstring(const char *s) : std::string(s) { };
+ std::vector<std::string>& split(char delim, int rep=0);
+};
+
+class cStopWatch {
+private:
+ uint64_t start;
+ uint64_t last;
+public:
+ cStopWatch(const char* message = NULL);
+ ~cStopWatch(void) {};
+ void Report(const char* message);
+ void Stop(const char* message);
+};
+
+string GetTimeString(int seconds);
+
+string GetScreenResolutionString(int width, int height, bool *isHD);
+string GetScreenAspectString(double aspect, bool *isWideScreen);
+
+#endif // __HELPERS_H
diff --git a/extensions/imagecache.c b/extensions/imagecache.c
new file mode 100644
index 0000000..d8c89c8
--- /dev/null
+++ b/extensions/imagecache.c
@@ -0,0 +1,579 @@
+#include <string>
+#include <sstream>
+#include <map>
+#include <fstream>
+#include <iostream>
+#include <sys/stat.h>
+#include "imagecache.h"
+#include "cairoimage.h"
+#include "../config.h"
+#include "helpers.h"
+
+
+cMutex cImageCache::mutex;
+
+string cImageCache::items[16] = { "Schedule", "Channels", "Timers", "Recordings", "Setup", "Commands",
+ "OSD", "EPG", "DVB", "LNB", "CAM", "Recording", "Replay", "Miscellaneous", "Plugins", "Restart"};
+
+cImageCache::cImageCache() {
+ tempStaticLogo = NULL;
+}
+
+cImageCache::~cImageCache() {
+ Clear();
+ if (tempStaticLogo) {
+ delete tempStaticLogo;
+ tempStaticLogo = NULL;
+ }
+}
+
+void cImageCache::SetPathes(void) {
+ cString skinPath = config.GetSkinPath(Setup.OSDSkin);
+
+ string logoPathSkin = *cString::sprintf("%s%s/themes/%s/logos/", *skinPath, Setup.OSDSkin, Setup.OSDTheme);
+ if (FolderExists(logoPathSkin)) {
+ logoPath = logoPathSkin;
+ } else {
+ logoPath = *config.logoPath;
+ }
+
+ iconPathSkin = *cString::sprintf("%s%s/", *skinPath, Setup.OSDSkin);
+ skinPartsPathSkin = *cString::sprintf("%s%s/skinparts/", *skinPath, Setup.OSDSkin);
+
+ iconPathTheme = *cString::sprintf("%s%s/themes/%s/", *skinPath, Setup.OSDSkin, Setup.OSDTheme);
+ skinPartsPathTheme = *cString::sprintf("%s%s/themes/%s/skinparts/", *skinPath, Setup.OSDSkin, Setup.OSDTheme);
+
+ svgTemplatePath = *cString::sprintf("%s%s/svgtemplates/", *skinPath, Setup.OSDSkin);
+
+ dsyslog("skindesigner: using channel logo path %s", logoPath.c_str());
+ dsyslog("skindesigner: using icon path %s", iconPathTheme.c_str());
+ dsyslog("skindesigner: using skinparts path %s", skinPartsPathTheme.c_str());
+ dsyslog("skindesigner: using svgtemplate path %s", svgTemplatePath.c_str());
+}
+
+void cImageCache::CacheLogo(int width, int height) {
+ if (config.numLogosPerSizeInitial == 0)
+ return;
+ if (width == 0 || height == 0)
+ return;
+
+ int logosCached = 0;
+
+ if (config.numLogosMax && config.numLogosMax < (int)channelLogoCache.size())
+ return;
+
+ for (const cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
+ if (logosCached >= config.numLogosPerSizeInitial)
+ break;
+ if (channel->GroupSep()) {
+ continue;
+ }
+ stringstream logoName;
+ logoName << *channel->GetChannelID().ToString() << "_" << width << "x" << height;
+ map<string, cImage*>::iterator hit = channelLogoCache.find(logoName.str());
+ if (hit != channelLogoCache.end()) {
+ continue;
+ }
+ bool success = LoadLogo(channel);
+ if (success) {
+ logosCached++;
+ cImage *image = CreateImage(width, height);
+ channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image));
+ }
+ if (config.numLogosMax && config.numLogosMax < (int)channelLogoCache.size())
+ return;
+ }
+}
+
+cImage *cImageCache::GetLogo(string channelID, int width, int height) {
+ cMutexLock MutexLock(&mutex);
+
+ stringstream logoName;
+ logoName << channelID << "_" << width << "x" << height;
+
+ std::map<std::string, cImage*>::iterator hit = channelLogoCache.find(logoName.str());
+
+ if (hit != channelLogoCache.end()) {
+ return (cImage*)hit->second;
+ } else {
+ tChannelID chanID = tChannelID::FromString(channelID.c_str());
+ const cChannel *channel = Channels.GetByChannelID(chanID);
+ if (!channel)
+ return NULL;
+ bool success = LoadLogo(channel);
+ if (success) {
+ if (config.limitLogoCache && ((int)channelLogoCache.size() >= config.numLogosMax)) {
+ //logo cache is full, don't cache anymore
+ if (tempStaticLogo) {
+ delete tempStaticLogo;
+ tempStaticLogo = NULL;
+ }
+ tempStaticLogo = CreateImage(width, height);
+ return tempStaticLogo;
+ } else {
+ //add requested logo to cache
+ cImage *image = CreateImage(width, height);
+ channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image));
+ hit = channelLogoCache.find(logoName.str());
+ if (hit != channelLogoCache.end()) {
+ return (cImage*)hit->second;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+cImage *cImageCache::GetSeparatorLogo(string name, int width, int height) {
+ cMutexLock MutexLock(&mutex);
+
+ stringstream logoName;
+ logoName << name << "_" << width << "x" << height;
+
+ std::map<std::string, cImage*>::iterator hit = channelLogoCache.find(logoName.str());
+
+ if (hit != channelLogoCache.end()) {
+ return (cImage*)hit->second;
+ } else {
+ bool success = LoadSeparatorLogo(name);
+ if (success) {
+ //add requested logo to cache
+ cImage *image = CreateImage(width, height);
+ channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image));
+ hit = channelLogoCache.find(logoName.str());
+ if (hit != channelLogoCache.end()) {
+ return (cImage*)hit->second;
+ }
+ }
+ }
+ return NULL;
+}
+
+bool cImageCache::LogoExists(string channelID) {
+ tChannelID chanID = tChannelID::FromString(channelID.c_str());
+ const cChannel *channel = Channels.GetByChannelID(chanID);
+ if (!channel)
+ return false;
+ string logoLower = StrToLowerCase(channel->Name());
+ string channelIDLower = StrToLowerCase(channelID.c_str());
+
+ return (FileExists(logoPath.c_str(), logoLower, "svg") ||
+ FileExists(logoPath.c_str(), logoLower, "png") ||
+ FileExists(logoPath.c_str(), channelIDLower, "svg") ||
+ FileExists(logoPath.c_str(), channelIDLower, "png"));
+}
+
+bool cImageCache::SeparatorLogoExists(string name) {
+ string separatorPath = *cString::sprintf("%sseparatorlogos/", logoPath.c_str());
+ string nameLower = StrToLowerCase(name.c_str());
+
+ return (FileExists(separatorPath, nameLower, "svg") ||
+ FileExists(separatorPath, nameLower, "png"));
+}
+
+void cImageCache::CacheIcon(eImageType type, string name, int width, int height) {
+ if (width < 1 || width > 1920 || height < 1 || height > 1080)
+ return;
+ GetIcon(type, name, width, height);
+}
+
+cCachedImage *cImageCache::GetIcon(eImageType type, string name, int width, int height) {
+ if (width < 1 || width > 1920 || height < 1 || height > 1080)
+ return NULL;
+ cMutexLock MutexLock(&mutex);
+ stringstream iconName;
+ iconName << name << "_" << width << "x" << height;
+ map<string, cCachedImage*>::iterator hit = iconCache.find(iconName.str());
+ if (hit != iconCache.end()) {
+ return (cCachedImage*)hit->second;
+ } else {
+ bool success = LoadIcon(type, name);
+ if (!success)
+ return NULL;
+ cImage *image = CreateImage(width, height, true);
+ cCachedImage *cachedImg = new cCachedImage();
+ cachedImg->size = image->Width() * image->Height() * sizeof(tColor);
+ int handle = cOsdProvider::StoreImage(*image);
+ if (handle) {
+ cachedImg->handle = handle;
+ delete image;
+ } else {
+ cachedImg->image = image;
+ }
+
+ iconCache.insert(pair<string, cCachedImage*>(iconName.str(), cachedImg));
+ hit = iconCache.find(iconName.str());
+ if (hit != iconCache.end()) {
+ return (cCachedImage*)hit->second;
+ }
+ }
+ return NULL;
+}
+
+string cImageCache::GetIconName(string label, eMenuCategory cat, string plugName) {
+ //if cat is set, use standard menu entries
+ switch (cat) {
+ case mcSchedule:
+ case mcScheduleNow:
+ case mcScheduleNext:
+ case mcEvent:
+ return "standardicons/Schedule";
+ case mcChannel:
+ case mcChannelEdit:
+ return "standardicons/Channels";
+ case mcTimer:
+ case mcTimerEdit:
+ return "standardicons/Timers";
+ case mcRecording:
+ case mcRecordingInfo:
+ case mcSetupRecord:
+ case mcSetupReplay:
+ return "standardicons/Recordings";
+ case mcPlugin: {
+ //check for Plugins
+ for (int i = 0; ; i++) {
+ cPlugin *p = cPluginManager::GetPlugin(i);
+ if (p) {
+ const char *mainMenuEntry = p->MainMenuEntry();
+ if (mainMenuEntry) {
+ string plugMainEntry = mainMenuEntry;
+ try {
+ if (label.substr(0, plugMainEntry.size()) == plugMainEntry) {
+ return *cString::sprintf("pluginicons/%s", p->Name());
+ }
+ } catch (...) {}
+ }
+ } else
+ break;
+ }
+ return "standardicons/Plugins";
+ }
+ case mcPluginSetup:
+ case mcSetupPlugins:
+ return "standardicons/Plugins";
+ case mcSetup:
+ return "standardicons/Setup";
+ case mcSetupOsd:
+ return "standardicons/OSD";
+ case mcSetupEpg:
+ return "standardicons/EPG";
+ case mcSetupDvb:
+ return "standardicons/DVB";
+ case mcSetupLnb:
+ return "standardicons/LNB";
+ case mcSetupCam:
+ return "standardicons/CAM";
+ case mcSetupMisc:
+ return "standardicons/Miscellaneous";
+ case mcCommand:
+ return "standardicons/Commands";
+ default:
+ break;
+ }
+ //check for standard menu entries
+ for (int i=0; i<16; i++) {
+ string s = trVDR(items[i].c_str());
+ if (s == label) {
+ return *cString::sprintf("standardicons/%s", items[i].c_str());
+ }
+ }
+ //check for special main menu entries "stop recording", "stop replay"
+ string stopRecording = skipspace(trVDR(" Stop recording "));
+ string stopReplay = skipspace(trVDR(" Stop replaying"));
+ try {
+ if (label.substr(0, stopRecording.size()) == stopRecording) {
+ return "standardicons/StopRecording";
+ }
+ if (label.substr(0, stopReplay.size()) == stopReplay) {
+ return "standardicons/StopReplay";
+ }
+ } catch (...) {}
+ //check for Plugins
+ if (plugName.size() > 0) {
+ return *cString::sprintf("pluginicons/%s", plugName.c_str());
+ }
+ for (int i = 0; ; i++) {
+ cPlugin *p = cPluginManager::GetPlugin(i);
+ if (p) {
+ const char *mainMenuEntry = p->MainMenuEntry();
+ if (mainMenuEntry) {
+ string plugMainEntry = mainMenuEntry;
+ try {
+ if (label.substr(0, plugMainEntry.size()) == plugMainEntry) {
+ return *cString::sprintf("pluginicons/%s", p->Name());
+ }
+ } catch (...) {}
+ }
+ } else
+ break;
+ }
+ return *cString::sprintf("customicons/%s", label.c_str());
+}
+
+bool cImageCache::MenuIconExists(string name) {
+ //first check in theme specific icon folder
+ cString iconThemePath = cString::sprintf("%smenuicons/", iconPathTheme.c_str());
+ if (FileExists(*iconThemePath, name, "svg")) {
+ return true;
+ }
+ if (FileExists(*iconThemePath, name, "png")) {
+ return true;
+ }
+ //then check skin icon folder
+ cString iconSkinPath = cString::sprintf("%smenuicons/", iconPathSkin.c_str());
+ if (FileExists(*iconSkinPath, name, "svg")) {
+ return true;
+ }
+ if (FileExists(*iconSkinPath, name, "png")) {
+ return true;
+ }
+ return false;
+}
+
+void cImageCache::CacheSkinpart(string name, int width, int height) {
+ if (width < 1 || width > 1920 || height < 1 || height > 1080)
+ return;
+ GetSkinpart(name, width, height);
+}
+
+cCachedImage *cImageCache::GetSkinpart(string name, int width, int height) {
+ if (width < 1 || width > 1920 || height < 1 || height > 1080)
+ return NULL;
+ cMutexLock MutexLock(&mutex);
+ stringstream iconName;
+ iconName << name << "_" << width << "x" << height;
+ map<string, cCachedImage*>::iterator hit = skinPartsCache.find(iconName.str());
+ if (hit != skinPartsCache.end()) {
+ return (cCachedImage*)hit->second;
+ } else {
+ bool success = LoadSkinpart(name);
+ if (!success)
+ return NULL;
+ cImage *image = CreateImage(width, height, false);
+ cCachedImage *cachedImg = new cCachedImage();
+ cachedImg->size = image->Width() * image->Height() * sizeof(tColor);
+ int handle = cOsdProvider::StoreImage(*image);
+ if (handle) {
+ cachedImg->handle = handle;
+ delete image;
+ } else {
+ cachedImg->image = image;
+ }
+ skinPartsCache.insert(pair<string, cCachedImage*>(iconName.str(), cachedImg));
+ hit = skinPartsCache.find(iconName.str());
+ if (hit != skinPartsCache.end()) {
+ return (cCachedImage*)hit->second;
+ }
+ }
+ return NULL;
+}
+
+cImage *cImageCache::GetVerticalText(string text, tColor color, string font, int size, int direction) {
+ cMutexLock MutexLock(&mutex);
+ stringstream buf;
+ buf << text << "_" << size << "_" << direction;
+ string imgName = buf.str();
+ map<string, cImage*>::iterator hit = cairoImageCache.find(imgName);
+ if (hit != cairoImageCache.end()) {
+ return (cImage*)hit->second;
+ } else {
+ cCairoImage c;
+ c.DrawTextVertical(text, color, font, size, direction);
+ cImage *image = c.GetImage();
+ cairoImageCache.insert(pair<string, cImage*>(imgName, image));
+ hit = cairoImageCache.find(imgName);
+ if (hit != cairoImageCache.end()) {
+ return (cImage*)hit->second;
+ }
+ }
+ return NULL;
+}
+
+
+bool cImageCache::LoadIcon(eImageType type, string name) {
+ cString subdir("");
+ if (type == eImageType::menuicon)
+ subdir = "menuicons";
+ else if (type == eImageType::icon)
+ subdir = "icons";
+
+ //first check in theme specific icon path
+ cString subIconThemePath = cString::sprintf("%s%s/", iconPathTheme.c_str(), *subdir);
+
+ if (FileExists(*subIconThemePath, name, "svg"))
+ return LoadImage(*subIconThemePath, name, "svg");
+ else if (FileExists(*subIconThemePath, name, "png"))
+ return LoadImage(*subIconThemePath, name, "png");
+
+ //then check in skin icon path
+ cString subIconSkinPath = cString::sprintf("%s%s/", iconPathSkin.c_str(), *subdir);
+
+ if (FileExists(*subIconSkinPath, name, "svg"))
+ return LoadImage(*subIconSkinPath, name, "svg");
+ else if (FileExists(*subIconSkinPath, name, "png"))
+ return LoadImage(*subIconSkinPath, name, "png");
+
+ //and finally check if a svg template exists
+ cSVGTemplate svgTemplate(name, svgTemplatePath);
+ if (!svgTemplate.Exists())
+ return false;
+ svgTemplate.ReadTemplate();
+ if (!svgTemplate.ParseTemplate())
+ return false;
+ string tmpImageName = svgTemplate.WriteImage();
+ return LoadImage(tmpImageName.c_str());
+}
+
+bool cImageCache::LoadLogo(const cChannel *channel) {
+ if (!channel)
+ return false;
+ string channelID = StrToLowerCase(*(channel->GetChannelID().ToString()));
+ string logoLower = StrToLowerCase(channel->Name());
+
+ if (FileExists(logoPath.c_str(), channelID.c_str(), "svg"))
+ return LoadImage(logoPath.c_str(), channelID.c_str(), "svg");
+ if (FileExists(logoPath.c_str(), channelID.c_str(), "png"))
+ return LoadImage(logoPath.c_str(), channelID.c_str(), "png");
+ if (FileExists(logoPath.c_str(), logoLower.c_str(), "svg"))
+ return LoadImage(logoPath.c_str(), logoLower.c_str(), "svg");
+ if (FileExists(logoPath.c_str(), logoLower.c_str(), "png"))
+ return LoadImage(logoPath.c_str(), logoLower.c_str(), "png");
+
+ return false;
+}
+
+bool cImageCache::LoadSeparatorLogo(string name) {
+ string separatorPath = *cString::sprintf("%sseparatorlogos/", logoPath.c_str());
+ string nameLower = StrToLowerCase(name.c_str());
+ if (FileExists(separatorPath, nameLower.c_str(), "svg"))
+ return LoadImage(separatorPath, nameLower.c_str(), "svg");
+ else
+ return LoadImage(separatorPath, nameLower.c_str(), "png");
+}
+
+bool cImageCache::LoadSkinpart(string name) {
+ if (FileExists(skinPartsPathTheme.c_str(), name, "svg"))
+ return LoadImage(skinPartsPathTheme.c_str(), name, "svg");
+
+ else if (FileExists(skinPartsPathTheme.c_str(), name, "png"))
+ return LoadImage(skinPartsPathTheme.c_str(), name, "png");
+
+ else if (FileExists(skinPartsPathSkin.c_str(), name, "svg"))
+ return LoadImage(skinPartsPathSkin.c_str(), name, "svg");
+
+ else if (FileExists(skinPartsPathSkin.c_str(), name, "png"))
+ return LoadImage(skinPartsPathSkin.c_str(), name, "png");
+
+ //check if a svg template exists
+ cSVGTemplate svgTemplate(name, svgTemplatePath);
+ if (!svgTemplate.Exists())
+ return false;
+ svgTemplate.ReadTemplate();
+ if (!svgTemplate.ParseTemplate())
+ return false;
+ string tmpImageName = svgTemplate.WriteImage();
+ return LoadImage(tmpImageName.c_str());
+}
+
+void cImageCache::Clear(void) {
+ for(map<string, cCachedImage*>::const_iterator it = iconCache.begin(); it != iconCache.end(); it++) {
+ cCachedImage *img = (cCachedImage*)it->second;
+ delete img;
+ }
+ iconCache.clear();
+
+ for(map<string, cImage*>::const_iterator it = channelLogoCache.begin(); it != channelLogoCache.end(); it++) {
+ cImage *img = (cImage*)it->second;
+ delete img;
+ }
+ channelLogoCache.clear();
+
+ for(map<string, cCachedImage*>::const_iterator it = skinPartsCache.begin(); it != skinPartsCache.end(); it++) {
+ cCachedImage *img = (cCachedImage*)it->second;
+ delete img;
+ }
+ skinPartsCache.clear();
+
+ for(map<string, cImage*>::const_iterator it = cairoImageCache.begin(); it != cairoImageCache.end(); it++) {
+ cImage *img = (cImage*)it->second;
+ delete img;
+ }
+ cairoImageCache.clear();
+}
+
+void cImageCache::Debug(bool full) {
+ float sizeIconCacheInternal = 0;
+ float sizeIconCacheExternal = 0;
+ int numIcons = 0;
+ GetIconCacheSize(numIcons, sizeIconCacheInternal, sizeIconCacheExternal);
+ dsyslog("skindesigner: cached %d icons - size internal mem %.2fMB, high level mem %.2fMB", numIcons, sizeIconCacheInternal, sizeIconCacheExternal);
+ if (full) {
+ for(std::map<std::string, cCachedImage*>::const_iterator it = iconCache.begin(); it != iconCache.end(); it++) {
+ string name = it->first;
+ cCachedImage *img = (cCachedImage*)it->second;
+ dsyslog("skindesigner: cached icon %s, handle %d", name.c_str(), img->handle);
+ }
+ }
+
+ float sizeLogoCache = 0;
+ int numLogos = 0;
+ GetLogoCacheSize(numLogos, sizeLogoCache);
+ dsyslog("skindesigner: cached %d logos - size %.2fMB internal mem", numLogos, sizeLogoCache);
+ if (full) {
+ for(std::map<std::string, cImage*>::const_iterator it = channelLogoCache.begin(); it != channelLogoCache.end(); it++) {
+ string name = it->first;
+ dsyslog("skindesigner: cached logo %s", name.c_str());
+ }
+ }
+ float sizeSkinpartCacheInternal = 0;
+ float sizeSkinpartCacheExternal = 0;
+ int numSkinparts = 0;
+ GetSkinpartsCacheSize(numSkinparts, sizeSkinpartCacheInternal, sizeSkinpartCacheExternal);
+ dsyslog("skindesigner: cached %d skinparts - size internal mem %.2fMB, high level mem %.2fMB", numSkinparts, sizeSkinpartCacheInternal, sizeSkinpartCacheExternal);
+ if (full) {
+ for(std::map<std::string, cCachedImage*>::const_iterator it = skinPartsCache.begin(); it != skinPartsCache.end(); it++) {
+ string name = it->first;
+ dsyslog("skindesigner: cached skinpart %s", name.c_str());
+ }
+ }
+}
+
+void cImageCache::GetIconCacheSize(int &num, float &sizeInternal, float &sizeExternal) {
+ num = iconCache.size();
+ int sizeByteInternal = 0;
+ int sizeByteExternal = 0;
+ for (map<string, cCachedImage*>::iterator icon = iconCache.begin(); icon != iconCache.end(); icon++) {
+ cCachedImage* img = icon->second;
+ if (img->image)
+ sizeByteInternal += img->size;
+ else
+ sizeByteExternal += img->size;
+ }
+ sizeInternal = sizeByteInternal / 1024.0f / 1024.0f;
+ sizeExternal = sizeByteExternal / 1024.0f / 1024.0f;
+}
+
+void cImageCache::GetLogoCacheSize(int &num, float &size) {
+ num = channelLogoCache.size();
+ int sizeByte = 0;
+ for (map<string, cImage*>::iterator logo = channelLogoCache.begin(); logo != channelLogoCache.end(); logo++) {
+ cImage* img = logo->second;
+ sizeByte += img->Width() * img->Height() * sizeof(tColor);
+ }
+ size = sizeByte / 1024.0f;
+}
+
+void cImageCache::GetSkinpartsCacheSize(int &num, float &sizeInternal, float &sizeExternal) {
+ num = skinPartsCache.size();
+ int sizeByteInternal = 0;
+ int sizeByteExternal = 0;
+ for (map<string, cCachedImage*>::iterator skinpart = skinPartsCache.begin(); skinpart != skinPartsCache.end(); skinpart++) {
+ cCachedImage* img = skinpart->second;
+ if (img->image)
+ sizeByteInternal += img->size;
+ else
+ sizeByteExternal += img->size;
+ }
+ sizeInternal = sizeByteInternal / 1024.0f / 1024.0f;
+ sizeExternal = sizeByteExternal / 1024.0f / 1024.0f;
+}
diff --git a/extensions/imagecache.h b/extensions/imagecache.h
new file mode 100644
index 0000000..13f882b
--- /dev/null
+++ b/extensions/imagecache.h
@@ -0,0 +1,79 @@
+#ifndef __NOPACITY_IMAGECACHE_H
+#define __NOPACITY_IMAGECACHE_H
+
+#define X_DISPLAY_MISSING
+
+#include <vdr/osd.h>
+#include <vdr/skins.h>
+#include <vector>
+#include "imageloader.h"
+#include "../coreengine/definitions.h"
+
+class cCachedImage {
+public:
+ int handle;
+ cImage *image;
+ int size;
+ cCachedImage(void) {
+ handle = 0;
+ image = NULL;
+ size = 0;
+ };
+ ~cCachedImage(void) {
+ if (handle)
+ cOsdProvider::DropImage(handle);
+ if (image)
+ delete image;
+ };
+};
+
+class cImageCache : public cImageLoader {
+public:
+ cImageCache();
+ ~cImageCache();
+ void Lock(void) { mutex.Lock(); }
+ void Unlock(void) { mutex.Unlock(); }
+ void SetPathes(void);
+ //channel logos
+ void CacheLogo(int width, int height);
+ cImage *GetLogo(string channelID, int width, int height);
+ bool LogoExists(string channelID);
+ cImage *GetSeparatorLogo(string name, int width, int height);
+ bool SeparatorLogoExists(string name);
+ //icons
+ void CacheIcon(eImageType type, string path, int width, int height);
+ cCachedImage *GetIcon(eImageType type, string name, int width, int height);
+ string GetIconName(string label, eMenuCategory cat = mcUndefined, string plugName = "");
+ bool MenuIconExists(string name);
+ //skinparts
+ void CacheSkinpart(string path, int width, int height);
+ cCachedImage *GetSkinpart(string name, int width, int height);
+ //cairo special images
+ cImage *GetVerticalText(string text, tColor color, string font, int size, int direction);
+ //helpers
+ void Clear(void);
+ void Debug(bool full);
+ void GetIconCacheSize(int &num, float &sizeInternal, float &sizeExternal);
+ void GetLogoCacheSize(int &num, float &size);
+ void GetSkinpartsCacheSize(int &num, float &sizeInternal, float &sizeExternal);
+private:
+ static cMutex mutex;
+ static string items[16];
+ cImage *tempStaticLogo;
+ string logoPath;
+ string iconPathSkin;
+ string skinPartsPathSkin;
+ string iconPathTheme;
+ string skinPartsPathTheme;
+ string svgTemplatePath;
+ map<string, cCachedImage*> iconCache;
+ map<string, cImage*> channelLogoCache;
+ map<string, cCachedImage*> skinPartsCache;
+ map<string, cImage*> cairoImageCache;
+ bool LoadIcon(eImageType type, string name);
+ bool LoadLogo(const cChannel *channel);
+ bool LoadSeparatorLogo(string name);
+ bool LoadSkinpart(string name);
+};
+
+#endif //__NOPACITY_IMAGECACHE_H
diff --git a/extensions/imageloader.c b/extensions/imageloader.c
new file mode 100644
index 0000000..62d284f
--- /dev/null
+++ b/extensions/imageloader.c
@@ -0,0 +1,507 @@
+#include "../config.h"
+#include "helpers.h"
+#include "imageloader.h"
+#include <string>
+#include <dirent.h>
+#include <iostream>
+#include <fstream>
+
+cImageLoader::cImageLoader() {
+ importer = NULL;
+}
+
+cImageLoader::~cImageLoader() {
+ delete(importer);
+}
+
+cImage *cImageLoader::CreateImage(int width, int height, bool preserveAspect) {
+ if (!importer)
+ return NULL;
+ int w, h;
+ importer->GetImageSize(w, h);
+ if (width == 0)
+ width = w;
+ if (height == 0)
+ height = h;
+
+ cairo_surface_t *surface;
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+
+ cairo_t *cr;
+ cr = cairo_create(surface);
+
+ double sx = width / (double)w;
+ double sy = height / (double)h;
+ if (preserveAspect) {
+ double tx = 0;
+ double ty = 0;
+ if (sx < sy) {
+ sy = sx;
+ ty = (height - h * sy) / 2;
+ }
+ if (sy < sx) {
+ sx = sy;
+ tx = (width - w * sx) / 2;
+ }
+ cairo_translate(cr, tx, ty);
+ }
+ cairo_scale(cr, sx, sy);
+
+ importer->DrawToCairo(cr);
+
+ cairo_status_t status = cairo_status(cr);
+ if (status && config.debugImageLoading)
+ dsyslog("skindesigner: Cairo CreateImage Error %s", cairo_status_to_string(status));
+
+ unsigned char *data = cairo_image_surface_get_data(surface);
+ cImage *image = new cImage(cSize(width, height), (tColor*)data);
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+
+ return image;
+}
+
+bool cImageLoader::LoadImage(const char *fullpath) {
+ if ((fullpath == NULL) || (strlen(fullpath) < 5))
+ return false;
+
+ if (config.debugImageLoading)
+ dsyslog("skindesigner: trying to load: %s", fullpath);
+
+ delete(importer);
+ importer = NULL;
+
+ if (endswith(fullpath, ".png"))
+ importer = new cImageImporterPNG;
+ else if (endswith(fullpath, ".svg"))
+ importer = new cImageImporterSVG;
+ else if (endswith(fullpath, ".jpg"))
+ importer = new cImageImporterJPG;
+ else {
+ importer = cImageImporter::CreateImageImporter(fullpath);
+ if (!importer)
+ return false;
+ }
+
+ return importer->LoadImage(fullpath);
+}
+
+// Just a different way to call LoadImage. Calls the above one.
+bool cImageLoader::LoadImage(std::string Path, std::string FileName, std::string Extension) {
+ std::stringstream sstrImgFile;
+ sstrImgFile << Path << FileName << "." << Extension;
+ std::string imgFile = sstrImgFile.str();
+ return LoadImage(imgFile.c_str());
+}
+
+cImageImporter* cImageImporter::CreateImageImporter(const char* path) {
+ char pngSig[] = { char(0x89), char(0x50), char(0x4E), char(0x47), char(0x0D), char(0x0A), char(0x1A), char(0x0A) };
+ char jpgSig[] = { char(0xFF), char(0xD8), char(0xFF), char(0xD9) };
+ char buffer[8] = { 0 };
+ ifstream f(path, ios::in | ios::binary);
+ f.read(buffer, 8);
+ if (!f)
+ return NULL;
+ if(buffer[0] == jpgSig[0] && buffer[1] == jpgSig[1]) {
+ f.seekg(-2, f.end);
+ f.read(buffer, 2);
+ if(buffer[0] == jpgSig[2] && buffer[1] == jpgSig[3]) {
+ f.close();
+ return new cImageImporterJPG;
+ }
+ } else if(buffer[0] == pngSig[0]
+ && buffer[1] == pngSig[1]
+ && buffer[2] == pngSig[2]
+ && buffer[3] == pngSig[3]
+ && buffer[4] == pngSig[4]
+ && buffer[5] == pngSig[5]
+ && buffer[6] == pngSig[6]
+ && buffer[7] == pngSig[7]) {
+ f.close();
+ return new cImageImporterPNG;
+ }
+ f.close();
+ return NULL;
+}
+
+//
+// Image importer for PNG
+//
+
+cImageImporterPNG::cImageImporterPNG() {
+ surface = NULL;
+}
+
+cImageImporterPNG::~cImageImporterPNG() {
+ if (surface)
+ cairo_surface_destroy(surface);
+}
+
+bool cImageImporterPNG::LoadImage(const char *path) {
+ if (surface)
+ cairo_surface_destroy(surface);
+
+ surface = cairo_image_surface_create_from_png(path);
+
+ if (cairo_surface_status(surface)) {
+ if (config.debugImageLoading)
+ dsyslog("skindesigner: Cairo LoadImage Error: %s", cairo_status_to_string(cairo_surface_status(surface)));
+ surface = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+void cImageImporterPNG::DrawToCairo(cairo_t *cr) {
+ if (surface) {
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ }
+}
+
+void cImageImporterPNG::GetImageSize(int &width, int &height) {
+ if (surface) {
+ width = cairo_image_surface_get_width(surface);
+ height = cairo_image_surface_get_height(surface);
+ }
+}
+
+//
+// Image importer for SVG
+//
+
+cImageImporterSVG::cImageImporterSVG() {
+ handle = NULL;
+}
+
+cImageImporterSVG::~cImageImporterSVG() {
+ if (handle) {
+ rsvg_handle_close(handle, NULL);
+ g_object_unref(handle);
+ }
+}
+
+bool cImageImporterSVG::LoadImage(const char *path) {
+ if (handle) {
+ rsvg_handle_close(handle, NULL);
+ g_object_unref(handle);
+ }
+
+ GError *error = NULL;
+ handle = rsvg_handle_new_from_file(path, &error);
+ if (!handle) {
+ if (config.debugImageLoading && error) {
+ dsyslog("skindesigner: RSVG Error: %s", error->message);
+ }
+ return false;
+ }
+
+ // 90 dpi is the hardcoded default setting of the Inkscape SVG editor
+ rsvg_handle_set_dpi(handle, 90);
+
+ return true;
+}
+
+void cImageImporterSVG::DrawToCairo(cairo_t *cr) {
+ if (handle)
+ rsvg_handle_render_cairo(handle, cr);
+}
+
+void cImageImporterSVG::GetImageSize(int &width, int &height) {
+ if (handle) {
+ RsvgDimensionData dim;
+ rsvg_handle_get_dimensions(handle, &dim);
+ width = dim.width;
+ height = dim.height;
+ }
+}
+
+void cImageImporterSVG::InitLibRSVG() {
+ #if !GLIB_CHECK_VERSION(2, 35, 0)
+ g_type_init();
+ #endif
+}
+
+//
+// Image importer for JPG
+//
+
+struct my_error_mgr {
+ struct jpeg_error_mgr pub; // "public" fields
+ jmp_buf setjmp_buffer; // for return to caller
+};
+
+METHODDEF(void)
+my_error_exit(j_common_ptr cinfo) {
+ // cinfo->err really points to a my_error_mgr struct, so coerce pointer
+ my_error_mgr *myerr = (my_error_mgr*) cinfo->err;
+
+ // Always display the message.
+ (*cinfo->err->output_message) (cinfo);
+
+ // Return control to the setjmp point
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+METHODDEF(void)
+my_output_message(j_common_ptr cinfo) {
+ char buf[JMSG_LENGTH_MAX];
+ cinfo->err->format_message(cinfo, buf);
+ if (config.debugImageLoading)
+ dsyslog("skindesigner: libjpeg error: %s", buf);
+}
+
+cImageImporterJPG::cImageImporterJPG() {
+ cinfo = NULL;
+}
+
+cImageImporterJPG::~cImageImporterJPG() {
+ if (cinfo) {
+ jpeg_destroy_decompress(cinfo);
+ free(cinfo);
+ fclose(infile);
+ }
+}
+
+bool cImageImporterJPG::LoadImage(const char *path) {
+ if (cinfo) {
+ jpeg_destroy_decompress(cinfo);
+ free(cinfo);
+ fclose(infile);
+ cinfo = NULL;
+ }
+
+ // Open input file
+ if ((infile = fopen(path, "rb")) == NULL) {
+ if (config.debugImageLoading)
+ dsyslog("skindesigner: Can't open %s", path);
+ return false;
+ }
+
+ // Allocate space for our decompress struct
+ cinfo = (j_decompress_ptr)malloc(sizeof(struct jpeg_decompress_struct));
+
+ // We set up the normal JPEG error routines, then override error_exit
+ // and output_message.
+ struct my_error_mgr jerr;
+ cinfo->err = jpeg_std_error(&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+ jerr.pub.output_message = my_output_message;
+ // Establish the setjmp return context for my_error_exit to use.
+ if (setjmp(jerr.setjmp_buffer)) {
+ // If we get here, the JPEG code has signaled an error.
+ jpeg_destroy_decompress(cinfo);
+ free(cinfo);
+ fclose(infile);
+ cinfo = NULL;
+ return false;
+ }
+
+ // Now we can initialize the JPEG decompression object.
+ jpeg_create_decompress(cinfo);
+
+ // Step 2: specify data source (eg, a file)
+ jpeg_stdio_src(cinfo, infile);
+
+ // Step 3: read file parameters with jpeg_read_header()
+ (void) jpeg_read_header(cinfo, TRUE);
+ return true;
+}
+
+void cImageImporterJPG::DrawToCairo(cairo_t *cr) {
+ if (!cinfo)
+ return;
+
+ unsigned char *bmp_buffer = NULL;
+
+ // Re-establish error handling. We have to do this again as the saved
+ // calling environment of "LoadImage" is invalid if we reach here!
+ struct my_error_mgr jerr;
+ cinfo->err = jpeg_std_error(&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+ jerr.pub.output_message = my_output_message;
+ if (setjmp(jerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(cinfo);
+ free(cinfo);
+ fclose(infile);
+ free(bmp_buffer);
+ cinfo = NULL;
+ return;
+ }
+
+ // Step 4: set parameters for decompression
+ cinfo->out_color_space = JCS_RGB;
+
+ // Step 5: Start decompressor
+ (void) jpeg_start_decompress(cinfo);
+
+ // Allocate buffer. Directly allocate the space needed for ARGB
+ unsigned int width = cinfo->output_width;
+ unsigned int height = cinfo->output_height;
+ bmp_buffer = (unsigned char*)malloc(width * height * 4);
+
+ // Step 6: while (scan lines remain to be read)
+ int jpg_stride = width * cinfo->output_components;
+ while (cinfo->output_scanline < height) {
+ unsigned char *buffer_array[1];
+ buffer_array[0] = bmp_buffer + (cinfo->output_scanline) * jpg_stride;
+ jpeg_read_scanlines(cinfo, buffer_array, 1);
+ }
+
+ // Step 7: Finish decompression.
+ (void)jpeg_finish_decompress(cinfo);
+
+ // Cleanup. In this "ImageImporter" we clean up everything in "DrawToCairo"
+ // as I'm not really sure whether we are able to draw a second time.
+ fclose(infile);
+ jpeg_destroy_decompress(cinfo);
+ free(cinfo);
+ cinfo = NULL;
+
+ // --> At this point we have raw RGB data in bmp_buffer
+
+ // Do some ugly byte shifting.
+ // Byte order in libjpeg: RGB
+ // Byte order in cairo and VDR: BGRA
+ unsigned char temp[3];
+ for (int index = (width * height) - 1; index >= 0; index--) {
+ unsigned char *target = bmp_buffer + (index * 4);
+ unsigned char *source = bmp_buffer + (index * 3);
+ memcpy(&temp[0], source + 2, 1);
+ memcpy(&temp[1], source + 1, 1);
+ memcpy(&temp[2], source, 1);
+ memcpy(target, &temp, 3);
+ }
+
+ // Create new Cairo surface from our raw image data
+ cairo_surface_t *surface;
+ surface = cairo_image_surface_create_for_data(bmp_buffer,
+ CAIRO_FORMAT_RGB24,
+ width,
+ height,
+ width * 4);
+
+ // Draw surface to Cairo
+ if (surface) {
+ cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_paint(cr);
+ cairo_surface_destroy(surface);
+ }
+
+ // Free our memory
+ free(bmp_buffer);
+}
+
+void cImageImporterJPG::GetImageSize(int &width, int &height) {
+ if (cinfo) {
+ width = cinfo->image_width;
+ height = cinfo->image_height;
+ }
+}
+
+//
+// SVG Template class
+//
+
+cSVGTemplate::cSVGTemplate(string imageName, string templatePath) {
+ this->imageName = imageName;
+ this->templatePath = templatePath;
+ startTokenColor = "{sdcol(";
+ startTokenOpac = "{sdopac(";
+ endToken = ")}";
+ filePath = templatePath;
+ filePath += imageName + ".svg";
+}
+
+cSVGTemplate::~cSVGTemplate(void) {
+}
+
+bool cSVGTemplate::Exists(void) {
+ return FileExists(templatePath, imageName, "svg");
+}
+
+void cSVGTemplate::ReadTemplate(void) {
+ string line;
+ ifstream templatefile(filePath.c_str());
+ if (templatefile.is_open()) {
+ while ( getline (templatefile, line) ) {
+ svgTemplate.push_back(line);
+ }
+ templatefile.close();
+ }
+}
+
+bool cSVGTemplate::ParseTemplate(void) {
+ int i = 0;
+ for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) {
+ string line = *it;
+ size_t hit = line.find(startTokenColor);
+ if (hit == string::npos) {
+ i++;
+ continue;
+ }
+ while (hit != string::npos) {
+ size_t hitEnd = line.find(endToken, hit);
+ if (hitEnd == string::npos) {
+ esyslog("skindesigner: error in SVG Template %s: invalid tag", imageName.c_str());
+ return false;
+ }
+ string colorName = GetColorName(line, hit, hitEnd);
+ tColor replaceColor = 0x0;
+ if (!config.GetThemeColor(colorName, replaceColor)) {
+ esyslog("skindesigner: error in SVG Template %s: invalid color %x", imageName.c_str(), replaceColor);
+ return false;
+ }
+ ReplaceTokens(line, hit, hitEnd, replaceColor);
+ hit = line.find(startTokenColor);
+ }
+ svgTemplate[i] = line;
+ i++;
+ }
+ return true;
+}
+
+string cSVGTemplate::WriteImage(void) {
+ string tempPath = *cString::sprintf("/tmp/skindesigner/svg/%s/%s/", Setup.OSDSkin, Setup.OSDTheme);
+ CreateFolder(tempPath);
+ string fileName = tempPath + imageName + ".svg";
+ ofstream tmpimg;
+ tmpimg.open (fileName.c_str(), ios::out | ios::trunc);
+ if (!tmpimg.is_open()) {
+ return "";
+ }
+ for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) {
+ tmpimg << (*it) << "\n";
+ }
+ tmpimg.close();
+ return fileName;
+}
+
+string cSVGTemplate::GetColorName(string line, size_t tokenStart, size_t tokenEnd) {
+ string colorName = line.substr(tokenStart + startTokenColor.size(), tokenEnd - tokenStart - startTokenColor.size());
+ if (colorName.size() > 0) {
+ stringstream name;
+ name << "{" << colorName << "}";
+ return name.str();
+ }
+ return "";
+}
+
+void cSVGTemplate::ReplaceTokens(string &line, size_t tokenStart, size_t tokenEnd, tColor color) {
+ string rgbColor = *cString::sprintf("%06x", color & 0x00FFFFFF);
+ line.replace(tokenStart, tokenEnd - tokenStart + 2, rgbColor);
+ size_t hitAlpha = line.find(startTokenOpac);
+ if (hitAlpha == string::npos) {
+ return;
+ }
+ size_t hitAlphaEnd = line.find(endToken, hitAlpha);
+ if (hitAlphaEnd == string::npos) {
+ return;
+ }
+ tIndex alpha = (color & 0xFF000000) >> 24;
+ string svgAlpha = *cString::sprintf("%f", (float)(alpha / (float)255));
+ std::replace( svgAlpha.begin(), svgAlpha.end(), ',', '.');
+ line.replace(hitAlpha, hitAlphaEnd - hitAlpha + 2, svgAlpha);
+}
diff --git a/extensions/imageloader.h b/extensions/imageloader.h
new file mode 100644
index 0000000..12bd8d2
--- /dev/null
+++ b/extensions/imageloader.h
@@ -0,0 +1,115 @@
+#ifndef __SKINDESIGNER_IMAGELOADER_H
+#define __SKINDESIGNER_IMAGELOADER_H
+
+#include <string>
+#include <cairo.h>
+#include <librsvg/rsvg.h>
+#ifndef LIBRSVG_CHECK_VERSION // Workaround for librsvg < 2.36.2
+ #include <librsvg/rsvg-cairo.h>
+ #include <librsvg/librsvg-features.h>
+#endif
+#include <jpeglib.h>
+#include <setjmp.h>
+#include <vdr/osd.h>
+#include <vdr/tools.h>
+
+using namespace std;
+
+//
+// Image importers
+//
+class cImageImporter {
+public:
+ cImageImporter() {};
+ virtual ~cImageImporter() {};
+ virtual bool LoadImage(const char *path) { return false; };
+ virtual void DrawToCairo(cairo_t *cr) {};
+ virtual void GetImageSize(int &width, int &height) {};
+ static cImageImporter* CreateImageImporter(const char* path);
+};
+
+// Image importer for PNG
+class cImageImporterPNG : public cImageImporter {
+public:
+ cImageImporterPNG();
+ ~cImageImporterPNG();
+ bool LoadImage(const char *path);
+ void DrawToCairo(cairo_t *cr);
+ void GetImageSize(int &width, int &height);
+private:
+ cairo_surface_t *surface;
+};
+
+// Image importer for SVG
+#if !LIBRSVG_CHECK_VERSION(2, 36, 0)
+ #error librsvg version 2.36.0 or above required!
+#endif
+
+class cImageImporterSVG : public cImageImporter {
+public:
+ cImageImporterSVG();
+ ~cImageImporterSVG();
+ bool LoadImage(const char *path);
+ void DrawToCairo(cairo_t *cr);
+ void GetImageSize(int &width, int &height);
+ static void InitLibRSVG();
+private:
+ RsvgHandle *handle;
+};
+
+// Image importer for JPG
+#if BITS_IN_JSAMPLE != 8
+ #error libjpeg has to be compiled with 8-bit samples!
+#endif
+
+class cImageImporterJPG : public cImageImporter {
+public:
+ cImageImporterJPG();
+ ~cImageImporterJPG();
+ bool LoadImage(const char *path);
+ void DrawToCairo(cairo_t *cr);
+ void GetImageSize(int &width, int &height);
+private:
+ j_decompress_ptr cinfo;
+ FILE *infile;
+};
+
+//
+// Image loader class
+//
+class cImageLoader {
+private:
+ cImageImporter *importer;
+public:
+ cImageLoader();
+ virtual ~cImageLoader();
+ cImage *CreateImage(int width, int height, bool preserveAspect = true);
+ bool LoadImage(std::string Path, std::string FileName, std::string Extension);
+ bool LoadImage(const char *fullpath);
+};
+
+//
+// SVG Template class
+//
+
+class cSVGTemplate {
+private:
+ string imageName;
+ string templatePath;
+ string filePath;
+ string startTokenColor;
+ string startTokenOpac;
+ string endToken;
+ vector<string> svgTemplate;
+ string GetColorName(string line, size_t tokenStart, size_t tokenEnd);
+ void ReplaceTokens(string &line, size_t tokenStart, size_t tokenEnd, tColor color);
+public:
+ cSVGTemplate(string imageName, string templatePath);
+ virtual ~cSVGTemplate(void);
+ bool Exists(void);
+ void ReadTemplate(void);
+ bool ParseTemplate(void);
+ string WriteImage(void);
+};
+
+#endif //__SKINDESIGNER_IMAGELOADER_H
diff --git a/extensions/libxmlwrapper.c b/extensions/libxmlwrapper.c
new file mode 100644
index 0000000..117815e
--- /dev/null
+++ b/extensions/libxmlwrapper.c
@@ -0,0 +1,189 @@
+#include "helpers.h"
+#include "libxmlwrapper.h"
+
+void SkinDesignerXMLErrorHandler (void * userData, xmlErrorPtr error) {
+ esyslog("skindesigner: Error in XML: %s", error->message);
+}
+
+cLibXMLWrapper::cLibXMLWrapper(void) {
+ ctxt = NULL;
+ doc = NULL;
+ root = NULL;
+
+ initGenericErrorDefaultFunc(NULL);
+ xmlSetStructuredErrorFunc(NULL, SkinDesignerXMLErrorHandler);
+ ctxt = xmlNewParserCtxt();
+}
+
+cLibXMLWrapper::~cLibXMLWrapper() {
+ DeleteDocument();
+ xmlFreeParserCtxt(ctxt);
+}
+
+void cLibXMLWrapper::InitLibXML() {
+ xmlInitParser();
+}
+
+void cLibXMLWrapper::CleanupLibXML() {
+ xmlCleanupParser();
+}
+
+void cLibXMLWrapper::DeleteDocument(void) {
+ if (doc) {
+ xmlFreeDoc(doc);
+ doc = NULL;
+ }
+ while (!nodeStack.empty())
+ nodeStack.pop();
+}
+
+bool cLibXMLWrapper::ReadXMLFile(const char *path, bool validate) {
+ if (!ctxt) {
+ esyslog("skindesigner: Failed to allocate parser context");
+ return false;
+ }
+ if (!FileExists(path)) {
+ dsyslog("skindesigner: reading XML Template %s failed", path);
+ return false;
+ }
+ if (validate)
+ doc = xmlCtxtReadFile(ctxt, path, NULL, XML_PARSE_NOENT | XML_PARSE_DTDVALID);
+ else
+ doc = xmlCtxtReadFile(ctxt, path, NULL, XML_PARSE_NOENT);
+
+ if (!doc) {
+ dsyslog("skindesigner: reading XML Template %s failed", path);
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::SetDocument(void) {
+ if (!doc)
+ return false;
+ root = xmlDocGetRootElement(doc);
+ nodeStack.push(root);
+ if (root == NULL) {
+ esyslog("skindesigner: ERROR: XML File is empty");
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::Validate(void) {
+ if (!ctxt)
+ return false;
+ if (ctxt->valid == 0) {
+ esyslog("skindesigner: Failed to validate XML File");
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::CheckNodeName(const char *name) {
+ if (nodeStack.empty())
+ return false;
+ xmlNodePtr current = nodeStack.top();
+ if (xmlStrcmp(current->name, (const xmlChar *) name)) {
+ return false;
+ }
+ return true;
+}
+
+const char *cLibXMLWrapper::NodeName(void) {
+ xmlNodePtr current = nodeStack.top();
+ return (const char*)current->name;
+}
+
+vector<stringpair> cLibXMLWrapper::ParseAttributes(void) {
+ vector<stringpair> attributes;
+ if (nodeStack.empty())
+ return attributes;
+ xmlNodePtr current = nodeStack.top();
+ xmlAttrPtr attrPtr = current->properties;
+ if (attrPtr == NULL) {
+ return attributes;
+ }
+ while (NULL != attrPtr) {
+ string name = (const char*)attrPtr->name;
+ xmlChar *value = NULL;
+ value = xmlGetProp(current, attrPtr->name);
+ if (value)
+ attributes.push_back(stringpair((const char*)attrPtr->name, (const char*)value));
+ attrPtr = attrPtr->next;
+ if (value)
+ xmlFree(value);
+ }
+ return attributes;
+}
+
+bool cLibXMLWrapper::LevelDown(void) {
+ if (nodeStack.empty())
+ return false;
+ xmlNodePtr current = nodeStack.top();
+ xmlNodePtr child = current->xmlChildrenNode;
+ while (child && child->type != XML_ELEMENT_NODE) {
+ child = child->next;
+ }
+ if (!child)
+ return false;
+ nodeStack.push(child);
+ return true;
+}
+
+bool cLibXMLWrapper::LevelUp(void) {
+ if (nodeStack.size() == 1)
+ return false;
+ nodeStack.pop();
+ return true;
+}
+
+bool cLibXMLWrapper::NextNode(void) {
+ xmlNodePtr current = nodeStack.top();
+ current = current->next;
+ while (current && current->type != XML_ELEMENT_NODE) {
+ current = current->next;
+ }
+ if (!current)
+ return false;
+ nodeStack.pop();
+ nodeStack.push(current);
+ return true;
+}
+
+bool cLibXMLWrapper::GetAttribute(string &name, string &value) {
+ bool ok = false;
+ xmlNodePtr current = nodeStack.top();
+ xmlAttrPtr attr = current->properties;
+ if (attr == NULL) {
+ return ok;
+ }
+ xmlChar *xmlValue = NULL;
+ while (NULL != attr) {
+ if (xmlStrcmp(attr->name, (const xmlChar *) name.c_str())) {
+ attr = attr->next;
+ continue;
+ }
+ ok = true;
+ xmlValue = xmlGetProp(current, attr->name);
+ if (xmlValue) {
+ value = (const char*)xmlValue;
+ xmlFree(xmlValue);
+ }
+ break;
+ }
+ return ok;
+}
+
+bool cLibXMLWrapper::GetNodeValue(string &value) {
+ xmlChar *val = NULL;
+ xmlNodePtr current = nodeStack.top();
+ val = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
+ if (val) {
+ value = (const char*)val;
+ xmlFree(val);
+ return true;
+ }
+ value = "";
+ return false;
+}
diff --git a/extensions/libxmlwrapper.h b/extensions/libxmlwrapper.h
new file mode 100644
index 0000000..3971bb9
--- /dev/null
+++ b/extensions/libxmlwrapper.h
@@ -0,0 +1,46 @@
+#ifndef __LIBXMLWRAPPER_H
+#define __LIBXMLWRAPPER_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <stack>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlerror.h>
+#include <vdr/plugin.h>
+
+using namespace std;
+typedef pair<string,string> stringpair;
+typedef map<string,string> stringmap;
+
+class cLibXMLWrapper {
+private:
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr current;
+ stack<xmlNodePtr> nodeStack;
+protected:
+ void DeleteDocument(void);
+ bool ReadXMLFile(const char *path, bool validate = true);
+ bool SetDocument(void);
+ bool Validate(void);
+ bool CheckNodeName(const char *name);
+ const char *NodeName(void);
+ vector<stringpair> ParseAttributes(void);
+ bool LevelDown(void);
+ bool LevelUp(void);
+ bool NextNode(void);
+ bool GetAttribute(string &name, string &value);
+ bool GetNodeValue(string &value);
+public:
+ cLibXMLWrapper(void);
+ virtual ~cLibXMLWrapper(void);
+ static void InitLibXML();
+ static void CleanupLibXML();
+};
+
+#endif //__LIBXMLWRAPPER_H \ No newline at end of file
diff --git a/extensions/pluginmanager.c b/extensions/pluginmanager.c
new file mode 100644
index 0000000..2f839f8
--- /dev/null
+++ b/extensions/pluginmanager.c
@@ -0,0 +1,301 @@
+#include "pluginmanager.h"
+
+cSDPluginManager::cSDPluginManager(void) {
+ lastId = 0;
+ subviewsfound = false;
+}
+
+cSDPluginManager::~cSDPluginManager(void) {
+}
+
+void cSDPluginManager::Reset(void) {
+ subViewMapping.clear();
+}
+
+void cSDPluginManager::RegisterBasicPlugin(skindesignerapi::cPluginStructure *plugStructure) {
+ dsyslog("skindesigner: plugin %s uses libskindesigner API Version %s",
+ plugStructure->name.c_str(),
+ plugStructure->libskindesignerAPIVersion.c_str());
+ plugStructure->id = lastId;
+ registeredPlugins.insert(pair<int,string>(lastId, plugStructure->name));
+ lastId++;
+ map< int, skindesignerapi::sPlugMenu > menusNew;
+ for (map< int, skindesignerapi::sPlugMenu >::iterator it = plugStructure->menus.begin(); it != plugStructure->menus.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugMenu menu = it->second;
+ skindesignerapi::sPlugMenu menuNew;
+ menuNew.type = menu.type;
+ menuNew.tplname = menu.tplname;
+ menuNew.tokenContainer = new skindesignerapi::cTokenContainer(*menu.tokenContainer);
+ menusNew.insert(pair<int, skindesignerapi::sPlugMenu>(key, menuNew));
+ }
+ pluginMenus.insert(pair< int, map < int, skindesignerapi::sPlugMenu > >(plugStructure->id, menusNew));
+
+ if (plugStructure->menus.size() > 0)
+ dsyslog("skindesigner: plugin %s has registered %ld menus", plugStructure->name.c_str(), plugStructure->menus.size());
+
+}
+
+int cSDPluginManager::GetNumPluginMenus(void) {
+ int numMenusTotal = 0;
+ for (map < int, map < int, skindesignerapi::sPlugMenu > >::iterator it = pluginMenus.begin(); it != pluginMenus.end(); it++) {
+ numMenusTotal += (it->second).size();
+ }
+ return numMenusTotal;
+}
+
+void cSDPluginManager::InitPluginMenuIterator(void) {
+ plugMenuIt = pluginMenus.begin();
+}
+
+map <int,skindesignerapi::sPlugMenu> *cSDPluginManager::GetPluginMenus(string &name, int &id) {
+ if (plugMenuIt == pluginMenus.end())
+ return NULL;
+ id = plugMenuIt->first;
+ map<int,string>::iterator hit = registeredPlugins.find(id);
+ if (hit != registeredPlugins.end())
+ name = hit->second;
+ map <int,skindesignerapi::sPlugMenu> *templates = &plugMenuIt->second;
+ plugMenuIt++;
+ return templates;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainer(int plugId, int plugMenuId) {
+ map <int, map<int, skindesignerapi::sPlugMenu> >::iterator hit = pluginMenus.find(plugId);
+ if (hit == pluginMenus.end())
+ return NULL;
+ map<int, skindesignerapi::sPlugMenu>::iterator hit2 = (hit->second).find(plugMenuId);
+ if (hit2 == (hit->second).end())
+ return NULL;
+ skindesignerapi::cTokenContainer *tk = hit2->second.tokenContainer;
+ return tk;
+}
+
+void cSDPluginManager::AddSubviewMapping(int plugId, int plugMenuId, int subViewId) {
+ map <int, map<int,int> >::iterator hit = subViewMapping.find(plugId);
+ if (hit == subViewMapping.end()) {
+ map<int,int> menus;
+ menus.insert(pair<int,int>(plugMenuId, subViewId));
+ subViewMapping.insert(pair<int, map<int,int> >(plugId, menus));
+ } else {
+ (hit->second).insert(pair<int,int>(plugMenuId, subViewId));
+ }
+}
+
+int cSDPluginManager::GetSubviewId(int plugId, int plugMenuId) {
+ map <int, map<int,int> >::iterator hit = subViewMapping.find(plugId);
+ if (hit == subViewMapping.end())
+ return -1;
+ map<int,int>::iterator hit2 = (hit->second).find(plugMenuId);
+ if (hit2 == (hit->second).end())
+ return -1;
+ return hit2->second;
+}
+
+void cSDPluginManager::RegisterAdvancedPlugin(skindesignerapi::cPluginStructure *plugStructure) {
+ dsyslog("skindesigner: plugin %s uses libskindesigner API Version %s",
+ plugStructure->name.c_str(),
+ plugStructure->libskindesignerAPIVersion.c_str());
+ plugStructure->id = lastId;
+ registeredPlugins.insert(pair<int,string>(lastId, plugStructure->name));
+ lastId++;
+
+ rootviews.insert(pair<int,string>(plugStructure->id, plugStructure->rootview));
+ subviews.insert(pair<int,map<int,string> >(plugStructure->id, plugStructure->subviews));
+
+ multimap< int, skindesignerapi::sPlugViewElement > viewelementsNew;
+ for (map< int, skindesignerapi::sPlugViewElement >::iterator it = plugStructure->viewelements.begin(); it != plugStructure->viewelements.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugViewElement ve = it->second;
+ skindesignerapi::sPlugViewElement veNew;
+ veNew.id = ve.id;
+ veNew.viewId = ve.viewId;
+ veNew.name = ve.name;
+ veNew.tokenContainer = new skindesignerapi::cTokenContainer(*ve.tokenContainer);
+ viewelementsNew.insert(pair<int, skindesignerapi::sPlugViewElement>(key, veNew));
+ }
+ viewelements.insert(pair< int, multimap < int, skindesignerapi::sPlugViewElement > >(plugStructure->id, viewelementsNew));
+
+ multimap< int, skindesignerapi::sPlugViewGrid > viewgridsNew;
+ for (map< int, skindesignerapi::sPlugViewGrid >::iterator it = plugStructure->viewgrids.begin(); it != plugStructure->viewgrids.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugViewGrid vg = it->second;
+ skindesignerapi::sPlugViewGrid vgNew;
+ vgNew.id = vg.id;
+ vgNew.viewId = vg.viewId;
+ vgNew.name = vg.name;
+ vgNew.tokenContainer = new skindesignerapi::cTokenContainer(*vg.tokenContainer);
+ viewgridsNew.insert(pair<int, skindesignerapi::sPlugViewGrid>(key, vgNew));
+ }
+ viewgrids.insert(pair< int, multimap < int, skindesignerapi::sPlugViewGrid > >(plugStructure->id, viewgridsNew));
+
+ map< int, skindesignerapi::cTokenContainer* > viewtabsNew;
+ for (map<int,skindesignerapi::cTokenContainer*>::iterator it = plugStructure->viewtabs.begin(); it != plugStructure->viewtabs.end(); it++) {
+ int id = it->first;
+ skindesignerapi::cTokenContainer *tk = it->second;
+ viewtabsNew.insert(pair<int,skindesignerapi::cTokenContainer*>(id, new skindesignerapi::cTokenContainer(*tk)));
+ }
+ viewtabs.insert(pair< int, map<int,skindesignerapi::cTokenContainer*> >(plugStructure->id, viewtabsNew));
+
+ if (plugStructure->rootview.size() > 0)
+ dsyslog("skindesigner: plugin %s has registered %ld views with %ld viewelements and %ld viewgrids",
+ plugStructure->name.c_str(),
+ 1 + plugStructure->subviews.size(),
+ plugStructure->viewelements.size(),
+ plugStructure->viewgrids.size());
+}
+
+void cSDPluginManager::InitPluginViewIterator(void) {
+ rootViewsIt = rootviews.begin();
+}
+
+bool cSDPluginManager::GetNextPluginView(string &plugName, int &plugId, string &tplName) {
+ if (rootViewsIt == rootviews.end())
+ return false;
+ plugId = rootViewsIt->first;
+ tplName = rootViewsIt->second;
+ map<int,string>::iterator hit = registeredPlugins.find(plugId);
+ if (hit != registeredPlugins.end())
+ plugName = hit->second;
+ rootViewsIt++;
+ return true;
+}
+
+int cSDPluginManager::GetNumSubviews(int plugId) {
+ map< int, map< int, string > >::iterator hit = subviews.find(plugId);
+ if (hit == subviews.end())
+ return 0;
+ return (hit->second).size();
+}
+
+void cSDPluginManager::InitPluginSubviewIterator(int plugId) {
+ map< int, map< int, string > >::iterator hit = subviews.find(plugId);
+ if (hit == subviews.end()) {
+ subviewsfound = false;
+ return;
+ }
+ subviewsCurrent = hit->second;
+ subviewsfound = true;
+ svIt = subviewsCurrent.begin();
+}
+
+bool cSDPluginManager::GetNextSubView(int &id, string &tplname) {
+ if (!subviewsfound)
+ return false;
+ if( svIt == subviewsCurrent.end() ) {
+ return false;
+ }
+ id = svIt->first;
+ tplname = svIt->second;
+ svIt++;
+ return true;
+}
+
+int cSDPluginManager::GetNumViewElements(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return 0;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ pair<multimap<int, skindesignerapi::sPlugViewElement>::iterator, multimap<int, skindesignerapi::sPlugViewElement>::iterator> range;
+ range = plugVEs->equal_range(viewId);
+ int numVEs = 0;
+ for (multimap<int, skindesignerapi::sPlugViewElement>::iterator it=range.first; it!=range.second; ++it) {
+ numVEs++;
+ }
+ return numVEs;
+}
+
+void cSDPluginManager::InitViewElementIterator(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ veRange = plugVEs->equal_range(viewId);
+ veIt = veRange.first;
+}
+
+bool cSDPluginManager::GetNextViewElement(int &veId, string &veName) {
+ if (veIt == veRange.second)
+ return false;
+ skindesignerapi::sPlugViewElement *ve = &veIt->second;
+ veId = ve->id;
+ veName = ve->name;
+ veIt++;
+ return true;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerVE(int plugId, int viewId, int veId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return NULL;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ for (multimap<int, skindesignerapi::sPlugViewElement>::iterator it = plugVEs->begin(); it != plugVEs->end(); it++) {
+ int view = it->first;
+ if (view != viewId)
+ continue;
+ skindesignerapi::sPlugViewElement *ve = &it->second;
+ if (ve->id == veId)
+ return ve->tokenContainer;
+ }
+ return NULL;
+}
+
+int cSDPluginManager::GetNumViewGrids(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return 0;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugVGs = &hit->second;
+ pair<multimap<int, skindesignerapi::sPlugViewGrid>::iterator, multimap<int, skindesignerapi::sPlugViewGrid>::iterator> range;
+ range = plugVGs->equal_range(viewId);
+ int numVGs = 0;
+ for (multimap<int, skindesignerapi::sPlugViewGrid>::iterator it=range.first; it!=range.second; ++it) {
+ numVGs++;
+ }
+ return numVGs;
+}
+
+void cSDPluginManager::InitViewGridIterator(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugGEs = &hit->second;
+ gRange = plugGEs->equal_range(viewId);
+ gIt = gRange.first;
+}
+
+bool cSDPluginManager::GetNextViewGrid(int &gId, string &gName) {
+ if (gIt == gRange.second)
+ return false;
+ skindesignerapi::sPlugViewGrid *ge = &gIt->second;
+ gId = ge->id;
+ gName = ge->name;
+ gIt++;
+ return true;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerGE(int plugId, int viewId, int gId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return NULL;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugGEs = &hit->second;
+ for (multimap<int, skindesignerapi::sPlugViewGrid>::iterator it = plugGEs->begin(); it != plugGEs->end(); it++) {
+ int view = it->first;
+ if (view != viewId)
+ continue;
+ skindesignerapi::sPlugViewGrid *g = &it->second;
+ if (g->id == gId)
+ return g->tokenContainer;
+ }
+ return NULL;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerTab(int plugId, int viewId) {
+ map< int, map< int, skindesignerapi::cTokenContainer* > >::iterator hit = viewtabs.find(plugId);
+ if (hit == viewtabs.end())
+ return NULL;
+ map< int, skindesignerapi::cTokenContainer* > *tabs = &hit->second;
+ map< int, skindesignerapi::cTokenContainer* >::iterator hit2 = tabs->find(viewId);
+ if (hit2 == tabs->end())
+ return NULL;
+ return (hit2->second);
+}
diff --git a/extensions/pluginmanager.h b/extensions/pluginmanager.h
new file mode 100644
index 0000000..399c1d8
--- /dev/null
+++ b/extensions/pluginmanager.h
@@ -0,0 +1,71 @@
+#ifndef __PLUGINMANAGER_H
+#define __PLUGINMANAGER_H
+
+#include <string>
+#include <map>
+#include "libskindesignerapi/skindesignerapi.h"
+
+using namespace std;
+
+class cSDPluginManager {
+private:
+ int lastId;
+ //plugin id --> plugin name
+ map < int, string > registeredPlugins;
+ //Basic Plugin Interface
+ //plugin id --> plugin definition
+ map < int, map < int, skindesignerapi::sPlugMenu > > pluginMenus;
+ map < int, map < int, skindesignerapi::sPlugMenu > >::iterator plugMenuIt;
+ //plugin id - menuId --> subviewid
+ map < int, map<int, int> > subViewMapping;
+
+ //Advanced Plugin Interface
+ //plugin id --> rootview templatename definition
+ map< int, string > rootviews;
+ map< int, string >::iterator rootViewsIt;
+ //plugin id --> subviewid /templatename definition
+ map< int, map< int, string > > subviews;
+ map< int, string> subviewsCurrent;
+ map< int, string>::iterator svIt;
+ bool subviewsfound;
+ //plugin id --> view id --> viewelement definition
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > > viewelements;
+ pair<multimap<int, skindesignerapi::sPlugViewElement>::iterator, multimap<int, skindesignerapi::sPlugViewElement>::iterator> veRange;
+ multimap<int, skindesignerapi::sPlugViewElement>::iterator veIt;
+ //plugin id --> view id --> viewgrid definition
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > > viewgrids;
+ pair<multimap<int, skindesignerapi::sPlugViewGrid>::iterator, multimap<int, skindesignerapi::sPlugViewGrid>::iterator> gRange;
+ multimap<int, skindesignerapi::sPlugViewGrid>::iterator gIt;
+ //plugin id --> view id --> tokencontainer of detailedview definition
+ map< int, map< int, skindesignerapi::cTokenContainer* > > viewtabs;
+public:
+ cSDPluginManager(void);
+ ~cSDPluginManager(void);
+ void Reset(void);
+ //Basic Plugin Interface
+ void RegisterBasicPlugin(skindesignerapi::cPluginStructure *plugStructure);
+ int GetNumPluginMenus(void);
+ void InitPluginMenuIterator(void);
+ map <int,skindesignerapi::sPlugMenu> *GetPluginMenus(string &name, int &id);
+ skindesignerapi::cTokenContainer *GetTokenContainer(int plugId, int plugMenuId);
+ void AddSubviewMapping(int plugId, int plugMenuId, int subViewId);
+ int GetSubviewId(int plugId, int plugMenuId);
+ //Advanced Plugin Interface
+ void RegisterAdvancedPlugin(skindesignerapi::cPluginStructure *plugStructure);
+ void InitPluginViewIterator(void);
+ bool GetNextPluginView(string &plugName, int &plugId, string &tplName);
+ int GetNumSubviews(int plugId);
+ void InitPluginSubviewIterator(int plugId);
+ bool GetNextSubView(int &id, string &tplname);
+ int GetNumViewElements(int plugId, int viewId);
+ void InitViewElementIterator(int plugId, int viewId);
+ bool GetNextViewElement(int &veId, string &veName);
+ skindesignerapi::cTokenContainer *GetTokenContainerVE(int plugId, int viewId, int veId);
+ int GetNumViewGrids(int plugId, int viewId);
+ void InitViewGridIterator(int plugId, int viewId);
+ bool GetNextViewGrid(int &gId, string &gName);
+ skindesignerapi::cTokenContainer *GetTokenContainerGE(int plugId, int viewId, int gId);
+ skindesignerapi::cTokenContainer *GetTokenContainerTab(int plugId, int viewId);
+};
+
+#endif //__PLUGINMANAGER_H \ No newline at end of file
diff --git a/extensions/recfolderinfo.c b/extensions/recfolderinfo.c
new file mode 100644
index 0000000..d03ed6c
--- /dev/null
+++ b/extensions/recfolderinfo.c
@@ -0,0 +1,219 @@
+#include "recfolderinfo.h"
+
+
+class cFolderInfoInternList;
+
+class cRecordingsFolderInfo::cFolderInfoIntern:public cListObject {
+private:
+ cFolderInfoIntern *_parent;
+ cList<cFolderInfoIntern> *_subFolders;
+
+ cString _name;
+ time_t _latest;
+ int _count;
+ cString _latestFileName;
+
+ void UpdateData(cRecording *Recording);
+ cFolderInfoIntern *FindSubFolder(const char *Name) const;
+
+public:
+ cFolderInfoIntern(cFolderInfoIntern *Parent, const char *Name);
+ virtual ~cFolderInfoIntern(void);
+
+ // split Name and find folder-info in tree
+ // if "Add", missing folders are created
+ cFolderInfoIntern *Find(const char *Name, bool Add);
+
+ void Add(cRecording *Recording);
+
+ cRecordingsFolderInfo::cFolderInfo *GetInfo(void) const;
+
+ cString FullName(void) const;
+ cString ToText(void) const;
+ cString DebugOutput(void) const;
+};
+
+
+cRecordingsFolderInfo::cFolderInfo::cFolderInfo(const char *Name, const char *FullName, time_t Latest, int Count, const char *LatestFileName)
+{
+ this->Name = Name;
+ this->FullName = FullName;
+ this->Latest = Latest;
+ this->Count = Count;
+ this->LatestFileName= LatestFileName;
+}
+
+
+cRecordingsFolderInfo::cRecordingsFolderInfo(cRecordings &Recordings)
+:_recordings(Recordings)
+,_root(NULL)
+{
+ Rebuild();
+}
+
+cRecordingsFolderInfo::~cRecordingsFolderInfo(void)
+{
+ delete _root;
+ _root = NULL;
+}
+
+void cRecordingsFolderInfo::Rebuild(void)
+{
+ delete _root;
+ _root = new cFolderInfoIntern(NULL, "");
+
+ cThreadLock RecordingsLock(&_recordings);
+ // re-get state with lock held
+ _recordings.StateChanged(_recState);
+ cFolderInfoIntern *info;
+ cString folder;
+ for (cRecording *rec = _recordings.First(); rec; rec = _recordings.Next(rec)) {
+#if APIVERSNUM < 20102
+ //cRecording::Folder() first available since VDR 2.1.2
+ const char *recName = rec->Name();
+ if (const char *s = strrchr(recName, FOLDERDELIMCHAR)) {
+ folder = recName;
+ folder.Truncate(s - recName);
+ }
+ else
+ folder = "";
+#else
+ folder = rec->Folder();
+#endif
+ info = _root->Find(*folder, true);
+ info->Add(rec);
+ }
+}
+
+cRecordingsFolderInfo::cFolderInfo *cRecordingsFolderInfo::Get(const char *Folder)
+{
+ cMutexLock lock(&_rootLock);
+
+ if (_recordings.StateChanged(_recState) || (_root == NULL))
+ Rebuild();
+
+ cFolderInfoIntern *info = _root->Find(Folder, false);
+ if (info == NULL)
+ return NULL;
+
+ return info->GetInfo();
+}
+
+cString cRecordingsFolderInfo::DebugOutput(void) const
+{
+ cMutexLock lock(&_rootLock);
+
+ return _root->DebugOutput();
+}
+
+
+cRecordingsFolderInfo::cFolderInfoIntern::cFolderInfoIntern(cFolderInfoIntern *Parent, const char *Name)
+:_parent(Parent)
+,_name(Name)
+,_latest(0)
+,_count(0)
+,_latestFileName("")
+{
+ _subFolders = new cList<cFolderInfoIntern>();
+}
+
+cRecordingsFolderInfo::cFolderInfoIntern::~cFolderInfoIntern(void)
+{
+ delete _subFolders;
+ _subFolders = NULL;
+}
+
+cRecordingsFolderInfo::cFolderInfoIntern *cRecordingsFolderInfo::cFolderInfoIntern::Find(const char *Name, bool Add)
+{
+ cFolderInfoIntern *info = NULL;
+ if (Add)
+ info = this;
+
+ if (Name && *Name) {
+ static char delim[2] = { FOLDERDELIMCHAR, 0 };
+ char *strtok_next;
+ cFolderInfoIntern *next;
+ char *folder = strdup(Name);
+ info = this;
+ for (char *t = strtok_r(folder, delim, &strtok_next); t; t = strtok_r(NULL, delim, &strtok_next)) {
+ next = info->FindSubFolder(t);
+ if (next == NULL) {
+ if (!Add) {
+ info = NULL;
+ break;
+ }
+
+ next = new cFolderInfoIntern(info, t);
+ info->_subFolders->Add(next);
+ }
+ info = next;
+ }
+ free(folder);
+ }
+
+ return info;
+}
+
+void cRecordingsFolderInfo::cFolderInfoIntern::UpdateData(cRecording *Recording)
+{
+ // count every recording
+ _count++;
+
+ // update date if newer
+ time_t recdate = Recording->Start();
+ if (_latest < recdate) {
+ _latest = recdate;
+ _latestFileName = Recording->FileName();
+ }
+}
+
+cRecordingsFolderInfo::cFolderInfoIntern *cRecordingsFolderInfo::cFolderInfoIntern::FindSubFolder(const char *Name) const
+{
+ for (cRecordingsFolderInfo::cFolderInfoIntern *info = _subFolders->First(); info; info = _subFolders->Next(info)) {
+ if (strcmp(info->_name, Name) == 0)
+ return info;
+ }
+ return NULL;
+}
+
+void cRecordingsFolderInfo::cFolderInfoIntern::Add(cRecording *Recording)
+{
+ if (Recording == NULL)
+ return;
+
+ // update this and all parent folders
+ for (cFolderInfoIntern *p = this; p; p = p->_parent)
+ p->UpdateData(Recording);
+}
+
+cRecordingsFolderInfo::cFolderInfo *cRecordingsFolderInfo::cFolderInfoIntern::GetInfo(void) const
+{
+ return new cRecordingsFolderInfo::cFolderInfo(*_name, *FullName(), _latest, _count, *_latestFileName);
+}
+
+cString cRecordingsFolderInfo::cFolderInfoIntern::FullName(void) const
+{
+ static char delim[2] = { FOLDERDELIMCHAR, 0 };
+
+ cString name = _name;
+ for (cFolderInfoIntern *p = _parent; p; p = p->_parent) {
+ // don't add FOLDERDELIMCHAR at start of FullName
+ if (p->_parent == NULL)
+ break;
+ name = cString::sprintf("%s%s%s", *p->_name, delim, *name);
+ }
+ return name;
+}
+
+cString cRecordingsFolderInfo::cFolderInfoIntern::ToText(void) const
+{
+ return cString::sprintf("%s (%d recordings, latest: %s)\n", *FullName(), _count, *ShortDateString(_latest));
+}
+
+cString cRecordingsFolderInfo::cFolderInfoIntern::DebugOutput(void) const
+{
+ cString output = ToText();
+ for (cFolderInfoIntern *i = _subFolders->First(); i; i = _subFolders->Next(i))
+ output = cString::sprintf("%s%s", *output, *i->DebugOutput());
+ return output;
+} \ No newline at end of file
diff --git a/extensions/recfolderinfo.h b/extensions/recfolderinfo.h
new file mode 100644
index 0000000..8148ea7
--- /dev/null
+++ b/extensions/recfolderinfo.h
@@ -0,0 +1,43 @@
+#ifndef __RECFOLDERINFO_H
+#define __RECFOLDERINFO_H
+
+#include <vdr/recording.h>
+
+
+class cRecordingsFolderInfo {
+public:
+ class cFolderInfoIntern;
+
+private:
+ cRecordings &_recordings;
+ int _recState;
+ cFolderInfoIntern *_root;
+ mutable cMutex _rootLock;
+
+ void Rebuild(void);
+
+public:
+ class cFolderInfo {
+ public:
+ cString Name;
+ cString FullName;
+ time_t Latest;
+ int Count;
+ cString LatestFileName;
+
+ cFolderInfo(const char *Name, const char *FullName, time_t Latest, int Count, const char *LatestFileName);
+ };
+
+ cRecordingsFolderInfo(cRecordings &Recordings);
+ ~cRecordingsFolderInfo(void);
+
+ // caller must delete the cInfo object!
+ // returns NULL if folder doesn't exists
+ // will rebuild tree if recordings' state has changed
+ // is thread-safe
+ cFolderInfo *Get(const char *Folder);
+
+ cString DebugOutput(void) const;
+};
+
+#endif // __RECFOLDERINFO_H \ No newline at end of file
diff --git a/extensions/scrapmanager.c b/extensions/scrapmanager.c
new file mode 100644
index 0000000..8452d75
--- /dev/null
+++ b/extensions/scrapmanager.c
@@ -0,0 +1,359 @@
+#include "scrapmanager.h"
+#include "../coreengine/definitions.h"
+#include "helpers.h"
+
+cPlugin *cScrapManager::pScraper = NULL;
+
+cScrapManager::cScrapManager(void) {
+ if (!pScraper) {
+ pScraper = GetScraperPlugin();
+ }
+ movie = NULL;
+ series = NULL;
+}
+
+cScrapManager::~cScrapManager(void) {
+ delete movie;
+ delete series;
+}
+
+bool cScrapManager::LoadFullScrapInfo(const cEvent *event, const cRecording *recording) {
+ if (!pScraper) {
+ return false;
+ }
+ delete movie;
+ movie = NULL;
+ delete series;
+ series = NULL;
+
+ ScraperGetEventType getType;
+ getType.event = event;
+ getType.recording = recording;
+ if (!pScraper->Service("GetEventType", &getType)) {
+ return false;
+ }
+ if (getType.type == tMovie) {
+ movie = new cMovie();
+ movie->movieId = getType.movieId;
+ pScraper->Service("GetMovie", movie);
+ return true;
+ } else if (getType.type == tSeries) {
+ series = new cSeries();
+ series->seriesId = getType.seriesId;
+ series->episodeId = getType.episodeId;
+ pScraper->Service("GetSeries", series);
+ return true;
+ }
+ return false;
+}
+
+void cScrapManager::SetFullScrapInfo(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ if (series) {
+ tk->AddIntToken((int)eScraperIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperIT::isseries, 1);
+ SetSeries(tk, actorsIndex);
+ } else if (movie) {
+ tk->AddIntToken((int)eScraperIT::ismovie, 1);
+ tk->AddIntToken((int)eScraperIT::isseries, 0);
+ SetMovie(tk, actorsIndex);
+ } else {
+ tk->AddIntToken((int)eScraperIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperIT::isseries, 0);
+ }
+}
+
+
+int cScrapManager::NumActors(void) {
+ if (series) {
+ return series->actors.size();
+ } else if (movie) {
+ return movie->actors.size();
+ }
+ return 0;
+}
+
+void cScrapManager::SetHeaderScrapInfo(skindesignerapi::cTokenContainer *tk) {
+ if (series) {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 1);
+ vector<cTvMedia>::iterator poster = series->posters.begin();
+ if (poster != series->posters.end()) {
+ tk->AddIntToken((int)eScraperHeaderIT::posteravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::posterwidth, (*poster).width);
+ tk->AddIntToken((int)eScraperHeaderIT::posterheight, (*poster).height);
+ tk->AddStringToken((int)eScraperHeaderST::posterpath, (*poster).path.c_str());
+ }
+ vector<cTvMedia>::iterator banner = series->banners.begin();
+ if (banner != series->banners.end()) {
+ tk->AddIntToken((int)eScraperHeaderIT::banneravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::bannerwidth, (*banner).width);
+ tk->AddIntToken((int)eScraperHeaderIT::bannerheight, (*banner).height);
+ tk->AddStringToken((int)eScraperHeaderST::bannerpath, (*banner).path.c_str());
+ }
+ } else if (movie) {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 1);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::posteravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::banneravailable, false);
+ tk->AddIntToken((int)eScraperHeaderIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eScraperHeaderIT::posterheight, movie->poster.height);
+ tk->AddStringToken((int)eScraperHeaderST::posterpath, movie->poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 0);
+ }
+}
+
+void cScrapManager::SetScraperPosterBanner(skindesignerapi::cTokenContainer *tk) {
+ if (movie) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasposter, 1);
+ tk->AddStringToken((int)eCeMenuSchedulesST::posterpath, movie->poster.path.c_str());
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterheight, movie->poster.height);
+ } else if (series) {
+ vector<cTvMedia>::iterator poster = series->posters.begin();
+ if (poster != series->posters.end()) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasposter, 1);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterwidth, (*poster).width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterheight, (*poster).height);
+ tk->AddStringToken((int)eCeMenuSchedulesST::posterpath, (*poster).path.c_str());
+ }
+ vector<cTvMedia>::iterator banner = series->banners.begin();
+ if (banner != series->banners.end()) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasbanner, 1);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::bannerwidth, (*banner).width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::bannerheight, (*banner).height);
+ tk->AddStringToken((int)eCeMenuSchedulesST::bannerpath, (*banner).path.c_str());
+ }
+ }
+}
+
+void cScrapManager::SetScraperRecordingPoster(skindesignerapi::cTokenContainer *tk, const cRecording *recording, bool isListElement) {
+ if (!pScraper) {
+ return;
+ }
+ ScraperGetPosterThumb call;
+ call.event = NULL;
+ call.recording = recording;
+ if (pScraper->Service("GetPosterThumb", &call)) {
+ if (isListElement) {
+ tk->AddIntToken((int)eLeMenuRecordingsIT::hasposterthumbnail, FileExists(call.poster.path));
+ tk->AddIntToken((int)eLeMenuRecordingsIT::thumbnailwidth, call.poster.width);
+ tk->AddIntToken((int)eLeMenuRecordingsIT::thumbnailheight, call.poster.height);
+ tk->AddStringToken((int)eLeMenuRecordingsST::thumbnailpath, call.poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eCeMenuRecordingsIT::hasposterthumbnail, FileExists(call.poster.path));
+ tk->AddIntToken((int)eCeMenuRecordingsIT::thumbnailwidth, call.poster.width);
+ tk->AddIntToken((int)eCeMenuRecordingsIT::thumbnailheight, call.poster.height);
+ tk->AddStringToken((int)eCeMenuRecordingsST::thumbnailpath, call.poster.path.c_str());
+ }
+ }
+
+ ScraperGetPoster call2;
+ call2.event = NULL;
+ call2.recording = recording;
+ if (pScraper->Service("GetPoster", &call2)) {
+ if (isListElement) {
+ tk->AddIntToken((int)eLeMenuRecordingsIT::hasposter, FileExists(call2.poster.path));
+ tk->AddIntToken((int)eLeMenuRecordingsIT::posterwidth, call2.poster.width);
+ tk->AddIntToken((int)eLeMenuRecordingsIT::posterheight, call2.poster.height);
+ tk->AddStringToken((int)eLeMenuRecordingsST::posterpath, call2.poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eCeMenuRecordingsIT::hasposter, FileExists(call2.poster.path));
+ tk->AddIntToken((int)eCeMenuRecordingsIT::posterwidth, call2.poster.width);
+ tk->AddIntToken((int)eCeMenuRecordingsIT::posterheight, call2.poster.height);
+ tk->AddStringToken((int)eCeMenuRecordingsST::posterpath, call2.poster.path.c_str());
+ }
+ }
+}
+
+cPlugin *cScrapManager::GetScraperPlugin(void) {
+ static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+ if( !pScraper ) // if it doesn't exit, try tvscraper
+ pScraper = cPluginManager::GetPlugin("tvscraper");
+ return pScraper;
+}
+
+void cScrapManager::SetMovie(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ tk->AddStringToken((int)eScraperST::movietitle, movie->title.c_str());
+ tk->AddStringToken((int)eScraperST::movieoriginalTitle, movie->originalTitle.c_str());
+ tk->AddStringToken((int)eScraperST::movietagline, movie->tagline.c_str());
+ tk->AddStringToken((int)eScraperST::movieoverview, movie->overview.c_str());
+ tk->AddStringToken((int)eScraperST::moviegenres, movie->genres.c_str());
+ tk->AddStringToken((int)eScraperST::moviehomepage, movie->homepage.c_str());
+ tk->AddStringToken((int)eScraperST::moviereleasedate, movie->releaseDate.c_str());
+ tk->AddStringToken((int)eScraperST::moviepopularity, *cString::sprintf("%f", movie->popularity));
+ tk->AddStringToken((int)eScraperST::movievoteaverage, *cString::sprintf("%f", movie->voteAverage));
+ tk->AddStringToken((int)eScraperST::posterpath, movie->poster.path.c_str());
+ tk->AddStringToken((int)eScraperST::fanartpath, movie->fanart.path.c_str());
+ tk->AddStringToken((int)eScraperST::collectionposterpath, movie->collectionPoster.path.c_str());
+ tk->AddStringToken((int)eScraperST::collectionfanartpath, movie->collectionFanart.path.c_str());
+ tk->AddIntToken((int)eScraperIT::movieadult, movie->adult);
+ tk->AddIntToken((int)eScraperIT::moviebudget, movie->budget);
+ tk->AddIntToken((int)eScraperIT::movierevenue, movie->revenue);
+ tk->AddIntToken((int)eScraperIT::movieruntime, movie->runtime);
+ tk->AddIntToken((int)eScraperIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eScraperIT::posterheight, movie->poster.height);
+ tk->AddIntToken((int)eScraperIT::fanartwidth, movie->fanart.width);
+ tk->AddIntToken((int)eScraperIT::fanartheight, movie->fanart.height);
+ tk->AddIntToken((int)eScraperIT::collectionposterwidth, movie->collectionPoster.width);
+ tk->AddIntToken((int)eScraperIT::collectionposterheight, movie->collectionPoster.height);
+ tk->AddIntToken((int)eScraperIT::collectionfanartwidth, movie->collectionFanart.width);
+ tk->AddIntToken((int)eScraperIT::collectionfanartheight, movie->collectionFanart.height);
+ if (movie->collectionPoster.path.size() > 0)
+ tk->AddIntToken((int)eScraperIT::movieiscollection, 1);
+ int i=0;
+ for (vector<cActor>::iterator act = movie->actors.begin(); act != movie->actors.end(); act++) {
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::name, (*act).name.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::role, (*act).role.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumb, (*act).actorThumb.path.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbwidth, *cString::sprintf("%d", (*act).actorThumb.width));
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbheight, *cString::sprintf("%d", (*act).actorThumb.height));
+ i++;
+ }
+}
+
+void cScrapManager::SetSeries(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ //Series Basics
+ tk->AddStringToken((int)eScraperST::seriesname, series->name.c_str());
+ tk->AddStringToken((int)eScraperST::seriesoverview, series->overview.c_str());
+ tk->AddStringToken((int)eScraperST::seriesfirstaired, series->firstAired.c_str());
+ tk->AddStringToken((int)eScraperST::seriesnetwork, series->network.c_str());
+ tk->AddStringToken((int)eScraperST::seriesgenre, series->genre.c_str());
+ tk->AddStringToken((int)eScraperST::seriesrating, *cString::sprintf("%f", series->rating));
+ tk->AddStringToken((int)eScraperST::seriesstatus, series->status.c_str());
+ //Episode Information
+ tk->AddIntToken((int)eScraperIT::episodenumber, series->episode.number);
+ tk->AddIntToken((int)eScraperIT::episodeseason, series->episode.season);
+ tk->AddStringToken((int)eScraperST::episodetitle, series->episode.name.c_str());
+ tk->AddStringToken((int)eScraperST::episodefirstaired, series->episode.firstAired.c_str());
+ tk->AddStringToken((int)eScraperST::episodegueststars, series->episode.guestStars.c_str());
+ tk->AddStringToken((int)eScraperST::episodeoverview, series->episode.overview.c_str());
+ tk->AddStringToken((int)eScraperST::episoderating, *cString::sprintf("%f", series->episode.rating));
+ tk->AddIntToken((int)eScraperIT::episodeimagewidth, series->episode.episodeImage.width);
+ tk->AddIntToken((int)eScraperIT::episodeimageheight, series->episode.episodeImage.height);
+ tk->AddStringToken((int)eScraperST::episodeimagepath, series->episode.episodeImage.path.c_str());
+ //Seasonposter
+ tk->AddIntToken((int)eScraperIT::seasonposterwidth, series->seasonPoster.width);
+ tk->AddIntToken((int)eScraperIT::seasonposterheight, series->seasonPoster.height);
+ tk->AddStringToken((int)eScraperST::seasonposterpath, series->seasonPoster.path.c_str());
+ //Posters
+ int indexInt = (int)eScraperIT::seriesposter1width;
+ int indexStr = (int)eScraperST::seriesposter1path;
+ for(vector<cTvMedia>::iterator poster = series->posters.begin(); poster != series->posters.end(); poster++) {
+ tk->AddIntToken(indexInt, (*poster).width);
+ tk->AddIntToken(indexInt+1, (*poster).height);
+ tk->AddStringToken(indexStr, (*poster).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Banners
+ indexInt = (int)eScraperIT::seriesbanner1width;
+ indexStr = (int)eScraperST::seriesbanner1path;
+ for(vector<cTvMedia>::iterator banner = series->banners.begin(); banner != series->banners.end(); banner++) {
+ tk->AddIntToken(indexInt, (*banner).width);
+ tk->AddIntToken(indexInt+1, (*banner).height);
+ tk->AddStringToken(indexStr, (*banner).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Fanarts
+ indexInt = (int)eScraperIT::seriesfanart1width;
+ indexStr = (int)eScraperST::seriesfanart1path;
+ for(vector<cTvMedia>::iterator fanart = series->fanarts.begin(); fanart != series->fanarts.end(); fanart++) {
+ tk->AddIntToken(indexInt, (*fanart).width);
+ tk->AddIntToken(indexInt+1, (*fanart).height);
+ tk->AddStringToken(indexStr, (*fanart).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Actors
+ int i=0;
+ for (vector<cActor>::iterator act = series->actors.begin(); act != series->actors.end(); act++) {
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::name, (*act).name.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::role, (*act).role.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumb, (*act).actorThumb.path.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbwidth, *cString::sprintf("%d", (*act).actorThumb.width));
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbheight, *cString::sprintf("%d", (*act).actorThumb.height));
+ i++;
+ }
+}
+
+void cScrapManager::RecPoster(const cRecording *rec, int &posterWidth, int &posterHeight, string &path, bool &hasPoster) {
+ if (!pScraper) {
+ return;
+ }
+ ScraperGetPoster callPoster;
+ callPoster.event = NULL;
+ callPoster.recording = rec;
+ if (pScraper->Service("GetPoster", &callPoster)) {
+ posterWidth = callPoster.poster.width;
+ posterHeight = callPoster.poster.height;
+ path = callPoster.poster.path;
+ if (path.size() > 0)
+ hasPoster = true;
+ }
+}
+
+void cScrapManager::SetPosterBanner(skindesignerapi::cTokenContainer *tk, const cEvent *event, const cRecording *recording) {
+ if (!pScraper) {
+ return;
+ }
+ int mediaWidth = 0;
+ int mediaHeight = 0;
+ string mediaPath = "";
+ bool isBanner = false;
+ int posterWidth = 0;
+ int posterHeight = 0;
+ string posterPath = "";
+ bool hasPoster = false;
+ int bannerWidth = 0;
+ int bannerHeight = 0;
+ string bannerPath = "";
+ bool hasBanner = false;
+
+ ScraperGetPosterBannerV2 call;
+ call.event = event;
+ call.recording = recording;
+ if (pScraper->Service("GetPosterBannerV2", &call)) {
+ if ((call.type == tSeries) && call.banner.path.size() > 0) {
+ mediaWidth = call.banner.width;
+ mediaHeight = call.banner.height;
+ mediaPath = call.banner.path;
+ isBanner = true;
+ bannerWidth = mediaWidth;
+ bannerHeight = mediaHeight;
+ bannerPath = mediaPath;
+ hasBanner = true;
+ ScraperGetPoster callPoster;
+ callPoster.event = event;
+ callPoster.recording = recording;
+ if (pScraper->Service("GetPoster", &callPoster)) {
+ posterWidth = callPoster.poster.width;
+ posterHeight = callPoster.poster.height;
+ posterPath = callPoster.poster.path;
+ hasPoster = true;
+ }
+ } else if (call.type == tMovie && call.poster.path.size() > 0 && call.poster.height > 0) {
+ mediaWidth = call.poster.width;
+ mediaHeight = call.poster.height;
+ mediaPath = call.poster.path;
+ posterWidth = call.poster.width;
+ posterHeight = call.poster.height;
+ posterPath = call.poster.path;
+ hasPoster = true;
+ }
+ }
+ tk->AddIntToken((int)eScraperPosterBannerIT::mediawidth, mediaWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::mediaheight, mediaHeight);
+ tk->AddIntToken((int)eScraperPosterBannerIT::isbanner, isBanner);
+ tk->AddStringToken((int)eScraperPosterBannerST::mediapath, mediaPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::posterwidth, posterWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::posterheight, posterHeight);
+ tk->AddStringToken((int)eScraperPosterBannerST::posterpath, posterPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::hasposter, hasPoster);
+ tk->AddIntToken((int)eScraperPosterBannerIT::bannerwidth, bannerWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::bannerheight, bannerHeight);
+ tk->AddStringToken((int)eScraperPosterBannerST::bannerpath, bannerPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::hasbanner, hasBanner);
+} \ No newline at end of file
diff --git a/extensions/scrapmanager.h b/extensions/scrapmanager.h
new file mode 100644
index 0000000..0e026fc
--- /dev/null
+++ b/extensions/scrapmanager.h
@@ -0,0 +1,28 @@
+#ifndef __SCRAPMANAGER_H
+#define __SCRAPMANAGER_H
+
+#include "../services/scraper2vdr.h"
+#include "../libskindesignerapi/tokencontainer.h"
+
+class cScrapManager {
+private:
+ static cPlugin *pScraper;
+ cMovie *movie;
+ cSeries *series;
+ cPlugin *GetScraperPlugin(void);
+ void SetMovie(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+ void SetSeries(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+protected:
+ bool LoadFullScrapInfo(const cEvent *event, const cRecording *recording);
+ void SetFullScrapInfo(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+ int NumActors(void);
+ void SetHeaderScrapInfo(skindesignerapi::cTokenContainer *tk);
+ void SetScraperPosterBanner(skindesignerapi::cTokenContainer *tk);
+ void SetScraperRecordingPoster(skindesignerapi::cTokenContainer *tk, const cRecording *recording, bool isListElement);
+ void RecPoster(const cRecording *rec, int &posterWidth, int &posterHeight, string &path, bool &hasPoster);
+ void SetPosterBanner(skindesignerapi::cTokenContainer *tk, const cEvent *event, const cRecording *recording);
+public:
+ cScrapManager(void);
+ virtual ~cScrapManager(void);
+};
+#endif //__SCRAPMANAGER_H \ No newline at end of file
diff --git a/extensions/skinrepo.c b/extensions/skinrepo.c
new file mode 100644
index 0000000..16919e8
--- /dev/null
+++ b/extensions/skinrepo.c
@@ -0,0 +1,375 @@
+#include <iostream>
+#include <fstream>
+#include "skinrepo.h"
+#include "helpers.h"
+
+using namespace std;
+
+// --- cSkinRepo -------------------------------------------------------------
+
+cSkinRepo::cSkinRepo(void) {
+ name = "";
+ repoType = rtUndefined;
+ action = eaUndefined;
+ url = "";
+ author = "unknown";
+ minSDVersion = "0.0.1";
+ command = "";
+ command2 = "";
+ tempfile = "";
+ result = -1;
+ skinPath = "";
+ themesPath = "";
+}
+
+cSkinRepo::~cSkinRepo() {
+}
+
+bool cSkinRepo::Valid(void) {
+ if (!name.size())
+ return false;
+ if (repoType == rtUndefined)
+ return false;
+ if (!url.size())
+ return false;
+ return true;
+}
+
+void cSkinRepo::Install(string path, string themesPath) {
+ if (Running())
+ return;
+ action = eaInstall;
+ this->skinPath = path + name;
+ this->themesPath = themesPath;
+ if (repoType == rtGit) {
+
+ command = *cString::sprintf("git clone --depth=1 --progress %s %s", url.c_str(), skinPath.c_str());
+ tempfile = *cString::sprintf("gitclone_%s_%ld.out", name.c_str(), time(0));
+
+ dsyslog("skindesigner: installing skin from Git, command: %s, logfile: %s", command.c_str(), tempfile.c_str());
+
+ Start();
+
+ } else if (repoType == rtZipUrl) {
+
+ size_t hit = url.find_last_of('/');
+ if (hit == string::npos)
+ return;
+ string filename = url.substr(hit+1);
+
+ command = *cString::sprintf("wget -P /tmp/ %s", url.c_str());
+ command2 = *cString::sprintf("unzip /tmp/%s -d %s", filename.c_str(), path.c_str());
+
+ dsyslog("skindesigner: installing skin from Zip, command: %s, %s", command.c_str(), command2.c_str());
+
+ Start();
+ }
+}
+
+void cSkinRepo::Update(string path) {
+ if (Running())
+ return;
+ action = eaUpdate;
+ this->skinPath = path + name;
+ if (repoType == rtGit) {
+
+ command = *cString::sprintf("cd %s; git pull", skinPath.c_str());
+ tempfile = *cString::sprintf("gitpull_%s_%ld.out", name.c_str(), time(0));
+
+ dsyslog("skindesigner: updating skin from Git, command: %s, logfile: /tmp/%s", command.c_str(), tempfile.c_str());
+
+ Start();
+
+ } else if (repoType == rtZipUrl) {
+
+ //TODO
+
+ }
+}
+
+void cSkinRepo::Action(void) {
+ if (command.size() < 1)
+ return;
+ if (tempfile.size() > 0) {
+ command = *cString::sprintf("%s > /tmp/%s 2>&1", command.c_str(), tempfile.c_str());
+ }
+
+ result = system (command.c_str());
+
+ if (result == 0 && command2.size() > 0) {
+ result = system (command2.c_str());
+ }
+
+ if (result == 0) {
+ if (action == eaInstall)
+ CreateThemeFiles();
+ dsyslog("skindesigner: %s successfully executed", command.c_str());
+ } else {
+ esyslog("skindesigner: ERROR executing %s", command.c_str());
+ }
+}
+
+void cSkinRepo::CreateThemeFiles(void) {
+ string availableThemesPath = skinPath + "/themes/";
+ DIR *folder = NULL;
+ struct dirent *dirEntry;
+ folder = opendir(availableThemesPath.c_str());
+ if (!folder) {
+ return;
+ }
+ vector<string> skinThemes;
+ while (dirEntry = readdir(folder)) {
+ string dirEntryName = dirEntry->d_name;
+ int dirEntryType = dirEntry->d_type;
+ if (!dirEntryName.compare(".") || !dirEntryName.compare("..") || dirEntryType != DT_DIR)
+ continue;
+ skinThemes.push_back(dirEntryName);
+ }
+ for (vector<string>::iterator it = skinThemes.begin(); it != skinThemes.end(); it++) {
+ string themeName = *it;
+ string themeFileName = themesPath;
+ themeFileName += name + "-" + themeName + ".theme";
+ if (FileExists(themeFileName)) {
+ continue;
+ }
+ ofstream themeFile (themeFileName.c_str());
+ if (themeFile.is_open()) {
+ themeFile << "Description = ";
+ themeFile << themeName << "\n";
+ themeFile.close();
+ }
+ }
+}
+
+bool cSkinRepo::SuccessfullyUpdated(void) {
+ string logfilePath = "/tmp/" + tempfile;
+ bool updated = true;
+ string line;
+ ifstream logfile(logfilePath.c_str());
+ if (logfile.is_open()) {
+ while ( getline (logfile, line) ) {
+ if (line.find("up-to-date") != string::npos) {
+ updated = false;
+ break;
+ }
+ }
+ logfile.close();
+ }
+ return updated;
+}
+
+void cSkinRepo::Debug() {
+ string strRepoType = "Undefined";
+ if (repoType == rtGit)
+ strRepoType = "Git";
+ else if (repoType == rtZipUrl)
+ strRepoType = "ZipUrl";
+ dsyslog("skindesigner: --- skinrepo %s, Type %s ---", name.c_str(), strRepoType.c_str());
+ dsyslog("skindesigner: url %s", url.c_str());
+ dsyslog("skindesigner: author %s", author.c_str());
+ dsyslog("skindesigner: minimum Skindesigner Version required %s", minSDVersion.c_str());
+ if (specialFonts.size() > 0) {
+ for (vector<string>::iterator it = specialFonts.begin(); it != specialFonts.end(); it++) {
+ dsyslog("skindesigner: special font %s", (*it).c_str());
+ }
+ }
+ if (supportedPlugins.size() > 0) {
+ for (vector<string>::iterator it = supportedPlugins.begin(); it != supportedPlugins.end(); it++) {
+ dsyslog("skindesigner: supported plugin %s", (*it).c_str());
+ }
+ }
+ if (screenshots.size() > 0) {
+ for (vector<pair<string,string> >::iterator it = screenshots.begin(); it != screenshots.end(); it++) {
+ string desc = (it->first).c_str();
+ string url = (it->second).c_str();
+ dsyslog("skindesigner: screenshot \"%s\", url %s", desc.c_str(), url.c_str());
+ }
+ }
+}
+
+// --- cSkinRepos -------------------------------------------------------------
+
+cSkinRepos::cSkinRepos(void) {
+ skinRepoUrl = "https://github.com/louisbraun/skinrepository.git";
+ repoFolder = "skinrepositories/";
+}
+
+cSkinRepos::~cSkinRepos() {
+ for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) {
+ delete (*it);
+ }
+}
+
+void cSkinRepos::Init(string path) {
+ string repoPath = path + repoFolder;
+ if (FolderExists(repoPath)) {
+ PullRepoGit(repoPath);
+ } else {
+ InitRepoGit(repoPath);
+ }
+}
+
+void cSkinRepos::Read(string path) {
+ string repoPath = path + repoFolder;
+ DIR *folder = NULL;
+ struct dirent *dirEntry;
+ folder = opendir(repoPath.c_str());
+ if (!folder) {
+ esyslog("skindesigner: no skinrepository folder available in %s", repoPath.c_str());
+ return;
+ }
+ while (dirEntry = readdir(folder)) {
+ string fileName = dirEntry->d_name;
+ if (!fileName.compare(".") || !fileName.compare("..") || !fileName.compare(".git"))
+ continue;
+ string filePath = repoPath + fileName;
+ if (! ReadXMLFile(filePath.c_str(), false) ) {
+ esyslog("skindesigner: error reading skinrepo %s", filePath.c_str());
+ continue;
+ }
+ if (! SetDocument() )
+ continue;
+ if (!ParseRepository())
+ esyslog("skindesigner: error parsing skinrepository %s", filePath.c_str());
+ DeleteDocument();
+ }
+
+}
+
+cSkinRepo *cSkinRepos::GetRepo(string name) {
+ for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) {
+ cSkinRepo *repo = (*it);
+ if (!name.compare(repo->Name()))
+ return repo;
+ }
+ return NULL;
+}
+
+cSkinRepo *cSkinRepos::GetNextRepo(void) {
+ if (repoIt == repos.end())
+ return NULL;
+ cSkinRepo *repo = *repoIt;
+ repoIt++;
+ return repo;
+}
+
+
+void cSkinRepos::Debug(void) {
+ for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) {
+ (*it)->Debug();
+ }
+}
+
+bool cSkinRepos::ParseRepository(void) {
+ if (!LevelDown())
+ return false;
+
+ cSkinRepo *repo = new cSkinRepo();
+
+ do {
+ string value = "";
+ if (CheckNodeName("name")) {
+ if (GetNodeValue(value)) {
+ repo->SetName(value);
+ }
+ } else if (CheckNodeName("type")) {
+ if (GetNodeValue(value)) {
+ eRepoType repoType = rtUndefined;
+ if (!value.compare("git"))
+ repoType = rtGit;
+ else if (!value.compare("zip"))
+ repoType = rtZipUrl;
+ repo->SetRepoType(repoType);
+ }
+ } else if (CheckNodeName("url")) {
+ if (GetNodeValue(value)) {
+ repo->SetUrl(value);
+ }
+ } else if (CheckNodeName("author")) {
+ if (GetNodeValue(value)) {
+ repo->SetAuthor(value);
+ }
+ } else if (CheckNodeName("minimumskindesignerversion")) {
+ if (GetNodeValue(value)) {
+ repo->SetMinSDVersion(value);
+ }
+ } else if (CheckNodeName("specialfonts")) {
+ if (!LevelDown())
+ continue;
+ do {
+ if (CheckNodeName("font")) {
+ if (GetNodeValue(value)) {
+ repo->SetSpecialFont(value);
+ }
+ }
+ } while (NextNode());
+ LevelUp();
+ } else if (CheckNodeName("supportedplugins")) {
+ if (!LevelDown())
+ continue;
+ do {
+ if (CheckNodeName("plugin")) {
+ if (GetNodeValue(value)) {
+ repo->SetSupportedPlugin(value);
+ }
+ }
+ } while (NextNode());
+ LevelUp();
+ } else if (CheckNodeName("screenshots")) {
+ if (!LevelDown()) {
+ continue;
+ }
+ do {
+ if (CheckNodeName("screenshot")) {
+ if (!LevelDown()) {
+ continue;
+ }
+ string desc = "";
+ string url = "";
+ do {
+ if (CheckNodeName("description")) {
+ GetNodeValue(desc);
+ } else if (CheckNodeName("url")) {
+ GetNodeValue(url);
+ }
+ } while (NextNode());
+ LevelUp();
+ if (desc.size() && url.size())
+ repo->SetScreenshot(desc, url);
+ }
+ } while (NextNode());
+ LevelUp();
+ }
+ } while (NextNode());
+ LevelUp();
+ if (repo->Valid()) {
+ repos.push_back(repo);
+ return true;
+ }
+ return false;
+}
+
+void cSkinRepos::InitRepoGit(string path) {
+ dsyslog("skindesigner: initiating skin repository %s", path.c_str());
+ CreateFolder(path);
+
+ cString command = cString::sprintf("git clone --depth=1 %s %s", skinRepoUrl.c_str(), path.c_str());
+ int result = system (*command);
+
+ if (result == 0) {
+ dsyslog("skindesigner: skinrepository successfully initiated");
+ } else {
+ esyslog("skindesigner: ERROR initiating skinrepository. Command: %s", *command);
+ }
+}
+
+void cSkinRepos::PullRepoGit(string path) {
+ dsyslog("skindesigner: updating skin repository %s", path.c_str());
+ cString command = *cString::sprintf("cd %s; git pull", path.c_str());
+ int result = system (*command);
+ if (result == 0) {
+ dsyslog("skindesigner: skinrepository successfully updated");
+ } else {
+ esyslog("skindesigner: ERROR updating skinrepository. Command: %s", *command);
+ }
+}
diff --git a/extensions/skinrepo.h b/extensions/skinrepo.h
new file mode 100644
index 0000000..2f10929
--- /dev/null
+++ b/extensions/skinrepo.h
@@ -0,0 +1,98 @@
+#ifndef __SKINREPO_H
+#define __SKINREPO_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include "libxmlwrapper.h"
+#include <vdr/plugin.h>
+
+using namespace std;
+
+enum eRepoType {
+ rtUndefined,
+ rtGit,
+ rtZipUrl
+};
+
+enum eAction {
+ eaUndefined,
+ eaInstall,
+ eaUpdate
+};
+
+// --- cSkinRepo -------------------------------------------------------------
+
+class cSkinRepo : public cThread {
+private:
+ string name;
+ eRepoType repoType;
+ eAction action;
+ string url;
+ string author;
+ string minSDVersion;
+ vector<string> specialFonts;
+ vector<string> supportedPlugins;
+ vector< pair < string, string > > screenshots;
+ //helpers for execution
+ string command;
+ string command2;
+ string tempfile;
+ int result;
+ string skinPath;
+ string themesPath;
+ virtual void Action(void);
+ void CreateThemeFiles(void);
+public:
+ cSkinRepo(void);
+ virtual ~cSkinRepo(void);
+ void SetName(string name) { this->name = name; };
+ void SetRepoType(eRepoType type) { this->repoType = type; };
+ void SetUrl(string url) { this->url = url; };
+ void SetAuthor(string author) { this->author = author; };
+ void SetMinSDVersion(string minSDVersion) { this->minSDVersion = minSDVersion; };
+ void SetSpecialFont(string font) { specialFonts.push_back(font); };
+ void SetSupportedPlugin(string plugin) { supportedPlugins.push_back(plugin); };
+ void SetScreenshot(string desc, string url) { screenshots.push_back(pair<string, string>(desc, url)); };
+ bool Valid(void);
+ eRepoType Type(void) { return repoType; };
+ string Name(void) { return name; };
+ string Author(void) { return author; };
+ string MinSDVersion(void) { return minSDVersion; };
+ string Url(void) { return url; };
+ vector<string> *SpecialFonts(void) { return &specialFonts; };
+ vector<string> *SupportedPlugins(void) { return &supportedPlugins; };
+ vector< pair < string, string > > *Screenshots(void) { return &screenshots; };
+ void Install(string path, string themesPath);
+ void Update(string path);
+ bool InstallationFinished(void) { return !(Running()); };
+ bool SuccessfullyInstalled(void) { if (result == 0) return true; return false; };
+ bool SuccessfullyUpdated(void);
+ void Debug(void);
+};
+
+// --- cSkinRepos -------------------------------------------------------------
+
+class cSkinRepos : public cLibXMLWrapper {
+private:
+ string skinRepoUrl;
+ string repoFolder;
+ vector<cSkinRepo*> repos;
+ vector<cSkinRepo*>::iterator repoIt;
+ bool ParseRepository(void);
+ void InitRepoGit(string path);
+ void PullRepoGit(string path);
+public:
+ cSkinRepos(void);
+ virtual ~cSkinRepos(void);
+ void Init(string path);
+ void Read(string path);
+ int Count(void) { return repos.size(); };
+ cSkinRepo *GetRepo(string name);
+ void InitRepoIterator(void) { repoIt = repos.begin(); };
+ cSkinRepo *GetNextRepo(void);
+ void Debug(void);
+};
+
+#endif //__SKINREPO_H
diff --git a/extensions/skinsetup.c b/extensions/skinsetup.c
new file mode 100644
index 0000000..f622336
--- /dev/null
+++ b/extensions/skinsetup.c
@@ -0,0 +1,377 @@
+#include "skinsetup.h"
+#include "../config.h"
+#include "../coreengine/xmlparser.h"
+#include "helpers.h"
+
+// --- cSkinSetupParameter -----------------------------------------------------------
+
+cSkinSetupParameter::cSkinSetupParameter(void) {
+ type = sptUnknown;
+ name = "";
+ displayText = "";
+ helpText = "";
+ min = 0;
+ max = 1000;
+ value = 0;
+ options = NULL;
+ optionsTranslated = NULL;
+ numOptions = 0;
+}
+
+cSkinSetupParameter::~cSkinSetupParameter(void) {
+ if (numOptions > 0 && options && optionsTranslated) {
+ delete[] options;
+ delete[] optionsTranslated;
+ }
+}
+
+void cSkinSetupParameter::Debug(void) {
+ string sType = "unknown";
+ if (type == sptBool)
+ sType = "bool";
+ else if (type == sptInt)
+ sType = "int";
+ else if (type == sptString)
+ sType = "string";
+ dsyslog("skindesigner: name \"%s\", type %s, displayText \"%s\", Value: %d", name.c_str(), sType.c_str(), displayText.c_str(), value);
+ if (type == sptInt)
+ dsyslog("skindesigner: min %d, max %d", min, max);
+ if (type == sptString && options) {
+ for (int i=0; i < numOptions; i++) {
+ dsyslog("skindesigner: option %d: %s", i+1, options[i]);
+ }
+ }
+}
+
+// --- cSkinSetupMenu -----------------------------------------------------------
+cSkinSetupMenu::cSkinSetupMenu(void) {
+ name = "";
+ displayText = "";
+ parent = NULL;
+}
+
+cSkinSetupMenu::~cSkinSetupMenu(void) {
+ for (vector < cSkinSetupParameter* >::iterator p = parameters.begin(); p != parameters.end(); p++) {
+ delete (*p);
+ }
+ for (vector < cSkinSetupMenu* >::iterator s = subMenus.begin(); s != subMenus.end(); s++) {
+ delete (*s);
+ }
+}
+
+cSkinSetupParameter *cSkinSetupMenu::GetNextParameter(bool deep) {
+ cSkinSetupParameter *param = NULL;
+ if (paramIt != parameters.end()) {
+ param = *paramIt;
+ paramIt++;
+ return param;
+ }
+ if (!deep)
+ return NULL;
+
+ if (subMenuIt != subMenus.end()) {
+ param = (*subMenuIt)->GetNextParameter();
+ if (!param) {
+ subMenuIt++;
+ if (subMenuIt != subMenus.end()) {
+ (*subMenuIt)->InitIterators();
+ param = (*subMenuIt)->GetNextParameter();
+ }
+ }
+ }
+ return param;
+}
+
+cSkinSetupParameter *cSkinSetupMenu::GetParameter(string name) {
+ for (vector < cSkinSetupParameter* >::iterator it = parameters.begin(); it != parameters.end(); it++) {
+ if (!name.compare((*it)->name))
+ return *it;
+ }
+
+ cSkinSetupParameter *paramHit = NULL;
+ for (vector < cSkinSetupMenu* >::iterator subMenu = subMenus.begin(); subMenu != subMenus.end(); subMenu++) {
+ paramHit = (*subMenu)->GetParameter(name);
+ if (paramHit)
+ return paramHit;
+ }
+ return NULL;
+}
+
+void cSkinSetupMenu::InitIterators(void) {
+ paramIt = parameters.begin();
+ subMenuIt = subMenus.begin();
+ while (subMenuIt != subMenus.end()) {
+ (*subMenuIt)->InitIterators();
+ subMenuIt++;
+ }
+ subMenuIt = subMenus.begin();
+}
+
+void cSkinSetupMenu::SetParameter(eSetupParameterType paramType, string name, string displayText, string helpText, string min, string max, string value, string options) {
+ cSkinSetupParameter *param = new cSkinSetupParameter();
+ param->type = paramType;
+ param->name = name;
+ param->displayText = displayText;
+ param->helpText = helpText;
+
+ if (min.size() && paramType == sptInt) {
+ param->min = atoi(min.c_str());
+ }
+ if (max.size() && paramType == sptInt) {
+ param->max = atoi(max.c_str());
+ }
+
+ param->value = atoi(value.c_str());
+
+ if (paramType == sptString) {
+ splitstring o(options.c_str());
+ vector<string> opts = o.split(',', 1);
+ int numOpts = opts.size();
+ if (numOpts > 0) {
+ param->numOptions = numOpts;
+ param->options = new const char*[numOpts];
+ int i=0;
+ for (vector<string>::iterator it = opts.begin(); it != opts.end(); it++) {
+ string option = trim(*it);
+ char *s = new char[option.size()];
+ strcpy(s, option.c_str());
+ param->options[i++] = s;
+ }
+ }
+ }
+
+ parameters.push_back(param);
+}
+
+cSkinSetupMenu *cSkinSetupMenu::GetMenu(string &name) {
+ for (vector<cSkinSetupMenu*>::iterator m = subMenus.begin(); m != subMenus.end(); m++) {
+ cSkinSetupMenu *menu = (*m);
+ if (!name.compare(menu->GetName()))
+ return menu;
+ menu = menu->GetMenu(name);
+ if (menu)
+ return menu;
+ }
+ return NULL;
+}
+
+cSkinSetupMenu *cSkinSetupMenu::GetNextSubMenu(bool deep) {
+ cSkinSetupMenu *menu = NULL;
+ if (subMenuIt != subMenus.end()) {
+ if (deep) {
+ menu = (*subMenuIt)->GetNextSubMenu(deep);
+ if (menu)
+ return menu;
+ }
+ menu = *subMenuIt;
+ subMenuIt++;
+ return menu;
+ }
+ return NULL;
+}
+
+
+void cSkinSetupMenu::Debug(bool deep) {
+ dsyslog("skindesigner: Menu %s Setup Parameters", name.c_str());
+ for (vector < cSkinSetupParameter* >::iterator p = parameters.begin(); p != parameters.end(); p++) {
+ (*p)->Debug();
+ }
+ if (subMenus.empty())
+ return;
+ for (vector < cSkinSetupMenu* >::iterator s = subMenus.begin(); s != subMenus.end(); s++) {
+ dsyslog("skindesigner: SubMenu %s, Parent %s", ((*s)->GetName()).c_str(), ((*s)->GetParent()->GetName()).c_str());
+ if (deep)
+ (*s)->Debug();
+ }
+}
+// --- cSkinSetup -----------------------------------------------------------
+
+cSkinSetup::cSkinSetup(string skin) {
+ this->skin = skin;
+ rootMenu = new cSkinSetupMenu();
+ rootMenu->SetName("root");
+ currentMenu = rootMenu;
+}
+
+cSkinSetup::~cSkinSetup() {
+ delete rootMenu;
+}
+
+bool cSkinSetup::ReadFromXML(void) {
+ string xmlPath = *cString::sprintf("%s%s/setup.xml", *config.GetSkinPath(skin), skin.c_str());
+ cXmlParser parser;
+ if (!parser.ReadSkinSetup(this, xmlPath)) {
+ return false;
+ }
+ parser.ParseSkinSetup(skin);
+ return true;
+}
+
+void cSkinSetup::SetSubMenu(string name, string displayText) {
+ cSkinSetupMenu *subMenu = new cSkinSetupMenu();
+ subMenu->SetName(name);
+ subMenu->SetDisplayText(displayText);
+ subMenu->SetParent(currentMenu);
+ currentMenu->AddSubMenu(subMenu);
+ currentMenu = subMenu;
+}
+
+void cSkinSetup::SubMenuDone(void) {
+ cSkinSetupMenu *parent = currentMenu->GetParent();
+ if (parent) {
+ currentMenu = parent;
+ }
+}
+
+void cSkinSetup::SetParameter(string type, string name, string displayText, string helpText, string min, string max, string value, string options) {
+ if (!type.size() || !name.size() || !displayText.size() || !value.size()) {
+ esyslog("skindesigner: invalid setup parameter for skin %s", skin.c_str());
+ return;
+ }
+ eSetupParameterType paramType = sptUnknown;
+ if (!type.compare("int")) {
+ paramType = sptInt;
+ } else if (!type.compare("bool")) {
+ paramType = sptBool;
+ } else if (!type.compare("string")) {
+ paramType = sptString;
+ }
+ if (paramType == sptUnknown) {
+ esyslog("skindesigner: invalid setup parameter for skin %s", skin.c_str());
+ return;
+ }
+ currentMenu->SetParameter(paramType, name, displayText, helpText, min, max, value, options);
+}
+
+cSkinSetupParameter *cSkinSetup::GetNextParameter(void) {
+ return rootMenu->GetNextParameter();
+}
+
+cSkinSetupParameter *cSkinSetup::GetParameter(string name) {
+ return rootMenu->GetParameter(name);
+}
+
+void cSkinSetup::SetTranslation(string translationToken, map < string, string > transl) {
+ translations.insert(pair<string, map < string, string > >(translationToken, transl));
+}
+
+void cSkinSetup::AddToGlobals(cGlobals *globals) {
+ if (!globals)
+ return;
+ rootMenu->InitIterators();
+ cSkinSetupParameter *param = NULL;
+ while (param = rootMenu->GetNextParameter()) {
+ if (param->type == sptString) {
+ string value = param->options[param->value];
+ globals->AddString(param->name, value);
+ string intName = "index" + param->name;
+ globals->AddInt(intName, param->value);
+ } else {
+ globals->AddInt(param->name, param->value);
+ }
+ }
+}
+
+void cSkinSetup::TranslateSetup(void) {
+ rootMenu->InitIterators();
+ cSkinSetupParameter *param = NULL;
+ while (param = rootMenu->GetNextParameter()) {
+ string transl = "";
+ if (Translate(param->displayText, transl)) {
+ param->displayText = transl;
+ }
+ string translHelp = "";
+ if (Translate(param->helpText, translHelp)) {
+ param->helpText = translHelp;
+ }
+ if (param->type == sptString && param->numOptions > 0) {
+ param->optionsTranslated = new const char*[param->numOptions];
+ for (int i = 0; i < param->numOptions; i++) {
+ string option = param->options[i];
+ string optionTransToken = "{tr(" + option + ")}";
+ string optionTranslated = "";
+ if (Translate(optionTransToken, optionTranslated)) {
+ char *s = new char[optionTranslated.size()];
+ strcpy(s, optionTranslated.c_str());
+ param->optionsTranslated[i] = s;
+ } else {
+ char *s = new char[option.size()];
+ strcpy(s, option.c_str());
+ param->optionsTranslated[i] = s;
+ }
+ }
+ }
+ }
+
+ rootMenu->InitIterators();
+ cSkinSetupMenu *subMenu = NULL;
+ while (subMenu = rootMenu->GetNextSubMenu()) {
+ string transl = "";
+ if (Translate(subMenu->GetDisplayText(), transl)) {
+ subMenu->SetDisplayText(transl);
+ }
+ }
+}
+
+cSkinSetupMenu *cSkinSetup::GetMenu(string &name) {
+ if (name.size() == 0)
+ return rootMenu;
+ return rootMenu->GetMenu(name);
+}
+
+bool cSkinSetup::Translate(string text, string &translation) {
+ string transStart = "{tr(";
+ string transEnd = ")}";
+ size_t foundStart = text.find(transStart);
+ size_t foundEnd = text.find(transEnd);
+ bool translated = false;
+
+ while (foundStart != string::npos && foundEnd != string::npos) {
+ string token = text.substr(foundStart + 1, foundEnd - foundStart);
+ string transToken = DoTranslate(token);
+ if (transToken.size() > 0)
+ translated = true;
+ else
+ return false;
+ text.replace(foundStart, foundEnd - foundStart + 2, transToken);
+ foundStart = text.find(transStart);
+ foundEnd = text.find(transEnd);
+ }
+ if (translated)
+ translation = text;
+ return translated;
+}
+
+string cSkinSetup::DoTranslate(string token) {
+ string translation = "";
+ map <string, map< string, string > >::iterator hit = translations.find(token);
+ if (hit == translations.end()) {
+ esyslog("skindesigner: invalid translation token %s", token.c_str());
+ return translation;
+ }
+ map< string, string > translats = hit->second;
+ map< string, string >::iterator trans = translats.find(Setup.OSDLanguage);
+ if (trans != translats.end()) {
+ translation = trans->second;
+ } else {
+ map< string, string >::iterator transDefault = translats.find("en_EN");
+ if (transDefault != translats.end()) {
+ translation = transDefault->second;
+ }
+ }
+ return translation;
+}
+
+void cSkinSetup::Debug(void) {
+ rootMenu->Debug();
+ return;
+ dsyslog("skindesigner: Skin \"%s\" Setup Parameter Translations", skin.c_str());
+ for (map<string, map<string,string> >::iterator trans = translations.begin(); trans != translations.end(); trans++) {
+ dsyslog("skindesigner: translation token %s", (trans->first).c_str());
+ map<string,string> transValues = trans->second;
+ for (map<string,string>::iterator trans2 = transValues.begin(); trans2 != transValues.end(); trans2++) {
+ dsyslog("skindesigner: translation language %s value \"%s\"", (trans2->first).c_str(), (trans2->second).c_str());
+ }
+ }
+
+}
diff --git a/extensions/skinsetup.h b/extensions/skinsetup.h
new file mode 100644
index 0000000..e3b9cc3
--- /dev/null
+++ b/extensions/skinsetup.h
@@ -0,0 +1,102 @@
+#ifndef __SKINSETUP_H
+#define __SKINSETUP_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <sstream>
+#include <vdr/plugin.h>
+#include <libxml/xmlstring.h>
+#include "../coreengine/globals.h"
+
+using namespace std;
+
+enum eSetupParameterType {
+ sptInt,
+ sptBool,
+ sptString,
+ sptUnknown
+};
+
+// --- cSkinSetupParameter -----------------------------------------------------------
+
+class cSkinSetupParameter {
+private:
+public:
+ cSkinSetupParameter(void);
+ virtual ~cSkinSetupParameter(void);
+ eSetupParameterType type;
+ string name;
+ string displayText;
+ string helpText;
+ int min;
+ int max;
+ int value;
+ const char* *options;
+ const char* *optionsTranslated;
+ int numOptions;
+ void Debug(void);
+};
+
+// --- cSkinSetupMenu -----------------------------------------------------------
+
+class cSkinSetupMenu {
+private:
+ string name;
+ string displayText;
+ cSkinSetupMenu *parent;
+ vector < cSkinSetupMenu* > subMenus;
+ vector < cSkinSetupMenu* >::iterator subMenuIt;
+ vector < cSkinSetupParameter* > parameters;
+ vector < cSkinSetupParameter* >::iterator paramIt;
+public:
+ cSkinSetupMenu(void);
+ virtual ~cSkinSetupMenu(void);
+ void SetName(string name) { this->name = name; };
+ void SetDisplayText(string displayText) { this->displayText = displayText; };
+ string GetName(void) { return name; };
+ string GetDisplayText(void) { return displayText; };
+ void SetParent(cSkinSetupMenu *p) { parent = p; };
+ cSkinSetupMenu *GetParent(void) { return parent; };
+ void AddSubMenu(cSkinSetupMenu *sub) { subMenus.push_back(sub); };
+ void SetParameter(eSetupParameterType paramType, string name, string displayText, string helpText, string min, string max, string value, string options);
+ void InitIterators(void);
+ void InitParameterIterator(void) { paramIt = parameters.begin(); };
+ cSkinSetupParameter *GetNextParameter(bool deep = true);
+ cSkinSetupParameter *GetParameter(string name);
+ void InitSubmenuIterator(void) { subMenuIt = subMenus.begin(); };
+ cSkinSetupMenu *GetNextSubMenu(bool deep = true);
+ cSkinSetupMenu *GetMenu(string &name);
+ void Debug(bool deep = true);
+};
+
+// --- cSkinSetup -----------------------------------------------------------
+
+class cSkinSetup {
+private:
+ string skin;
+ cSkinSetupMenu *rootMenu;
+ cSkinSetupMenu *currentMenu;
+ map < string, map< string, string > > translations;
+ string DoTranslate(string token);
+ bool Translate(string text, string &translation);
+public:
+ cSkinSetup(string skin);
+ virtual ~cSkinSetup(void);
+ bool ReadFromXML(void);
+ void SetSubMenu(string name, string displayText);
+ void SubMenuDone(void);
+ void SetParameter(string type, string name, string displayText, string helpText, string min, string max, string value, string options);
+ void InitParameterIterator(void) { rootMenu->InitIterators(); };
+ cSkinSetupParameter *GetNextParameter(void);
+ cSkinSetupParameter *GetParameter(string name);
+ void SetTranslation(string translationToken, map < string, string > transl);
+ void AddToGlobals(cGlobals *globals);
+ void TranslateSetup(void);
+ string GetSkin(void) { return skin; };
+ cSkinSetupMenu *GetMenu(string &name);
+ void Debug(void);
+};
+
+#endif //__SKINSETUP_H
diff --git a/extensions/timers.c b/extensions/timers.c
new file mode 100644
index 0000000..b93aada
--- /dev/null
+++ b/extensions/timers.c
@@ -0,0 +1,121 @@
+#include "timers.h"
+#include "../services/epgsearch.h"
+#include "../services/remotetimers.h"
+
+static int CompareTimers(const void *a, const void *b) {
+ return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
+}
+
+cGlobalSortedTimers::cGlobalSortedTimers(bool forceRefresh) : cVector<const cTimer *>(Timers.Count()) {
+ static bool initial = true;
+ static cRemoteTimerRefresh *remoteTimerRefresh = NULL;
+ localTimer = NULL;
+
+ if (forceRefresh)
+ initial = true;
+ //check if remotetimers plugin is available
+ static cPlugin* pRemoteTimers = cPluginManager::GetPlugin("remotetimers");
+
+ cSchedulesLock SchedulesLock;
+ const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
+
+ if (pRemoteTimers && initial) {
+ cString errorMsg;
+ pRemoteTimers->Service("RemoteTimers::RefreshTimers-v1.0", &errorMsg);
+ initial = false;
+ }
+
+ for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
+ if (Timer->HasFlags(tfActive))
+ Append(Timer);
+ }
+
+ //if remotetimers plugin is available, take timers also from him
+ if (pRemoteTimers) {
+ cTimer* remoteTimer = NULL;
+ while (pRemoteTimers->Service("RemoteTimers::ForEach-v1.0", &remoteTimer) && remoteTimer != NULL) {
+ remoteTimer->SetEventFromSchedule(Schedules); // make sure the event is current
+ if (remoteTimer->HasFlags(tfActive))
+ Append(remoteTimer);
+ }
+ }
+
+ Sort(CompareTimers);
+
+ int numTimers = Size();
+ if (numTimers > 0) {
+ localTimer = new bool[numTimers];
+ for (int i=0; i < numTimers; i++) {
+ if (!pRemoteTimers) {
+ localTimer[i] = true;
+ } else {
+ localTimer[i] = false;
+ for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
+ if (Timer == At(i)) {
+ localTimer[i] = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (pRemoteTimers && (remoteTimerRefresh == NULL))
+ remoteTimerRefresh = new cRemoteTimerRefresh();
+}
+
+cGlobalSortedTimers::~cGlobalSortedTimers(void) {
+ if (localTimer) {
+ delete[] localTimer;
+ }
+}
+
+bool cGlobalSortedTimers::IsRemoteTimer(int i) {
+ if (!localTimer)
+ return true;
+ if (i >= Size())
+ return true;
+ return !(localTimer[i]);
+}
+
+
+int cGlobalSortedTimers::NumTimerConfilicts(void) {
+ int numConflicts = 0;
+ cPlugin *p = cPluginManager::GetPlugin("epgsearch");
+ if (p) {
+ Epgsearch_lastconflictinfo_v1_0 *serviceData = new Epgsearch_lastconflictinfo_v1_0;
+ if (serviceData) {
+ serviceData->nextConflict = 0;
+ serviceData->relevantConflicts = 0;
+ serviceData->totalConflicts = 0;
+ p->Service("Epgsearch-lastconflictinfo-v1.0", serviceData);
+ if (serviceData->relevantConflicts > 0) {
+ numConflicts = serviceData->relevantConflicts;
+ }
+ delete serviceData;
+ }
+ }
+ return numConflicts;
+}
+
+cRemoteTimerRefresh::cRemoteTimerRefresh(): cThread("skindesigner: RemoteTimers::RefreshTimers") {
+ Start();
+}
+
+cRemoteTimerRefresh::~cRemoteTimerRefresh(void) {
+ Cancel(-1);
+ while (Active())
+ cCondWait::SleepMs(10);
+}
+
+void cRemoteTimerRefresh::Action(void) {
+ #define REFESH_INTERVALL_MS 30000
+ while (Running()) {
+ cCondWait::SleepMs(REFESH_INTERVALL_MS);
+ if (!cOsd::IsOpen()) {//make sure that no timer is currently being edited
+ cGlobalSortedTimers(true);
+ Timers.SetModified();
+ }
+ }
+}
+ \ No newline at end of file
diff --git a/extensions/timers.h b/extensions/timers.h
new file mode 100644
index 0000000..aba873c
--- /dev/null
+++ b/extensions/timers.h
@@ -0,0 +1,24 @@
+#ifndef __NOPACITY_TIMERS_H
+#define __NOPACITY_TIMERS_H
+
+#include <vdr/timers.h>
+#include <vdr/plugin.h>
+
+class cGlobalSortedTimers : public cVector<const cTimer *> {
+ private:
+ bool *localTimer;
+ public:
+ cGlobalSortedTimers(bool forceRefresh = false);
+ virtual ~cGlobalSortedTimers(void);
+ bool IsRemoteTimer(int i);
+ int NumTimerConfilicts(void);
+};
+
+class cRemoteTimerRefresh: public cThread {
+ protected:
+ virtual void Action(void);
+ public:
+ cRemoteTimerRefresh(void);
+ virtual ~cRemoteTimerRefresh(void);
+};
+#endif //__NOPACITY_TIMERS_H