summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile119
-rw-r--r--lib/common.c1921
-rw-r--r--lib/common.h560
-rw-r--r--lib/config.c59
-rw-r--r--lib/config.h47
-rw-r--r--lib/configuration.c238
-rw-r--r--lib/configuration.h164
-rw-r--r--lib/curl.c454
-rw-r--r--lib/curl.h77
-rw-r--r--lib/db.c1646
-rw-r--r--lib/db.h1367
-rw-r--r--lib/dbdict.c527
-rw-r--r--lib/dbdict.h471
-rw-r--r--lib/demo.c531
-rw-r--r--lib/demo.dat17
-rw-r--r--lib/epgservice.c121
-rw-r--r--lib/epgservice.h469
-rw-r--r--lib/imgtools.c217
-rw-r--r--lib/imgtools.h31
-rw-r--r--lib/json.c168
-rw-r--r--lib/json.h36
-rw-r--r--lib/parameters.c257
-rw-r--r--lib/parameters.h59
-rw-r--r--lib/python.c421
-rw-r--r--lib/python.h78
-rw-r--r--lib/pytst.c128
-rw-r--r--lib/searchtimer.c1777
-rw-r--r--lib/searchtimer.h111
-rw-r--r--lib/semtst.c42
-rw-r--r--lib/test.c738
-rw-r--r--lib/thread.c342
-rw-r--r--lib/thread.h92
-rw-r--r--lib/wol.c116
-rw-r--r--lib/wol.h31
34 files changed, 13432 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..8c713f3
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,119 @@
+#
+# Makefile
+#
+# See the README file for copyright information and how to reach the author.
+#
+
+include ../Make.config
+
+LIBTARGET = libhorchi
+HLIB = -L. -lhorchi
+
+DEMO = demo
+TEST = tst
+
+LIBOBJS = common.o configuration.o parameters.o thread.o config.o db.o epgservice.o dbdict.o json.o
+
+ifdef USEWOL
+ LIBOBJS += wol.o
+endif
+
+ifdef USEJPEG
+ LIBOBJS += imgtools.o
+endif
+
+ifdef USECURL
+ LIBOBJS += curl.o
+endif
+
+BASELIBS = -lrt -lz -luuid
+BASELIBS += $(shell mysql_config --libs_r)
+
+ifdef USECURL
+ BASELIBS += -lcurl
+endif
+
+ifdef USEEPGS
+ LIBOBJS += searchtimer.o
+endif
+
+ifdef USEPYTHON
+ BASELIBS += $(shell python-config --libs)
+ LIBOBJS += python.o
+endif
+
+ifdef USELIBXML
+ BASELIBS += $(shell xml2-config --libs) $(shell xslt-config --libs)
+endif
+
+ifdef SYSD_NOTIFY
+ ifdef SYSDLIB_210
+ BASELIBS += $(shell pkg-config --libs libsystemd)
+ CFLAGS += $(shell pkg-config --cflags libsystemd)
+ else
+ BASELIBS += $(shell pkg-config --libs libsystemd-daemon)
+ CFLAGS += $(shell pkg-config --cflags libsystemd-daemon)
+ endif
+endif
+
+ifdef DEBUG
+ CFLAGS += -ggdb -O0
+endif
+
+CFLAGS += $(shell mysql_config --include)
+DEFINES += $(USES)
+
+ifdef USEPYTHON
+ CFLAGS += $(shell python-config --includes)
+endif
+
+all: lib $(TEST) $(DEMO)
+lib: $(LIBTARGET).a
+
+$(LIBTARGET).a : $(LIBOBJS)
+ @echo Building Lib ...
+ $(doLib) $@ $(LIBOBJS)
+
+tst: test.o lib
+ $(doLink) test.o $(HLIB) -larchive -lcrypto $(BASELIBS) -o $@
+
+demo: demo.o lib
+ $(doLink) demo.o $(HLIB) -larchive -lcrypto $(BASELIBS) -o $@
+
+pytst: pytst.o lib
+ $(CC) pytst.o $(HLIB) -larchive -lcrypto $(BASELIBS) -o $@
+
+clean:
+ rm -f *.o *~ core $(TEST) $(DEMO) $(LIBTARGET).a
+
+cppchk:
+ cppcheck --template="{file}:{line}:{severity}:{message}" --quiet --force *.c *.h
+
+%.o: %.c
+ @echo Compile "$(*F)" ...
+ $(doCompile) $(*F).c -o $@
+
+#--------------------------------------------------------
+# dependencies
+#--------------------------------------------------------
+
+HEADER = db.h common.h config.h dbdict.h
+
+common.o : common.c $(HEADER) common.h
+configuration.o : configuration.c $(HEADER) configuration.h
+parameters.o : parameters.c $(HEADER) parameters.h
+thread.o : thread.c $(HEADER) thread.h
+curl.o : curl.c $(HEADER) curl.h
+imgtools.o : imgtools.c $(HEADER) imgtools.h
+config.o : config.c $(HEADER) config.h
+db.o : db.c $(HEADER) db.h
+epgservice.o : epgservice.c $(HEADER) epgservice.h
+dbdict.o : dbdict.c $(HEADER) dbdict.h
+json.o : json.c $(HEADER) json.h
+python.o : python.c $(HEADER) python.h
+searchtimer.o : searchtimer.c $(HEADER) searchtimer.h
+wol.o : wol.c $(HEADER) wol.h
+
+demo.o : demo.c $(HEADER)
+test.o : test.c $(HEADER)
+pytst.o : pytst.c $(HEADER)
diff --git a/lib/common.c b/lib/common.c
new file mode 100644
index 0000000..8a0e9ac
--- /dev/null
+++ b/lib/common.c
@@ -0,0 +1,1921 @@
+/*
+ * common.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+#ifdef USEUUID
+# include <uuid/uuid.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <errno.h>
+#include <regex.h>
+#include <limits.h>
+
+#ifdef USELIBARCHIVE
+# include <archive.h>
+# include <archive_entry.h>
+#endif
+
+#include "common.h"
+#include "config.h"
+
+cMyMutex logMutex;
+
+//***************************************************************************
+// Tell
+//***************************************************************************
+
+const char* getLogPrefix()
+{
+ return logPrefix;
+}
+
+void tell(int eloquence, const char* format, ...)
+{
+ if (cEpgConfig::loglevel < eloquence)
+ return ;
+
+ logMutex.Lock();
+
+#ifndef VDR_PLUGIN
+ static int init = no;
+
+ if (!init)
+ {
+ init = yes;
+ openlog(cEpgConfig::logName, LOG_CONS, cEpgConfig::logFacility);
+ }
+#endif
+
+ const int sizeBuffer = 100000;
+ char t[sizeBuffer+100]; *t = 0;
+ va_list ap;
+
+ va_start(ap, format);
+
+ if (getLogPrefix())
+ snprintf(t, sizeBuffer, "%s", getLogPrefix());
+
+ vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap);
+
+ strReplace(t, '\n', '$');
+
+ if (cEpgConfig::logstdout)
+ {
+ char buf[50+TB];
+ timeval tp;
+
+ gettimeofday(&tp, 0);
+
+ tm* tm = localtime(&tp.tv_sec);
+
+ sprintf(buf,"%2.2d:%2.2d:%2.2d,%3.3ld ",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tp.tv_usec / 1000);
+
+ printf("%s %s\n", buf, t);
+ }
+ else
+ syslog(LOG_ERR, "%s", t);
+
+ logMutex.Unlock();
+
+ va_end(ap);
+}
+
+//***************************************************************************
+// Syslog Facilities
+//***************************************************************************
+
+const Syslog::Facilities Syslog::facilities[] =
+{
+ { "auth", LOG_AUTH },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+ { "security", LOG_AUTH },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { 0, 0 }
+};
+
+//***************************************************************************
+// To Name
+//***************************************************************************
+
+char* Syslog::toName(int code)
+{
+ for (int i = 0; facilities[i].name; i++)
+ if (facilities[i].code == code)
+ return (char*) facilities[i].name; // #83
+
+ return 0;
+}
+
+//***************************************************************************
+// To Code
+//***************************************************************************
+
+int Syslog::toCode(const char* name)
+{
+ for (int i = 0; facilities[i].name; i++)
+ if (strcmp(facilities[i].name, name) == 0)
+ return facilities[i].code;
+
+ return na;
+}
+
+//***************************************************************************
+// Save Realloc
+//***************************************************************************
+
+char* srealloc(void* ptr, size_t size)
+{
+ void* n = realloc(ptr, size);
+
+ if (!n)
+ {
+ free(ptr);
+ ptr = 0;
+ }
+
+ return (char*)n;
+}
+
+//***************************************************************************
+// us now
+//***************************************************************************
+
+double usNow()
+{
+ struct timeval tp;
+
+ gettimeofday(&tp, 0);
+
+ return tp.tv_sec * 1000000.0 + tp.tv_usec;
+}
+
+//***************************************************************************
+// Host ID
+//***************************************************************************
+
+unsigned int getHostId()
+{
+ static unsigned int id = gethostid() & 0xFFFFFFFF;
+ return id;
+}
+
+//***************************************************************************
+// String Operations
+//***************************************************************************
+
+void toUpper(std::string& str)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ if (csSrc == 1)
+ *d++ = toupper(s[ps]);
+ else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+ {
+ *d++ = s[ps];
+ *d++ = s[ps+1] - 32;
+ }
+ else
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+//***************************************************************************
+// To Case (UTF-8 save)
+//***************************************************************************
+
+const char* toCase(Case cs, char* str)
+{
+ char* s = str;
+ int lenSrc = strlen(str);
+
+ int csSrc; // size of character
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ if (csSrc == 1)
+ s[ps] = cs == cUpper ? toupper(s[ps]) : tolower(s[ps]);
+ else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+ {
+ s[ps] = s[ps];
+ s[ps+1] = cs == cUpper ? toupper(s[ps+1]) : tolower(s[ps+1]);
+ }
+ else
+ {
+ for (int i = 0; i < csSrc; i++)
+ s[ps+i] = s[ps+i];
+ }
+ }
+
+ return str;
+}
+
+void removeChars(std::string& str, const char* ignore)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+ int lenIgn = strlen(ignore);
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+ int csIgn; //
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ int skip = no;
+
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ for (int pi = 0; pi < lenIgn; pi += csIgn)
+ {
+ csIgn = std::max(mblen(&ignore[pi], lenIgn-pi), 1);
+
+ if (csSrc == csIgn && strncmp(&s[ps], &ignore[pi], csSrc) == 0)
+ {
+ skip = yes;
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+void removeCharsExcept(std::string& str, const char* except)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+ int lenIgn = strlen(except);
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+ int csIgn; //
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ int skip = yes;
+
+ mblen(0,0);
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ for (int pi = 0; pi < lenIgn; pi += csIgn)
+ {
+ csIgn = std::max(mblen(&except[pi], lenIgn-pi), 1);
+
+ if (csSrc == csIgn && strncmp(&s[ps], &except[pi], csSrc) == 0)
+ {
+ skip = no;
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+void removeWord(std::string& pattern, std::string word)
+{
+ size_t pos;
+
+ if ((pos = pattern.find(word)) != std::string::npos)
+ pattern.swap(pattern.erase(pos, word.length()));
+}
+
+//***************************************************************************
+// String Manipulation
+//***************************************************************************
+
+void prepareCompressed(std::string& pattern)
+{
+ // const char* ignore = " (),.;:-_+*!#?=&%$<>§/'`´@~\"[]{}";
+ const char* notignore = "ABCDEFGHIJKLMNOPQRSTUVWXYZßÖÄÜöäü0123456789";
+
+ toUpper(pattern);
+ removeWord(pattern, " TEIL ");
+ removeWord(pattern, " FOLGE ");
+ removeCharsExcept(pattern, notignore);
+}
+
+//***************************************************************************
+// Get Range Parts of String like '33-123' or '-123' or '21-'
+//***************************************************************************
+
+int rangeFrom(const char* s)
+{
+ return atoi(s);
+}
+
+int rangeTo(const char* s)
+{
+ int to = INT_MAX;
+
+ const char* p = strchr(s, '-');
+
+ if (p && *(p+1))
+ to = atoi(p+1);
+
+ return to;
+}
+
+//***************************************************************************
+// Left Trim
+//***************************************************************************
+
+char* lTrim(char* buf)
+{
+ if (buf)
+ {
+ char *tp = buf;
+
+ while (*tp && strchr("\n\r\t ",*tp))
+ tp++;
+
+ memmove(buf, tp, strlen(tp) +1);
+ }
+
+ return buf;
+}
+
+//*************************************************************************
+// Right Trim
+//*************************************************************************
+
+char* rTrim(char* buf)
+{
+ if (buf)
+ {
+ char *tp = buf + strlen(buf);
+
+ while (tp >= buf && strchr("\n\r\t ",*tp))
+ tp--;
+
+ *(tp+1) = 0;
+ }
+
+ return buf;
+}
+
+//*************************************************************************
+// All Trim
+//*************************************************************************
+
+char* allTrim(char* buf)
+{
+ return lTrim(rTrim(buf));
+}
+
+std::string strReplace(const std::string& what, const std::string& with, const std::string& subject)
+{
+ std::string str = subject;
+ size_t pos = 0;
+
+ while ((pos = str.find(what, pos)) != std::string::npos)
+ {
+ str.replace(pos, what.length(), with);
+ pos += with.length();
+ }
+
+ return str;
+}
+
+std::string strReplace(const std::string& what, long with, const std::string& subject)
+{
+ char swith[100];
+
+ sprintf(swith, "%ld", with);
+
+ return strReplace(what, swith, subject);
+}
+
+std::string strReplace(const std::string& what, double with, const std::string& subject)
+{
+ char swith[100];
+
+ sprintf(swith, "%.2f", with);
+
+ return strReplace(what, swith, subject);
+}
+
+char* strReplace(char* buffer, char from, char to)
+{
+ char* p = buffer;
+
+ while (*p)
+ {
+ if (*p == from)
+ *p = to;
+
+ p++;
+ }
+
+ return buffer;
+}
+
+//***************************************************************************
+// Number to String
+//***************************************************************************
+
+std::string num2Str(int num)
+{
+ char txt[16];
+
+ snprintf(txt, sizeof(txt), "%d", num);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Long to Pretty Time
+//***************************************************************************
+
+std::string l2pTime(time_t t, const char* format)
+{
+ char txt[100];
+ tm* tmp = localtime(&t);
+
+ strftime(txt, sizeof(txt), format, tmp);
+
+ return std::string(txt);
+}
+
+std::string l2pDate(time_t t)
+{
+ char txt[30];
+ tm* tmp = localtime(&t);
+
+ strftime(txt, sizeof(txt), "%d.%m.%Y", tmp);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// To HTTP Header Date Format
+//***************************************************************************
+
+std::string l2HttpTime(time_t t)
+{
+ char date[128+TB];
+ tm now;
+
+ static const char *const days[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+ static const char *const mons[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ gmtime_r(&t, &now);
+
+ sprintf(date, "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
+ days[now.tm_wday % 7],
+ (unsigned int)now.tm_mday,
+ mons[now.tm_mon % 12],
+ (unsigned int)(1900 + now.tm_year),
+ (unsigned int)now.tm_hour,
+ (unsigned int)now.tm_min,
+ (unsigned int)now.tm_sec);
+
+ return std::string(date);
+}
+
+//***************************************************************************
+// Is Daylight Saving Time
+//***************************************************************************
+
+int isDST(time_t t)
+{
+ struct tm tm;
+
+ if (!t)
+ t = time(0);
+
+ localtime_r(&t, &tm);
+ tm.tm_isdst = -1; // force DST auto detect
+ mktime(&tm);
+
+ return tm.tm_isdst;
+}
+
+//***************************************************************************
+// Time of
+//***************************************************************************
+
+time_t timeOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_year = 0;
+ tm.tm_mon = 0;
+ tm.tm_mday = 0;
+ tm.tm_wday = 0;
+ tm.tm_yday = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// Weekday Of
+//***************************************************************************
+
+int weekdayOf(time_t t)
+{
+ struct tm tm_r;
+ int weekday = localtime_r(&t, &tm_r)->tm_wday;
+
+ return weekday == 0 ? 6 : weekday - 1; // Monday is 0
+}
+
+//***************************************************************************
+// To Weekday Name
+//***************************************************************************
+
+const char* toWeekdayName(uint day)
+{
+ const char* dayNames[] =
+ {
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ 0
+ };
+
+ if (day > 6)
+ return "<unknown>";
+
+ return dayNames[day];
+}
+
+//***************************************************************************
+// hhmm of
+//***************************************************************************
+
+time_t hhmmOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_year = 0;
+ tm.tm_mon = 0;
+ tm.tm_mday = 0;
+ tm.tm_wday = 0;
+ tm.tm_yday = 0;
+ tm.tm_sec = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// time_t to hhmm like '2015'
+//***************************************************************************
+
+int l2hhmm(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ return tm.tm_hour * 100 + tm.tm_min;
+}
+
+//***************************************************************************
+// HHMM to Pretty Time
+//***************************************************************************
+
+std::string hhmm2pTime(int hhmm)
+{
+ char txt[100];
+
+ sprintf(txt, "%02d:%02d", hhmm / 100, hhmm % 100);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Midnight of
+//***************************************************************************
+
+time_t midnightOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// MS to Duration
+//***************************************************************************
+
+std::string ms2Dur(uint64_t t)
+{
+ char txt[30];
+
+ int s = t / 1000;
+ int ms = t % 1000;
+
+ if (s != 0)
+ snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms);
+ else
+ snprintf(txt, sizeof(txt), "%d ms", ms);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Char to Char-String
+//***************************************************************************
+
+const char* c2s(char c, char* buf)
+{
+ sprintf(buf, "%c", c);
+
+ return buf;
+}
+
+//***************************************************************************
+// End Of String
+//***************************************************************************
+
+char* eos(char* s)
+{
+ if (!s)
+ return 0;
+
+ return s + strlen(s);
+}
+
+//***************************************************************************
+// Store To File
+//***************************************************************************
+
+int storeToFile(const char* filename, const char* data, int size)
+{
+ FILE* fout;
+
+ if ((fout = fopen(filename, "w+")))
+ {
+ fwrite(data, sizeof(char), size, fout);
+ fclose(fout);
+ }
+ else
+ {
+ tell(0, "Error, can't store '%s' to filesystem '%s'", filename, strerror(errno));
+ return fail;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Load From File
+//***************************************************************************
+
+int loadFromFile(const char* infile, MemoryStruct* data)
+{
+ FILE* fin;
+ struct stat sb;
+
+ data->clear();
+
+ if (!fileExists(infile))
+ {
+ tell(0, "File '%s' not found'", infile);
+ return fail;
+ }
+
+ if (stat(infile, &sb) < 0)
+ {
+ tell(0, "Can't get info of '%s', error was '%s'", infile, strerror(errno));
+ return fail;
+ }
+
+ if ((fin = fopen(infile, "r")))
+ {
+ const char* sfx = suffixOf(infile);
+
+ data->size = sb.st_size;
+ data->modTime = sb.st_mtime;
+ data->memory = (char*)malloc(data->size);
+ fread(data->memory, sizeof(char), data->size, fin);
+ fclose(fin);
+ sprintf(data->tag, "%ld", (long int)data->size);
+
+ if (strcmp(sfx, "gz") == 0)
+ sprintf(data->contentEncoding, "gzip");
+
+ if (strcmp(sfx, "js") == 0)
+ sprintf(data->contentType, "application/javascript");
+
+ else if (strcmp(sfx, "png") == 0 || strcmp(sfx, "jpg") == 0 || strcmp(sfx, "gif") == 0)
+ sprintf(data->contentType, "image/%s", sfx);
+ else if (strcmp(sfx, "svg") == 0)
+ sprintf(data->contentType, "image/%s+xml", sfx);
+ else if (strcmp(sfx, "ico") == 0)
+ strcpy(data->contentType, "image/x-icon");
+
+ else
+ sprintf(data->contentType, "text/%s", sfx);
+ }
+ else
+ {
+ tell(0, "Error, can't open '%s' for reading, error was '%s'", infile, strerror(errno));
+ return fail;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// TOOLS
+//***************************************************************************
+
+int isEmpty(const char* str)
+{
+ return !str || !*str;
+}
+
+const char* notNull(const char* str, const char* def)
+{
+ if (!str)
+ return def;
+
+ return str;
+}
+
+int isZero(const char* str)
+{
+ const char* p = str;
+
+ while (p && *p)
+ {
+ if (*p != '0')
+ return no;
+
+ p++;
+ }
+
+ return yes;
+}
+
+//***************************************************************************
+// Is Member
+//***************************************************************************
+
+int isMember(const char** list, const char* item)
+{
+ const char** p;
+ int i;
+
+ for (i = 0, p = list; *p; i++, p++)
+ if (strcmp(*p, item) == 0)
+ return i;
+
+ return na;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+char* sstrcpy(char* dest, const char* src, int max)
+{
+ if (!dest || !src)
+ return 0;
+
+ strncpy(dest, src, max);
+ dest[max-1] = 0;
+
+ return dest;
+}
+
+int isLink(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return S_ISLNK(sb.st_mode);
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return false;
+}
+
+const char* suffixOf(const char* path)
+{
+ const char* p;
+
+ if (path && (p = strrchr(path, '.')))
+ return p+1;
+
+ return "";
+}
+
+int fileSize(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return sb.st_size;
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return 0;
+}
+
+time_t fileModTime(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return sb.st_mtime;
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return 0;
+}
+
+int folderExists(const char* path)
+{
+ struct stat fs;
+
+ return stat(path, &fs) == 0 && S_ISDIR(fs.st_mode);
+}
+
+int fileExists(const char* path)
+{
+ return access(path, F_OK) == 0;
+}
+
+int createLink(const char* link, const char* dest, int force)
+{
+ if (!fileExists(link) || force)
+ {
+ // may be the link exists and point to a wrong or already deleted destination ...
+ // .. therefore we delete the link at first
+
+ unlink(link);
+
+ if (symlink(dest, link) != 0)
+ {
+ tell(0, "Failed to create symlink '%s', error was '%s'", link, strerror(errno));
+ return fail;
+ }
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Remove File
+//***************************************************************************
+
+int removeFile(const char* filename)
+{
+ int lnk = isLink(filename);
+
+ if (unlink(filename) != 0)
+ {
+ tell(0, "Can't remove file '%s', '%s'", filename, strerror(errno));
+
+ return 1;
+ }
+
+ tell(3, "Removed %s '%s'", lnk ? "link" : "file", filename);
+
+ return 0;
+}
+
+//***************************************************************************
+// Check Dir
+//***************************************************************************
+
+int chkDir(const char* path)
+{
+ struct stat fs;
+
+ if (stat(path, &fs) != 0 || !S_ISDIR(fs.st_mode))
+ {
+ tell(0, "Creating directory '%s'", path);
+
+ if (mkdir(path, ACCESSPERMS) == -1)
+ {
+ tell(0, "Can't create directory '%s'", strerror(errno));
+ return fail;
+ }
+ }
+
+ return success;
+}
+
+#ifdef USELIBXML
+
+//***************************************************************************
+// Load XSLT
+//***************************************************************************
+
+xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8)
+{
+ xsltStylesheetPtr stylesheet;
+ char* xsltfile;
+
+ asprintf(&xsltfile, "%s/%s-%s.xsl", path, name, utf8 ? "utf-8" : "iso-8859-1");
+
+ if ((stylesheet = xsltParseStylesheetFile((const xmlChar*)xsltfile)) == 0)
+ tell(0, "Error: Can't load xsltfile %s", xsltfile);
+ else
+ tell(0, "Info: Stylesheet '%s' loaded", xsltfile);
+
+ free(xsltfile);
+ return stylesheet;
+}
+#endif
+
+#ifdef USEGUNZIP
+
+//***************************************************************************
+// Gnu Unzip
+//***************************************************************************
+
+int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData)
+{
+ const int growthStep = 1024;
+
+ z_stream stream = {0,0,0,0,0,0,0,0,0,0,0,Z_NULL,Z_NULL,Z_NULL};
+ unsigned int resultSize = 0;
+ int res = 0;
+
+ unzippedData->clear();
+
+ // determining the size in this way is taken from the sources of the gzip utility.
+
+ memcpy(&unzippedData->size, zippedData->memory + zippedData->size -4, 4);
+ unzippedData->memory = (char*)malloc(unzippedData->size);
+
+ // zlib initialisation
+
+ stream.avail_in = zippedData->size;
+ stream.next_in = (Bytef*)zippedData->memory;
+ stream.avail_out = unzippedData->size;
+ stream.next_out = (Bytef*)unzippedData->memory;
+
+ // The '+ 32' tells zlib to process zlib&gzlib headers
+
+ res = inflateInit2(&stream, MAX_WBITS + 32);
+
+ if (res != Z_OK)
+ {
+ tellZipError(res, " during zlib initialisation", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ // skip the header
+
+ res = inflate(&stream, Z_BLOCK);
+
+ if (res != Z_OK)
+ {
+ tellZipError(res, " while skipping the header", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ while (res == Z_OK)
+ {
+ if (stream.avail_out == 0)
+ {
+ unzippedData->size += growthStep;
+ unzippedData->memory = (char*)realloc(unzippedData->memory, unzippedData->size);
+
+ // Set the stream pointers to the potentially changed buffer!
+
+ stream.avail_out = resultSize - stream.total_out;
+ stream.next_out = (Bytef*)(unzippedData + stream.total_out);
+ }
+
+ res = inflate(&stream, Z_SYNC_FLUSH);
+ resultSize = stream.total_out;
+ }
+
+ if (res != Z_STREAM_END)
+ {
+ tellZipError(res, " during inflating", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ unzippedData->size = resultSize;
+ inflateEnd(&stream);
+
+ return success;
+}
+
+//***************************************************************************
+// gzip
+//***************************************************************************
+
+int gzip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen)
+{
+ z_stream stream;
+ int res;
+
+ stream.next_in = (Bytef *)source;
+ stream.avail_in = (uInt)sourceLen;
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ res = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
+
+ if (res == Z_OK)
+ {
+ res = deflate(&stream, Z_FINISH);
+
+ if (res != Z_STREAM_END)
+ {
+ deflateEnd(&stream);
+ res = res == Z_OK ? Z_BUF_ERROR : res;
+ }
+ }
+
+ if (res == Z_STREAM_END)
+ {
+ *destLen = stream.total_out;
+ res = deflateEnd(&stream);
+ }
+
+ if (res != Z_OK)
+ tellZipError(res, " during compression", "");
+
+ return res == Z_OK ? success : fail;
+}
+
+ulong gzipBound(ulong size)
+{
+ return compressBound(size);
+}
+
+//*************************************************************************
+// tellZipError
+//*************************************************************************
+
+void tellZipError(int errorCode, const char* op, const char* msg)
+{
+ if (!op) op = "";
+ if (!msg) msg = "None";
+
+ switch (errorCode)
+ {
+ case Z_OK: return;
+ case Z_STREAM_END: return;
+ case Z_MEM_ERROR: tell(0, "Error: Not enough memory to zip/unzip file%s!\n", op); return;
+ case Z_BUF_ERROR: tell(0, "Error: Couldn't zip/unzip data due to output buffer size problem%s!\n", op); return;
+ case Z_DATA_ERROR: tell(0, "Error: Zipped input data corrupted%s! Details: %s\n", op, msg); return;
+ case Z_STREAM_ERROR: tell(0, "Error: Invalid stream structure%s. Details: %s\n", op, msg); return;
+ default: tell(0, "Error: Couldn't zip/unzip data for unknown reason (%6d)%s!\n", errorCode, op); return;
+ }
+}
+
+#endif // USEGUNZIP
+
+//*************************************************************************
+// Host Data
+//*************************************************************************
+
+#include <sys/utsname.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+static struct utsname info;
+
+const char* getHostName()
+{
+ // get info from kernel
+
+ if (uname(&info) == -1)
+ return "";
+
+ return info.nodename;
+}
+
+const char* getFirstIp(int skipLo)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ static char host[NI_MAXHOST] = "";
+ static char netMask[INET6_ADDRSTRLEN] = "";
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() for '%s' failed: %s", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ // skip loopback interface
+
+ if (skipLo && strcmp(host, "127.0.0.1") == 0)
+ continue;
+
+ if (ifa->ifa_netmask && ifa->ifa_netmask->sa_family == AF_INET)
+ {
+ void* p = &((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr;
+ inet_ntop(ifa->ifa_netmask->sa_family, p, netMask, sizeof(netMask));
+ }
+
+ tell(1, "%-8s %-15s %s; netmask '%s'", ifa->ifa_name, host,
+ ifa->ifa_addr->sa_family == AF_INET ? " (AF_INET)" :
+ ifa->ifa_addr->sa_family == AF_INET6 ? " (AF_INET6)" : "",
+ netMask);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return host;
+}
+
+//***************************************************************************
+// Get Interfaces
+//***************************************************************************
+
+const char* getInterfaces()
+{
+ static char buffer[1000+TB] = "";
+ static char host[NI_MAXHOST] = "";
+ struct ifaddrs *ifaddr, *ifa;
+
+ *buffer = 0;
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() failed: %s, skipping interface '%s'", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ sprintf(eos(buffer), "%s:%s ", ifa->ifa_name, host);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return buffer;
+}
+
+//***************************************************************************
+// Get First Interface
+//***************************************************************************
+
+const char* getFirstInterface()
+{
+ static char buffer[1000+TB] = "";
+ static char host[NI_MAXHOST] = "";
+ struct ifaddrs *ifaddr, *ifa;
+
+ *buffer = 0;
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() failed: %s, skipping interface '%s'", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ if (strcasecmp(ifa->ifa_name, "lo") != 0)
+ sprintf(buffer, "%s", ifa->ifa_name);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return buffer;
+}
+
+//***************************************************************************
+// Get IP Of
+//***************************************************************************
+
+const char* getIpOf(const char* device)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if (isEmpty(device))
+ return getFirstIp();
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, device, IFNAMSIZ-1);
+
+ ioctl(fd, SIOCGIFADDR, &ifr);
+ close(fd);
+
+ // caller has to copy the result string 'before' calling again!
+
+ return inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr);
+}
+
+//***************************************************************************
+// Get Mask Of
+//***************************************************************************
+
+const char* getMaskOf(const char* device)
+{
+ struct ifreq ifr;
+ static char netMask[INET6_ADDRSTRLEN] = "";
+ int fd;
+
+ if (isEmpty(device))
+ device = getFirstInterface();
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, device, IFNAMSIZ-1);
+ ioctl(fd, SIOCGIFNETMASK, &ifr);
+ close(fd);
+
+ if (ifr.ifr_netmask.sa_family == AF_INET)
+ {
+ void* p = &((struct sockaddr_in*)(&ifr.ifr_netmask))->sin_addr;
+ inet_ntop(ifr.ifr_netmask.sa_family, p, netMask, sizeof(netMask));
+
+ tell(5, "netmask for device '%s' is '%s'", device, netMask);
+ }
+
+ return netMask;
+}
+
+//***************************************************************************
+// Broadcast Address Of
+//***************************************************************************
+
+const char* bcastAddressOf(const char* ipStr, const char* maskStr)
+{
+ struct in_addr host, mask, broadcast;
+ static char bcastAddress[INET_ADDRSTRLEN] = "";
+
+ if (isEmpty(maskStr))
+ maskStr = "255.255.255.0";
+
+ if (inet_pton(AF_INET, ipStr, &host) == 1 && inet_pton(AF_INET, maskStr, &mask) == 1)
+ {
+ broadcast.s_addr = host.s_addr | ~mask.s_addr;
+
+ if (inet_ntop(AF_INET, &broadcast, bcastAddress, INET_ADDRSTRLEN))
+ tell(5, "Bcast for '%s' is '%s'", ipStr, bcastAddress);
+ else
+ tell(0, "Error: Failed converting number to string");
+ }
+ else
+ {
+ tell(0, "Error: Failed converting strings to numbers");
+ }
+
+ return bcastAddress;
+}
+
+//***************************************************************************
+// MAC Address Of
+//***************************************************************************
+
+const char* getMacOf(const char* device)
+{
+ enum { macTuppel = 6 };
+
+ static char mac[20+TB];
+ struct ifreq ifr;
+ int s;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ strcpy(ifr.ifr_name, "eth0");
+ ioctl(s, SIOCGIFHWADDR, &ifr);
+
+ for (int i = 0; i < macTuppel; i++)
+ sprintf(&mac[i*3],"%02x:",((unsigned char*)ifr.ifr_hwaddr.sa_data)[i]);
+
+ mac[17] = 0;
+
+ close(s);
+
+ return mac;
+}
+
+#ifdef USELIBARCHIVE
+
+//***************************************************************************
+// unzip <file> and get data of first content which name matches <filter>
+//***************************************************************************
+
+int unzip(const char* file, const char* filter, char*& buffer, int& size, char* entryName)
+{
+ const int step = 1024*10;
+
+ int bufSize = 0;
+ int r;
+ int res;
+
+ struct archive_entry* entry;
+ struct archive* a = archive_read_new();
+
+ *entryName = 0;
+ buffer = 0;
+ size = 0;
+
+ archive_read_support_filter_all(a);
+ archive_read_support_format_all(a);
+
+ r = archive_read_open_filename(a, file, 10204);
+
+ if (r != ARCHIVE_OK)
+ {
+ tell(0, "Error: Open '%s' failed - %s", file, strerror(errno));
+ return 1;
+ }
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
+ {
+ strcpy(entryName, archive_entry_pathname(entry));
+
+ if (strstr(entryName, filter))
+ {
+ bufSize = step;
+ buffer = (char*)malloc(bufSize+1);
+
+ while ((res = archive_read_data(a, buffer+size, step)) > 0)
+ {
+ size += res;
+ bufSize += step;
+
+ buffer = (char*)realloc(buffer, bufSize+1);
+ }
+
+ buffer[size] = 0;
+
+ break;
+ }
+ }
+
+ r = archive_read_free(a);
+
+ if (r != ARCHIVE_OK)
+ {
+ size = 0;
+ free(buffer);
+ return fail;
+ }
+
+ return size > 0 ? success : fail;
+}
+
+#endif
+
+//***************************************************************************
+// cMyMutex
+//***************************************************************************
+
+cMyMutex::cMyMutex (void)
+{
+ locked = 0;
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
+ pthread_mutex_init(&mutex, &attr);
+}
+
+cMyMutex::~cMyMutex()
+{
+ pthread_mutex_destroy(&mutex);
+}
+
+void cMyMutex::Lock(void)
+{
+ pthread_mutex_lock(&mutex);
+ locked++;
+}
+
+void cMyMutex::Unlock(void)
+{
+ if (!--locked)
+ pthread_mutex_unlock(&mutex);
+}
+
+//***************************************************************************
+// cMyTimeMs
+//***************************************************************************
+
+cMyTimeMs::cMyTimeMs(int Ms)
+{
+ if (Ms >= 0)
+ Set(Ms);
+ else
+ begin = 0;
+}
+
+uint64_t cMyTimeMs::Now(void)
+{
+#define MIN_RESOLUTION 5 // ms
+ static bool initialized = false;
+ static bool monotonic = false;
+ struct timespec tp;
+ if (!initialized) {
+ // check if monotonic timer is available and provides enough accurate resolution:
+ if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) {
+ // long Resolution = tp.tv_nsec;
+ // require a minimum resolution:
+ if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) {
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
+ monotonic = true;
+ }
+ else
+ tell(0, "Error: cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+ }
+ else
+ tell(0, "Info: cMyTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec);
+ }
+ else
+ tell(0, "Error: cMyTimeMs: clock_getres(CLOCK_MONOTONIC) failed");
+ initialized = true;
+ }
+ if (monotonic) {
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0)
+ return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000;
+ tell(0, "Error: cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+ monotonic = false;
+ // fall back to gettimeofday()
+ }
+ struct timeval t;
+ if (gettimeofday(&t, NULL) == 0)
+ return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000;
+ return 0;
+}
+
+void cMyTimeMs::Set(int Ms)
+{
+ begin = Now() + Ms;
+}
+
+bool cMyTimeMs::TimedOut(void)
+{
+ return Now() >= begin;
+}
+
+uint64_t cMyTimeMs::Elapsed(void)
+{
+ return Now() - begin;
+}
+
+//**************************************************************************
+// Regular Expression Searching
+//**************************************************************************
+
+int rep(const char* string, const char* expression, Option options)
+{
+ const char* tmpA;
+ const char* tmpB;
+
+ return rep(string, expression, tmpA, tmpB, options);
+}
+
+int rep(const char* string, const char* expression, const char*& s_location,
+ Option options)
+{
+ const char* tmpA;
+
+ return rep(string, expression, s_location, tmpA, options);
+}
+
+
+int rep(const char* string, const char* expression, const char*& s_location,
+ const char*& e_location, Option options)
+{
+ regex_t reg;
+ regmatch_t rm;
+ int status;
+ int opt = 0;
+
+ // Vorbereiten von reg fuer die Expressionsuche mit regexec
+ // Flags: REG_EXTENDED = Use Extended Regular Expressions
+ // REG_ICASE = Ignore case in match.
+
+ reg.re_nsub = 0;
+
+ // Options umwandeln
+ if (options & repUseRegularExpression)
+ opt = opt | REG_EXTENDED;
+ if (options & repIgnoreCase)
+ opt = opt | REG_ICASE;
+
+ if (regcomp( &reg, expression, opt) != 0)
+ return fail;
+
+ // Suchen des ersten Vorkommens von reg in string
+
+ status = regexec(&reg, string, 1, &rm, 0);
+ regfree(&reg);
+
+ if (status != 0)
+ return fail;
+
+ // Suche erfolgreich =>
+ // Setzen der ermittelten Start- und Endpositionen
+
+ s_location = (char*)(string + rm.rm_so);
+ e_location = (char*)(string + rm.rm_eo);
+
+ return success;
+}
+
+//***************************************************************************
+// Class LogDuration
+//***************************************************************************
+
+LogDuration::LogDuration(const char* aMessage, int aLogLevel)
+{
+ logLevel = aLogLevel;
+ strcpy(message, aMessage);
+
+ // at last !
+
+ durationStart = cMyTimeMs::Now();
+}
+
+LogDuration::~LogDuration()
+{
+ tell(logLevel, "duration '%s' was (%ldms)",
+ message, (long)(cMyTimeMs::Now() - durationStart));
+}
+
+void LogDuration::show(const char* label)
+{
+ tell(logLevel, "elapsed '%s' at '%s' was (%ldms)",
+ message, label, (long)(cMyTimeMs::Now() - durationStart));
+}
+
+//***************************************************************************
+// Get Unique ID
+//***************************************************************************
+
+#ifdef USEUUID
+const char* getUniqueId()
+{
+ static char uuid[sizeUuid+TB] = "";
+
+ uuid_t id;
+ uuid_generate(id);
+ uuid_unparse_upper(id, uuid);
+
+ return uuid;
+}
+#endif // USEUUID
+
+//***************************************************************************
+// Create MD5
+//***************************************************************************
+
+#ifdef USEMD5
+
+int createMd5(const char* buf, md5* md5)
+{
+ MD5_CTX c;
+ unsigned char out[MD5_DIGEST_LENGTH];
+
+ MD5_Init(&c);
+ MD5_Update(&c, buf, strlen(buf));
+ MD5_Final(out, &c);
+
+ for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+ sprintf(md5+2*n, "%02x", out[n]);
+
+ md5[sizeMd5] = 0;
+
+ return done;
+}
+
+int createMd5OfFile(const char* path, const char* name, md5* md5)
+{
+ FILE* f;
+ char buffer[1000];
+ int nread = 0;
+ MD5_CTX c;
+ unsigned char out[MD5_DIGEST_LENGTH];
+ char* file = 0;
+
+ asprintf(&file, "%s/%s", path, name);
+
+ if (!(f = fopen(file, "r")))
+ {
+ tell(0, "Fatal: Cannot build MD5 of '%s'; Error was '%s'", file, strerror(errno));
+ free(file);
+ return fail;
+ }
+
+ free(file);
+
+ MD5_Init(&c);
+
+ while ((nread = fread(buffer, 1, 1000, f)) > 0)
+ MD5_Update(&c, buffer, nread);
+
+ fclose(f);
+
+ MD5_Final(out, &c);
+
+ for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+ sprintf(md5+2*n, "%02x", out[n]);
+
+ md5[sizeMd5] = 0;
+
+ return success;
+}
+
+#endif // USEMD5
+
+//***************************************************************************
+// Url Unescape
+//***************************************************************************
+/*
+ * The buffer pointed to by @dst must be at least strlen(@src) bytes.
+ * Decoding stops at the first character from @src that decodes to null.
+
+ * Path normalization will remove redundant slashes and slash+dot sequences,
+ * as well as removing path components when slash+dot+dot is found. It will
+ * keep the root slash (if one was present) and will stop normalization
+ * at the first questionmark found (so query parameters won't be normalized).
+ *
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param normalize perform path normalization if nonzero
+ * @return number of valid characters in @dst
+ */
+
+int urlUnescape(char* dst, const char* src, int normalize)
+{
+// CURL* curl;
+// int resultSize;
+
+// if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+// {
+// tell(0, "Error, something went wrong with curl_global_init()");
+
+// return fail;
+// }
+
+// curl = curl_easy_init();
+
+// if (!curl)
+// {
+// tell(0, "Error, unable to get handle from curl_easy_init()");
+
+// return fail;
+// }
+
+// dst = curl_easy_unescape(curl, src, strlen(src), &resultSize);
+
+// tell(0, " [%.40s]", src);
+
+// tell(0, "res size %d [%.40s]", resultSize, dst);
+// return resultSize;
+
+ char* org_dst = dst;
+ int slash_dot_dot = 0;
+ char ch, a, b;
+
+ a = 0;
+
+ do {
+ ch = *src++;
+
+ if (ch == '%' && isxdigit(a = src[0]) && isxdigit(b = src[1]))
+ {
+ if (a < 'A')
+ a -= '0';
+ else if
+ (a < 'a') a -= 'A' - 10;
+ else
+ a -= 'a' - 10;
+
+ if (b < 'A')
+ b -= '0';
+ else if (b < 'a')
+ b -= 'A' - 10;
+ else
+ b -= 'a' - 10;
+
+ ch = 16 * a + b;
+ src += 2;
+ }
+
+ if (normalize)
+ {
+ switch (ch)
+ {
+ case '/': // compress consecutive slashes and remove slash-dot
+ if (slash_dot_dot < 3)
+ {
+
+ dst -= slash_dot_dot;
+ slash_dot_dot = 1;
+ break;
+ }
+ // fall-through
+
+ case '?': // at start of query, stop normalizing
+ if (ch == '?')
+ normalize = 0;
+
+ // fall-through
+
+ case '\0': // remove trailing slash-dot-(dot)
+ if (slash_dot_dot > 1)
+ {
+ dst -= slash_dot_dot;
+
+ // remove parent directory if it was two dots
+
+ if (slash_dot_dot == 3)
+ while (dst > org_dst && *--dst != '/')
+ ; // empty body
+ slash_dot_dot = (ch == '/') ? 1 : 0;
+
+ // keep the root slash if any
+
+ if (!slash_dot_dot && dst == org_dst && *dst == '/')
+ ++dst;
+
+ }
+ break;
+
+ case '.':
+ if (slash_dot_dot == 1 || slash_dot_dot == 2)
+ {
+ ++slash_dot_dot;
+ break;
+ }
+ // fall-through
+
+ default:
+ slash_dot_dot = 0;
+ }
+ }
+
+ *dst++ = ch;
+ } while(ch);
+
+ return (dst - org_dst) - 1;
+}
diff --git a/lib/common.h b/lib/common.h
new file mode 100644
index 0000000..df1c236
--- /dev/null
+++ b/lib/common.h
@@ -0,0 +1,560 @@
+/*
+ * common.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include <stdint.h> // uint_64_t
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+#include <errno.h>
+#include <string>
+#include <map>
+
+#ifdef USESYSD
+# include <systemd/sd-daemon.h>
+#endif
+
+#ifdef USEMD5
+# include <openssl/md5.h> // MD5_*
+#endif
+
+#ifdef USELIBXML
+# include <libxslt/transform.h>
+# include <libxslt/xsltutils.h>
+# include <libexslt/exslt.h>
+#endif
+
+#ifdef VDR_PLUGIN
+# include <vdr/tools.h>
+#endif
+
+class MemoryStruct;
+
+//***************************************************************************
+// Misc
+//***************************************************************************
+
+#ifndef VDR_PLUGIN
+ inline long min(long a, long b) { return a < b ? a : b; }
+ inline long max(long a, long b) { return a > b ? a : b; }
+#else
+# define __STL_CONFIG_H
+# include <vdr/tools.h>
+#endif
+
+enum Misc
+{
+ success = 0,
+ done = success,
+ fail = -1,
+ na = -1,
+ ignore = -2,
+ all = -3,
+ abrt = -4,
+ yes = 1,
+ on = 1,
+ off = 0,
+ no = 0,
+ TB = 1,
+
+#ifdef USEMD5
+ sizeMd5 = 2 * MD5_DIGEST_LENGTH,
+#endif
+ sizeUuid = 36,
+
+ tmeSecondsPerMinute = 60,
+ tmeSecondsPerHour = tmeSecondsPerMinute * 60,
+ tmeSecondsPerDay = 24 * tmeSecondsPerHour,
+ tmeUsecondsPerSecond = 1000 * 1000
+};
+
+enum Case
+{
+ cUpper,
+ cLower
+};
+
+const char* toCase(Case cs, char* str);
+
+//***************************************************************************
+// Tell
+//***************************************************************************
+
+extern const char* logPrefix;
+
+const char* getLogPrefix();
+void __attribute__ ((format(printf, 2, 3))) tell(int eloquence, const char* format, ...);
+
+//***************************************************************************
+// Syslog
+//***************************************************************************
+
+class Syslog
+{
+ public:
+
+ struct Facilities
+ {
+ const char* name; // #83
+ int code;
+ };
+
+ static const Facilities facilities[];
+
+ static char* toName(int code);
+ static int toCode(const char* name);
+
+ static int syslogFacility;
+};
+
+//***************************************************************************
+//
+//***************************************************************************
+
+char* srealloc(void* ptr, size_t size);
+
+//***************************************************************************
+// Gun-Zip
+//***************************************************************************
+
+ulong gzipBound(ulong size);
+int gzip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen);
+void tellZipError(int errorCode, const char* op, const char* msg);
+int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData);
+
+//***************************************************************************
+// MemoryStruct
+//***************************************************************************
+
+struct MemoryStruct
+{
+ public:
+
+ MemoryStruct() { expireAt = 0; memory = 0; zmemory = 0; clear(); }
+ MemoryStruct(const MemoryStruct* o)
+ {
+ size = o->size;
+ memory = (char*)malloc(size);
+ memcpy(memory, o->memory, size);
+
+ zsize = o->zsize;
+ zmemory = (char*)malloc(zsize);
+ memcpy(zmemory, o->zmemory, zsize);
+
+ copyAttributes(o);
+ }
+
+ ~MemoryStruct() { clear(); }
+
+ int isEmpty() { return memory == 0; }
+ int isZipped() { return zmemory != 0 && zsize > 0; }
+
+ int append(const char* buf, int len)
+ {
+ memory = srealloc(memory, size+len);
+ memcpy(memory+size, buf, len);
+ size += len;
+
+ return success;
+ }
+
+ void copyAttributes(const MemoryStruct* o)
+ {
+ strcpy(tag, o->tag);
+ strcpy(name, o->name);
+ strcpy(contentType, o->contentType);
+ strcpy(contentEncoding, o->contentEncoding);
+ strcpy(mimeType, o->mimeType);
+ headerOnly = o->headerOnly;
+ modTime = o->modTime;
+ expireAt = o->expireAt;
+ }
+
+ int toGzip()
+ {
+ free(zmemory);
+ zsize = 0;
+
+ if (isEmpty())
+ return fail;
+
+ zsize = gzipBound(size) + 512; // the maximum calculated by the lib, will adusted at gzip() call
+ zmemory = (char*)malloc(zsize);
+
+ if (gzip((Bytef*)zmemory, &zsize, (Bytef*)memory, size) != success)
+ {
+ free(zmemory);
+ zsize = 0;
+ tell(0, "Error gzip failed!");
+
+ return fail;
+ }
+
+ sprintf(contentEncoding, "gzip");
+
+ return success;
+ }
+
+ void clear()
+ {
+ free(memory);
+ memory = 0;
+ size = 0;
+ free(zmemory);
+ zmemory = 0;
+ zsize = 0;
+ *tag = 0;
+ *name = 0;
+ *contentType = 0;
+ *contentEncoding = 0;
+ *mimeType = 0;
+ modTime = time(0);
+ headerOnly = no;
+ headers.clear();
+ statusCode = 0;
+ // expireAt = time(0); -> don't reset 'expireAt' here !!!!
+ }
+
+ // data
+
+ char* memory;
+ long unsigned int size;
+
+ char* zmemory;
+ long unsigned int zsize;
+
+ // tag attribute
+
+ char tag[100+TB]; // the tag to be compared
+ char name[100+TB]; // content name (filename)
+ char contentType[100+TB]; // e.g. text/html
+ char mimeType[100+TB]; //
+ char contentEncoding[100+TB]; //
+ int headerOnly;
+ long statusCode;
+ time_t modTime;
+ time_t expireAt;
+ std::map<std::string, std::string> headers;
+};
+
+//***************************************************************************
+// Tools
+//***************************************************************************
+
+double usNow();
+unsigned int getHostId();
+const char* getHostName();
+const char* getFirstInterface();
+const char* getInterfaces();
+const char* getFirstIp(int skipLo = yes);
+const char* getIpOf(const char* device);
+const char* getMacOf(const char* device);
+const char* getMaskOf(const char* device);
+const char* bcastAddressOf(const char* ipStr, const char* maskStr = 0);
+
+//***************************************************************************
+
+#ifdef USEUUID
+ const char* getUniqueId();
+#endif
+
+void removeChars(std::string& str, const char* ignore);
+void removeCharsExcept(std::string& str, const char* except);
+void removeWord(std::string& pattern, std::string word);
+void prepareCompressed(std::string& pattern);
+std::string strReplace(const std::string& what, const std::string& with, const std::string& subject);
+std::string strReplace(const std::string& what, long with, const std::string& subject);
+std::string strReplace(const std::string& what, double with, const std::string& subject);
+char* strReplace(char* buffer, char from, char to);
+
+int rangeFrom(const char* s);
+int rangeTo(const char* s);
+
+char* rTrim(char* buf);
+char* lTrim(char* buf);
+char* allTrim(char* buf);
+
+int isMember(const char** list, const char* item);
+char* sstrcpy(char* dest, const char* src, int max);
+std::string num2Str(int num);
+int isDST(time_t t = 0);
+time_t timeOf(time_t t);
+int weekdayOf(time_t t);
+const char* toWeekdayName(uint day);
+time_t hhmmOf(time_t t);
+int l2hhmm(time_t t);
+std::string hhmm2pTime(int hhmm);
+time_t midnightOf(time_t t);
+std::string l2pTime(time_t t, const char* format = "%d.%m.%Y %T");
+std::string l2pDate(time_t t);
+std::string l2HttpTime(time_t t);
+std::string ms2Dur(uint64_t t);
+const char* c2s(char c, char* buf);
+char* eos(char* s);
+int urlUnescape(char* dst, const char* src, int normalize = yes);
+
+int storeToFile(const char* filename, const char* data, int size);
+int loadFromFile(const char* infile, MemoryStruct* data);
+
+int folderExists(const char* path);
+int fileExists(const char* path);
+int fileSize(const char* path);
+time_t fileModTime(const char* path);
+int createLink(const char* link, const char* dest, int force);
+int isLink(const char* path);
+const char* suffixOf(const char* path);
+int isEmpty(const char* str);
+const char* notNull(const char* str, const char* def = "<null>");
+int isZero(const char* str);
+int removeFile(const char* filename);
+int chkDir(const char* path);
+
+#ifdef USELIBXML
+ xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8);
+#endif
+
+#ifdef USEMD5
+ typedef char md5Buf[sizeMd5+TB];
+ typedef char md5;
+ int createMd5(const char* buf, md5* md5);
+ int createMd5OfFile(const char* path, const char* name, md5* md5);
+#endif
+
+//***************************************************************************
+// Zip
+//***************************************************************************
+
+#ifdef USELIBARCHIVE
+int unzip(const char* file, const char* filter, char*& buffer,
+ int& size, char* entryName);
+#endif
+
+//***************************************************************************
+// cMyMutex
+//***************************************************************************
+
+class cMyMutex
+{
+ friend class cCondVar;
+
+ public:
+
+ cMyMutex(void);
+ ~cMyMutex();
+ void Lock(void);
+ void Unlock(void);
+
+ private:
+
+ pthread_mutex_t mutex;
+ int locked;
+};
+
+//***************************************************************************
+// cMyTimeMs
+//***************************************************************************
+
+class cMyTimeMs
+{
+ private:
+
+ uint64_t begin;
+
+ public:
+
+ cMyTimeMs(int Ms = 0);
+ static uint64_t Now(void);
+ void Set(int Ms = 0);
+ bool TimedOut(void);
+ uint64_t Elapsed(void);
+};
+
+//***************************************************************************
+// Wrapper for Regual Expression Library
+//***************************************************************************
+
+enum Option
+{
+ repUseRegularExpression = 1,
+ repIgnoreCase = 2
+};
+
+int rep(const char* string, const char* expression, Option options = repUseRegularExpression);
+
+int rep(const char* string, const char* expression,
+ const char*& s_location, Option options = repUseRegularExpression);
+
+int rep(const char* string, const char* expression, const char*& s_location,
+ const char*& e_location, Option options = repUseRegularExpression);
+
+//***************************************************************************
+// Log Duration
+//***************************************************************************
+
+class LogDuration
+{
+ public:
+
+ LogDuration(const char* aMessage, int aLogLevel = 2);
+ ~LogDuration();
+
+ void show(const char* label = "");
+
+ protected:
+
+ char message[1000];
+ uint64_t durationStart;
+ int logLevel;
+};
+
+//***************************************************************************
+// Semaphore
+//***************************************************************************
+
+#include <sys/sem.h>
+
+class Sem
+{
+ public:
+
+ Sem(key_t aKey)
+ {
+ locked = no;
+ key = aKey;
+
+ if ((id = semget(key, 1, 0666 | IPC_CREAT)) == -1)
+ tell(0, "Error: Can't get semaphore, errno (%d) '%s'",
+ errno, strerror(errno));
+ }
+
+ ~Sem()
+ {
+ if (locked)
+ v();
+ }
+
+ // ----------------------
+ // get lock
+
+ int p()
+ {
+ sembuf sops[2];
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0; // wait for lock
+ sops[0].sem_flg = SEM_UNDO;
+
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 1; // increment
+ sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+ if (semop(id, sops, 2) == -1)
+ {
+ tell(0, "Error: Can't lock semaphore, errno (%d) '%s'",
+ errno, strerror(errno));
+
+ return fail;
+ }
+
+ locked = yes;
+
+ return success;
+ }
+
+ // ----------------------
+ // increment
+
+ int inc()
+ {
+ sembuf sops[1];
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 1; // increment
+ sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+ if (semop(id, sops, 1) == -1)
+ {
+ if (errno != EAGAIN)
+ tell(0, "Error: Can't lock semaphore, errno was (%d) '%s'",
+ errno, strerror(errno));
+
+ return fail;
+ }
+
+ locked = yes;
+
+ return success;
+ }
+
+ // ----------------------
+ // decrement
+
+ int dec()
+ {
+ return v();
+ }
+
+ // ----------------------
+ // check
+
+ int check()
+ {
+ sembuf sops[1];
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+ if (semop(id, sops, 1) == -1)
+ {
+ if (errno != EAGAIN)
+ tell(0, "Error: Can't lock semaphore, errno was (%d) '%s'",
+ errno, strerror(errno));
+
+ return fail;
+ }
+
+ return success;
+ }
+
+ // ----------------------
+ // release lock
+
+ int v()
+ {
+ sembuf sops;
+
+ sops.sem_num = 0;
+ sops.sem_op = -1; // release control
+ sops.sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+ if (semop(id, &sops, 1) == -1)
+ {
+ if (errno != EAGAIN)
+ tell(0, "Error: Can't unlock semaphore, errno (%d) '%s'",
+ errno, strerror(errno));
+
+ return fail;
+ }
+
+ locked = no;
+
+ return success;
+ }
+
+ private:
+
+ key_t key;
+ int id;
+ int locked;
+};
+
+//***************************************************************************
+#endif //___COMMON_H
diff --git a/lib/config.c b/lib/config.c
new file mode 100644
index 0000000..f7d3fba
--- /dev/null
+++ b/lib/config.c
@@ -0,0 +1,59 @@
+/*
+ * config.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <syslog.h>
+
+#include "config.h"
+
+//***************************************************************************
+// Statics
+//***************************************************************************
+
+int cEpgConfig::logstdout = no;
+int cEpgConfig::loglevel = 1;
+int cEpgConfig::argLoglevel = na;
+int cEpgConfig::logFacility = LOG_USER;
+const char* cEpgConfig::logName = "unknown";
+
+//***************************************************************************
+// Common EPG Service Configuration
+//***************************************************************************
+
+cEpgConfig::cEpgConfig()
+{
+ // database connection
+
+ sstrcpy(dbHost, "localhost", sizeof(dbHost));
+ dbPort = 3306;
+ sstrcpy(dbName, "epg2vdr", sizeof(dbName));
+ sstrcpy(dbUser, "epg2vdr", sizeof(dbUser));
+ sstrcpy(dbPass, "epg", sizeof(dbPass));
+
+ sstrcpy(netDevice, getFirstInterface(), sizeof(netDevice));
+
+ uuid[0] = 0;
+
+ getepgimages = yes;
+}
+
+//***************************************************************************
+// Has DB Login Changed
+//***************************************************************************
+
+int cEpgConfig::hasDbLoginChanged(cEpgConfig* old)
+{
+ if (old->dbPort != dbPort ||
+ strcmp(old->dbHost, dbHost) != 0 ||
+ strcmp(old->dbName, dbName) != 0 ||
+ strcmp(old->dbUser, dbUser) != 0 ||
+ strcmp(old->dbPass, dbPass) != 0)
+ {
+ return yes;
+ }
+
+ return no;
+}
diff --git a/lib/config.h b/lib/config.h
new file mode 100644
index 0000000..a233bc3
--- /dev/null
+++ b/lib/config.h
@@ -0,0 +1,47 @@
+/*
+ * config.h:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __EPG_CONFIG_H
+#define __EPG_CONFIG_H
+
+#include "common.h"
+
+//***************************************************************************
+// Config
+//***************************************************************************
+
+struct cEpgConfig
+{
+ public:
+
+ cEpgConfig();
+
+ // database connection
+
+ int hasDbLoginChanged(cEpgConfig* old);
+
+ char dbHost[100+TB];
+ int dbPort;
+ char dbName[100+TB];
+ char dbUser[100+TB];
+ char dbPass[100+TB];
+
+ char netDevice[20+TB];
+ char uuid[sizeUuid+TB];
+
+ int getepgimages;
+
+ // static stuff
+
+ static int logstdout;
+ static int loglevel;
+ static int argLoglevel;
+ static int logFacility;
+ static const char* logName;
+};
+
+#endif // __EPG_CONFIG_H
diff --git a/lib/configuration.c b/lib/configuration.c
new file mode 100644
index 0000000..7f51896
--- /dev/null
+++ b/lib/configuration.c
@@ -0,0 +1,238 @@
+/*
+ * configuration.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "configuration.h"
+
+time_t cSystemNotification::lastWatchdogAt = time(0);
+const char* cSystemNotification::pidfile = "";
+
+//***************************************************************************
+// Class cSystemNotification
+//***************************************************************************
+
+cSystemNotification::cSystemNotification()
+ : cThread("SystemNotification", yes)
+{
+ interval = 0;
+ stop = no;
+}
+
+//***************************************************************************
+// System Watchdog Check / notify
+//***************************************************************************
+
+void cSystemNotification::check(int force)
+{
+ if (interval && (force || lastWatchdogAt <= time(0) - interval))
+ {
+ // notify all 'n' seconds
+
+ notify(evKeepalive);
+ lastWatchdogAt = time(0);
+ }
+}
+
+//***************************************************************************
+// Start / Stop notification thread
+//***************************************************************************
+
+int cSystemNotification::startNotifyThread(int timeout)
+{
+ threadTimeout = timeout;
+ Start(yes);
+ return success;
+}
+
+int cSystemNotification::stopNotifyThread()
+{
+ stop = yes;
+ waitCondition.Broadcast();
+ Cancel(3);
+ return success;
+}
+
+//***************************************************************************
+// action
+//***************************************************************************
+
+void cSystemNotification::action()
+{
+ cMyMutex mutex;
+ time_t timeoutAt = time(0) + threadTimeout;
+
+ stop = no;
+ mutex.Lock();
+
+ while (time(0) < timeoutAt && Running() && !stop)
+ {
+ // tell(0, "loop ...");
+
+ waitCondition.TimedWait(mutex, interval*1000);
+
+ if (!stop)
+ check();
+ }
+
+ if (time(0) >= timeoutAt)
+ tell(0, "Warning: Ending notification thread, timeout reached!");
+}
+
+//***************************************************************************
+// Notify System Deamon :o :(
+//***************************************************************************
+
+int cSystemNotification::notify(int event, const char* format, ...)
+{
+#ifdef USESYSD
+ char* message;
+ char* tmp;
+ va_list ap;
+
+ if (isEmpty(format))
+ format = "";
+
+ va_start(ap, format);
+ vasprintf(&tmp, format, ap);
+
+ switch (event)
+ {
+ case evStatus: asprintf(&message, "%s", tmp); break;
+ case evStopping: asprintf(&message, "STOPPING=1\n%s", tmp); break;
+ case evReady: asprintf(&message, "READY=1\nSTATUS=Ready\nMAINPID=%d\n%s", getpid(), tmp); break;
+ case evKeepalive: asprintf(&message, "WATCHDOG=1\n%s", tmp); break;
+ }
+
+ tell(event == evKeepalive ? 2 : 1, "Calling sd_notify(%s)", message);
+ sd_notify(0, message);
+
+ free(tmp);
+ free(message);
+#endif
+
+ // handle pidfile at evReady / evStopping
+
+ if (!isEmpty(pidfile))
+ {
+ if (event == evReady)
+ {
+ FILE* f = fopen(pidfile, "w");
+
+ if (f)
+ {
+ pid_t pid = getpid();
+ tell(1, "Creating pidfile '%s'; pid %d", pidfile, pid);
+ fprintf(f, "%d\n", pid);
+ fclose(f);
+ }
+ else
+ tell(0, "Error: Can't create pid file '%s' error was (%d) '%s'",
+ pidfile, errno, strerror(errno));
+ }
+ else if (event == evStopping)
+ {
+ if (fileExists(pidfile))
+ {
+ tell(1, "Removing pidfile '%s'", pidfile);
+ removeFile(pidfile);
+ }
+ }
+ }
+
+ return done;
+}
+
+//***************************************************************************
+// Get Watchdog State
+//***************************************************************************
+
+int cSystemNotification::getWatchdogState(int minInterval)
+{
+ interval = 0;
+
+#ifdef USESYSD
+
+# ifdef SYSDWDIFO
+ uint64_t us = 0;
+ int sec;
+
+ if (sd_watchdog_enabled(0, &us) > 0 && us > 0)
+ {
+ sec = us / tmeUsecondsPerSecond;
+
+ if (sec < minInterval*2)
+ {
+ tell(0, "Warning: Systemd watchdog configured to (%ds) but min %ds are required,"
+ " ignoring watchdog", sec, minInterval * 2);
+ return no;
+ }
+
+ interval = sec / 2;
+
+ tell(0, "Info: Systemd watchdog request interval"
+ " of at least(%ds), using (%ds) now!", sec, interval);
+
+ return yes;
+ }
+
+ tell(0, "Info: Systemd watchdog not configured, epgd won't be sending keep-alive messages!");
+
+# else
+ interval = defaultInterval;
+ return yes;
+# endif
+
+#else
+ tell(0, "Info: Systemd support not enabled, epgd won't be sending notifications!");
+#endif
+
+ return no;
+}
+
+//***************************************************************************
+// Class Frame
+//***************************************************************************
+
+//***************************************************************************
+// Send Mail
+//***************************************************************************
+
+int cFrame::sendMail(const char* mimeType, const char* receivers,
+ const char* subject, const char* body)
+{
+ int res;
+ char* command = 0;
+ char mailScript[255+TB] = "";
+ std::string mailBody;
+
+ getParameter("epgd", "mailScript", mailScript);
+
+ if (isEmpty(mailScript) || isEmpty(receivers))
+ return done;
+
+ // ' Zeichen maskieren sonst knallt es beim Versenden
+
+ mailBody = strReplace("\"", "'", body);
+ // mailBody = strReplace("!", "", mailBody);
+
+ tell(0, "Send mail '%s' with [%s] to '%s'", subject, body, receivers);
+
+ asprintf(&command, "%s \"%s\" \"%s\" \"%s\" %s", mailScript,
+ subject, mailBody.c_str(), mimeType, receivers);
+
+ if ((res = system(command)) != 0)
+ {
+ if (res == -1)
+ tell(0, "Error: Command '%s' failed, can't fork process, result was (%s)",
+ command, strerror(errno));
+ else
+ tell(0, "Error: Result of command '%s' was (%d)", mailScript, res);
+ }
+
+ free(command);
+
+ return success;
+}
diff --git a/lib/configuration.h b/lib/configuration.h
new file mode 100644
index 0000000..116ed0b
--- /dev/null
+++ b/lib/configuration.h
@@ -0,0 +1,164 @@
+/*
+ * configuration.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __CONFIGURATION_H
+#define __CONFIGURATION_H
+
+#include "thread.h"
+#include "common.h"
+#include "parameters.h"
+
+extern const char* confDir;
+
+//***************************************************************************
+// Configuration
+//***************************************************************************
+
+class cConfiguration
+{
+ public:
+
+ cConfiguration() {};
+ virtual ~cConfiguration() {};
+
+ virtual int atConfigItem(const char* Name, const char* Value) = 0;
+
+ virtual int readConfig(const char* file = 0)
+ {
+ int count = 0;
+ FILE* f;
+ char* line = 0;
+ size_t size = 0;
+ char* value;
+ char* name;
+ char* fileName;
+
+ if (!isEmpty(file))
+ asprintf(&fileName, "%s", file);
+ else
+ asprintf(&fileName, "%s/epgd.conf", confDir);
+
+ if (access(fileName, F_OK) != 0)
+ {
+ fprintf(stderr, "Cannot access configuration file '%s'\n", fileName);
+ free(fileName);
+ return fail;
+ }
+
+ f = fopen(fileName, "r");
+
+ while (getline(&line, &size, f) > 0)
+ {
+ char* p = strchr(line, '#');
+
+ if (p && (!*(p-1) || *(p-1) != '\\'))
+ *p = 0;
+ else if (p && *(p-1) && *(p-1) == '\\')
+ {
+ std::string s = strReplace("\\#", "#", line);
+ free(line);
+ line = strdup(s.c_str());
+ size = strlen(line+TB);
+ }
+
+ allTrim(line);
+
+ if (isEmpty(line))
+ continue;
+
+ if (!(value = strchr(line, '=')))
+ continue;
+
+ *value = 0;
+ value++;
+ lTrim(value);
+ name = line;
+ allTrim(name);
+
+ if (atConfigItem(name, value) != success)
+ {
+ fprintf(stderr, "Found unexpected parameter '%s', aborting\n", name);
+ free(fileName);
+ return fail;
+ }
+
+ count++;
+ }
+
+ free(line);
+ fclose(f);
+
+ tell(0, "Read %d option from %s", count , fileName);
+
+ free(fileName);
+
+ return success;
+ }
+};
+
+//***************************************************************************
+// System Notification Interface (systemd, watchdog, pidfile, ...)
+//***************************************************************************
+
+class cSystemNotification : public cThread
+{
+ public:
+
+ enum SystemEvent
+ {
+ evReady,
+ evStatus,
+ evKeepalive,
+ evStopping
+ };
+
+ enum Misc
+ {
+ defaultInterval = 60
+ };
+
+ cSystemNotification();
+
+ int __attribute__ ((format(printf, 3, 4))) notify(int event, const char* format = 0, ...);
+ int getWatchdogState(int minInterval);
+ void check(int force = no);
+
+ int startNotifyThread(int timeout);
+ int stopNotifyThread();
+
+ static void setPidFile(const char* file) { pidfile = file; }
+
+ protected:
+
+ virtual void action();
+
+ int interval;
+ int threadTimeout;
+ cCondVar waitCondition;
+ int stop;
+
+ static time_t lastWatchdogAt;
+ static const char* pidfile;
+};
+
+
+//***************************************************************************
+// Frame
+//***************************************************************************
+
+class cFrame : public cConfiguration, public cParameters
+{
+ public:
+
+ int sendMail(const char* mimeType, const char* receivers, const char* subject, const char* body);
+ virtual int __attribute__ ((format(printf, 5, 6))) message(int level, char type, const char* title, const char* format, ...) = 0;
+
+};
+
+//***************************************************************************
+
+#endif // __CONFIGURATION_H
diff --git a/lib/curl.c b/lib/curl.c
new file mode 100644
index 0000000..ab5cf82
--- /dev/null
+++ b/lib/curl.c
@@ -0,0 +1,454 @@
+/*
+ * curlfuncs.cpp
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "curl.h"
+
+//***************************************************************************
+// Singelton
+//***************************************************************************
+
+cCurl curl;
+
+std::string cCurl::sBuf = "";
+int cCurl::curlInitialized = no;
+cSystemNotification* cCurl::sysNotification = 0;
+
+//***************************************************************************
+// Callbacks
+//***************************************************************************
+
+size_t collect_data(void *ptr, size_t size, size_t nmemb, void* stream)
+{
+ std::string sTmp;
+ register size_t actualsize = size * nmemb;
+
+ if ((FILE *)stream == NULL)
+ {
+ sTmp.assign((char *)ptr, actualsize);
+ cCurl::sBuf += sTmp;
+ }
+ else
+ {
+ fwrite(ptr, size, nmemb, (FILE *)stream);
+ }
+
+ return actualsize;
+}
+
+//***************************************************************************
+// Object
+//***************************************************************************
+
+cCurl::cCurl()
+{
+ handle = 0;
+}
+
+cCurl::~cCurl()
+{
+ exit();
+}
+
+//***************************************************************************
+// Create / Destroy
+//***************************************************************************
+
+int cCurl::create()
+{
+ if (!curlInitialized)
+ {
+ // call only once per process and *before* any thread is started!
+
+ if (curl_global_init(CURL_GLOBAL_NOTHING /*CURL_GLOBAL_ALL*/) != 0)
+ {
+ tell(0, "Error, something went wrong with curl_global_init()");
+ return fail;
+ }
+
+ curlInitialized = yes;
+ }
+
+ return done;
+}
+
+int cCurl::destroy()
+{
+ if (curlInitialized)
+ curl_global_cleanup();
+
+ curlInitialized = no;
+
+ return done;
+}
+
+//***************************************************************************
+// Init / Exit
+//***************************************************************************
+
+int cCurl::init(const char* httpproxy)
+{
+ if (!handle)
+ {
+ if (!(handle = curl_easy_init()))
+ {
+ tell(0, "Could not create new curl instance");
+ return fail;
+ }
+ }
+
+ // Reset Options
+
+ if (!isEmpty(httpproxy))
+ {
+ curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ curl_easy_setopt(handle, CURLOPT_PROXY, httpproxy); // Specify HTTP proxy
+ }
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, collect_data);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, yes);
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, 0); // Send header to this function
+ curl_easy_setopt(handle, CURLOPT_WRITEHEADER, 0); // Pass some header details to this struct
+ curl_easy_setopt(handle, CURLOPT_MAXFILESIZE, 100*1024*1024); // Set maximum file size to get (bytes)
+ curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); // No progress meter
+ curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); // No signaling
+ curl_easy_setopt(handle, CURLOPT_TIMEOUT, 30); // Set timeout
+ curl_easy_setopt(handle, CURLOPT_NOBODY, 0); //
+ curl_easy_setopt(handle, CURLOPT_USERAGENT, CURL_USERAGENT); // Some servers don't like requests
+
+ return success;
+}
+
+int cCurl::exit()
+{
+ if (handle)
+ curl_easy_cleanup(handle);
+
+ handle = 0;
+
+ return done;
+}
+
+//***************************************************************************
+// Get Url
+//***************************************************************************
+
+int cCurl::GetUrl(const char *url, std::string *sOutput, const std::string &sReferer)
+{
+ CURLcode res;
+
+ init();
+
+ curl_easy_setopt(handle, CURLOPT_URL, url); // Set the URL to get
+
+ if (sReferer != "")
+ curl_easy_setopt(handle, CURLOPT_REFERER, sReferer.c_str());
+
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, yes);
+ curl_easy_setopt(handle, CURLOPT_FAILONERROR, yes);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ sBuf = "";
+
+ res = curl_easy_perform(handle);
+
+ if (res != CURLE_OK)
+ {
+ *sOutput = "";
+ return 0;
+ }
+
+ *sOutput = sBuf;
+ return 1;
+}
+
+int cCurl::GetUrlFile(const char *url, const char *filename, const std::string &sReferer)
+{
+ int nRet = 0;
+ init();
+
+ // Point the output to a file
+
+ FILE *fp;
+ if ((fp = fopen(filename, "w")) == NULL)
+ return 0;
+
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, fp); // Set option to write to file
+ curl_easy_setopt(handle, CURLOPT_URL, url); // Set the URL to get
+ if (sReferer != "")
+ curl_easy_setopt(handle, CURLOPT_REFERER, sReferer.c_str());
+ curl_easy_setopt(handle, CURLOPT_HTTPGET, yes);
+ if (curl_easy_perform(handle) == 0)
+ nRet = 1;
+ else
+ nRet = 0;
+
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); // Set option back to default (string)
+ fclose(fp);
+ return nRet;
+}
+
+int cCurl::PostUrl(const char *url, const std::string &sPost, std::string *sOutput, const std::string &sReferer)
+{
+ init();
+
+ int retval = 1;
+ std::string::size_type nStart = 0, nEnd, nPos;
+ std::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)) != std::string::npos) {
+ sTmp = sPost.substr(nStart, nEnd - nStart);
+ if ((nPos = sTmp.find("=")) == std::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("=")) == std::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 = curl.DoPost(url, sOutput, sReferer, formpost, headerlist);
+
+ curl_formfree(formpost);
+ curl_slist_free_all(headerlist);
+ return retval;
+}
+
+int cCurl::PostRaw(const char *url, const std::string &sPost, std::string *sOutput, const std::string &sReferer)
+{
+ init();
+
+ int retval;
+ struct curl_httppost *formpost=NULL;
+ struct curl_slist *headerlist=NULL;
+
+ curl_easy_setopt(handle, CURLOPT_POSTFIELDS, sPost.c_str());
+ curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, 0); //FIXME: Should this be the size instead, in case this is binary string?
+
+ retval = curl.DoPost(url, sOutput, sReferer, formpost, headerlist);
+
+ curl_formfree(formpost);
+ curl_slist_free_all(headerlist);
+ return retval;
+}
+
+int cCurl::DoPost(const char *url, std::string *sOutput, const std::string &sReferer,
+ struct curl_httppost *formpost, struct curl_slist *headerlist)
+{
+ headerlist = curl_slist_append(headerlist, "Expect:");
+
+ // Now do the form post
+ curl_easy_setopt(handle, CURLOPT_URL, url);
+ if (sReferer != "")
+ curl_easy_setopt(handle, CURLOPT_REFERER, sReferer.c_str());
+ curl_easy_setopt(handle, CURLOPT_HTTPPOST, formpost);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, 0); // Set option to write to string
+ sBuf = "";
+ if (curl_easy_perform(handle) == 0) {
+ *sOutput = sBuf;
+ return 1;
+ }
+ else {
+ // We have an error here mate!
+ *sOutput = "";
+ return 0;
+ }
+}
+
+int cCurl::SetCookieFile(char *filename)
+{
+ init();
+
+ if (curl_easy_setopt(handle, CURLOPT_COOKIEFILE, filename) != 0)
+ return 0;
+ if (curl_easy_setopt(handle, CURLOPT_COOKIEJAR, filename) != 0)
+ return 0;
+ return 1;
+}
+
+char* cCurl::EscapeUrl(const char *url)
+{
+ init();
+ return curl_easy_escape(handle, url , strlen(url));
+}
+
+void cCurl::Free(char* str)
+{
+ curl_free(str);
+}
+
+//***************************************************************************
+// Callbacks
+//***************************************************************************
+
+size_t cCurl::WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data)
+{
+ size_t realsize = size * nmemb;
+ struct MemoryStruct* mem = (struct MemoryStruct*)data;
+
+ if (sysNotification)
+ sysNotification->check();
+
+ if (mem->memory)
+ mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
+ else
+ mem->memory = (char*)malloc(mem->size + realsize + 1);
+
+ if (mem->memory)
+ {
+ memcpy (&(mem->memory[mem->size]), ptr, realsize);
+ mem->size += realsize;
+ mem->memory[mem->size] = 0;
+ }
+
+ return realsize;
+}
+
+size_t cCurl::WriteHeaderCallback(char* ptr, size_t size, size_t nmemb, void* data)
+{
+ size_t realsize = size * nmemb;
+ struct MemoryStruct* mem = (struct MemoryStruct*)data;
+ char* p;
+
+ if (sysNotification)
+ sysNotification->check();
+
+ if (ptr)
+ {
+ // add to Header to map
+
+ std::string header(ptr);
+ std::size_t pos = header.find(": ");
+
+ if(pos != std::string::npos)
+ {
+ std::string name = header.substr(0, pos);
+ std::string value = header.substr(pos+2, std::string::npos);
+ mem->headers[name] = value;
+ }
+
+ // get filename
+ {
+ // Content-Disposition: attachment; filename="20140103_20140103_de_qy.zip"
+
+ const char* attribute = "Content-disposition: ";
+
+ if ((p = strcasestr((char*)ptr, attribute)))
+ {
+ if ((p = strcasestr(p, "filename=")))
+ {
+ p += strlen("filename=");
+
+ tell(4, "found filename at [%s]", p);
+
+ if (*p == '"')
+ p++;
+
+ sprintf(mem->name, "%s", p);
+
+ if ((p = strchr(mem->name, '\n')))
+ *p = 0;
+
+ if ((p = strchr(mem->name, '\r')))
+ *p = 0;
+
+ if ((p = strchr(mem->name, '"')))
+ *p = 0;
+
+ tell(4, "set name to '%s'", mem->name);
+ }
+ }
+ }
+
+ // since some sources update "ETag" an "Last-Modified:" without changing the contents
+ // we have to use "Content-Length:" to check for updates :(
+ {
+ const char* attribute = "Content-Length: ";
+
+ if ((p = strcasestr((char*)ptr, attribute)))
+ {
+ sprintf(mem->tag, "%s", p+strlen(attribute));
+
+ if ((p = strchr(mem->tag, '\n')))
+ *p = 0;
+
+ if ((p = strchr(mem->tag, '\r')))
+ *p = 0;
+
+ if ((p = strchr(mem->tag, '"')))
+ *p = 0;
+ }
+ }
+ }
+
+ return realsize;
+}
+
+//***************************************************************************
+// Download File
+//***************************************************************************
+
+int cCurl::downloadFile(const char* url, int& size, MemoryStruct* data, int timeout,
+ const char* userAgent, struct curl_slist* headerlist)
+{
+ long code;
+ CURLcode res = CURLE_OK;
+
+ size = 0;
+
+ init();
+
+ curl_easy_setopt(handle, CURLOPT_URL, url); // Specify URL to get
+ curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, yes);
+ curl_easy_setopt(handle, CURLOPT_UNRESTRICTED_AUTH, yes); // continue to send authentication (user+password) when following locations
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); // Send all data to this function
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*)data); // Pass our 'data' struct to the callback function
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, WriteHeaderCallback); // Send header to this function
+ curl_easy_setopt(handle, CURLOPT_WRITEHEADER, (void*)data); // Pass some header details to this struct
+ curl_easy_setopt(handle, CURLOPT_MAXFILESIZE, 100*1024*1024); // Set maximum file size to get (bytes)
+ curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); // No progress meter
+ curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); // No signaling
+ curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout); // Set timeout
+ curl_easy_setopt(handle, CURLOPT_NOBODY, data->headerOnly ? 1 : 0); //
+ curl_easy_setopt(handle, CURLOPT_USERAGENT, userAgent); // Some servers don't like requests without a user-agent field
+ curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip"); //
+
+ if (headerlist)
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headerlist);
+
+ // perform http-get
+
+ if ((res = curl_easy_perform(handle)) != 0)
+ {
+ data->clear();
+ tell(1, "Error, download failed; %s (%d)", curl_easy_strerror(res), res);
+
+ return fail;
+ }
+
+ curl_easy_getinfo(handle, CURLINFO_HTTP_CODE, &code);
+ data->statusCode = code;
+
+ if (code == 404)
+ {
+ data->clear();
+ return fail;
+ }
+
+ size = data->size;
+
+ return success;
+}
diff --git a/lib/curl.h b/lib/curl.h
new file mode 100644
index 0000000..475675c
--- /dev/null
+++ b/lib/curl.h
@@ -0,0 +1,77 @@
+/*
+ * curlfuncs.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __LIB_CURL__
+#define __LIB_CURL__
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include <string>
+
+#include "common.h"
+#include "config.h"
+#include "configuration.h"
+
+#define CURL_USERAGENT "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Mayukh's libcurl wrapper http://www.mayukhbose.com/)"
+
+//***************************************************************************
+// CURL
+//***************************************************************************
+
+class cCurl
+{
+ public:
+
+ cCurl();
+ ~cCurl();
+
+ int init(const char* httpproxy = "");
+ int exit();
+
+ static int create();
+ static int destroy();
+
+ int GetUrl(const char *url, std::string *sOutput, const std::string &sReferer="");
+ int GetUrlFile(const char *url, const char *filename, const std::string &sReferer="");
+ int SetCookieFile(char *filename);
+ int PostUrl(const char *url, const std::string &sPost, std::string *sOutput, const std::string &sReferer = "");
+ int PostRaw(const char *url, const std::string &sPost, std::string *sOutput, const std::string &sReferer = "");
+ int DoPost(const char *url, std::string *sOutput, const std::string &sReferer,
+ struct curl_httppost *formpost, struct curl_slist *headerlist);
+
+ char* EscapeUrl(const char *url);
+ void Free(char* str);
+
+ int downloadFile(const char* url, int& size, MemoryStruct* data, int timeout = 30,
+ const char* userAgent = CURL_USERAGENT, struct curl_slist *headerlist = 0);
+
+ // static stuff
+
+ static void setSystemNotification(cSystemNotification* s) { sysNotification = s; }
+ static std::string sBuf; // dirty
+
+ protected:
+
+ // data
+
+ CURL* handle;
+
+ static cSystemNotification* sysNotification;
+
+ // statics
+
+ static size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data);
+ static size_t WriteHeaderCallback(char* ptr, size_t size, size_t nmemb, void* data);
+
+ static int curlInitialized;
+};
+
+extern cCurl curl;
+
+//***************************************************************************
+#endif // __LIB_CURL__
diff --git a/lib/db.c b/lib/db.c
new file mode 100644
index 0000000..7b50bcd
--- /dev/null
+++ b/lib/db.c
@@ -0,0 +1,1646 @@
+/*
+ * db.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <stdio.h>
+#include <errmsg.h>
+
+#include <map>
+
+#include "db.h"
+
+//***************************************************************************
+// DB Statement
+//***************************************************************************
+
+int cDbStatement::explain = no;
+
+cDbStatement::cDbStatement(cDbTable* aTable)
+{
+ table = aTable;
+ connection = table->getConnection();
+ stmtTxt = "";
+ stmt = 0;
+ inCount = 0;
+ outCount = 0;
+ inBind = 0;
+ outBind = 0;
+ affected = 0;
+ metaResult = 0;
+ bindPrefix = 0;
+ firstExec = yes;
+ buildErrors = 0;
+
+ callsPeriod = 0;
+ callsTotal = 0;
+ duration = 0;
+
+ if (connection)
+ connection->statements.append(this);
+}
+
+cDbStatement::cDbStatement(cDbConnection* aConnection, const char* sText)
+{
+ table = 0;
+ connection = aConnection;
+ stmtTxt = sText;
+ stmt = 0;
+ inCount = 0;
+ outCount = 0;
+ inBind = 0;
+ outBind = 0;
+ affected = 0;
+ metaResult = 0;
+ bindPrefix = 0;
+ firstExec = yes;
+
+ callsPeriod = 0;
+ callsTotal = 0;
+ duration = 0;
+ buildErrors = 0;
+
+ if (connection)
+ connection->statements.append(this);
+}
+
+cDbStatement::~cDbStatement()
+{
+ if (connection)
+ connection->statements.remove(this);
+
+ clear();
+}
+
+//***************************************************************************
+// Execute
+//***************************************************************************
+
+int cDbStatement::execute(int noResult)
+{
+ affected = 0;
+
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ if (!stmt)
+ return connection->errorSql(connection, "execute(missing statement)");
+
+// if (explain && firstExec)
+// {
+// firstExec = no;
+
+// if (strstr(stmtTxt.c_str(), "select "))
+// {
+// MYSQL_RES* result;
+// MYSQL_ROW row;
+// string q = "explain " + stmtTxt;
+
+// if (connection->query(q.c_str()) != success)
+// connection->errorSql(connection, "explain ", 0);
+// else if ((result = mysql_store_result(connection->getMySql())))
+// {
+// while ((row = mysql_fetch_row(result)))
+// {
+// tell(0, "EXPLAIN: %s) %s %s %s %s %s %s %s %s %s",
+// row[0], row[1], row[2], row[3],
+// row[4], row[5], row[6], row[7], row[8], row[9]);
+// }
+
+// mysql_free_result(result);
+// }
+// }
+// }
+
+ // tell(0, "execute %d [%s]", stmt, stmtTxt.c_str());
+
+ double start = usNow();
+
+ if (mysql_stmt_execute(stmt))
+ return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str());
+
+ duration += usNow() - start;
+ callsPeriod++;
+ callsTotal++;
+
+ // out binding - if needed
+
+ if (outCount && !noResult)
+ {
+ if (mysql_stmt_store_result(stmt))
+ return connection->errorSql(connection, "execute(store_result)", stmt, stmtTxt.c_str());
+
+ // fetch the first result - if any
+
+ if (mysql_stmt_affected_rows(stmt) > 0)
+ mysql_stmt_fetch(stmt);
+ }
+ else if (outCount)
+ {
+ mysql_stmt_store_result(stmt);
+ }
+
+ // result was stored (above) only if output (outCound) is expected,
+ // therefore we don't need to call freeResult() after insert() or update()
+
+ affected = mysql_stmt_affected_rows(stmt);
+
+ return success;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int cDbStatement::getLastInsertId()
+{
+ MYSQL_RES* result = 0;
+ int insertId = na;
+
+ if ((result = mysql_store_result(connection->getMySql())) == 0 &&
+ mysql_field_count(connection->getMySql()) == 0 &&
+ mysql_insert_id(connection->getMySql()) != 0)
+ {
+ insertId = mysql_insert_id(connection->getMySql());
+ }
+
+ mysql_free_result(result);
+
+ return insertId;
+}
+
+int cDbStatement::getResultCount()
+{
+ mysql_stmt_store_result(stmt);
+
+ return mysql_stmt_affected_rows(stmt);
+}
+
+int cDbStatement::find()
+{
+ if (execute() != success)
+ return fail;
+
+ return getAffected() > 0 ? yes : no;
+}
+
+int cDbStatement::fetch()
+{
+ if (!mysql_stmt_fetch(stmt))
+ return yes;
+
+ return no;
+}
+
+int cDbStatement::freeResult()
+{
+ if (metaResult)
+ mysql_free_result(metaResult);
+
+ if (stmt)
+ mysql_stmt_free_result(stmt);
+
+ return success;
+}
+
+//***************************************************************************
+// Build Statements - new Interface
+//***************************************************************************
+
+int cDbStatement::build(const char* format, ...)
+{
+ if (format)
+ {
+ char* tmp;
+
+ va_list more;
+ va_start(more, format);
+ vasprintf(&tmp, format, more);
+
+ stmtTxt += tmp;
+ free(tmp);
+ }
+
+ return success;
+}
+
+int cDbStatement::bind(const char* fname, int mode, const char* delim)
+{
+ return bind(table->getValue(fname), mode, delim);
+}
+
+int cDbStatement::bind(cDbFieldDef* field, int mode, const char* delim)
+{
+ return bind(table->getValue(field), mode, delim);
+}
+
+int cDbStatement::bind(cDbTable* aTable, cDbFieldDef* field, int mode, const char* delim)
+{
+ return bind(aTable->getValue(field), mode, delim);
+}
+
+int cDbStatement::bind(cDbTable* aTable, const char* fname, int mode, const char* delim)
+{
+ return bind(aTable->getValue(fname), mode, delim);
+}
+
+int cDbStatement::bind(cDbValue* value, int mode, const char* delim)
+{
+ if (!value || !value->getField())
+ {
+ tell(0, "Error: Missing %s value", !value ? "bind" : "field of bind");
+ buildErrors++;
+ return fail;
+ }
+
+ if (delim)
+ stmtTxt += delim;
+
+ if (bindPrefix)
+ stmtTxt += bindPrefix;
+
+ if (mode & bndIn)
+ {
+ if (mode & bndSet)
+ stmtTxt += value->getDbName() + std::string(" =");
+
+ stmtTxt += " ?";
+ appendBinding(value, bndIn);
+ }
+ else if (mode & bndOut)
+ {
+ stmtTxt += value->getDbName();
+ appendBinding(value, bndOut);
+ }
+
+ return success;
+}
+
+int cDbStatement::bindAllOut(const char* delim)
+{
+ int n = 0;
+ std::map<std::string, cDbFieldDef*>::iterator f;
+ cDbTableDef* tableDef = table->getTableDef();
+
+ if (delim)
+ stmtTxt += delim;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ if (f->second->getType() & ftMeta)
+ continue;
+
+ bind(f->second, bndOut, n++ ? ", " : "");
+ }
+
+ return success;
+}
+
+int cDbStatement::bindCmp(const char* ctable, cDbValue* value,
+ const char* comp, const char* delim)
+{
+ if (delim) build("%s", delim);
+ if (ctable) build("%s.", ctable);
+
+ build("%s%s %s ?", bindPrefix ? bindPrefix : "", value->getDbName(), comp);
+
+ appendBinding(value, bndIn);
+
+ return success;
+}
+
+int cDbStatement::bindCmp(const char* ctable, cDbFieldDef* field, cDbValue* value,
+ const char* comp, const char* delim)
+{
+ cDbValue* vf = table->getRow()->getValue(field);
+ cDbValue* vv = value ? value : vf;
+
+ if (!vf || !vv)
+ {
+ buildErrors++;
+ return fail;
+ }
+
+ if (delim) build("%s", delim);
+ if (ctable) build("%s.", ctable);
+
+ build("%s%s %s ?", bindPrefix ? bindPrefix : "", vf->getDbName(), comp);
+
+ appendBinding(vv, bndIn);
+
+ return success;
+}
+
+int cDbStatement::bindCmp(const char* ctable, const char* fname, cDbValue* value,
+ const char* comp, const char* delim)
+{
+ cDbValue* vf = table->getRow()->getValue(fname);
+ cDbValue* vv = value ? value : vf;
+
+ if (!vf || !vv)
+ {
+ buildErrors++;
+ return fail;
+ }
+
+ if (delim) build("%s", delim);
+ if (ctable) build("%s.", ctable);
+
+ build("%s%s %s ?", bindPrefix ? bindPrefix : "", vf->getDbName(), comp);
+
+ appendBinding(vv, bndIn);
+
+ return success;
+}
+
+int cDbStatement::bindText(const char* text, cDbValue* value,
+ const char* comp, const char* delim)
+{
+ if (!value)
+ {
+ buildErrors++;
+ return fail;
+ }
+
+ if (delim) build("%s", delim);
+
+ build("%s %s ?", text, comp);
+
+ appendBinding(value, bndIn);
+
+ return success;
+}
+
+int cDbStatement::bindTextFree(const char* text, cDbValue* value, int mode)
+{
+ if (!value)
+ {
+ buildErrors++;
+ return fail;
+ }
+
+ build("%s", text);
+
+ if (mode & bndIn)
+ appendBinding(value, bndIn);
+
+ else if (mode & bndOut)
+ appendBinding(value, bndOut);
+
+ return success;
+}
+
+//***************************************************************************
+// Bind In Char - like <field> in ('A','B','C')
+//
+// expected string in cDbValue is: "A,B,C"
+//***************************************************************************
+
+int cDbStatement::bindInChar(const char* ctable, const char* fname,
+ cDbValue* value, const char* delim)
+{
+ cDbValue* vf = table->getRow()->getValue(fname);
+ cDbValue* vv = value ? value : vf;
+
+ if (!vf || !vv)
+ {
+ buildErrors++;
+ return fail;
+ }
+
+ build("%s find_in_set(cast(%s%s%s%s as char),?)",
+ delim ? delim : "",
+ bindPrefix ? bindPrefix : "",
+ ctable ? ctable : "",
+ ctable ? "." : "",
+ vf->getDbName());
+
+ appendBinding(vv, bndIn);
+
+ return success;
+}
+
+//***************************************************************************
+// Clear
+//***************************************************************************
+
+void cDbStatement::clear()
+{
+ stmtTxt = "";
+ affected = 0;
+
+ if (inCount)
+ {
+ free(inBind);
+ inCount = 0;
+ inBind = 0;
+ }
+
+ if (outCount)
+ {
+ free(outBind);
+ outCount = 0;
+ outBind = 0;
+ }
+
+ if (stmt)
+ {
+ mysql_stmt_free_result(stmt);
+ mysql_stmt_close(stmt);
+ stmt = 0;
+ }
+}
+
+//***************************************************************************
+// Append Binding
+//***************************************************************************
+
+int cDbStatement::appendBinding(cDbValue* value, BindType bt)
+{
+ int count = 0;
+ MYSQL_BIND** bindings = 0;
+ MYSQL_BIND* newBinding;
+
+ if (bt & bndIn)
+ {
+ count = ++inCount;
+ bindings = &inBind;
+ }
+ else if (bt & bndOut)
+ {
+ count = ++outCount;
+ bindings = &outBind;
+ }
+ else
+ return 0;
+
+ if (!*bindings)
+ *bindings = (MYSQL_BIND*)malloc(count * sizeof(MYSQL_BIND));
+ else
+ *bindings = (MYSQL_BIND*)srealloc(*bindings, count * sizeof(MYSQL_BIND));
+
+ newBinding = &((*bindings)[count-1]);
+
+ memset(newBinding, 0, sizeof(MYSQL_BIND));
+
+ if (value->getField()->getFormat() == ffAscii || value->getField()->getFormat() == ffText || value->getField()->getFormat() == ffMText)
+ {
+ newBinding->buffer_type = MYSQL_TYPE_STRING;
+ newBinding->buffer = value->getStrValueRef();
+ newBinding->buffer_length = value->getField()->getSize();
+ newBinding->length = value->getStrValueSizeRef();
+
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+ else if (value->getField()->getFormat() == ffMlob)
+ {
+ newBinding->buffer_type = MYSQL_TYPE_BLOB;
+ newBinding->buffer = value->getStrValueRef();
+ newBinding->buffer_length = value->getField()->getSize();
+ newBinding->length = value->getStrValueSizeRef();
+
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+ else if (value->getField()->getFormat() == ffFloat)
+ {
+ newBinding->buffer_type = MYSQL_TYPE_FLOAT;
+ newBinding->buffer = value->getFloatValueRef();
+
+ newBinding->length = 0; // #TODO
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+ else if (value->getField()->getFormat() == ffDateTime)
+ {
+ newBinding->buffer_type = MYSQL_TYPE_DATETIME;
+ newBinding->buffer = value->getTimeValueRef();
+
+ newBinding->length = 0; // #TODO
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+ else if (value->getField()->getFormat() == ffBigInt || value->getField()->getFormat() == ffUBigInt)
+ {
+ newBinding->buffer_type = MYSQL_TYPE_LONGLONG;
+ newBinding->buffer = value->getBigIntValueRef();
+ newBinding->is_unsigned = (value->getField()->getFormat() == ffUBigInt);
+
+ newBinding->length = 0;
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+ else // ffInt, ffUInt
+ {
+ newBinding->buffer_type = MYSQL_TYPE_LONG;
+ newBinding->buffer = value->getIntValueRef();
+ newBinding->is_unsigned = (value->getField()->getFormat() == ffUInt);
+
+ newBinding->length = 0;
+ newBinding->is_null = value->getNullRef();
+ newBinding->error = 0; // #TODO
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Prepare Statement
+//***************************************************************************
+
+int cDbStatement::prepare()
+{
+ if (!connection->getMySql())
+ {
+ tell(0, "Error: Lost connection, can't prepare statement");
+ return fail;
+ }
+
+ if (!stmtTxt.length())
+ return fail;
+
+ if (buildErrors)
+ return fail;
+
+ stmt = mysql_stmt_init(connection->getMySql());
+
+ // prepare statement
+
+ if (mysql_stmt_prepare(stmt, stmtTxt.c_str(), stmtTxt.length()))
+ return connection->errorSql(connection, "prepare(stmt_prepare)", stmt, stmtTxt.c_str());
+
+ if (outBind)
+ {
+ if (mysql_stmt_bind_result(stmt, outBind))
+ return connection->errorSql(connection, "execute(bind_result)", stmt);
+ }
+
+ if (inBind)
+ {
+ if (mysql_stmt_bind_param(stmt, inBind))
+ return connection->errorSql(connection, "buildPrimarySelect(bind_param)", stmt);
+ }
+
+ tell(3, "Statement '%s' with (%ld) in parameters and (%d) out bindings prepared",
+ stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount);
+
+ return success;
+}
+
+//***************************************************************************
+// Show Statistic
+//***************************************************************************
+
+void cDbStatement::showStat()
+{
+ if (callsPeriod)
+ {
+ tell(0, "calls %4ld in %6.2fms; total %4ld [%s]",
+ callsPeriod, duration/1000, callsTotal, stmtTxt.c_str());
+
+ callsPeriod = 0;
+ duration = 0;
+ }
+}
+
+//***************************************************************************
+// cDbConnection statics
+//***************************************************************************
+
+char* cDbConnection::confPath = 0;
+char* cDbConnection::encoding = 0;
+char* cDbConnection::dbHost = strdup("localhost");
+int cDbConnection::dbPort = 3306;
+char* cDbConnection::dbUser = 0;
+char* cDbConnection::dbPass = 0;
+char* cDbConnection::dbName = 0;
+int cDbConnection::initThreads = 0;
+cMyMutex cDbConnection::initMutex;
+
+//***************************************************************************
+// Class cDbTable
+//***************************************************************************
+
+//***************************************************************************
+// Object
+//***************************************************************************
+
+cDbTable::cDbTable(cDbConnection* aConnection, const char* name)
+{
+ connection = aConnection;
+ holdInMemory = no;
+ attached = no;
+
+ row = 0;
+ stmtSelect = 0;
+ stmtInsert = 0;
+ stmtUpdate = 0;
+ lastInsertId = na;
+
+ tableDef = dbDict.getTable(name);
+
+ if (tableDef)
+ row = new cDbRow(tableDef);
+ else
+ tell(0, "Fatal: Table '%s' missing in dictionary '%s'!", name, dbDict.getPath());
+}
+
+cDbTable::~cDbTable()
+{
+ close();
+
+ delete row;
+}
+
+//***************************************************************************
+// Open / Close
+//***************************************************************************
+
+int cDbTable::open(int allowAlter)
+{
+ if (!tableDef || !row)
+ return abrt;
+
+ if (attach() != success)
+ {
+ tell(0, "Could not access database '%s:%d' (tried to open %s)",
+ connection->getHost(), connection->getPort(), TableName());
+
+ return fail;
+ }
+
+ return init(allowAlter);
+}
+
+int cDbTable::close()
+{
+ if (stmtSelect) { delete stmtSelect; stmtSelect = 0; }
+ if (stmtInsert) { delete stmtInsert; stmtInsert = 0; }
+ if (stmtUpdate) { delete stmtUpdate; stmtUpdate = 0; }
+
+ detach();
+
+ return success;
+}
+
+//***************************************************************************
+// Attach / Detach
+//***************************************************************************
+
+int cDbTable::attach()
+{
+ if (isAttached())
+ return success;
+
+ if (connection->attachConnection() != success)
+ {
+ tell(0, "Could not access database '%s:%d'",
+ connection->getHost(), connection->getPort());
+
+ return fail;
+ }
+
+ attached = yes;
+
+ return success;
+}
+
+int cDbTable::detach()
+{
+ if (isAttached())
+ {
+ connection->detachConnection();
+ attached = no;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Init
+//***************************************************************************
+
+int cDbTable::init(int allowAlter)
+{
+ std::string str;
+ std::map<std::string, cDbFieldDef*>::iterator f;
+ int n = 0;
+
+ if (!isConnected())
+ return fail;
+
+ // check/create table ...
+
+ if (exist() && allowAlter)
+ validateStructure(allowAlter);
+
+ if (createTable() != success)
+ return fail;
+
+ // check/create indices
+
+ createIndices();
+
+ // ------------------------------
+ // prepare BASIC statements
+ // ------------------------------
+
+ // select by primary key ...
+
+ stmtSelect = new cDbStatement(this);
+
+ stmtSelect->build("select ");
+
+ n = 0;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ stmtSelect->bind(f->second, bndOut, n++ ? ", " : "");
+
+ stmtSelect->build(" from %s where ", TableName());
+
+ n = 0;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ if (!(f->second->getType() & ftPrimary))
+ continue;
+
+ stmtSelect->bind(f->second, bndIn | bndSet, n++ ? " and " : "");
+ }
+
+ stmtSelect->build(";");
+
+ if (stmtSelect->prepare() != success)
+ return fail;
+
+ // -----------------------------------------
+ // insert
+
+ stmtInsert = new cDbStatement(this);
+
+ stmtInsert->build("insert into %s set ", TableName());
+
+ n = 0;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ // don't insert autoinc and calculated fields
+
+ if (f->second->getType() & ftAutoinc)
+ continue;
+
+ stmtInsert->bind(f->second, bndIn | bndSet, n++ ? ", " : "");
+ }
+
+ stmtInsert->build(";");
+
+ if (stmtInsert->prepare() != success)
+ return fail;
+
+ // -----------------------------------------
+ // update via primary key ...
+
+ stmtUpdate = new cDbStatement(this);
+
+ stmtUpdate->build("update %s set ", TableName());
+
+ n = 0;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ // don't update PKey, autoinc and not used fields
+
+ if (f->second->getType() & ftPrimary ||
+ f->second->getType() & ftAutoinc)
+ continue;
+
+ if (strcasecmp(f->second->getName(), "inssp") == 0) // don't update the insert stamp
+ continue;
+
+ stmtUpdate->bind(f->second, bndIn | bndSet, n++ ? ", " : "");
+ }
+
+ stmtUpdate->build(" where ");
+
+ n = 0;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ if (!(f->second->getType() & ftPrimary))
+ continue;
+
+ stmtUpdate->bind(f->second, bndIn | bndSet, n++ ? " and " : "");
+ }
+
+ stmtUpdate->build(";");
+
+ if (stmtUpdate->prepare() != success)
+ return fail;
+
+ return success;
+}
+
+//***************************************************************************
+// Check Table
+//***************************************************************************
+
+int cDbTable::exist(const char* name)
+{
+ if (isEmpty(name))
+ name = TableName();
+
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name);
+ MYSQL_ROW tabRow = mysql_fetch_row(result);
+ mysql_free_result(result);
+
+ return tabRow ? yes : no;
+}
+
+//***************************************************************************
+// Validate Structure
+//***************************************************************************
+
+struct FieldInfo
+{
+ std::string columnFormat;
+ std::string description;
+ std::string def;
+};
+
+int cDbTable::validateStructure(int allowAlter)
+{
+ std::map<std::string, FieldInfo, _casecmp_> fields;
+ MYSQL_RES* result;
+ MYSQL_ROW row;
+ std::map<std::string, FieldInfo, _casecmp_>::iterator it;
+ int needDetach = no;
+
+ if (!allowAlter)
+ return done;
+
+ const char* select = "select column_name, column_type, column_comment, data_type, is_nullable, "
+ " character_maximum_length, column_default, numeric_precision "
+ " from information_schema.columns "
+ " where table_name = '%s' and table_schema= '%s'";
+
+ if (!isAttached())
+ {
+ needDetach = yes;
+
+ if (attach() != success)
+ return fail;
+ }
+
+ // ------------------------
+ // execute query
+
+ if (connection->query(select, TableName(), connection->getName()) != success)
+ {
+ connection->errorSql(getConnection(), "validateStructure()", 0);
+ if (needDetach) detach();
+ return fail;
+ }
+
+ // ------------------------
+ // process the result
+
+ if (!(result = mysql_store_result(connection->getMySql())))
+ {
+ connection->errorSql(getConnection(), "validateStructure()");
+ if (needDetach) detach();
+ return fail;
+ }
+
+ while ((row = mysql_fetch_row(result)))
+ {
+ fields[row[0]].columnFormat = row[1];
+ fields[row[0]].description = row[2];
+ fields[row[0]].def = row[6] ? row[6] : "";
+ }
+
+ mysql_free_result(result);
+
+ // --------------------------------------
+ // validate if all fields of dict are in
+ // table and check their format, ...
+
+ for (int i = 0; i < fieldCount(); i++)
+ {
+ char colType[100];
+
+ tell(4, "Check field '%s'", getField(i)->getName());
+
+ if (fields.find(getField(i)->getDbName()) == fields.end())
+ alterAddField(getField(i));
+
+ else
+ {
+ FieldInfo* fieldInfo = &fields[getField(i)->getDbName()];
+
+ getField(i)->toColumnFormat(colType);
+
+ if (strcasecmp(fieldInfo->columnFormat.c_str(), colType) != 0 ||
+ strcasecmp(fieldInfo->description.c_str(), getField(i)->getDescription()) != 0 ||
+ (strcasecmp(fieldInfo->def.c_str(), getField(i)->getDefault()) != 0 && !(getField(i)->getType() & ftPrimary)))
+ {
+ alterModifyField(getField(i));
+ }
+ }
+ }
+
+ // --------------------------------------
+ // check if table contains unused fields
+ // and report them
+
+ for (it = fields.begin(); it != fields.end(); it++)
+ {
+ if (!getRow()->getFieldByDbName(it->first.c_str()))
+ {
+ if (allowAlter == 2)
+ alterDropField(it->first.c_str());
+ else
+ tell(0, "Info: Field '%s' not used anymore, "
+ "to remove it call 'ALTER TABLE %s DROP COLUMN %s;' manually",
+ it->first.c_str(), TableName(), it->first.c_str());
+ }
+ }
+
+ if (needDetach) detach();
+
+ return success;
+}
+
+//***************************************************************************
+// Alter 'Modify Field'
+//***************************************************************************
+
+int cDbTable::alterModifyField(cDbFieldDef* def)
+{
+ char* statement;
+ char colType[100];
+
+ tell(0, " Info: Definition of field '%s.%s' modified, try to alter table",
+ TableName(), def->getName());
+
+ // alter table events modify column guest varchar(50)
+
+ asprintf(&statement, "alter table %s modify column %s %s comment '%s' %s%s%s",
+ TableName(),
+ def->getDbName(),
+ def->toColumnFormat(colType),
+ def->getDbDescription(),
+ !isEmpty(def->getDefault()) ? "default '" : "",
+ !isEmpty(def->getDefault()) ? def->getDefault() : "",
+ !isEmpty(def->getDefault()) ? "'" : ""
+ );
+
+ tell(1, "Execute [%s]", statement);
+
+ if (connection->query("%s", statement))
+ return connection->errorSql(getConnection(), "alterAddField()",
+ 0, statement);
+
+ free(statement);
+
+ return done;
+}
+
+//***************************************************************************
+// Alter 'Add Field'
+//***************************************************************************
+
+int cDbTable::alterAddField(cDbFieldDef* def)
+{
+ std::string statement;
+ char colType[100];
+
+ tell(0, "Info: Missing field '%s.%s', try to alter table",
+ TableName(), def->getName());
+
+ // alter table channelmap add column ord int(11) [after source]
+
+ statement = std::string("alter table ") + TableName() + std::string(" add column ")
+ + def->getDbName() + std::string(" ") + def->toColumnFormat(colType);
+
+ if (def->getFormat() != ffMlob)
+ {
+ if (def->getType() & ftAutoinc)
+ statement += " not null auto_increment";
+ else if (!isEmpty(def->getDefault()))
+ statement += " default '" + std::string(def->getDefault()) + "'";
+ }
+
+ if (!isEmpty(def->getDbDescription()))
+ statement += std::string(" comment '") + def->getDbDescription() + std::string("'");
+
+ if (def->getIndex() > 0)
+ statement += std::string(" after ") + getField(def->getIndex()-1)->getDbName();
+
+ tell(1, "Execute [%s]", statement.c_str());
+
+ if (connection->query("%s", statement.c_str()))
+ return connection->errorSql(getConnection(), "alterAddField()",
+ 0, statement.c_str());
+
+ return done;
+}
+
+//***************************************************************************
+// Alter 'Drop Field'
+//***************************************************************************
+
+int cDbTable::alterDropField(const char* name)
+{
+ char* statement;
+
+ tell(0, "Info: Unused field '%s', try to drop it", name);
+
+ // alter table channelmap add column ord int(11) [after source]
+
+ asprintf(&statement, "alter table %s drop column %s", TableName(), name);
+
+ tell(1, "Execute [%s]", statement);
+
+ if (connection->query("%s", statement))
+ return connection->errorSql(getConnection(), "alterDropField()",
+ 0, statement);
+
+ free(statement);
+
+ return done;
+}
+
+//***************************************************************************
+// Create Table
+//***************************************************************************
+
+int cDbTable::createTable()
+{
+ std::string statement;
+ std::string aKey;
+ int needDetach = no;
+
+ if (!tableDef || !row)
+ return abrt;
+
+ if (!isAttached())
+ {
+ needDetach = yes;
+
+ if (attach() != success)
+ return fail;
+ }
+
+ // table exists -> nothing to do
+
+ if (exist())
+ {
+ if (needDetach) detach();
+ return done;
+ }
+
+ tell(0, "Initialy creating table '%s'", TableName());
+
+ // build 'create' statement ...
+
+ statement = std::string("create table ") + TableName() + std::string("(");
+
+ for (int i = 0; i < fieldCount(); i++)
+ {
+ char colType[100];
+
+ if (i) statement += std::string(", ");
+
+ statement += std::string(getField(i)->getDbName()) + " " + std::string(getField(i)->toColumnFormat(colType));
+
+ if (getField(i)->getFormat() != ffMlob)
+ {
+ if (getField(i)->getType() & ftAutoinc)
+ statement += " not null auto_increment";
+ else if (!isEmpty(getField(i)->getDefault()))
+ statement += " default '" + std::string(getField(i)->getDefault()) + "'";
+ }
+
+ if (!isEmpty(getField(i)->getDbDescription()))
+ statement += std::string(" comment '") + getField(i)->getDbDescription() + std::string("'");
+ }
+
+ aKey = "";
+
+ for (int i = 0, n = 0; i < fieldCount(); i++)
+ {
+ if (getField(i)->getType() & ftPrimary)
+ {
+ if (n++) aKey += std::string(", ");
+ aKey += std::string(getField(i)->getDbName()) + " DESC";
+ }
+ }
+
+ if (aKey.length())
+ {
+ statement += std::string(", PRIMARY KEY(");
+ statement += aKey;
+ statement += ")";
+ }
+
+ aKey = "";
+
+ for (int i = 0, n = 0; i < fieldCount(); i++)
+ {
+ if (getField(i)->getType() & ftAutoinc && !(getField(i)->getType() & ftPrimary))
+ {
+ if (n++) aKey += std::string(", ");
+ aKey += std::string(getField(i)->getDbName()) + " DESC";
+ }
+ }
+
+ if (aKey.length())
+ {
+ statement += std::string(", KEY(");
+ statement += aKey;
+ statement += ")";
+ }
+
+ statement += std::string(") ENGINE=InnoDB ROW_FORMAT=DYNAMIC;");
+
+ tell(1, "%s", statement.c_str());
+
+ if (connection->query("%s", statement.c_str()))
+ {
+ if (needDetach) detach();
+ return connection->errorSql(getConnection(), "createTable()",
+ 0, statement.c_str());
+ }
+
+ if (needDetach) detach();
+
+ return success;
+}
+
+//***************************************************************************
+// Create Indices
+//***************************************************************************
+
+int cDbTable::createIndices()
+{
+ std::string statement;
+
+ tell(5, "Initialy checking indices for '%s'", TableName());
+
+ // check/create indexes
+
+ for (int i = 0; i < tableDef->indexCount(); i++)
+ {
+ cDbIndexDef* index = tableDef->getIndex(i);
+ int fCount;
+ std::string idxName;
+
+ if (!index->fieldCount())
+ continue;
+
+ // check
+
+ idxName = "idx" + std::string(index->getName());
+
+ checkIndex(idxName.c_str(), fCount);
+
+ if (fCount != index->fieldCount())
+ {
+ // create index
+
+ statement = "create index " + idxName;
+ statement += " on " + std::string(TableName()) + "(";
+
+ int n = 0;
+
+ for (int f = 0; f < index->fieldCount(); f++)
+ {
+ cDbFieldDef* fld = index->getField(f);
+
+ if (fld)
+ {
+ if (n++) statement += std::string(", ");
+ statement += fld->getDbName();
+ }
+ }
+
+ if (!n) continue;
+
+ statement += ");";
+ tell(1, "%s", statement.c_str());
+
+ if (connection->query("%s", statement.c_str()))
+ return connection->errorSql(getConnection(), "createIndices()",
+ 0, statement.c_str());
+ }
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Check Index
+//***************************************************************************
+
+int cDbTable::checkIndex(const char* idxName, int& fieldCount)
+{
+ enum IndexQueryFields
+ {
+ idTable,
+ idNonUnique,
+ idKeyName,
+ idSeqInIndex,
+ idColumnName,
+ idCollation,
+ idCardinality,
+ idSubPart,
+ idPacked,
+ idNull,
+ idIndexType,
+ idComment,
+ idIndexComment,
+
+ idCount
+ };
+
+ MYSQL_RES* result;
+ MYSQL_ROW row;
+
+ fieldCount = 0;
+
+ if (connection->query("show index from %s", TableName()) != success)
+ {
+ connection->errorSql(getConnection(), "checkIndex()", 0);
+
+ return fail;
+ }
+
+ if ((result = mysql_store_result(connection->getMySql())))
+ {
+ while ((row = mysql_fetch_row(result)))
+ {
+ tell(5, "%s: %-20s %s %s",
+ row[idTable], row[idKeyName],
+ row[idSeqInIndex], row[idColumnName]);
+
+ if (strcasecmp(row[idKeyName], idxName) == 0)
+ fieldCount++;
+ }
+
+ mysql_free_result(result);
+
+ return success;
+ }
+
+ connection->errorSql(getConnection(), "checkIndex()");
+
+ return fail;
+}
+
+//***************************************************************************
+// Copy Values
+//***************************************************************************
+
+void cDbTable::copyValues(cDbRow* r, int typesFilter)
+{
+ std::map<std::string, cDbFieldDef*>::iterator f;
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ cDbFieldDef* fld = f->second;
+
+ if (r->isNull(fld)) // skip where source field is NULL
+ continue;
+
+ if (!(typesFilter & fld->getType())) // Filter
+ continue;
+
+ switch (fld->getFormat())
+ {
+ case ffAscii:
+ case ffText:
+ case ffMText:
+ case ffMlob:
+ row->setValue(fld, r->getStrValue(fld));
+ break;
+
+ case ffFloat:
+ row->setValue(fld, r->getFloatValue(fld));
+ break;
+
+ case ffDateTime:
+ row->setValue(fld, r->getTimeValue(fld));
+ break;
+
+ case ffBigInt:
+ case ffUBigInt:
+ row->setBigintValue(fld, r->getBigintValue(fld));
+ break;
+
+ case ffInt:
+ case ffUInt:
+ row->setValue(fld, r->getIntValue(fld));
+ break;
+
+ default:
+ tell(0, "Fatal unhandled field type %d", fld->getFormat());
+ }
+ }
+}
+
+//***************************************************************************
+// SQL Error
+//***************************************************************************
+
+int cDbConnection::errorSql(cDbConnection* connection, const char* prefix,
+ MYSQL_STMT* stmt, const char* stmtTxt)
+{
+ if (!connection || !connection->mysql)
+ {
+ tell(0, "SQL-Error in '%s'", prefix);
+ return fail;
+ }
+
+ int error = mysql_errno(connection->mysql);
+ char* conErr = 0;
+ char* stmtErr = 0;
+
+ if (error == CR_SERVER_LOST ||
+ error == CR_SERVER_GONE_ERROR ||
+ error == CR_INVALID_CONN_HANDLE ||
+ error == CR_COMMANDS_OUT_OF_SYNC ||
+ error == CR_SERVER_LOST_EXTENDED ||
+ error == CR_STMT_CLOSED ||
+ error == CR_CONN_UNKNOW_PROTOCOL ||
+ error == CR_UNSUPPORTED_PARAM_TYPE ||
+ error == CR_NO_PREPARE_STMT ||
+ error == CR_SERVER_HANDSHAKE_ERR ||
+ error == CR_WRONG_HOST_INFO ||
+ error == CR_OUT_OF_MEMORY ||
+ error == CR_IPSOCK_ERROR ||
+ error == CR_SOCKET_CREATE_ERROR ||
+ error == CR_CONNECTION_ERROR ||
+ error == CR_TCP_CONNECTION ||
+ error == CR_PARAMS_NOT_BOUND ||
+ error == CR_CONN_HOST_ERROR ||
+ error == CR_SSL_CONNECTION_ERROR
+
+ // to be continued - not all errors should result in a reconnect ...
+
+ )
+ {
+ connectDropped = yes;
+ }
+
+ if (error)
+ asprintf(&conErr, "%s (%d) ", mysql_error(connection->mysql), error);
+
+ if (stmt || stmtTxt)
+ asprintf(&stmtErr, "'%s' [%s]",
+ stmt ? mysql_stmt_error(stmt) : "",
+ stmtTxt ? stmtTxt : "");
+
+ tell(0, "SQL-Error in '%s' - %s%s", prefix,
+ conErr ? conErr : "", stmtErr ? stmtErr : "");
+
+ free(conErr);
+ free(stmtErr);
+
+ if (connectDropped)
+ tell(0, "Fatal, lost connection to mysql server, aborting pending actions");
+
+ return fail;
+}
+
+//***************************************************************************
+// Delete Where
+//***************************************************************************
+
+int cDbTable::deleteWhere(const char* where, ...)
+{
+ std::string stmt;
+ char* tmp;
+ va_list more;
+
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ va_start(more, where);
+ vasprintf(&tmp, where, more);
+
+ stmt = "delete from " + std::string(TableName()) + " where " + std::string(tmp);
+
+ free(tmp);
+
+ if (connection->query("%s", stmt.c_str()))
+ return connection->errorSql(connection, "deleteWhere()", 0, stmt.c_str());
+
+ return success;
+}
+
+//***************************************************************************
+// Coiunt Where
+//***************************************************************************
+
+int cDbTable::countWhere(const char* where, int& count, const char* what)
+{
+ std::string tmp;
+ MYSQL_RES* res;
+ MYSQL_ROW data;
+
+ count = 0;
+
+ if (isEmpty(what))
+ what = "count(1)";
+
+ if (!isEmpty(where))
+ tmp = "select " + std::string(what) + " from " + std::string(TableName()) + " where " + std::string(where);
+ else
+ tmp = "select " + std::string(what) + " from " + std::string(TableName());
+
+ if (connection->query("%s", tmp.c_str()))
+ return connection->errorSql(connection, "countWhere()", 0, tmp.c_str());
+
+ if ((res = mysql_store_result(connection->getMySql())))
+ {
+ data = mysql_fetch_row(res);
+
+ if (data)
+ count = atoi(data[0]);
+
+ mysql_free_result(res);
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Truncate
+//***************************************************************************
+
+int cDbTable::truncate()
+{
+ std::string tmp;
+
+ tmp = "delete from " + std::string(TableName());
+
+ if (connection->query("%s", tmp.c_str()))
+ return connection->errorSql(connection, "truncate()", 0, tmp.c_str());
+
+ return success;
+}
+
+
+//***************************************************************************
+// Store
+//***************************************************************************
+
+int cDbTable::store()
+{
+ int found;
+
+ // insert or just update ...
+
+ if (stmtSelect->execute(/*noResult =*/ yes) != success)
+ {
+ connection->errorSql(connection, "store()");
+ return no;
+ }
+
+ found = stmtSelect->getAffected() == 1;
+ stmtSelect->freeResult();
+
+ if (found)
+ return update();
+ else
+ return insert();
+}
+
+//***************************************************************************
+// Insert
+//***************************************************************************
+
+int cDbTable::insert(time_t inssp)
+{
+ std::map<std::string, cDbFieldDef*>::iterator f;
+ lastInsertId = na;
+
+ if (!stmtInsert)
+ {
+ tell(0, "Fatal missing insert statement\n");
+ return fail;
+ }
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ cDbFieldDef* fld = f->second;
+
+ if (strcasecmp(fld->getName(), "updsp") == 0 || strcasecmp(fld->getName(), "inssp") == 0)
+ setValue(fld, inssp ? inssp : time(0));
+
+ else if (getValue(fld)->isNull() && !isEmpty(fld->getDefault()))
+ setValue(fld, fld->getDefault());
+ }
+
+ if (stmtInsert->execute())
+ return fail;
+
+ lastInsertId = stmtInsert->getLastInsertId();
+
+ return stmtInsert->getAffected() == 1 ? success : fail;
+}
+
+//***************************************************************************
+// Update
+//***************************************************************************
+
+int cDbTable::update(time_t updsp)
+{
+ std::map<std::string, cDbFieldDef*>::iterator f;
+
+ if (!stmtUpdate)
+ {
+ tell(0, "Fatal missing update statement\n");
+ return fail;
+ }
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ {
+ cDbFieldDef* fld = f->second;
+
+ if (strcasecmp(fld->getName(), "updsp") == 0)
+ setValue(fld, updsp ? updsp : time(0));
+
+ else if (getValue(fld)->isNull() && !isEmpty(fld->getDefault()))
+ setValue(fld, fld->getDefault());
+ }
+
+ if (stmtUpdate->execute())
+ return fail;
+
+ return stmtUpdate->getAffected() == 1 ? success : fail;
+}
+
+//***************************************************************************
+// Find
+//***************************************************************************
+
+int cDbTable::find()
+{
+ if (!stmtSelect)
+ return no;
+
+ if (stmtSelect->execute() != success)
+ {
+ connection->errorSql(connection, "find()");
+ return no;
+ }
+
+ return stmtSelect->getAffected() == 1 ? yes : no;
+}
+
+//***************************************************************************
+// Find via Statement
+//***************************************************************************
+
+int cDbTable::find(cDbStatement* stmt)
+{
+ if (!stmt)
+ return no;
+
+ if (stmt->execute() != success)
+ {
+ connection->errorSql(connection, "find(stmt)");
+ return no;
+ }
+
+ return stmt->getAffected() > 0 ? yes : no;
+}
+
+//***************************************************************************
+// Fetch
+//***************************************************************************
+
+int cDbTable::fetch(cDbStatement* stmt)
+{
+ if (!stmt)
+ return no;
+
+ return stmt->fetch();
+}
+
+//***************************************************************************
+// Reset Fetch
+//***************************************************************************
+
+void cDbTable::reset(cDbStatement* stmt)
+{
+ if (stmt)
+ stmt->freeResult();
+}
diff --git a/lib/db.h b/lib/db.h
new file mode 100644
index 0000000..a93f840
--- /dev/null
+++ b/lib/db.h
@@ -0,0 +1,1367 @@
+/*
+ * db.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __DB_H
+#define __DB_H
+
+#include <linux/unistd.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <mysql/mysql.h>
+
+#include <list>
+
+#include "common.h"
+#include "dbdict.h"
+
+class cDbTable;
+class cDbConnection;
+
+//***************************************************************************
+// cDbValue
+//***************************************************************************
+
+class cDbValue : public cDbService
+{
+ public:
+
+ cDbValue(cDbFieldDef* f = 0)
+ {
+ field = 0;
+ strValue = 0;
+ ownField = 0;
+ changed = 0;
+
+ if (f) setField(f);
+ }
+
+ cDbValue(const char* name, FieldFormat format, int size)
+ {
+ strValue = 0;
+ changed = 0;
+ ownField = new cDbFieldDef(name, name, format, size, ftData, 0);
+
+ field = ownField;
+ strValue = (char*)calloc(field->getSize()+TB, sizeof(char));
+
+ clear();
+ }
+
+ virtual ~cDbValue()
+ {
+ free();
+ }
+
+ void free()
+ {
+ clear();
+ ::free(strValue);
+ strValue = 0;
+
+ if (ownField)
+ {
+ delete ownField;
+ ownField = 0;
+ }
+
+ field = 0;
+ }
+
+ void clear()
+ {
+ if (strValue)
+ *strValue = 0;
+
+ strValueSize = 0;
+ numValue = 0;
+ longlongValue = 0;
+ floatValue = 0;
+ memset(&timeValue, 0, sizeof(timeValue));
+
+ nullValue = 1;
+ changed = 0;
+ }
+
+ void clearChanged()
+ {
+ changed = 0;
+ }
+
+ virtual void setField(cDbFieldDef* f)
+ {
+ free();
+ field = f;
+
+ if (field)
+ strValue = (char*)calloc(field->getSize()+TB, sizeof(char));
+ }
+
+ virtual cDbFieldDef* getField() { return field; }
+ virtual const char* getName() { return field->getName(); }
+ virtual const char* getDbName() { return field->getDbName(); }
+
+ void setNull()
+ {
+ int c = changed;
+ int n = nullValue;
+
+ clear();
+ changed = c;
+
+ if (!n)
+ changed++;
+ }
+
+ void __attribute__ ((format(printf, 2, 3))) sPrintf(const char* format, ...)
+ {
+ va_list more;
+ char* buf = 0;
+
+ if (!format)
+ return ;
+
+ va_start(more, format);
+ vasprintf(&buf, format, more);
+
+ setValue(buf);
+
+ ::free(buf);
+ }
+
+ void setValue(const char* value, int size = 0)
+ {
+ int modified = no;
+
+ if (field->getFormat() != ffAscii && field->getFormat() != ffText &&
+ field->getFormat() != ffMText && field->getFormat() != ffMlob)
+ {
+ tell(0, "Setting invalid field format for '%s', expected ASCII, TEXT or MLOB",
+ field->getName());
+ return;
+ }
+
+ if (field->getFormat() == ffMlob && !size)
+ {
+ tell(0, "Missing size for MLOB field '%s'", field->getName());
+ return;
+ }
+
+ if (value && size)
+ {
+ if (size > field->getSize())
+ {
+ tell(0, "Warning, size of %d for '%s' exeeded, got %d bytes!",
+ field->getSize(), field->getName(), size);
+
+ size = field->getSize();
+ }
+
+ if (memcmp(strValue, value, size) != 0 || isNull())
+ modified = yes;
+
+ clear();
+ memcpy(strValue, value, size);
+ strValue[size] = 0;
+ strValueSize = size;
+ nullValue = 0;
+ }
+
+ else if (value)
+ {
+ if (strlen(value) > (size_t)field->getSize())
+ tell(0, "Warning, size of %d for '%s' exeeded (needed %ld) [%s]",
+ field->getSize(), field->getName(), (long)strlen(value), value);
+
+ if (strncmp(strValue, value, strlen(value)) != 0 || isNull())
+ modified = yes;
+
+ clear();
+ sprintf(strValue, "%.*s", field->getSize(), value);
+ strValueSize = strlen(strValue);
+ nullValue = 0;
+ }
+
+ if (modified) // increment changed after calling clear()
+ changed++;
+ }
+
+ void setCharValue(char value)
+ {
+ char tmp[2] = "";
+ tmp[0] = value;
+ tmp[1] = 0;
+ setValue(tmp);
+ }
+
+ void setValue(int value)
+ {
+ setValue((long)value);
+ }
+
+ void setValue(long value)
+ {
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ {
+ if (numValue != value || isNull())
+ changed++;
+
+ numValue = value;
+ nullValue = 0;
+ }
+ else if (field->getFormat() == ffDateTime)
+ {
+ struct tm tm;
+ time_t v = value;
+ time_t o = getTimeValue();
+
+ memset(&tm, 0, sizeof(tm));
+ localtime_r(&v, &tm);
+
+ timeValue.year = tm.tm_year + 1900;
+ timeValue.month = tm.tm_mon + 1;
+ timeValue.day = tm.tm_mday;
+
+ timeValue.hour = tm.tm_hour;
+ timeValue.minute = tm.tm_min;
+ timeValue.second = tm.tm_sec;
+
+ nullValue = 0;
+
+ if (o != getTimeValue())
+ changed++;
+ }
+ else
+ {
+ tell(0, "Setting invalid field format for '%s'", field->getName());
+ }
+ }
+
+ void setValue(double value)
+ {
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ {
+ if (numValue != value || isNull())
+ changed++;
+
+ numValue = value;
+ nullValue = 0;
+ }
+ else if (field->getFormat() == ffBigInt || field->getFormat() == ffUBigInt)
+ {
+ if (longlongValue != value || isNull())
+ changed++;
+
+ longlongValue = value;
+ nullValue = 0;
+ }
+ else if (field->getFormat() == ffFloat)
+ {
+ if (floatValue != value || isNull())
+ changed++;
+
+ floatValue = value;
+ nullValue = 0;
+ }
+ else
+ {
+ tell(0, "Setting invalid field format for '%s'", field->getName());
+ }
+ }
+
+ void setBigintValue(int64_t value)
+ {
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ {
+ if (numValue != value)
+ changed++;
+
+ numValue = value;
+ nullValue = 0;
+ }
+
+ else if (field->getFormat() == ffBigInt || field->getFormat() == ffUBigInt)
+ {
+ if (longlongValue != value)
+ changed++;
+
+ longlongValue = value;
+ nullValue = 0;
+ }
+ }
+
+ int hasValue(long value)
+ {
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ return numValue == value;
+
+ if (field->getFormat() == ffDateTime)
+ return no; // to be implemented!
+
+ tell(0, "Setting invalid field format for '%s'", field->getName());
+
+ return no;
+ }
+
+ int hasValue(double value)
+ {
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ return numValue == value;
+
+ if (field->getFormat() == ffBigInt || field->getFormat() == ffUBigInt)
+ return longlongValue == value;
+
+ if (field->getFormat() == ffFloat)
+ return floatValue == value;
+
+ tell(0, "Setting invalid field format for '%s'", field->getName());
+
+ return no;
+ }
+
+ int hasValue(const char* value)
+ {
+ if (!value)
+ value = "";
+
+ if (field->getFormat() != ffAscii && field->getFormat() != ffText &&
+ field->getFormat() != ffMText && field->getFormat() != ffMlob)
+ {
+ tell(0, "Checking invalid field format for '%s', expected ASCII or TEXT",
+ field->getName());
+ return no;
+ }
+
+ return strcmp(getStrValue(), value) == 0;
+ }
+
+ int hasCharValue(char value)
+ {
+ if (field->getFormat() != ffAscii)
+ {
+ tell(0, "Checking invalid field format for '%s', expected ASCII or TEXT",
+ field->getName());
+ return no;
+ }
+
+ return getStrValueSize() == 1 && toupper(getCharValue()) == toupper(value);
+ }
+
+ time_t getTimeValue()
+ {
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+
+ tm.tm_isdst = -1; // force DST auto detect
+ tm.tm_year = timeValue.year - 1900;
+ tm.tm_mon = timeValue.month - 1;
+ tm.tm_mday = timeValue.day;
+
+ tm.tm_hour = timeValue.hour;
+ tm.tm_min = timeValue.minute;
+ tm.tm_sec = timeValue.second;
+
+ return mktime(&tm);
+ }
+
+ unsigned long* getStrValueSizeRef() { return &strValueSize; }
+ unsigned long getStrValueSize() { return strValueSize; }
+ const char* getStrValue() { return !isNull() && strValue ? strValue : ""; }
+ char getCharValue() { return !isNull() && strValue ? strValue[0] : 0; }
+ long getIntValue() { return !isNull() ? numValue : 0; }
+
+ int64_t getBigintValue()
+ {
+ if (isNull())
+ return 0;
+
+ if (field->getFormat() == ffBigInt || field->getFormat() == ffUBigInt)
+ return longlongValue;
+
+ return numValue;
+ }
+
+ float getFloatValue() { return !isNull() ? floatValue : 0; }
+ int isNull() { return nullValue; }
+ int getChanges() { return changed; }
+
+ int isEmpty()
+ {
+ if (isNull())
+ return yes;
+
+ if (field->getFormat() == ffInt || field->getFormat() == ffUInt)
+ return numValue == 0;
+ else if (field->getFormat() == ffDateTime)
+ return no;
+ else if (field->getFormat() == ffAscii || field->getFormat() == ffText ||
+ field->getFormat() == ffMText || field->getFormat() == ffMlob)
+ return ::isEmpty(strValue);
+ else if (field->getFormat() == ffFloat)
+ return floatValue == 0;
+
+ return no;
+ }
+
+ char* getStrValueRef() { return strValue; }
+ long* getIntValueRef() { return &numValue; }
+ int64_t* getBigIntValueRef() { return &longlongValue; }
+ MYSQL_TIME* getTimeValueRef() { return &timeValue; }
+ float* getFloatValueRef() { return &floatValue; }
+ my_bool* getNullRef() { return &nullValue; }
+
+ private:
+
+ cDbFieldDef* ownField;
+ cDbFieldDef* field;
+ long numValue;
+ int64_t longlongValue;
+ float floatValue;
+ MYSQL_TIME timeValue;
+ char* strValue;
+ unsigned long strValueSize;
+ my_bool nullValue;
+ int changed;
+};
+
+//***************************************************************************
+// cDbStatement
+//***************************************************************************
+
+class cDbStatement : public cDbService
+{
+ public:
+
+ cDbStatement(cDbTable* aTable);
+ cDbStatement(cDbConnection* aConnection, const char* sText = "");
+ virtual ~cDbStatement();
+
+ int execute(int noResult = no);
+ int find();
+ int fetch();
+ int freeResult();
+ void clear();
+
+ // interface
+
+ virtual int __attribute__ ((format(printf, 2, 3))) build(const char* format, ...);
+
+ void setBindPrefix(const char* p) { bindPrefix = p; }
+ void clrBindPrefix() { bindPrefix = 0; }
+ int bind(const char* fname, int mode, const char* delim = 0);
+ int bind(cDbValue* value, int mode, const char* delim = 0);
+ int bind(cDbTable* aTable, cDbFieldDef* field, int mode, const char* delim);
+ int bind(cDbTable* aTable, const char* fname, int mode, const char* delim);
+ int bind(cDbFieldDef* field, int mode, const char* delim = 0);
+ int bindAllOut(const char* delim = 0);
+
+ int bindCmp(const char* ctable, cDbValue* value,
+ const char* comp, const char* delim = 0);
+ int bindCmp(const char* ctable, cDbFieldDef* field, cDbValue* value,
+ const char* comp, const char* delim = 0);
+ int bindCmp(const char* ctable, const char* fname, cDbValue* value,
+ const char* comp, const char* delim = 0);
+ int bindText(const char* text, cDbValue* value,
+ const char* comp, const char* delim = 0);
+ int bindTextFree(const char* text, cDbValue* value, int mode = bndIn);
+
+ int bindInChar(const char* ctable, const char* fname,
+ cDbValue* value = 0, const char* delim = 0);
+
+ int appendBinding(cDbValue* value, BindType bt); // use this interface method seldom from external and with care!
+
+ // ..
+
+ int prepare();
+ int getAffected() { return affected; }
+ int getResultCount();
+ int getLastInsertId();
+ const char* asText() { return stmtTxt.c_str(); }
+ cDbTable* getTable() { return table; }
+ void showStat();
+
+ // data
+
+ static int explain; // debug explain
+
+ private:
+
+ std::string stmtTxt;
+ MYSQL_STMT* stmt;
+ int affected;
+ cDbConnection* connection;
+ cDbTable* table;
+ int inCount;
+ MYSQL_BIND* inBind; // to db
+ int outCount;
+ MYSQL_BIND* outBind; // from db (result)
+ MYSQL_RES* metaResult;
+ const char* bindPrefix;
+ int firstExec; // debug explain
+ int buildErrors;
+
+ unsigned long callsPeriod;
+ unsigned long callsTotal;
+ double duration;
+};
+
+//***************************************************************************
+// cDbStatements
+//***************************************************************************
+
+class cDbStatements
+{
+ public:
+
+ cDbStatements() { statisticPeriod = time(0); }
+ ~cDbStatements() {};
+
+ void append(cDbStatement* s) { statements.push_back(s); }
+ void remove(cDbStatement* s) { statements.remove(s); }
+
+ void showStat(const char* name)
+ {
+ tell(0, "Statement statistic of last %ld seconds from '%s':", time(0) - statisticPeriod, name);
+
+ for (std::list<cDbStatement*>::iterator it = statements.begin() ; it != statements.end(); ++it)
+ {
+ if (*it)
+ (*it)->showStat();
+ }
+
+ statisticPeriod = time(0);
+ }
+
+ private:
+
+ time_t statisticPeriod;
+ std::list<cDbStatement*> statements;
+};
+
+//***************************************************************************
+// Class Database Row
+//***************************************************************************
+
+#define GET_FIELD(name) \
+ cDbFieldDef* f = tableDef->getField(name); \
+ if (!f) \
+ { \
+ tell(0, "Fatal: Field '%s.%s' not defined (missing in dictionary)", tableDef->getName(), name); \
+ return ; \
+ } \
+
+#define GET_FIELD_RES(name, def) \
+ cDbFieldDef* f = tableDef->getField(name); \
+ if (!f) \
+ { \
+ tell(0, "Fatal: Field '%s.%s' not defined (missing in dictionary)", tableDef->getName(), name); \
+ return def; \
+ } \
+
+class cDbRow : public cDbService
+{
+ public:
+
+ cDbRow(cDbTableDef* t)
+ {
+ set(t);
+ }
+
+ cDbRow(const char* name)
+ {
+ cDbTableDef* t = dbDict.getTable(name);
+
+ if (t)
+ set(t);
+ else
+ tell(0, "Fatal: Table '%s' missing in dictionary '%s'!", name, dbDict.getPath());
+ }
+
+ virtual ~cDbRow() { delete[] dbValues; }
+
+ void set(cDbTableDef* t)
+ {
+ std::map<std::string, cDbFieldDef*>::iterator f;
+
+ tableDef = t;
+ dbValues = new cDbValue[tableDef->fieldCount()];
+
+ for (f = tableDef->dfields.begin(); f != tableDef->dfields.end(); f++)
+ dbValues[f->second->getIndex()].setField(f->second);
+ }
+
+ void clear()
+ {
+ for (int f = 0; f < tableDef->fieldCount(); f++)
+ dbValues[f].clear();
+ }
+
+ void clearChanged()
+ {
+ for (int f = 0; f < tableDef->fieldCount(); f++)
+ dbValues[f].clearChanged();
+ }
+
+ int getChanges()
+ {
+ int count = 0;
+
+ for (int f = 0; f < tableDef->fieldCount(); f++)
+ count += dbValues[f].getChanges();
+
+ return count;
+ }
+
+ std::string getChangedFields()
+ {
+ std::string s = "";
+
+ for (int f = 0; f < tableDef->fieldCount(); f++)
+ {
+ if (dbValues[f].getChanges())
+ {
+ if (s.length())
+ s += ",";
+
+ s += dbValues[f].getName() + std::string("=");
+
+ if (dbValues[f].getField()->hasFormat(ffInt) || dbValues[f].getField()->hasFormat(ffUInt))
+ s += num2Str(dbValues[f].getIntValue());
+ else
+ s += dbValues[f].getStrValue();
+ }
+ }
+
+ return s;
+ }
+
+ virtual cDbFieldDef* getField(int id) { return tableDef->getField(id); }
+ virtual cDbFieldDef* getField(const char* name) { return tableDef->getField(name); }
+ virtual cDbFieldDef* getFieldByDbName(const char* dbname) { return tableDef->getFieldByDbName(dbname); }
+ virtual int fieldCount() { return tableDef->fieldCount(); }
+
+ void setValue(cDbFieldDef* f, const char* value,
+ int size = 0) { dbValues[f->getIndex()].setValue(value, size); }
+ void setValue(cDbFieldDef* f, int value) { dbValues[f->getIndex()].setValue(value); }
+ void setValue(cDbFieldDef* f, long value) { dbValues[f->getIndex()].setValue(value); }
+ void setValue(cDbFieldDef* f, double value) { dbValues[f->getIndex()].setValue(value); }
+ void setBigintValue(cDbFieldDef* f, int64_t value) { dbValues[f->getIndex()].setBigintValue(value); }
+ void setCharValue(cDbFieldDef* f, char value) { dbValues[f->getIndex()].setCharValue(value); }
+
+ void setValue(const char* n, const char* value,
+ int size = 0) { GET_FIELD(n); dbValues[f->getIndex()].setValue(value, size); }
+ void setValue(const char* n, int value) { GET_FIELD(n); dbValues[f->getIndex()].setValue(value); }
+ void setValue(const char* n, long value) { GET_FIELD(n); dbValues[f->getIndex()].setValue(value); }
+ void setValue(const char* n, double value) { GET_FIELD(n); dbValues[f->getIndex()].setValue(value); }
+ void setBigintValue(const char* n, int64_t value) { GET_FIELD(n); dbValues[f->getIndex()].setBigintValue(value); }
+ void setCharValue(const char* n, char value) { GET_FIELD(n); dbValues[f->getIndex()].setCharValue(value); }
+
+ int hasValue(cDbFieldDef* f, const char* value) const { return dbValues[f->getIndex()].hasValue(value); }
+ int hasCharValue(cDbFieldDef* f, char value) const { return dbValues[f->getIndex()].hasCharValue(value); }
+ int hasValue(cDbFieldDef* f, long value) const { return dbValues[f->getIndex()].hasValue(value); }
+ int hasValue(cDbFieldDef* f, double value) const { return dbValues[f->getIndex()].hasValue(value); }
+
+ int hasValue(const char* n, const char* value) const { GET_FIELD_RES(n, no); return dbValues[f->getIndex()].hasValue(value); }
+ int hasCharValue(const char* n, char value) const { GET_FIELD_RES(n, no); return dbValues[f->getIndex()].hasCharValue(value); }
+ int hasValue(const char* n, long value) const { GET_FIELD_RES(n, no); return dbValues[f->getIndex()].hasValue(value); }
+ int hasValue(const char* n, double value) const { GET_FIELD_RES(n, no); return dbValues[f->getIndex()].hasValue(value); }
+
+ cDbValue* getValue(cDbFieldDef* f) { return &dbValues[f->getIndex()]; }
+ cDbValue* getValue(const char* n) { GET_FIELD_RES(n, 0); return &dbValues[f->getIndex()]; }
+
+ time_t getTimeValue(cDbFieldDef* f) const { return dbValues[f->getIndex()].getTimeValue(); }
+ const char* getStrValue(cDbFieldDef* f) const { return dbValues[f->getIndex()].getStrValue(); }
+ long getIntValue(cDbFieldDef* f) const { return dbValues[f->getIndex()].getIntValue(); }
+ int64_t getBigintValue(cDbFieldDef* f) const { return dbValues[f->getIndex()].getBigintValue(); }
+ float getFloatValue(cDbFieldDef* f) const { return dbValues[f->getIndex()].getFloatValue(); }
+ int isNull(cDbFieldDef* f) const { return dbValues[f->getIndex()].isNull(); }
+
+ const char* getStrValue(const char* n) const { GET_FIELD_RES(n, ""); return dbValues[f->getIndex()].getStrValue(); }
+ long getIntValue(const char* n) const { GET_FIELD_RES(n, 0); return dbValues[f->getIndex()].getIntValue(); }
+ int64_t getBigintValue(const char* n) const { GET_FIELD_RES(n, 0); return dbValues[f->getIndex()].getBigintValue(); }
+ float getFloatValue(const char* n) const { GET_FIELD_RES(n, 0); return dbValues[f->getIndex()].getFloatValue(); }
+ int isNull(const char* n) const { GET_FIELD_RES(n, yes); return dbValues[f->getIndex()].isNull(); }
+
+ cDbTableDef* getTableDef() { return tableDef; }
+
+ protected:
+
+ cDbTableDef* tableDef;
+ cDbValue* dbValues;
+};
+
+//***************************************************************************
+// Connection
+//***************************************************************************
+
+class cDbConnection
+{
+ public:
+
+ cDbConnection()
+ {
+ mysql = 0;
+ attached = 0;
+ inTact = no;
+ connectDropped = yes;
+ }
+
+ virtual ~cDbConnection()
+ {
+ close();
+ }
+
+ int isConnected() { return getMySql() != 0; }
+
+ int attachConnection()
+ {
+ static int first = yes;
+
+ if (!mysql)
+ {
+ connectDropped = yes;
+
+ tell(0, "Calling mysql_init(%ld)", syscall(__NR_gettid));
+
+ if (!(mysql = mysql_init(0)))
+ return errorSql(this, "attachConnection(init)");
+
+ if (!mysql_real_connect(mysql, dbHost, dbUser, dbPass, dbName, dbPort, 0, 0))
+ {
+ errorSql(this, "connecting to database");
+ tell(0, "Error, connecting to database at '%s' on port (%d) failed", dbHost, dbPort);
+ close();
+ return fail;
+ }
+
+ connectDropped = no;
+
+ // init encoding
+
+ if (encoding && *encoding)
+ {
+ if (mysql_set_character_set(mysql, encoding))
+ errorSql(this, "init(character_set)");
+
+ if (first)
+ {
+ tell(0, "SQL client character now '%s'", mysql_character_set_name(mysql));
+ first = no;
+ }
+ }
+ }
+
+ attached++;
+
+ return success;
+ }
+
+ void detachConnection()
+ {
+ attached--;
+
+ if (!attached)
+ close();
+ }
+
+ void close()
+ {
+ if (mysql)
+ {
+ tell(0, "Closing mysql connection and calling mysql_thread_end(%ld)", syscall(__NR_gettid));
+
+ mysql_close(mysql);
+ mysql_thread_end();
+ mysql = 0;
+ attached = 0;
+ }
+ }
+
+ int check()
+ {
+ if (!isConnected())
+ return fail;
+
+ query("SELECT SYSDATE();");
+ queryReset();
+
+ return isConnected() ? success : fail;
+ }
+
+ virtual int __attribute__ ((format(printf, 2, 3))) query(const char* format, ...)
+ {
+ va_list more;
+
+ if (!format)
+ return fail;
+
+ va_start(more, format);
+
+ return vquery(format, more);
+ }
+
+ virtual int __attribute__ ((format(printf, 3, 4))) query(int& count, const char* format, ...)
+ {
+ int status;
+ va_list more;
+
+ count = 0;
+
+ if (!format)
+ return fail;
+
+ va_start(more, format);
+
+ if ((status = vquery(format, more)) == success)
+ {
+ MYSQL_RES* res;
+ MYSQL_ROW data;
+
+ // get affected rows ..
+
+ if ((res = mysql_store_result(getMySql())))
+ {
+ data = mysql_fetch_row(res);
+
+ if (data)
+ count = atoi(data[0]);
+
+ mysql_free_result(res);
+ }
+ }
+
+ return status;
+ }
+
+ virtual int vquery(const char* format, va_list more)
+ {
+ int status = 1;
+ MYSQL* h = getMySql();
+
+ if (h && format)
+ {
+ char* stmt;
+
+ vasprintf(&stmt, format, more);
+
+ if ((status = mysql_query(h, stmt)))
+ errorSql(this, stmt);
+
+ free(stmt);
+ }
+
+ return status ? fail : success;
+ }
+
+ virtual void queryReset()
+ {
+ if (getMySql())
+ {
+ MYSQL_RES* result = mysql_use_result(getMySql());
+ mysql_free_result(result);
+ }
+ }
+
+ // escapeSqlString - only need to be used in string statements not in bind values!!
+
+ virtual std::string escapeSqlString(const char* str)
+ {
+ std::string result = "";
+
+ if (!isConnected())
+ return result;
+
+ int length = strlen(str);
+ int bufferSize = length*2 + TB;
+
+ char* buffer = (char*)malloc(bufferSize);
+ mysql_real_escape_string(getMySql(), buffer, str, length);
+ result = buffer;
+ free(buffer);
+
+ return result;
+ }
+
+ virtual int executeSqlFile(const char* file)
+ {
+ FILE* f;
+ int res;
+ char* buffer;
+ int size = 1000;
+ int nread = 0;
+
+ if (!getMySql())
+ return fail;
+
+ if (!(f = fopen(file, "r")))
+ {
+ tell(0, "Fatal: Can't execute sql file '%s'; Error was '%s'", file, strerror(errno));
+ return fail;
+ }
+
+ buffer = (char*)malloc(size+1);
+
+ while ((res = fread(buffer+nread, 1, 1000, f)))
+ {
+ nread += res;
+ size += 1000;
+ buffer = srealloc(buffer, size+1);
+ }
+
+ fclose(f);
+ buffer[nread] = 0;
+
+ // execute statement
+
+ tell(2, "Executing '%s'", buffer);
+
+ if (query("%s", buffer))
+ {
+ free(buffer);
+ return errorSql(this, "executeSqlFile()");
+ }
+
+ free(buffer);
+
+ return success;
+ }
+
+ virtual int startTransaction()
+ {
+ inTact = yes;
+ return query("START TRANSACTION");
+ }
+
+ virtual int commit()
+ {
+ inTact = no;
+ return query("COMMIT");
+ }
+
+ virtual int rollback()
+ {
+ inTact = no;
+ return query("ROLLBACK");
+ }
+
+ virtual int inTransaction() { return inTact; }
+
+ MYSQL* getMySql()
+ {
+ if (connectDropped)
+ close();
+
+ return mysql;
+ }
+
+ int getAttachedCount() { return attached; }
+ void showStat(const char* name = "") { statements.showStat(name); }
+ int errorSql(cDbConnection* mysql, const char* prefix, MYSQL_STMT* stmt = 0, const char* stmtTxt = 0);
+
+ // data
+
+ cDbStatements statements; // all statements of this connection
+
+ // --------------
+ // static stuff
+
+ // set/get connecting data
+
+ static void setHost(const char* s) { free(dbHost); dbHost = strdup(s); }
+ static const char* getHost() { return dbHost; }
+ static void setName(const char* s) { free(dbName); dbName = strdup(s); }
+ static const char* getName() { return dbName; }
+ static void setUser(const char* s) { free(dbUser); dbUser = strdup(s); }
+ static const char* getUser() { return dbUser; }
+ static void setPass(const char* s) { free(dbPass); dbPass = strdup(s); }
+ static const char* getPass() { return dbPass; }
+ static void setPort(int port) { dbPort = port; }
+ static int getPort() { return dbPort; }
+ static void setEncoding(const char* enc) { free(encoding); encoding = strdup(enc); }
+ static const char* getEncoding() { return encoding; }
+ static void setConfPath(const char* cpath) { free(confPath); confPath = strdup(cpath); }
+ static const char* getConfPath() { return confPath; }
+
+ // -----------------------------------------------------------
+ // init() and exit() must exactly called 'once' per process
+
+ static int init()
+ {
+ int status = success;
+
+ initMutex.Lock();
+
+ if (!initThreads)
+ {
+ tell(1, "Info: Calling mysql_library_init()");
+
+ if (mysql_library_init(0, 0, 0))
+ {
+ tell(0, "Error: mysql_library_init() failed");
+ status = fail;
+ }
+ }
+ else
+ {
+ tell(1, "Info: Skipping calling mysql_library_init(), it's already done!");
+ }
+
+ initThreads++;
+ initMutex.Unlock();
+
+ return status;
+ }
+
+ static int exit()
+ {
+ initMutex.Lock();
+
+ initThreads--;
+
+ if (!initThreads)
+ {
+ tell(1, "Info: Released the last usage of mysql_lib, calling mysql_library_end() now");
+ mysql_library_end();
+
+ free(dbHost);
+ free(dbUser);
+ free(dbPass);
+ free(dbName);
+ free(encoding);
+ free(confPath);
+ }
+ else
+ {
+ tell(1, "Info: The mysql_lib is still in use, skipping mysql_library_end() call");
+ }
+
+ initMutex.Unlock();
+
+ return done;
+ }
+
+ private:
+
+ MYSQL* mysql;
+
+ int initialized;
+ int attached;
+ int inTact;
+ int connectDropped;
+
+ static cMyMutex initMutex;
+ static int initThreads;
+
+ static char* encoding;
+ static char* confPath;
+
+ // connecting data
+
+ static char* dbHost;
+ static int dbPort;
+ static char* dbName; // database name
+ static char* dbUser;
+ static char* dbPass;
+};
+
+//***************************************************************************
+// cDbTable
+//***************************************************************************
+
+class cDbTable : public cDbService
+{
+ public:
+
+ cDbTable(cDbConnection* aConnection, const char* name);
+ virtual ~cDbTable();
+
+ virtual const char* TableName() { return tableDef ? tableDef->getName() : "<unknown>"; }
+ virtual int fieldCount() { return tableDef->fieldCount(); }
+ cDbFieldDef* getField(int f) { return tableDef->getField(f); }
+ cDbFieldDef* getField(const char* name) { return tableDef->getField(name); }
+
+ virtual int open(int allowAlter = 0); // 0 - off, 1 - on, 2 on with allow drop unused columns
+ virtual int close();
+ virtual int attach();
+ virtual int detach();
+ int isAttached() { return attached; }
+
+ virtual int find();
+ virtual void reset() { reset(stmtSelect); }
+
+ virtual int find(cDbStatement* stmt);
+ virtual int fetch(cDbStatement* stmt);
+ virtual void reset(cDbStatement* stmt);
+
+ virtual int insert(time_t inssp = 0);
+ virtual int update(time_t updsp = 0);
+ virtual int store();
+
+ virtual int __attribute__ ((format(printf, 2, 3))) deleteWhere(const char* where, ...);
+ virtual int countWhere(const char* where, int& count, const char* what = 0);
+ virtual int truncate();
+
+ // interface to cDbRow
+
+ void clear() { row->clear(); }
+ void clearChanged() { row->clearChanged(); }
+ int getChanges() { return row->getChanges(); }
+ std::string getChangedFields() { return row->getChangedFields(); }
+ void setValue(cDbFieldDef* f, const char* value, int size = 0) { row->setValue(f, value, size); }
+ void setValue(cDbFieldDef* f, int value) { row->setValue(f, value); }
+ void setValue(cDbFieldDef* f, long value) { row->setValue(f, value); }
+ void setValue(cDbFieldDef* f, double value) { row->setValue(f, value); }
+ void setBigintValue(cDbFieldDef* f, int64_t value) { row->setBigintValue(f, value); }
+ void setCharValue(cDbFieldDef* f, char value) { row->setCharValue(f, value); }
+
+ void setValue(const char* n, const char* value, int size = 0) { row->setValue(n, value, size); }
+ void setValue(const char* n, int value) { row->setValue(n, value); }
+ void setValue(const char* n, long value) { row->setValue(n, value); }
+ void setValue(const char* n, double value) { row->setValue(n, value); }
+ void setBigintValue(const char* n, int64_t value) { row->setBigintValue(n, value); }
+ void setCharValue(const char* n, char value) { row->setCharValue(n, value); }
+
+ void copyValues(cDbRow* r, int types = ftData);
+
+ int hasValue(cDbFieldDef* f, const char* value) { return row->hasValue(f, value); }
+ int hasCharValue(cDbFieldDef* f, char value) { return row->hasCharValue(f, value); }
+ int hasValue(cDbFieldDef* f, long value) { return row->hasValue(f, value); }
+ int hasValue(cDbFieldDef* f, double value) { return row->hasValue(f, value); }
+
+ int hasValue(const char* n, const char* value) { return row->hasValue(n, value); }
+ int hasCharValue(const char* n, char value) { return row->hasCharValue(n, value); }
+ int hasValue(const char* n, long value) { return row->hasValue(n, value); }
+ int hasValue(const char* n, double value) { return row->hasValue(n, value); }
+
+ const char* getStrValue(cDbFieldDef* f) const { return row->getStrValue(f); }
+ long getIntValue(cDbFieldDef* f) const { return row->getIntValue(f); }
+ int64_t getBigintValue(cDbFieldDef* f) const { return row->getBigintValue(f); }
+ float getFloatValue(cDbFieldDef* f) const { return row->getFloatValue(f); }
+ int isNull(cDbFieldDef* f) const { return row->isNull(f); }
+
+ const char* getStrValue(const char* n) const { return row->getStrValue(n); }
+ long getIntValue(const char* n) const { return row->getIntValue(n); }
+ int64_t getBigintValue(const char* n) const { return row->getBigintValue(n); }
+ float getFloatValue(const char* n) const { return row->getFloatValue(n); }
+ int isNull(const char* n) const { return row->isNull(n); }
+
+ cDbValue* getValue(cDbFieldDef* f) { return row->getValue(f); }
+ cDbValue* getValue(const char* fname) { return row->getValue(fname); }
+ int init(cDbValue*& dbvalue, const char* fname) { dbvalue = row->getValue(fname); return dbvalue ? success : fail; }
+ cDbRow* getRow() { return row; }
+
+ cDbTableDef* getTableDef() { return tableDef; }
+ cDbConnection* getConnection() { return connection; }
+ MYSQL* getMySql() { return connection->getMySql(); }
+ int isConnected() { return connection && connection->getMySql(); }
+
+ int getLastInsertId() { return lastInsertId; }
+
+ virtual int exist(const char* name = 0);
+ virtual int validateStructure(int allowAlter = 1); // 0 - off, 1 - on, 2 on with allow drop unused columns
+ virtual int createTable();
+ virtual int createIndices();
+
+ protected:
+
+ virtual int init(int allowAlter = 0); // 0 - off, 1 - on, 2 on with allow drop unused columns
+ virtual int checkIndex(const char* idxName, int& fieldCount);
+ virtual int alterModifyField(cDbFieldDef* def);
+ virtual int alterAddField(cDbFieldDef* def);
+ virtual int alterDropField(const char* name);
+
+ // data
+
+ cDbRow* row;
+ int holdInMemory; // hold table additionally in memory (not implemented yet)
+ int attached;
+ int lastInsertId;
+
+ cDbConnection* connection;
+ cDbTableDef* tableDef;
+
+ // basic statements
+
+ cDbStatement* stmtSelect;
+ cDbStatement* stmtInsert;
+ cDbStatement* stmtUpdate;
+};
+
+//***************************************************************************
+// cDbView
+//***************************************************************************
+
+class cDbView : public cDbService
+{
+ public:
+
+ cDbView(cDbConnection* c, const char* aName)
+ {
+ connection = c;
+ name = strdup(aName);
+ }
+
+ ~cDbView() { free(name); }
+
+ int exist()
+ {
+ if (connection->getMySql())
+ {
+ MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name);
+ MYSQL_ROW tabRow = mysql_fetch_row(result);
+ mysql_free_result(result);
+
+ return tabRow ? yes : no;
+ }
+
+ return no;
+ }
+
+ int create(const char* path, const char* sqlFile)
+ {
+ int status;
+ char* file = 0;
+
+ asprintf(&file, "%s/%s", path, sqlFile);
+
+ tell(0, "Creating view '%s' using definition in '%s'",
+ name, file);
+
+ status = connection->executeSqlFile(file);
+
+ free(file);
+
+ return status;
+ }
+
+ int drop()
+ {
+ tell(0, "Drop view '%s'", name);
+
+ return connection->query("drop view %s", name);
+ }
+
+ protected:
+
+ cDbConnection* connection;
+ char* name;
+};
+
+//***************************************************************************
+// cDbProcedure
+//***************************************************************************
+
+class cDbProcedure : public cDbService
+{
+ public:
+
+ cDbProcedure(cDbConnection* c, const char* aName, ProcType pt = ptProcedure)
+ {
+ connection = c;
+ type = pt;
+ name = strdup(aName);
+ }
+
+ ~cDbProcedure() { free(name); }
+
+ const char* getName() { return name; }
+
+ int call(int ll = 1)
+ {
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ cDbStatement stmt(connection);
+
+ tell(ll, "Calling '%s'", name);
+
+ stmt.build("call %s", name);
+
+ if (stmt.prepare() != success || stmt.execute() != success)
+ return fail;
+
+ tell(ll, "'%s' suceeded", name);
+
+ return success;
+ }
+
+ int created()
+ {
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ cDbStatement stmt(connection);
+
+ stmt.build("show %s status where name = '%s'",
+ type == ptProcedure ? "procedure" : "function", name);
+
+ if (stmt.prepare() != success || stmt.execute() != success)
+ {
+ tell(0, "%s check of '%s' failed",
+ type == ptProcedure ? "Procedure" : "Function", name);
+ return no;
+ }
+ else
+ {
+ if (stmt.getResultCount() != 1)
+ return no;
+ }
+
+ return yes;
+ }
+
+ int create(const char* path)
+ {
+ int status;
+ char* file = 0;
+
+ asprintf(&file, "%s/%s.sql", path, name);
+
+ tell(1, "Creating %s '%s'",
+ type == ptProcedure ? "procedure" : "function", name);
+
+ status = connection->executeSqlFile(file);
+
+ free(file);
+
+ return status;
+ }
+
+ int drop()
+ {
+ tell(1, "Drop %s '%s'", type == ptProcedure ? "procedure" : "function", name);
+
+ return connection->query("drop %s %s", type == ptProcedure ? "procedure" : "function", name);
+ }
+
+ static int existOnFs(const char* path, const char* name)
+ {
+ int state;
+ char* file = 0;
+
+ asprintf(&file, "%s/%s.sql", path, name);
+ state = fileExists(file);
+
+ free(file);
+
+ return state;
+ }
+
+ protected:
+
+ cDbConnection* connection;
+ ProcType type;
+ char* name;
+
+};
+
+//***************************************************************************
+#endif //__DB_H
diff --git a/lib/dbdict.c b/lib/dbdict.c
new file mode 100644
index 0000000..1052a13
--- /dev/null
+++ b/lib/dbdict.c
@@ -0,0 +1,527 @@
+/*
+ * dbdict.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "errno.h"
+
+#include "common.h"
+#include "dbdict.h"
+
+//***************************************************************************
+// Get Token
+//***************************************************************************
+
+int getToken(const char*& p, char* token, int size, char delimiter = ' ', char end = ',')
+{
+ char* dest = token;
+ int num = 0;
+ int insideStr = no;
+
+ while (*p && (*p == ' ' || *p == delimiter))
+ p++;
+
+ if (*p == end || isEmpty(p))
+ return fail;
+
+ while (*p && num < size && !((*p == delimiter || *p == end) && !insideStr))
+ {
+ if (*p == '"')
+ {
+ insideStr = !insideStr;
+ p++;
+ }
+ else
+ {
+ *dest++ = *p++;
+ num++;
+ }
+ }
+
+ *dest = 0;
+
+ return success;
+}
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+const char* cDbService::formats[] =
+{
+ "INT",
+ "INT",
+ "BIGINT",
+ "BIGINT",
+ "VARCHAR",
+ "TEXT",
+ "MEDIUMTEXT",
+ "MEDIUMBLOB",
+ "FLOAT",
+ "DATETIME",
+
+ 0
+};
+
+const char* cDbService::toString(FieldFormat t)
+{
+ return formats[t];
+}
+
+const char* cDbService::dictFormats[] =
+{
+ "int",
+ "uint",
+ "bigint",
+ "ubigint",
+ "ascii",
+ "text",
+ "mtext",
+ "mlob",
+ "float",
+ "datetime",
+
+ 0
+};
+
+cDbService::FieldFormat cDbService::toDictFormat(const char* format)
+{
+ for (int i = 0; i < ffCount; i++)
+ if (strcasecmp(dictFormats[i], format) == 0)
+ return (FieldFormat)i;
+
+ return ffUnknown;
+}
+
+cDbService::TypeDef cDbService::types[] =
+{
+ { ftData, "data" },
+ { ftPrimary, "primary" },
+ { ftMeta, "meta" },
+ { ftAutoinc, "autoinc" },
+
+ { ftUnknown, "" }
+};
+
+int cDbService::toType(const char* theTypes)
+{
+ const int sizeTokenMax = 100;
+ char token[sizeTokenMax+TB];
+ const char* p = theTypes;
+
+ int type = ftNo;
+
+ // field can of more than one type -> bitmask
+
+ while (getToken(p, token, sizeTokenMax, '|') == success)
+ {
+ for (int i = 0; types[i].type != ftUnknown; i++)
+ {
+ if (strcasecmp(types[i].name, token) == 0)
+ {
+ type |= types[i].type;
+ continue;
+ }
+ }
+ }
+
+ return type;
+}
+
+const char* cDbService::toName(FieldType type, char* buf)
+{
+ *buf = 0;
+
+ // field can of more than one type -> bitmask !!
+
+ for (int i = 0; types[i].type != ftUnknown; i++)
+ {
+ if (types[i].type & type)
+ {
+ if (!isEmpty(buf))
+ strcat(buf, "|");
+
+ strcat(buf, types[i].name);
+ continue;
+ }
+ }
+
+ return buf;
+}
+
+//***************************************************************************
+//***************************************************************************
+// Class cDbDict
+//***************************************************************************
+
+cDbDict dbDict;
+
+//***************************************************************************
+// Object
+//***************************************************************************
+
+cDbDict::cDbDict()
+{
+ curTable = 0;
+ inside = no;
+ path = 0;
+ fieldFilter = 0; // 0 -> filter off use all fields
+ fltFromNameFct = 0;
+}
+
+cDbDict::~cDbDict()
+{
+ std::map<std::string, cDbTableDef*>::iterator t;
+
+ while ((t = tables.begin()) != tables.end())
+ {
+ if (t->second)
+ delete t->second;
+
+ tables.erase(t);
+ }
+
+ free(path);
+}
+
+//***************************************************************************
+// Get Table
+//***************************************************************************
+
+cDbTableDef* cDbDict::getTable(const char* aName)
+{
+ std::map<std::string, cDbTableDef*>::iterator t;
+
+ if ((t = tables.find(aName)) != tables.end())
+ return t->second;
+
+ return 0;
+}
+
+//***************************************************************************
+// Init
+//***************************************************************************
+
+int cDbDict::init(cDbFieldDef*& field, const char* tname, const char* fname)
+{
+ cDbTableDef* table = getTable(tname);
+
+ if (table)
+ if ((field = table->getField(fname)))
+ return success;
+
+ tell(0, "Fatal: Can't init field %s.%s, not found in dictionary", tname, fname);
+
+ return fail;
+}
+
+//***************************************************************************
+// In
+//***************************************************************************
+
+int cDbDict::in(const char* file, int filter)
+{
+ FILE* f;
+ char* line = 0;
+ size_t size = 0;
+
+ if (isEmpty(file))
+ return fail;
+
+ fieldFilter = filter;
+ asprintf(&path, "%s", file);
+
+ f = fopen(path, "r");
+
+ if (!f)
+ {
+ tell(0, "Error: Can' open file '%s', error was '%s'", path, strerror(errno));
+ return fail;
+ }
+
+ while (getline(&line, &size, f) > 0)
+ {
+ char* p = strstr(line, "//");
+
+ if (p) *p = 0;
+
+ allTrim(line);
+
+ if (isEmpty(line))
+ continue;
+
+ if (atLine(line) != success)
+ {
+ tell(0, "Error: Found unexpected definition '%s', aborting", line);
+ free(path);
+ return fail;
+ }
+ }
+
+ fclose(f);
+ free(line);
+
+ return success;
+}
+
+//***************************************************************************
+// Forget
+//***************************************************************************
+
+void cDbDict::forget()
+{
+ std::map<std::string, cDbTableDef*>::iterator t;
+
+ while ((t = tables.begin()) != tables.end())
+ {
+ if (t->second)
+ delete t->second;
+
+ tables.erase(t);
+ }
+
+ free(path);
+
+ curTable = 0;
+ inside = no;
+ path = 0;
+ fieldFilter = 0; // 0 -> filter off use all fields
+ fltFromNameFct = 0;
+}
+
+//***************************************************************************
+// Show
+//***************************************************************************
+
+void cDbDict::show()
+{
+ std::map<std::string, cDbTableDef*>::iterator t;
+
+ for (t = tables.begin(); t != tables.end(); t++)
+ {
+ tell(0, "-------------------------------------------------------------------------------------------------");
+ tell(0, "Table '%s'", t->first.c_str());
+ tell(0, "-------------------------------------------------------------------------------------------------");
+ t->second->show();
+ }
+}
+
+//***************************************************************************
+// At Line
+//***************************************************************************
+
+int cDbDict::atLine(const char* line)
+{
+ int status = success;
+ static int prsTable = no;
+ static int prsIndex = no;
+
+ const char* p;
+
+ if (strncasecmp(line, "Table ", 6) == 0)
+ {
+ char tableName[100];
+
+ prsTable = yes;
+ curTable = 0;
+ p = line + strlen("Table ");
+ strcpy(tableName, p);
+ allTrim(tableName);
+
+ if (!getTable(tableName))
+ {
+ curTable = new cDbTableDef(tableName);
+ tables[tableName] = curTable;
+ }
+ else
+ tell(0, "Fatal: Table '%s' doubly defined", tableName);
+ }
+ else if (strncasecmp(line, "Index ", 6) == 0)
+ {
+ prsIndex = yes;
+ p = line + strlen("Table ");
+ }
+
+ else if (strchr(line, '{'))
+ inside = yes;
+
+ else if (strchr(line, '}'))
+ {
+ inside = no;
+ prsTable = no;
+ prsIndex = no;
+ }
+
+ else if (inside && prsTable)
+ status += parseField(line);
+
+ else if (inside && prsIndex)
+ status += parseIndex(line);
+
+ else
+ tell(0, "Info: Ignoring extra line [%s]", line);
+
+ return status;
+}
+
+//***************************************************************************
+// Parse Filter
+//***************************************************************************
+
+int cDbDict::parseFilter(cDbFieldDef* f, const char* value)
+{
+ const int sizeNameMax = 50;
+ char name[sizeNameMax+TB];
+ const char* v = value;
+
+ f->filter = 0;
+
+ while (getToken(v, name, sizeNameMax, '|') == success)
+ {
+ if (isalpha(*name) && fltFromNameFct)
+ f->filter |= fltFromNameFct(name);
+ else
+ f->filter |= atoi(name);
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Parse Field
+//***************************************************************************
+
+int cDbDict::parseField(const char* line)
+{
+ const int sizeTokenMax = 100;
+ char token[sizeTokenMax+TB];
+ const char* p = line;
+
+ cDbFieldDef* f = new cDbFieldDef;
+ f->filter = 0xFFFF;
+
+ if (!curTable)
+ return fail;
+
+ // first parse fixed part of field definition up to the 'type'
+
+ for (int i = 0; i < dtCount; i++)
+ {
+ if (getToken(p, token, sizeTokenMax) != success)
+ {
+ if (i >= dtType) // all after type is optional (filter, ...)
+ break;
+
+ delete f;
+ tell(0, "Error: Can't parse line [%s]", line);
+ return fail;
+ }
+
+ if (i == dtCount-1 && strchr(token, ','))
+ *(strchr(token, ',')) = 0;
+
+ switch (i)
+ {
+ case dtName: f->name = strdup(token); break;
+ case dtDescription: f->setDescription(token); break;
+ case dtDbName: f->dbname = strdup(token); break;
+ case dtFormat: f->format = toDictFormat(token); break;
+ case dtSize: f->size = atoi(token); break;
+ case dtType: f->type = toType(token); break;
+ }
+ }
+
+ // second ... parse dynamic part of the field definition like filter and default
+
+ while (getToken(p, token, sizeTokenMax) == success)
+ {
+ char content[sizeTokenMax+TB];
+
+ if (getToken(p, content, sizeTokenMax) != success || isEmpty(content))
+ {
+ tell(0, "Error: Skipping token '%s' missing content!", token);
+ break;
+ }
+
+ if (strcasecmp(token, "filter") == 0)
+ parseFilter(f, content);
+
+ else if (strcasecmp(token, "default") == 0)
+ f->def = strdup(content);
+
+ else
+ tell(0, "Warning: Skipping unexpected token '%s'", token);
+ }
+
+ if (!f->isValid())
+ {
+ tell(0, "Error: Can't parse line [%s], invalid field configuration", line);
+ delete f;
+ return fail;
+ }
+
+ if (f->filterMatch(fieldFilter))
+ {
+ f->index = curTable->fieldCount();
+ curTable->addField(f);
+ }
+ else
+ {
+ tell(2, "Info: Ignoring field '%s' due to filter configiuration",
+ f->getName());
+ delete f;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Parse Index
+//***************************************************************************
+
+int cDbDict::parseIndex(const char* line)
+{
+ const int sizeTokenMax = 100;
+ char token[sizeTokenMax+TB];
+ const char* p = line;
+ int done = no;
+
+ if (!curTable)
+ return fail;
+
+ cDbIndexDef* index = new cDbIndexDef();
+
+ for (int i = 0; !done && i < 20; i++)
+ {
+ if (getToken(p, token, sizeTokenMax) != success)
+ {
+ if (i <= idtFields)
+ {
+ delete index;
+ tell(0, "Error: Can't parse line [%s]", line);
+ return fail;
+ }
+
+ break;
+ }
+
+ if (strchr(token, ','))
+ {
+ done = yes;
+ *(strchr(token, ',')) = 0;
+ }
+
+ if (i == idtName)
+ index->setName(token);
+ else if (i == idtDescription)
+ index->setDescription(token);
+ else if (i >= idtFields && i < idtFields+20)
+ index->addField(curTable->getField(token));
+ }
+
+ curTable->addIndex(index);
+
+ return success;
+}
diff --git a/lib/dbdict.h b/lib/dbdict.h
new file mode 100644
index 0000000..2999390
--- /dev/null
+++ b/lib/dbdict.h
@@ -0,0 +1,471 @@
+/*
+ * dbdict.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __DBDICT_H
+#define __DBDICT_H
+
+#include <stdio.h>
+
+#include <vector>
+#include <map>
+#include <string>
+
+class cDbDict;
+
+typedef int (*FilterFromName)(const char* name);
+
+//***************************************************************************
+// _casecmp_
+//***************************************************************************
+
+class _casecmp_
+{
+ public:
+
+ bool operator() (const std::string& a, const std::string& b) const
+ {
+ return strcasecmp(a.c_str(), b.c_str()) < 0;
+ }
+};
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+class cDbService
+{
+ public:
+
+ enum Misc
+ {
+ maxIndexFields = 20
+ };
+
+ enum FieldFormat
+ {
+ ffUnknown = na,
+
+ ffInt,
+ ffUInt,
+ ffBigInt,
+ ffUBigInt,
+ ffAscii, // -> VARCHAR
+ ffText,
+ ffMText,
+ ffMlob, // -> MEDIUMBLOB
+ ffFloat,
+ ffDateTime,
+
+ ffCount
+ };
+
+ enum FieldType
+ {
+ ftUnknown = na,
+ ftNo = 0,
+
+ ftData = 1,
+ ftPrimary = 2,
+ ftMeta = 4,
+ ftAutoinc = 8,
+
+ ftAll = ftData | ftPrimary | ftMeta
+ };
+
+ enum BindType
+ {
+ bndIn = 0x001,
+ bndOut = 0x002,
+ bndSet = 0x004
+ };
+
+ enum ProcType
+ {
+ ptProcedure,
+ ptFunction
+ };
+
+ struct TypeDef
+ {
+ FieldType type;
+ const char* name;
+ };
+
+ static const char* toString(FieldFormat t);
+ static FieldFormat toDictFormat(const char* format);
+ static const char* formats[];
+ static const char* dictFormats[];
+
+ static int toType(const char* type);
+ static const char* toName(FieldType type, char* buf);
+ static TypeDef types[];
+};
+
+typedef cDbService cDBS;
+
+//***************************************************************************
+// cDbFieldDef
+//***************************************************************************
+
+class cDbFieldDef : public cDbService
+{
+ public:
+
+ friend class cDbDict;
+
+ cDbFieldDef()
+ {
+ name = 0;
+ dbname = 0,
+ format = ffUnknown;
+ size = na;
+ type = ftUnknown;
+ description = 0;
+ dbdescription = 0;
+ filter = 0xFFFF;
+ def = 0;
+ }
+
+ cDbFieldDef(const char* n, const char* dn, FieldFormat f, int s, int t, int flt = 0xFFFF)
+ {
+ name = strdup(n);
+ dbname = strdup(dn);
+ format = f;
+ size = s;
+ type = t;
+ filter = flt;
+ description = 0;
+ dbdescription = 0;
+ def = 0;
+ }
+
+ ~cDbFieldDef() { free(name); free(dbname); free(description); free(dbdescription); free(def); }
+
+ int getIndex() { return index; }
+ const char* getName() { return name; }
+ int hasName(const char* n) { return strcasecmp(n, name) == 0; }
+ int hasDbName(const char* n) { return strcasecmp(n, dbname) == 0; }
+ const char* getDescription() { return description; }
+ const char* getDbDescription() { return dbdescription; }
+ const char* getDbName() { return dbname; }
+ int getSize() { return size; }
+ FieldFormat getFormat() { return format; }
+ int getType() { return type; }
+ const char* getDefault() { return def ? def : ""; }
+ int getFilter() { return filter; }
+ int filterMatch(int f) { return !f || filter & f; }
+ int hasType(int types) { return types & type; }
+ int hasFormat(int f) { return format == f; }
+
+ int isString() { return format == ffAscii || format == ffText ||
+ format == ffMText || format == ffMlob; }
+ int isInt() { return format == ffInt || format == ffUInt; }
+ int isBigInt() { return format == ffBigInt || format == ffUBigInt; }
+ int isFloat() { return format == ffFloat; }
+ int isDateTime() { return format == ffDateTime; }
+
+ void setDescription(const char* desc)
+ {
+ description = strdup(desc);
+ dbdescription = strdup(strReplace("'", "\\'", description).c_str());
+ }
+
+ const char* toColumnFormat(char* buf) // column type to be used for create/alter
+ {
+ if (!buf)
+ return 0;
+
+ sprintf(buf, "%s", toString(format));
+
+ if (format != ffMlob)
+ {
+ if (!size)
+ {
+ if (format == ffAscii)
+ size = 100;
+ else if (format == ffInt || format == ffUInt || format == ffBigInt || format == ffUBigInt)
+ size = 11;
+ else if (format == ffFloat)
+ size = 10;
+ }
+
+ if (format == ffFloat)
+ sprintf(eos(buf), "(%d,%d)", size/10 + size%10, size%10); // 62 -> 8,2
+ else if (format == ffInt || format == ffUInt || format == ffBigInt || format == ffUBigInt || format == ffAscii)
+ sprintf(eos(buf), "(%d)", size);
+
+ if (format == ffUInt || format == ffUBigInt)
+ sprintf(eos(buf), " unsigned");
+ }
+
+ return buf;
+ }
+
+ int isValid()
+ {
+ if (!name) { tell(0, "Missing field name"); return no; }
+ if (!dbname) { tell(0, "Missing fields database name"); return no; }
+ if (size == na) { tell(0, "Missing field size"); return no; }
+ if (type == ftUnknown) { tell(0, "Missing or invalid field type"); return no; }
+ if (format == ffUnknown) { tell(0, "Missing or invalid field format"); return no; }
+
+ return yes;
+ }
+
+ void show()
+ {
+ char colFmt[100];
+ char fType[100];
+ char tmp[100];
+
+ sprintf(fType, "(%s)", toName((FieldType)type, tmp));
+
+ tell(0, "%-20s %-25s %-17s %-20s (0x%04X) default '%s' '%s'", name, dbname,
+ toColumnFormat(colFmt), fType, filter, notNull(def, ""), description);
+ }
+
+ protected:
+
+ char* name;
+ char* dbname;
+ char* description;
+ char* dbdescription;
+ FieldFormat format;
+ int size;
+ int index;
+ int type;
+ int filter; // bitmask (defaults to 0xFFFF)
+ char* def;
+};
+
+//***************************************************************************
+// cDbIndexDef
+//***************************************************************************
+
+class cDbIndexDef
+{
+ public:
+
+ cDbIndexDef() { name = 0; description = 0; }
+ ~cDbIndexDef() { free(name); free(description); }
+
+ void setName(const char* n) { free(name); name = strdup(n); }
+ const char* getName() { return name; }
+
+ void setDescription(const char* d) { free(description); description = strdup(d); }
+ const char* getDescription() { return description; }
+
+ int fieldCount() { return dfields.size(); }
+ void addField(cDbFieldDef* f) { dfields.push_back(f); }
+ cDbFieldDef* getField(int i) { return dfields[i]; }
+
+ void show()
+ {
+ std::string s = "";
+
+ for (uint i = 0; i < dfields.size(); i++)
+ s += dfields[i]->getName() + std::string(" ");
+
+ s.erase(s.find_last_not_of(' ')+1);
+
+ tell(0, "Index %-25s (%s)", getName(), s.c_str());
+ }
+
+ protected:
+
+ char* name;
+ char* description;
+ std::vector<cDbFieldDef*> dfields; // index fields
+};
+
+//***************************************************************************
+// cDbTableDef
+//***************************************************************************
+
+class cDbTableDef : public cDbService
+{
+ public:
+
+ friend class cDbRow;
+ friend class cDbTable;
+ friend class cDbStatement;
+
+ cDbTableDef(const char* n) { name = strdup(n); }
+
+ ~cDbTableDef()
+ {
+ for (uint i = 0; i < indices.size(); i++)
+ delete indices[i];
+
+ indices.clear();
+
+ free(name);
+ clear();
+ }
+
+ const char* getName() { return name; }
+ int fieldCount() { return dfields.size(); }
+ cDbFieldDef* getField(int id) { return _dfields[id]; }
+
+ cDbFieldDef* getField(const char* fname, int silent = no)
+ {
+ std::map<std::string, cDbFieldDef*, _casecmp_>::iterator f;
+
+ if ((f = dfields.find(fname)) != dfields.end())
+ return f->second;
+
+ if (!silent)
+ tell(0, "Fatal: Missing definition of field '%s.%s' in dictionary!", name, fname);
+
+ return 0;
+ }
+
+ cDbFieldDef* getFieldByDbName(const char* dbname)
+ {
+ std::map<std::string, cDbFieldDef*, _casecmp_>::iterator it;
+
+ for (it = dfields.begin(); it != dfields.end(); it++)
+ {
+ if (it->second->hasDbName(dbname))
+ return it->second;
+ }
+
+ tell(5, "Fatal: Missing definition of field '%s.%s' in dictionary!", name, dbname);
+
+ return 0;
+ }
+
+ void addField(cDbFieldDef* f)
+ {
+ if (dfields.find(f->getName()) == dfields.end())
+ {
+ dfields[f->getName()] = f; // add to named map
+ _dfields.push_back(f); // add to indexed list
+ }
+ else
+ tell(0, "Fatal: Field '%s.%s' doubly defined", getName(), f->getName());
+ }
+
+ int indexCount() { return indices.size(); }
+ cDbIndexDef* getIndex(int i) { return indices[i]; }
+ void addIndex(cDbIndexDef* i) { indices.push_back(i); }
+
+ void clear()
+ {
+ std::map<std::string, cDbFieldDef*>::iterator f;
+
+ while ((f = dfields.begin()) != dfields.end())
+ {
+ if (f->second)
+ delete f->second;
+
+ dfields.erase(f);
+ }
+ }
+
+ void show()
+ {
+ // show fields
+
+ for (uint i = 0; i < _dfields.size(); i++)
+ _dfields[i]->show();
+
+ // show indices
+
+ if (!indices.size())
+ {
+ tell(0, " ");
+ return;
+ }
+
+ tell(0, "-----------------------------------------------------");
+ tell(0, "Indices from '%s'", getName());
+ tell(0, "-----------------------------------------------------");
+
+ for (uint i = 0; i < indices.size(); i++)
+ indices[i]->show();
+
+ tell(0, " ");
+ }
+
+ private:
+
+ char* name;
+ std::vector<cDbIndexDef*> indices;
+
+ // FiledDefs stored as list to have access via index
+ std::vector<cDbFieldDef*> _dfields;
+
+ // same FiledDef references stored as a map to have access via name
+ std::map<std::string, cDbFieldDef*, _casecmp_> dfields;
+};
+
+//***************************************************************************
+// cDbDict
+//***************************************************************************
+
+class cDbDict : public cDbService
+{
+ public:
+
+ // declarations
+
+ enum TableDictToken
+ {
+ dtName,
+ dtDescription,
+ dtDbName,
+ dtFormat,
+ dtSize,
+ dtType,
+
+ dtCount
+ };
+
+ enum IndexDictToken
+ {
+ idtName,
+ idtDescription,
+ idtFields
+ };
+
+ cDbDict();
+ virtual ~cDbDict();
+
+ int in(const char* file, int filter = 0);
+ void setFilterFromNameFct(FilterFromName fct) { fltFromNameFct = fct; }
+
+ cDbTableDef* getTable(const char* name);
+ void show();
+ int init(cDbFieldDef*& field, const char* tname, const char* fname);
+ const char* getPath() { return path ? path : ""; }
+ void forget();
+
+ std::map<std::string, cDbTableDef*>::iterator getFirstTableIterator() { return tables.begin(); }
+ std::map<std::string, cDbTableDef*>::iterator getTableEndIterator() { return tables.end(); }
+
+ protected:
+
+ int atLine(const char* line);
+ int parseField(const char* line);
+ int parseIndex(const char* line);
+ int parseFilter(cDbFieldDef* f, const char* value);
+
+ // data
+
+ int inside;
+ cDbTableDef* curTable;
+ std::map<std::string, cDbTableDef*, _casecmp_> tables;
+ char* path;
+ int fieldFilter;
+ FilterFromName fltFromNameFct;
+};
+
+extern cDbDict dbDict;
+
+//***************************************************************************
+#endif // __DBDICT_H
diff --git a/lib/demo.c b/lib/demo.c
new file mode 100644
index 0000000..0c4c234
--- /dev/null
+++ b/lib/demo.c
@@ -0,0 +1,531 @@
+/*
+ * demo.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "config.h"
+#include "common.h"
+
+#include "db.h"
+#include "epgservice.h"
+
+cDbConnection* connection = 0;
+const char* logPrefix = "";
+
+//***************************************************************************
+// Init Connection
+//***************************************************************************
+
+void initConnection()
+{
+ cDbConnection::init();
+
+ cDbConnection::setHost("localhost");
+ // cDbConnection::setHost("192.168.200.101");
+ cDbConnection::setPort(3306);
+ cDbConnection::setName("epg2vdr");
+ cDbConnection::setUser("epg2vdr");
+ cDbConnection::setPass("epg");
+
+ cDbConnection::setConfPath("/etc/epgd/");
+ cDbConnection::setEncoding("utf8");
+
+ connection = new cDbConnection();
+}
+
+void exitConnection()
+{
+ cDbConnection::exit();
+
+ if (connection)
+ delete connection;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int demoStatement()
+{
+ int status = success;
+
+ cDbTable* eventsDb = new cDbTable(connection, "events");
+
+ tell(0, "------------------- attach table ---------------");
+
+ // open table (attach)
+
+ if (eventsDb->open() != success)
+ return fail;
+
+ tell(0, "---------------- prepare select statement -------------");
+
+ // vorbereiten (prepare) eines statement, am besten einmal bei programmstart!
+ // ----------
+ // select eventid, compshorttext, episodepart, episodelang
+ // from events
+ // where eventid > ?
+
+ cDbStatement* selectByCompTitle = new cDbStatement(eventsDb);
+
+ status += selectByCompTitle->build("select ");
+ status += selectByCompTitle->bind("EventId", cDBS::bndOut);
+ status += selectByCompTitle->bind("ChannelId", cDBS::bndOut, ", ");
+ status += selectByCompTitle->bind("Title", cDBS::bndOut, ", ");
+ status += selectByCompTitle->build(" from %s where ", eventsDb->TableName());
+ status += selectByCompTitle->bindCmp(0, eventsDb->getField("EventId"), 0, ">");
+
+ status += selectByCompTitle->prepare(); // prepare statement
+
+ if (status != success)
+ {
+ // prepare sollte MySQL fehler ausgegeben haben!
+
+ delete eventsDb;
+ delete selectByCompTitle;
+
+ return fail;
+ }
+
+ tell(0, "------------------ prepare done ----------------------");
+
+ tell(0, "------------------ create some rows ----------------------");
+
+ eventsDb->clear(); // alle values löschen
+
+ for (int i = 0; i < 10; i++)
+ {
+ char* title;
+ asprintf(&title, "title %d", i);
+
+ eventsDb->setValue(eventsDb->getField("EventId"), 800 + i * 100);
+ eventsDb->setValue(eventsDb->getField("ChannelId"), "xxx-yyyy-zzz");
+ eventsDb->setValue(eventsDb->getField("Title"), title);
+
+ eventsDb->store(); // store -> select mit anschl. update oder insert je nachdem ob dier PKey bereits vorhanden ist
+ // eventsDb->insert(); // sofern man schon weiß das es ein insert ist
+ // eventsDb->update(); // sofern man schon weiß das der Datensatz vorhanden ist
+
+ free(title);
+ }
+
+ tell(0, "------------------ done ----------------------");
+
+ tell(0, "-------- select all where eventid > 1000 -------------");
+
+ eventsDb->clear(); // alle values löschen
+ eventsDb->setValue(eventsDb->getField("EventId"), 1000);
+
+ for (int f = selectByCompTitle->find(); f; f = selectByCompTitle->fetch())
+ {
+ tell(0, "id: %ld", eventsDb->getIntValue(eventsDb->getField("EventId")));
+ tell(0, "channel: %s", eventsDb->getStrValue(eventsDb->getField("ChannelId")));
+ tell(0, "titel: %s", eventsDb->getStrValue(eventsDb->getField("Title")));
+ }
+
+ // freigeben der Ergebnissmenge !!
+
+ selectByCompTitle->freeResult();
+
+ // folgendes am programmende
+
+ delete eventsDb; // implizietes close (detach)
+ delete selectByCompTitle; // statement freigeben (auch gegen die DB)
+
+ return success;
+}
+
+//***************************************************************************
+// Join
+//***************************************************************************
+
+// #define F_INIT(a,b) cDbFieldDef* a##b = 0; dbDict.init(a##b, #a, #b)
+
+int joinDemo()
+{
+ int status = success;
+
+ // grundsätzlich genügt hier auch eine Tabelle, für die anderen sind cDbValue Instanzen außreichend
+ // so ist es etwas einfacher die cDbValues zu initialisieren.
+ // Ich habe statische "virtual FieldDef* getFieldDef(int f)" Methode in der Tabellenklassen geplant
+ // um ohne Instanz der cTable ein Feld einfach initialisieren zu können
+
+ cDbTable* eventsDb = new cDbTable(connection, "events");
+ cDbTable* imageRefDb = new cDbTable(connection, "imagerefs");
+ cDbTable* imageDb = new cDbTable(connection, "images");
+
+ cDbTable* timerDb = new cDbTable(connection, "timers");
+ timerDb->open(yes);
+ delete timerDb;
+
+ // init dict fields as needed (normaly done once at programm start)
+ // init and using the pointer improve the speed since the lookup via
+ // the name is dine only once
+
+ // F_INIT(events, EventId); // ergibt: cDbFieldDef* eventsEventId; dbDict.init(eventsEventId, "events", "EventId");
+
+ tell(0, "----------------- open table connection ---------------");
+
+ // open tables (attach)
+
+ if (eventsDb->open() != success)
+ return fail;
+
+ if (imageDb->open() != success)
+ return fail;
+
+ if (imageRefDb->open() != success)
+ return fail;
+
+ tell(0, "---------------- prepare select statement -------------");
+
+ // all images
+
+ cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+ // prepare fields
+
+ cDbValue imageUpdSp;
+ cDbValue imageSize;
+ cDbValue masterId;
+
+ cDbFieldDef imageSizeDef("image", "image", cDBS::ffUInt, 999, cDBS::ftData, 0); // eine Art ein Feld zu erzeugen
+ imageSize.setField(&imageSizeDef);
+ imageUpdSp.setField(imageDb->getField("UpdSp"));
+ masterId.setField(eventsDb->getField("MasterId"));
+
+ // select e.masterid, r.imagename, r.eventid, r.lfn, length(i.image)
+ // from imagerefs r, images i, events e
+ // where i.imagename = r.imagename
+ // and e.eventid = r.eventid
+ // and (i.updsp > ? or r.updsp > ?)
+
+ selectAllImages->build("select ");
+ selectAllImages->setBindPrefix("e.");
+ selectAllImages->bind(&masterId, cDBS::bndOut);
+ selectAllImages->setBindPrefix("r.");
+ selectAllImages->bind("ImgName", cDBS::bndOut, ", ");
+ selectAllImages->bind("EventId", cDBS::bndOut, ", ");
+ selectAllImages->bind("Lfn", cDBS::bndOut, ", ");
+ selectAllImages->setBindPrefix("i.");
+ selectAllImages->build(", length(");
+ selectAllImages->bind(&imageSize, cDBS::bndOut);
+ selectAllImages->build(")");
+ selectAllImages->clrBindPrefix();
+ selectAllImages->build(" from %s r, %s i, %s e where ",
+ imageRefDb->TableName(), imageDb->TableName(), eventsDb->TableName());
+ selectAllImages->build("e.%s = r.%s and i.%s = r.%s and (",
+ "EventId", // eventsEventId->getDbName(),
+ imageRefDb->getField("EventId")->getDbName(),
+ "imagename", // direkt den DB Feldnamen verwenden -> nicht so schön da nicht dynamisch
+ imageRefDb->getField("ImgName")->getDbName()); // ordentlich via dictionary übersetzt -> schön ;)
+ selectAllImages->bindCmp("i", &imageUpdSp, ">");
+ selectAllImages->build(" or ");
+ selectAllImages->bindCmp("r", imageRefDb->getField("UpdSp"), 0, ">");
+ selectAllImages->build(")");
+
+ status += selectAllImages->prepare();
+
+ if (status != success)
+ {
+ // prepare sollte MySQL Fehler ausgegeben haben!
+
+ delete eventsDb;
+ delete imageDb;
+ delete imageRefDb;
+ delete selectAllImages;
+
+ return fail;
+ }
+
+ tell(0, "------------------ prepare done ----------------------");
+
+
+ tell(0, "------------------ select ----------------------");
+
+ time_t since = 0; //time(0) - 60 * 60;
+ imageRefDb->clear();
+ imageRefDb->setValue(imageRefDb->getField("UpdSp"), since);
+ imageUpdSp.setValue(since);
+
+ for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+ {
+ // so kommst du an die Werte der unterschiedlichen Tabellen
+
+ int eventid = masterId.getIntValue();
+ const char* imageName = imageRefDb->getStrValue("ImgName");
+ int lfn = imageRefDb->getIntValue(imageRefDb->getField("Lfn"));
+ int size = imageSize.getIntValue();
+
+ tell(0, "Found (%d) '%s', %d, %d", eventid, imageName, lfn, size);
+ }
+
+ tell(0, "------------------ done ----------------------");
+
+ // freigeben der Ergebnissmenge !!
+
+ selectAllImages->freeResult();
+
+
+ // folgendes am programmende
+
+ delete eventsDb; // implizites close (detach)
+ delete imageDb;
+ delete imageRefDb;
+ delete selectAllImages; // statement freigeben (auch gegen die DB)
+
+ return success;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int insertDemo()
+{
+ cDbTable* timerDb = new cDbTable(connection, "timers");
+ if (timerDb->open() != success) return fail;
+
+ timerDb->clear();
+ timerDb->setValue("EventId", 4444444);
+
+ if (timerDb->insert() == success)
+ {
+ tell(0, "Insert succeeded with ID %d", timerDb->getLastInsertId());
+ }
+
+ delete timerDb;
+
+ return done;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int findUseEvent()
+{
+ cDbTable* useeventsDb = new cDbTable(connection, "useevents");
+ if (useeventsDb->open() != success) return fail;
+
+ // select event by useid
+
+ tell(0, "========================================");
+
+ cDbStatement* selectEventById = new cDbStatement(useeventsDb);
+
+ // select * from eventsview
+ // where useid = ?
+ // and updflg in (.....)
+
+ selectEventById->build("select ");
+ selectEventById->bindAllOut();
+ selectEventById->build(" from %s where ", useeventsDb->TableName());
+ selectEventById->bind("USEID", cDBS::bndIn | cDBS::bndSet);
+ selectEventById->build(" and %s in (%s)",
+ useeventsDb->getField("UPDFLG")->getDbName(),
+ Us::getNeeded());
+
+ selectEventById->prepare();
+
+ tell(0, "========================================");
+
+ useeventsDb->clear();
+ useeventsDb->setValue("USEID", 1146680);
+
+ const char* flds[] =
+ {
+ "ACTOR",
+ "AUDIO",
+ "CATEGORY",
+ "COUNTRY",
+ "DIRECTOR",
+ "FLAGS",
+ "GENRE",
+ "INFO",
+ "MUSIC",
+ "PRODUCER",
+ "SCREENPLAY",
+ "SHORTREVIEW",
+ "TIPP",
+ "TOPIC",
+ "YEAR",
+ "RATING",
+ "NUMRATING",
+ "MODERATOR",
+ "OTHER",
+ "GUEST",
+ "CAMERA",
+
+ 0
+ };
+
+ if (selectEventById->find())
+ {
+ FILE* f;
+ char* fileName = 0;
+
+ asprintf(&fileName, "%s/info.epg2vdr", ".");
+
+ if (!(f = fopen(fileName, "w")))
+ {
+ selectEventById->freeResult();
+ tell(0, "Error opening file '%s' failed, %s", fileName, strerror(errno));
+ free(fileName);
+
+ return fail;
+ }
+
+ tell(0, "Storing event details to '%s'", fileName);
+
+ for (int i = 0; flds[i]; i++)
+ {
+ cDbFieldDef* field = useeventsDb->getField(flds[i]);
+
+ if (!field)
+ {
+ tell(0, "Warning: Field '%s' not found at table '%s'", flds[i], useeventsDb->TableName());
+ continue;
+ }
+
+ if (field->getFormat() == cDbService::ffAscii ||
+ field->getFormat() == cDbService::ffText ||
+ field->getFormat() == cDbService::ffMText)
+ {
+ fprintf(f, "%s: %s\n", flds[i], useeventsDb->getStrValue(flds[i]));
+ }
+ else
+ {
+ fprintf(f, "%s: %ld\n", flds[i], useeventsDb->getIntValue(flds[i]));
+ }
+ }
+
+ selectEventById->freeResult();
+ free(fileName);
+ fclose(f);
+ }
+
+ tell(0, "========================================");
+
+ selectEventById->freeResult();
+
+ return done;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int updateRecordingDirectory()
+{
+ int ins = 0;
+
+ cDbTable* recordingDirDb = new cDbTable(connection, "recordingdirs");
+ if (recordingDirDb->open() != success) return fail;
+
+ // char* dir = strdup("more~Marvel's Agents of S.H.I.E.L.D.~xxx.ts");
+ char* dir = strdup("aaaaa~bbbbbb~ccccc.ts");
+ char* pos = strrchr(dir, '~');
+
+ if (pos)
+ {
+ *pos = 0;
+
+ for (int level = 0; level < 3; level++)
+ {
+ recordingDirDb->clear();
+
+ recordingDirDb->setValue("VDRUUID", "foobar");
+ recordingDirDb->setValue("DIRECTORY", dir);
+
+ if (!recordingDirDb->find())
+ {
+ ins++;
+ recordingDirDb->store();
+ }
+
+ recordingDirDb->reset();
+
+ char* pos = strrchr(dir, '~');
+ if (pos) *pos=0;
+ }
+ }
+
+ tell(0, "inserted %d directories", ins);
+
+ delete recordingDirDb;
+ free(dir);
+
+ return ins;
+}
+
+//***************************************************************************
+// Main
+//***************************************************************************
+
+int main(int argc, char** argv)
+{
+ cEpgConfig::logstdout = yes;
+ cEpgConfig::loglevel = 2;
+
+ const char* path = "/etc/epgd/epg.dat";
+
+ if (argc > 1)
+ path = argv[1];
+
+ // read deictionary
+
+ dbDict.setFilterFromNameFct(toFieldFilter);
+
+ if (dbDict.in(path, ffEpgd) != success)
+ {
+ tell(0, "Invalid dictionary configuration, aborting!");
+ return 1;
+ }
+
+ cUserTimes userTimes;
+
+ tell(0, "--------------");
+ //userTimes.clear();
+ tell(0, "--------------");
+
+ userTimes.add("@Now", "What's on now?") ;
+ userTimes.add("@Next", "What's on next?" );
+ userTimes.add("!20:15", "prime time");
+ userTimes.add("22:20", "late night");
+ userTimes.add("00:00");
+
+ tell(0, "--------------");
+
+ for (int i = 0; i < 6; i++)
+ {
+ cUserTimes::UserTime* t = userTimes.next();
+
+ tell(0, "%d - %s (%s) %s", t->getHHMM(), t->getTitle(), t->getHHMMStr(), t->isHighlighted() ? "highlighted" : "");
+ }
+
+ return 0;
+
+ // dbDict.show();
+
+ initConnection();
+
+ // demoStatement();
+ // joinDemo();
+ // insertDemo();
+
+ tell(0, "uuid: '%s'", getUniqueId());
+
+ tell(0, "- - - - - - - - - - - - - - - - - ");
+
+ //updateRecordingDirectory();
+ findUseEvent();
+
+ tell(0, "- - - - - - - - - - - - - - - - - ");
+
+ exitConnection();
+
+ return 0;
+}
diff --git a/lib/demo.dat b/lib/demo.dat
new file mode 100644
index 0000000..5ae9123
--- /dev/null
+++ b/lib/demo.dat
@@ -0,0 +1,17 @@
+
+
+Table _test
+{
+ ID "" id UInt 0 Primary|Autoinc,
+ VDRUUID "" vdruuid Ascii 40 Primary,
+
+ INSSP "" inssp Int 0 Meta,
+ UPDSP "" updsp Int 0 Meta,
+
+ EVENTID "" eventid UInt 0 Data,
+ CHANNELID "" channelid Ascii 50 Data default none,
+
+ SOURCE "" source Ascii 20 Data default WEB filter osd|webif|10,
+ STATE "'D'eleted, 'R'unning, 'F'inished" state Ascii 1 Data default -,
+ INFO "error reason if state is failed" info Ascii 255 Data,
+}
diff --git a/lib/epgservice.c b/lib/epgservice.c
new file mode 100644
index 0000000..69e5a8e
--- /dev/null
+++ b/lib/epgservice.c
@@ -0,0 +1,121 @@
+/*
+ * epgservice.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "epgservice.h"
+
+//***************************************************************************
+// Timer State / Action
+//***************************************************************************
+
+const char* toName(TimerState s)
+{
+ switch (s)
+ {
+ case tsPending: return "pending";
+ case tsRunning: return "running";
+ case tsFinished: return "finished";
+ case tsDeleted: return "deleted";
+ case tsError: return "failed";
+ case tsIgnore: return "ignore";
+ case tsUnknown: return "unknown";
+ }
+
+ return "unknown";
+}
+
+const char* toName(TimerAction a, int nice)
+{
+ switch (a)
+ {
+ case taCreate: return "create";
+ case taModify: return "modify";
+ case taAdjust: return "adjust";
+ case taDelete: return "delete";
+ case taAssumed: return nice ? "-" : "assumed";
+ case taFailed: return "failed";
+ case taReject: return "reject";
+ }
+
+ return nice ? "-" : "unknown";
+}
+
+//***************************************************************************
+// cEpgdState
+//***************************************************************************
+
+const char* cEpgdState::states[] =
+{
+ "init",
+ "standby",
+ "stopped",
+
+ "busy (events)",
+ "busy (match)",
+ "busy (scraping)",
+ "busy (images)",
+
+ 0
+};
+
+const char* cEpgdState::toName(cEpgdState::State s)
+{
+ if (!isValid(s))
+ return "unknown";
+
+ return states[s];
+}
+
+cEpgdState::State cEpgdState::toState(const char* name)
+{
+ for (int i = 0; i < esCount; i++)
+ if (strcmp(states[i], name) == 0)
+ return (State)i;
+
+ return esUnknown;
+}
+
+//***************************************************************************
+// Field Filter
+//***************************************************************************
+
+FieldFilterDef fieldFilters[] =
+{
+ { ffAll, "all" },
+ { ffEpgd, "epgd" },
+ { ffEpgHttpd, "httpd" },
+ { ffEpg2Vdr, "epg2vdr" },
+ { ffScraper2Vdr, "scraper" },
+
+ { 0, 0 }
+};
+
+const char* toName(FieldFilter f)
+{
+ for (int i = 0; fieldFilters[i].name; i++)
+ if (fieldFilters[i].filter == f)
+ return fieldFilters[i].name;
+
+ return "unknown";
+}
+
+int toFieldFilter(const char* name)
+{
+ for (int i = 0; fieldFilters[i].name; i++)
+ if (strcasecmp(fieldFilters[i].name, name) == 0)
+ return fieldFilters[i].filter;
+
+ return ffAll;
+}
+
+//***************************************************************************
+// User Rights Check
+//***************************************************************************
+
+int hasUserMask(unsigned int rights, UserMask mask)
+{
+ return rights & mask;
+}
diff --git a/lib/epgservice.h b/lib/epgservice.h
new file mode 100644
index 0000000..a63ed56
--- /dev/null
+++ b/lib/epgservice.h
@@ -0,0 +1,469 @@
+/*
+ * epgservice.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __EPGSERVICE_H
+#define __EPGSERVICE_H
+
+#include <list>
+
+#include "common.h"
+
+#define EPG_PLUGIN_SEM_KEY 0x3db00001
+
+//***************************************************************************
+// Globals
+//***************************************************************************
+
+typedef unsigned long long tEventId;
+
+enum EpgServiceMisc
+{
+ sizeMaxParameterValue = 150 // should match the field size in parameters table
+};
+
+enum FieldFilter
+{
+ ffAll = 0xFFFF,
+ ffEpgd = 1,
+ ffEpgHttpd = 2,
+ ffEpg2Vdr = 4,
+ ffScraper2Vdr = 8,
+
+ ffCount = 5
+};
+
+struct FieldFilterDef
+{
+ int filter;
+ const char* name;
+};
+
+const char* toName(FieldFilter f);
+int toFieldFilter(const char* name);
+
+enum SearchFields
+{
+ sfNone = 0, // off
+ sfTitle = 1,
+ sfFolge = 2,
+ sfDescription = 4
+};
+
+enum SearchMode
+{
+ smExact = 1,
+ smRegexp,
+ smLike,
+ smContained
+};
+
+enum TimerNamingMode
+{
+ tnmDefault = 0, // naming would done by VDR
+
+ // naming of following modes handled by recording.py an can 'configured' there
+
+ tnmAuto = 1, // autodetect if 'constabel', 'serie' or 'normal movie'
+ tnmConstabel = 2, // naming in constabel series style with season, number, ..
+ tnmSerie = 3, // series style, like Title/Subtitle
+ tnmCategorized = 4, // sorted in sub folders which are auto-named by category
+ tnmUser = 5, // user defined mode 'to be implemented in recording.py'
+ tnmTemplate = 6 // Templating mode
+
+};
+
+enum RecordingState
+{
+ rsFinished = 'F',
+ rsRecording = 'R',
+ rsDeleted = 'D'
+};
+
+enum TimerState
+{
+ tsUnknown = 'U',
+ tsPending = 'P',
+ tsRunning = 'R', // timer is recording
+ tsFinished = 'F',
+ tsDeleted = 'D',
+ tsError = 'E',
+ tsIgnore = '-' // ignore in timer menu -> already tooked 'any-VDR' timer
+};
+
+const char* toName(TimerState s);
+
+enum TimerAction
+{
+ taCreate = 'C',
+ taModify = 'M',
+ taAdjust = 'J',
+ taDelete = 'D',
+ taAssumed = 'A',
+ taFailed = 'F',
+ taReject = 'T'
+};
+
+enum TimerType
+{
+ ttRecord = 'R', // Aufnahme-Timer
+ ttView = 'V', // Umschalt-Timer
+ ttSearch = 'S' // Such-Timer
+};
+
+const char* toName(TimerAction a, int nice = no);
+
+enum TimerDoneState
+{
+ tdsTimerRequested = 'Q', // timer already requested by epgd/webif
+ tdsTimerCreated = 'C', // timer created by VDR
+ tdsTimerCreateFailed = 'f', // create/delete of timer failed by VDR
+
+ tdsRecordingDone = 'R', // Recording finished successfull
+ tdsRecordingFailed = 'F', // Recording failed
+
+ tdsTimerDeleted = 'D', // timer deleted by user
+ tdsTimerRejected = 'J' // timer rejected due to user action or timer conflict, ...
+};
+
+enum UserMask
+{
+ umNone = 0x0,
+
+ umNologin = 0x1, // the right without a session
+
+ umConfig = 0x2,
+ umConfigEdit = 0x4,
+ umConfigUsers = 0x8,
+
+ umFree1 = 0x10,
+ umFree2 = 0x20,
+
+ umTimer = 0x40,
+ umTimerEdit = 0x80,
+ umSearchTimer = 0x100,
+ umSearchTimerEdit = 0x200,
+
+ umFree3 = 0x400,
+ umFree4 = 0x800,
+
+ umFsk = 0x1000,
+
+ umFree5 = 0x2000,
+ umFree6 = 0x4000,
+
+ umRecordings = 0x8000,
+ umRecordingsEdit = 0x10000,
+
+ umAll = 0xFFFFFFFF & ~umNologin
+};
+
+int hasUserMask(unsigned int rights, UserMask mask);
+
+//***************************************************************************
+// cEpgdState
+//***************************************************************************
+
+class cEpgdState
+{
+ public:
+
+ enum State
+ {
+ esUnknown = na,
+
+ esInit,
+ esStandby,
+ esStopped,
+
+ // handler pause on this states!
+
+ esBusy,
+ esBusyEvents = esBusy,
+ esBusyMatch,
+ esBusyScraping,
+
+ // handler don't pause on this states!
+
+ esBusyImages,
+
+ esCount
+ };
+
+ static const char* toName(State s);
+ static State toState(const char* name);
+ static int isValid(State s) { return s > esUnknown && s < esCount; }
+
+ static const char* states[];
+};
+
+typedef cEpgdState Es;
+
+//***************************************************************************
+// cEventState
+//***************************************************************************
+
+class cEventState
+{
+ public:
+
+ enum State
+ {
+ // add to VDRs EPG => wird von allen VDR angezeigt
+
+ usActive = 'A', // => Aktiv
+ usLink = 'L', // => DVB Event welches auf ein externes zeigt
+ usPassthrough = 'P', // => Durchgeleitetes Event: vdr:000
+
+ // remove from VDRs EPG => Löschsignal wird an alle vdr gesendet
+
+ usChanged = 'C', // => war mal aktiv wurde jedoch später zum Target,seine eigene ID muss also aus dem epg gelöscht werden. C ist also eine Ausprägung von T
+ usDelete = 'D', // => Sender oder Providerseitig gelöscht
+ usRemove = 'R', // => von uns gelöscht weil wir uns für ein anderes Event entschieden haben, zB eins mit Bildern oder Serieninformationen.
+
+ // don't care for VDRs EPG => werden von den VDRs nicht beachtet und nicht mal gesehen.
+
+ usInactive = 'I', // => inaktiv
+ usTarget = 'T', // => verknüftes Event zum Link, wird unter seiner ID mithilfe des Link im vdr geführt
+ usMergeSpare = 'S' // => ersatzevent welches für Multimerge ur Verfügung steht
+ };
+
+ // get lists for SQL 'in' statements
+
+ static const char* getDeletable() { return "'A','L','P','R','I'"; } // epg plugins
+ static const char* getNeeded() { return "'A','L','P','C','D','R'"; } // epg2vdr
+ static const char* getVisible() { return "'A','L','P'"; } // epghttpd
+
+ // checks
+
+ static int isNeeded(char c) { return strchr("ALPCDR", c) != 0; } // epgd2vdr
+ static int isRemove(char c) { return strchr("CDR", c) != 0; } // epgd2vdr
+
+};
+
+typedef cEventState Us; // remove later, not uses anymore
+
+//***************************************************************************
+// cUserTimes
+//***************************************************************************
+
+class cUserTimes
+{
+ public:
+
+ enum Mode
+ {
+ mUnknown = na,
+ mNow,
+ mNext,
+ mTime,
+ mSearch
+ };
+
+ struct UserTime
+ {
+ UserTime(const char* strTime, const char* strTitle = 0)
+ {
+ highlighted = yes;
+ mode = mUnknown;
+ *hhmmStr = 0;
+ title = 0;
+ hhmm = 0;
+ search = 0;
+ setTime(strTime, strTitle);
+ }
+
+ UserTime(const UserTime& cp)
+ {
+ highlighted = cp.highlighted;
+ mode = cp.mode;
+ hhmm = cp.hhmm;
+ strcpy(hhmmStr, cp.hhmmStr);
+ title = strdup(cp.title);
+ search = cp.search ? strdup(cp.search) : 0;
+ }
+
+ ~UserTime() { free(title); free(search); }
+
+ void setTime(const char* strTime, const char* strTitle)
+ {
+ hhmm = 0;
+ *hhmmStr = 0;
+
+ if (strTime[0] == '!')
+ {
+ highlighted = no;
+ strTime++;
+ }
+
+ if (strchr(strTime, ':'))
+ {
+ hhmm = atoi(strTime) * 100 + atoi(strchr(strTime, ':')+1);
+ sprintf(hhmmStr, "%02d:%02d", hhmm / 100, hhmm % 100);
+ mode = mTime;
+ }
+ else if (*strTime == '@')
+ {
+ highlighted = no;
+
+ if (strcmp(strTime, "@Now") == 0)
+ mode = mNow;
+ else if (strcmp(strTime, "@Next") == 0)
+ mode = mNext;
+ else
+ mode = mSearch;
+ }
+
+ // title
+
+ free(title);
+ title = 0;
+
+ if (!isEmpty(strTitle))
+ title = strdup(strTitle);
+ else if (!isEmpty(hhmmStr))
+ asprintf(&title, "%02d:%02d", hhmm / 100, hhmm % 100);
+
+ // search
+
+ if (*strTime == '@')
+ {
+ free(search);
+ search = strdup(strTime+1);
+ }
+ }
+
+ int getHHMM() const { return hhmm; }
+ const char* getHHMMStr() const { return hhmmStr; }
+ const char* getTitle() const { return title; }
+ const char* getSearch() const { return search; }
+ int getMode() const { return mode; }
+ const char* getHelpKey() const { return title; }
+ int isHighlighted() const { return highlighted; }
+
+ time_t getTime()
+ {
+ struct tm tmnow;
+ time_t now = time(0);
+
+ localtime_r(&now, &tmnow);
+ tmnow.tm_hour = hhmm / 100;
+ tmnow.tm_min = hhmm % 100;
+ tmnow.tm_sec = 0;
+
+ time_t ltime = mktime(&tmnow);
+
+ if (ltime < time(0)-tmeSecondsPerHour)
+ ltime += tmeSecondsPerDay;
+
+ return ltime;
+ }
+
+ int mode;
+ int highlighted;
+ char* title;
+ char* search;
+ int hhmm;
+ char hhmmStr[5+TB];
+ };
+
+ cUserTimes()
+ {
+ clear();
+ it = times.end();
+ }
+
+ void clear()
+ {
+ times.clear();
+ }
+
+ void add(const char* strTime, const char* strTitle = 0)
+ {
+ UserTime ut(strTime, strTitle);
+ times.push_back(ut);
+ }
+
+ UserTime* first()
+ {
+ it = times.begin();
+
+ if (it == times.end())
+ return 0;
+
+ return &(*it);
+ }
+
+ UserTime* next()
+ {
+ if (it == times.end())
+ it = times.begin();
+ else
+ it++;
+
+ if (it == times.end())
+ it = times.begin();
+
+ return &(*it);
+ }
+
+ UserTime* getFirst()
+ {
+ std::list<UserTime>::iterator i;
+
+ i = times.begin();
+
+ return &(*i);
+ }
+
+ UserTime* getNext()
+ {
+ std::list<UserTime>::iterator i = it;
+
+ if (i == times.end())
+ i = times.begin();
+ else
+ i++;
+
+ if (i == times.end())
+ i = times.begin();
+
+ return &(*i);
+ }
+
+ UserTime* current() { return &(*it); }
+
+ private:
+
+ std::list<UserTime>::iterator it;
+ std::list<UserTime> times;
+};
+
+//***************************************************************************
+// EPG Services
+//***************************************************************************
+
+#define EPG2VDR_UUID_SERVICE "epg2vdr-UuidService-v1.0"
+
+struct Epg2vdr_UUID_v1_0
+{
+ const char* uuid;
+};
+
+#define MYSQL_INIT_EXIT "Mysql_Init_Exit-v1.0"
+
+enum MysqlInitExitAction
+{
+ mieaInit = 0,
+ mieaExit = 1
+};
+
+struct Mysql_Init_Exit_v1_0
+{
+ MysqlInitExitAction action;
+};
+
+#endif // __EPGSERVICE_H
diff --git a/lib/imgtools.c b/lib/imgtools.c
new file mode 100644
index 0000000..63007e5
--- /dev/null
+++ b/lib/imgtools.c
@@ -0,0 +1,217 @@
+/*
+ * imgtools.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "imgtools.h"
+
+//***************************************************************************
+// Image converting stuff
+//***************************************************************************
+
+int fromJpeg(Imlib_Image& image, unsigned char* buffer, int size)
+{
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ int w, h;
+ DATA8 *ptr, *line[16], *data;
+ DATA32 *ptr2, *dest;
+ int x, y;
+
+ cinfo.err = jpeg_std_error(&jerr);
+
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, buffer, size);
+ jpeg_read_header(&cinfo, TRUE);
+ cinfo.do_fancy_upsampling = FALSE;
+ cinfo.do_block_smoothing = FALSE;
+
+ jpeg_start_decompress(&cinfo);
+
+ w = cinfo.output_width;
+ h = cinfo.output_height;
+
+ image = imlib_create_image(w, h);
+ imlib_context_set_image(image);
+
+ dest = ptr2 = imlib_image_get_data();
+ data = (DATA8*)malloc(w * 16 * cinfo.output_components);
+
+ for (int i = 0; i < cinfo.rec_outbuf_height; i++)
+ line[i] = data + (i * w * cinfo.output_components);
+
+ for (int l = 0; l < h; l += cinfo.rec_outbuf_height)
+ {
+ jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
+ int scans = cinfo.rec_outbuf_height;
+
+ if (h - l < scans)
+ scans = h - l;
+
+ ptr = data;
+
+ for (y = 0; y < scans; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ *ptr2 = (0xff000000) | ((ptr[0]) << 16) | ((ptr[1]) << 8) | (ptr[2]);
+ ptr += cinfo.output_components;
+ ptr2++;
+ }
+ }
+ }
+
+ free(data);
+
+ imlib_image_put_back_data(dest);
+
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ return success;
+}
+
+long toJpeg(Imlib_Image image, MemoryStruct* data, int quality)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ DATA32* ptr;
+ DATA8* buf;
+
+ imlib_context_set_image(image);
+
+ data->clear();
+
+ cinfo.err = jpeg_std_error(&jerr);
+
+ jpeg_create_compress(&cinfo);
+ jpeg_mem_dest(&cinfo, (unsigned char**)(&data->memory), &data->size);
+
+ cinfo.image_width = imlib_image_get_width();
+ cinfo.image_height = imlib_image_get_height();
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE);
+ jpeg_start_compress(&cinfo, TRUE);
+
+ // get data pointer
+
+ if (!(ptr = imlib_image_get_data_for_reading_only()))
+ return 0;
+
+ // allocate a small buffer to convert image data */
+
+ buf = (DATA8*)malloc(imlib_image_get_width() * 3 * sizeof(DATA8));
+
+ while (cinfo.next_scanline < cinfo.image_height)
+ {
+ // convert scanline from ARGB to RGB packed
+
+ for (int j = 0, i = 0; i < imlib_image_get_width(); i++)
+ {
+ buf[j++] = ((*ptr) >> 16) & 0xff;
+ buf[j++] = ((*ptr) >> 8) & 0xff;
+ buf[j++] = ((*ptr)) & 0xff;
+
+ ptr++;
+ }
+
+ // write scanline
+
+ jpeg_write_scanlines(&cinfo, (JSAMPROW*)(&buf), 1);
+ }
+
+ free(buf);
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ return data->size;
+}
+
+int scaleImageToJpegBuffer(Imlib_Image image, MemoryStruct* data, int width, int height)
+{
+ if (width && height)
+ {
+ Imlib_Image scaledImage;
+
+ imlib_context_set_image(image);
+
+ int imgWidth = imlib_image_get_width();
+ int imgHeight = imlib_image_get_height();
+ double ratio = (double)imgWidth / (double)imgHeight;
+
+ if ((double)width/(double)imgWidth < (double)height/(double)imgHeight)
+ height = (int)((double)width / ratio);
+ else
+ width = (int)((double)height * ratio);
+
+ scaledImage = imlib_create_image(width, height);
+ imlib_context_set_image(scaledImage);
+
+ imlib_context_set_color(240, 240, 240, 255);
+ imlib_image_fill_rectangle(0, 0, width, height);
+
+ imlib_blend_image_onto_image(image, 0, 0, 0,
+ imgWidth, imgHeight, 0, 0,
+ width, height);
+
+ toJpeg(scaledImage, data, 70);
+
+ imlib_context_set_image(scaledImage);
+ imlib_free_image();
+
+ tell(2, "Scaled image to %d/%d, now %d bytes", width, height, (int)data->size);
+ }
+ else
+ {
+ toJpeg(image, data, 70);
+ }
+
+ return success;
+}
+
+int scaleJpegBuffer(MemoryStruct* data, int width, int height)
+{
+ Imlib_Image image;
+
+ fromJpeg(image, (unsigned char*)data->memory, data->size);
+
+ scaleImageToJpegBuffer(image, data, width, height);
+
+ imlib_context_set_image(image);
+ imlib_free_image();
+
+ return success;
+}
+
+//***************************************************************************
+// Str Imlib Error
+//***************************************************************************
+
+const char* strImlibError(Imlib_Load_Error err)
+{
+ switch (err)
+ {
+ case IMLIB_LOAD_ERROR_NONE:
+ case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST: return "File does not exist";
+ case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY: return "File is directory";
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ: return "Permission denied to read";
+ case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT: return "No loader for file format";
+ case IMLIB_LOAD_ERROR_PATH_TOO_LONG: return "Path too long";
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT: return "Path component non existant";
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY: return "Path component not directory";
+ case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE: return "Path points outside address space";
+ case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS: return "Too many symbolic links";
+ case IMLIB_LOAD_ERROR_OUT_OF_MEMORY: return "Out of memory";
+ case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS: return "Out of file descriptors";
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE: return "Permission denied to write";
+ case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE: return "Out of disk space";
+ case IMLIB_LOAD_ERROR_UNKNOWN: return "Unknown format";
+ }
+
+ return "";
+}
diff --git a/lib/imgtools.h b/lib/imgtools.h
new file mode 100644
index 0000000..315afaf
--- /dev/null
+++ b/lib/imgtools.h
@@ -0,0 +1,31 @@
+/*
+ * imgtools.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __IMGTOOLS_H
+#define __IMGTOOLS_H
+
+#include <stdio.h>
+
+#define X_DISPLAY_MISSING 1
+
+#include <jpeglib.h>
+#include <Imlib2.h>
+
+#include "common.h"
+
+//***************************************************************************
+// Image Manipulating
+//***************************************************************************
+
+int fromJpeg(Imlib_Image& image, unsigned char* buffer, int size);
+long toJpeg(Imlib_Image image, MemoryStruct* data, int quality);
+int scaleImageToJpegBuffer(Imlib_Image image, MemoryStruct* data, int width = 0, int height = 0);
+int scaleJpegBuffer(MemoryStruct* data, int width = 0, int height = 0);
+const char* strImlibError(Imlib_Load_Error err);
+
+//***************************************************************************
+#endif // __IMGTOOLS_H
diff --git a/lib/json.c b/lib/json.c
new file mode 100644
index 0000000..0511dcd
--- /dev/null
+++ b/lib/json.c
@@ -0,0 +1,168 @@
+/*
+ * json.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "json.h"
+
+#ifdef USEJSON
+
+const char* charset = "utf-8"; // #TODO, move to configuration?
+
+//***************************************************************************
+// Copy JSON Object to Data Buffer
+//***************************************************************************
+
+int json2Data(json_t* obj, MemoryStruct* data, const char* encoding)
+{
+ int status = success;
+
+ // will be freed by data's dtor
+
+ data->memory = json_dumps(obj, JSON_PRESERVE_ORDER);
+ data->size = strlen(data->memory);
+ sprintf(data->contentType, "application/json; charset=%s", charset);
+
+ // gzip content if supported by browser ...
+
+ if (data->size && encoding && strstr(encoding, "gzip"))
+ status = data->toGzip();
+
+ return status;
+}
+
+//***************************************************************************
+// Add field to json object
+//***************************************************************************
+
+int addFieldToJson(json_t* obj, cDbValue* value, int ignoreEmpty, const char* extName)
+{
+ char* name;
+
+ if (!value || !value->getField())
+ return fail;
+
+ name = strdup(extName ? extName : value->getField()->getName());
+ toCase(cLower, name); // use always lower case names in json
+
+ switch (value->getField()->getFormat())
+ {
+ case cDBS::ffAscii:
+ case cDBS::ffText:
+ case cDBS::ffMText:
+ if (!ignoreEmpty || !isEmpty(value->getStrValue()))
+ json_object_set_new(obj, name, json_string(value->getStrValue()));
+ break;
+
+ case cDBS::ffInt:
+ case cDBS::ffUInt:
+ json_object_set_new(obj, name, json_integer(value->getIntValue()));
+ break;
+
+ case cDBS::ffFloat:
+ json_object_set_new(obj, name, json_real(value->getFloatValue()));
+ break;
+
+ default:
+ break;
+ }
+
+ free(name);
+
+ return success;
+}
+
+//***************************************************************************
+// Add field to json object
+//***************************************************************************
+
+int addFieldToJson(json_t* obj, cDbTable* table, const char* fname,
+ int ignoreEmpty, const char* extName)
+{
+ return addFieldToJson(obj, table->getValue(fname), ignoreEmpty, extName);
+}
+
+//***************************************************************************
+// Get Field From Json
+// - if a default is required put it into the row
+// before calling this function
+//***************************************************************************
+
+int getFieldFromJson(json_t* obj, cDbRow* row, const char* fname, const char* extName)
+{
+ cDbValue* value = row->getValue(fname);
+ char* jname;
+
+ if (!value)
+ return fail;
+
+ jname = strdup(!isEmpty(extName) ? extName : value->getField()->getName());
+ toCase(cLower, jname); // use always lower case names in json
+
+ switch (value->getField()->getFormat())
+ {
+ case cDBS::ffAscii:
+ case cDBS::ffText:
+ case cDBS::ffMText:
+ {
+ const char* v = getStringFromJson(obj, jname, "");
+ if (!isEmpty(v) || !value->isEmpty())
+ value->setValue(v);
+ break;
+ }
+
+ case cDBS::ffInt:
+ case cDBS::ffUInt:
+ {
+ int v = getIntFromJson(obj, jname, na);
+ const char* s = getStringFromJson(obj, jname, "");
+
+ if (s && strcmp(s, "null") == 0)
+ value->setNull();
+ else if (v != na || !value->isEmpty())
+ value->setValue(v);
+ break;
+ }
+
+// case cDBS::ffFloat: #TODO to be implemented
+// {
+// double v = getFloatFromJson(obj, jname, na);
+// if (v != na) value->setValue(v);
+// break;
+// }
+
+ default:
+ break;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Get Elements
+//***************************************************************************
+
+const char* getStringFromJson(json_t* obj, const char* name, const char* def)
+{
+ json_t* o = json_object_get(obj, name);
+
+ if (!o)
+ return def;
+
+ return json_string_value(o);
+}
+
+int getIntFromJson(json_t* obj, const char* name, int def)
+{
+ json_t* o = json_object_get(obj, name);
+
+ if (!o)
+ return def;
+
+ return json_integer_value(o);
+}
+
+//***************************************************************************
+#endif // USEJSON
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 0000000..dd66a93
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,36 @@
+/*
+ * json.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __JSON_H
+#define __JSON_H
+
+//***************************************************************************
+// Include
+//***************************************************************************
+
+#ifdef USEJSON
+
+#include <jansson.h>
+
+#include "db.h"
+
+//***************************************************************************
+// JSON Helper Functions
+//***************************************************************************
+
+int json2Data(json_t* obj, MemoryStruct* data, const char* encoding = 0);
+
+int addFieldToJson(json_t* obj, cDbTable* table, const char* fname, int ignoreEmpty = yes, const char* extName = 0);
+int addFieldToJson(json_t* obj, cDbValue* value, int ignoreEmpty = yes, const char* extName = 0);
+int getFieldFromJson(json_t* obj, cDbRow* row, const char* fname, const char* extName = 0);
+
+const char* getStringFromJson(json_t* obj, const char* name, const char* def = 0);
+int getIntFromJson(json_t* obj, const char* name, int def = na);
+
+#endif // USEJSON
+
+#endif // __JSON_H
diff --git a/lib/parameters.c b/lib/parameters.c
new file mode 100644
index 0000000..43a6fc5
--- /dev/null
+++ b/lib/parameters.c
@@ -0,0 +1,257 @@
+/*
+ * parameters.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "parameters.h"
+
+//***************************************************************************
+// Parameters
+// !!! Array initialize is epgd/epg2vdr specific
+// -> therefore it hase to be move to epg code outside lib!
+// maybe by using addItem Methods for each line
+//***************************************************************************
+
+cParameters::Parameter cParameters::parameters[] =
+{
+ // owner, name, type, default, regexp, readonly, visible
+
+ // --------------------------------------------
+ // epgd / epghttpd
+
+ { "epgd", "manualTimer2Done", ptBool, "1", "^[01]$", no, yes },
+ { "epgd", "timerJobFailedHistory", ptNum, "10", "^[0-9]{1,3}$", no, yes },
+
+ { "epgd", "logoUpperCase", ptBool, "0", "^[01]$", no, yes },
+ { "epgd", "logoById", ptBool, "0", "^[01]$", no, yes },
+ { "epgd", "logoSuffix", ptAscii, "png", "^.{1,5}$", no, yes },
+ { "epgd", "logoShowName", ptBool, "1", "^[01]$", no, yes },
+
+ { "epgd", "mailScript", ptAscii, BINDEST"/epgd-sendmail", "^.{0,150}$", no, yes },
+
+ { "epgd", "mergeStart", ptTime, "0", "^[0-9]{1,20}$", yes, no },
+ { "epgd", "lastMergeAt", ptTime, "0", "^[0-9]{1,20}$", yes, no },
+ { "epgd", "lastScrRefUpdate", ptTime, "0", "^[0-9]{1,20}$", yes, no },
+ { "epgd", "lastEpisodeRun", ptTime, "0", "^[0-9]{1,20}$", yes, no },
+ { "epgd", "lastEpisodeFullRun", ptTime, "0", "^[0-9]{1,20}$", yes, no },
+
+ { "epgd", "maxEventTime", ptTime, "0", "^[0-9]{1,20}$", yes, yes },
+ { "epgd", "minEventTime", ptTime, "0", "^[0-9]{1,20}$", yes, yes },
+
+ { "epgd", "md5", ptAscii, "", "^.{0,150}$", yes, no },
+
+ // --------------------------------------------
+ // epg2vdr / scraper2vdr
+
+ { "uuid", "lastEventsUpdateAt", ptNum, NULL, "^[0-9]{1,20}$", yes, yes },
+
+ // --------------------------------------------
+ // webif
+
+ { "webif", "needLogin", ptBool, "0", "^[01]$", no, yes },
+
+ // --------------------------------------------
+ // user
+
+ { "@", "chFormat", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "defaultVDRuuid", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "startPage", ptAscii, "menu_magazine", "^.{0,150}$", no, yes },
+ { "@", "timerDefaultVDRuuid", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "quickTimes", ptAscii, "Jetzt=@Now~Nächste=@Next~PrimeTime=20:20~EarlyNight=!22:20~MidNight=!00:00~LateNight=!02:00~Tipp=@TagesTipps~Action=@Action",
+ "^(~?[^=]+=!?(([0-1]?[0-9]|2[0-4]):[0-5]?[0-9]|@Now|@Next|@[A-Za-z0-9]*))*$",
+ no, yes },
+ { "@", "startWithSched", ptBool, "0", "^[01]$", no, yes },
+ { "@", "searchAdv", ptBool, "1", "^[01]$", no, yes },
+ { "@", "namingModeSerie", ptNum, "1", "^[0-6]$", no, yes },
+ { "@", "namingModeSearchSerie", ptNum, "1", "^[0-6]$", no, yes },
+ { "@", "namingModeMovie", ptNum, "1", "^[0-6]$", no, yes },
+ { "@", "namingModeSearchMovie", ptNum, "1", "^[0-6]$", no, yes },
+ { "@", "pickerFirstDay", ptNum, "1", "^[0-6]$", no, yes },
+ { "@", "sendTCC", ptBool, "1", "^[01]$", no, yes },
+ { "@", "constabelLoginPath", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "mailReceiver", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "magazinePan", ptNum, "10", "^[0-9]{1,2}$", no, yes },
+ { "@", "magazinePanDelay", ptNum, "400", "^[1-9][0-9]{2,3}$", no, yes },
+ { "@", "maxListEntries", ptNum, "100", "^[0-9]{0,3}$", no, yes },
+ { "@", "recordSubFolderSort", ptNum, "1", "^[1-6]$", no, yes },
+ { "@", "messageMailTypes", ptAscii, "FEW", "^[EWIF]{0,5}$" , no, yes },
+ { "@", "namingModeSerieTemplate", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "namingModeSearchSerieTemplate", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "namingModeMovieTemplate", ptAscii, "", "^.{0,150}$", no, yes },
+ { "@", "namingModeSearchMovieTemplate", ptAscii, "", "^.{0,150}$", no, yes },
+
+ { 0, 0, 0, 0, 0, 0, 0 }
+};
+
+//***************************************************************************
+// Get Definition
+//***************************************************************************
+
+cParameters::Parameter* cParameters::getDefinition(const char* owner, const char* name)
+{
+ if (isEmpty(owner) || isEmpty(name))
+ return 0;
+
+ // some specials for dynamic owner or name
+
+ if (owner[0] == '@')
+ owner = "@";
+
+ if (strcmp(owner, "epgd") == 0 && strstr(name, ".md5"))
+ name = "md5";
+
+ // lookup
+
+ for (int i = 0; parameters[i].name != 0; i++)
+ {
+ if (strcmp(parameters[i].owner, owner) == 0 && strcasecmp(parameters[i].name, name) == 0)
+ return &parameters[i];
+ }
+
+ tell(0, "Warning: Requested parameter '%s/%s' not known, ignoring", owner, name);
+
+ return 0;
+}
+
+//***************************************************************************
+// Object / Init / Exit
+//***************************************************************************
+
+cParameters::cParameters()
+{
+ parametersDb = 0;
+ selectParameters = 0;
+}
+
+int cParameters::initDb(cDbConnection* connection)
+{
+ int status = success;
+
+ parametersDb = new cDbTable(connection, "parameters");
+ if (parametersDb->open() != success) return fail;
+
+ // ----------
+ // select * from parameters
+ // where owner = ?
+
+ selectParameters = new cDbStatement(parametersDb);
+
+ selectParameters->build("select ");
+ selectParameters->bindAllOut();
+ selectParameters->build(" from %s", parametersDb->TableName());
+
+ status += selectParameters->prepare();
+
+ return status;
+}
+
+int cParameters::exitDb()
+{
+ delete parametersDb; parametersDb = 0;
+ delete selectParameters; selectParameters = 0;
+
+ return done;
+}
+
+//***************************************************************************
+// Get String Parameter
+//***************************************************************************
+
+int cParameters::getParameter(const char* owner, const char* name, char* value)
+{
+ int found;
+ Parameter* definition = getDefinition(owner, name);
+
+ if (value)
+ *value = 0;
+
+ if (!definition)
+ return no;
+
+ parametersDb->clear();
+ parametersDb->setValue("OWNER", owner);
+ parametersDb->setValue("NAME", name);
+
+ if ((found = parametersDb->find()))
+ {
+ if (value)
+ sprintf(value, "%s", parametersDb->getStrValue("Value"));
+ }
+ else if (value && definition->def)
+ {
+ sprintf(value, "%s", definition->def);
+ found = yes;
+ setParameter(owner, name, value);
+ }
+
+ parametersDb->reset();
+
+ return found;
+}
+
+//***************************************************************************
+// Get Integer Parameter
+//***************************************************************************
+
+int cParameters::getParameter(const char* owner, const char* name, long int& value)
+{
+ char txt[100]; *txt = 0;
+ int found;
+
+ found = getParameter(owner, name, txt);
+
+ if (!isEmpty(txt))
+ value = atol(txt);
+ else
+ value = 0;
+
+ return found;
+}
+
+//***************************************************************************
+// Set String Parameter
+//***************************************************************************
+
+int cParameters::setParameter(const char* owner, const char* name, const char* value)
+{
+ Parameter* definition = getDefinition(owner, name);
+
+ if (!definition)
+ return fail;
+
+ if (!value)
+ return fail;
+
+ tell(2, "Storing '%s' for '%s' with value '%s'", name, owner, value);
+
+ // validate parameter
+
+ if (definition->regexp)
+ {
+ if (rep(value, definition->regexp) != success)
+ {
+ tell(0, "Warning: Ignoring '%s' for parameter '%s/%s' don't match expression '%s'",
+ value, owner, name, definition->regexp);
+
+ return fail;
+ }
+ }
+
+ parametersDb->clear();
+ parametersDb->setValue("OWNER", owner);
+ parametersDb->setValue("NAME", name);
+ parametersDb->setValue("VALUE", value);
+
+ return parametersDb->store();
+}
+
+int cParameters::setParameter(const char* owner, const char* name, long int value)
+{
+ char txt[16];
+
+ snprintf(txt, sizeof(txt), "%ld", value);
+
+ return setParameter(owner, name, txt);
+}
diff --git a/lib/parameters.h b/lib/parameters.h
new file mode 100644
index 0000000..df7994b
--- /dev/null
+++ b/lib/parameters.h
@@ -0,0 +1,59 @@
+/*
+ * parameters.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __PARAMETERS_H
+#define __PARAMETERS_H
+
+#include "db.h"
+
+//***************************************************************************
+// Parameters
+//***************************************************************************
+
+class cParameters
+{
+ public:
+
+ enum Type
+ {
+ ptNum,
+ ptTime, // unix time
+ ptBool,
+ ptAscii
+ };
+
+ struct Parameter
+ {
+ const char* owner;
+ const char* name;
+ int type;
+ const char* def;
+ const char* regexp;
+ int readonly;
+ int visible;
+ };
+
+ cParameters();
+
+ int initDb(cDbConnection* connection);
+ int exitDb();
+
+ int getParameter(const char* owner, const char* name, char* value = 0);
+ int getParameter(const char* owner, const char* name, long int& value);
+ int setParameter(const char* owner, const char* name, const char* value);
+ int setParameter(const char* owner, const char* name, long int value);
+
+ protected:
+
+ cDbTable* parametersDb;
+ cDbStatement* selectParameters;
+
+ static Parameter parameters[];
+ static Parameter* getDefinition(const char* owner, const char* name);
+};
+
+#endif // __PARAMETERS_H
diff --git a/lib/python.c b/lib/python.c
new file mode 100644
index 0000000..da6f25e
--- /dev/null
+++ b/lib/python.c
@@ -0,0 +1,421 @@
+/*
+ * python.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "python.h"
+
+cDbTable* Python::globalEventsDb = 0;
+int Python::globalNamingMode = 0;
+const char* Python::globalTmplExpression = "";
+
+//***************************************************************************
+// Static Interface Methods (Table events)
+//***************************************************************************
+
+PyObject* Python::eventTitle(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("TITLE"));
+}
+
+PyObject* Python::eventShortText(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("SHORTTEXT"));
+}
+
+PyObject* Python::eventStartTime(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("i", 0);
+
+ return Py_BuildValue("i", globalEventsDb->getIntValue("STARTTIME"));
+}
+
+PyObject* Python::eventYear(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("YEAR"));
+}
+
+PyObject* Python::eventCategory(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("CATEGORY"));
+}
+
+PyObject* Python::episodeName(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODENAME"));
+}
+
+PyObject* Python::episodeShortName(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODESHORTNAME"));
+}
+
+PyObject* Python::episodePartName(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEPARTNAME"));
+}
+
+PyObject* Python::extracol1(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL1"));
+}
+
+PyObject* Python::extracol2(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL2"));
+}
+
+PyObject* Python::extracol3(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("s", "");
+
+ return Py_BuildValue("s", globalEventsDb->getStrValue("EPISODEEXTRACOL3"));
+}
+
+PyObject* Python::episodeSeason(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("i", na);
+
+ return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODESEASON"));
+}
+
+PyObject* Python::episodePart(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("i", na);
+
+ return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODEPART"));
+}
+
+PyObject* Python::episodeNumber(PyObject* /*self*/, PyObject* /*args*/)
+{
+ if (!globalEventsDb)
+ return Py_BuildValue("i", na);
+
+ return Py_BuildValue("i", globalEventsDb->getIntValue("EPISODENUMBER"));
+}
+
+PyObject* Python::namingMode(PyObject* /*self*/, PyObject* /*args*/)
+{
+ return Py_BuildValue("i", globalNamingMode);
+}
+
+PyObject* Python::tmplExpression(PyObject* /*self*/, PyObject* /*args*/)
+{
+ return Py_BuildValue("s", globalTmplExpression);
+}
+
+//***************************************************************************
+// The Methods
+//***************************************************************************
+
+PyMethodDef Python::eventMethods[] =
+{
+ { "title", Python::eventTitle, METH_VARARGS, "Return the events ..." },
+ { "shorttext", Python::eventShortText, METH_VARARGS, "Return the events ..." },
+ { "starttime", Python::eventStartTime, METH_VARARGS, "Return the events ..." },
+ { "year", Python::eventYear, METH_VARARGS, "Return the events ..." },
+ { "category", Python::eventCategory, METH_VARARGS, "Return the events ..." },
+
+ { "episodname", Python::episodeName, METH_VARARGS, "Return the events ..." },
+ { "shortname", Python::episodeShortName, METH_VARARGS, "Return the events ..." },
+ { "partname", Python::episodePartName, METH_VARARGS, "Return the events ..." },
+
+ { "season", Python::episodeSeason, METH_VARARGS, "Return the events ..." },
+ { "part", Python::episodePart, METH_VARARGS, "Return the events ..." },
+ { "number", Python::episodeNumber, METH_VARARGS, "Return the events ..." },
+
+ { "extracol1", Python::extracol1, METH_VARARGS, "Return the events ..." },
+ { "extracol2", Python::extracol2, METH_VARARGS, "Return the events ..." },
+ { "extracol3", Python::extracol3, METH_VARARGS, "Return the events ..." },
+
+ { "namingmode", Python::namingMode, METH_VARARGS, "Return the ..." },
+ { "tmplExpression", Python::tmplExpression, METH_VARARGS, "Return the ..." },
+
+ { 0, 0, 0, 0 }
+};
+
+#if PY_MAJOR_VERSION >= 3
+
+PyModuleDef Python::moduledef =
+{
+ PyModuleDef_HEAD_INIT, // m_base
+ "event", // m_name - name of module
+ 0, // m_doc - module documentation, may be NULL
+ -1, // m_size - size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
+ eventMethods, // m_methods
+ 0, // m_slots - array of slot definitions for multi-phase initialization, terminated by a {0, NULL} entry.
+ // when using single-phase initialization, m_slots must be NULL.
+ 0, // traverseproc m_traverse - traversal function to call during GC traversal of the module object, or NULL if not needed.
+ 0, // inquiry m_clear - clear function to call during GC clearing of the module object, or NULL if not needed.
+ 0 // freefunc m_free - function to call during deallocation of the module object or NULL if not needed.
+};
+
+#endif
+
+//***************************************************************************
+// Object
+//***************************************************************************
+
+Python::Python(const char* aFile, const char* aFunction)
+{
+ pFunc = 0;
+ pModule = 0;
+ result = 0;
+ file = strdup(aFile);
+ function = strdup(aFunction);
+}
+
+Python::~Python()
+{
+ exit();
+
+ free(result);
+ free(file);
+ free(function);
+}
+
+//***************************************************************************
+// Init / Exit
+//***************************************************************************
+
+int Python::init(const char* modulePath)
+{
+ PyObject* pName;
+
+ tell(0, "Initialize python script '%s/%s.py'", modulePath, file);
+
+ // register event methods
+
+#if PY_MAJOR_VERSION >= 3
+ PyImport_AppendInittab("event", &PyInitEvent);
+ Py_Initialize(); // initialize the Python interpreter
+ pName = PyUnicode_FromString(file);
+#else
+ Py_Initialize(); // initialize the Python interpreter
+ Py_InitModule("event", eventMethods);
+ pName = PyString_FromString(file);
+#endif
+
+ // add search path for Python modules
+
+ if (modulePath)
+ {
+ char* p;
+ asprintf(&p, "sys.path.append(\"%s\")", modulePath);
+ PyRun_SimpleString("import sys");
+ PyRun_SimpleString(p);
+ free(p);
+ }
+
+ // import the module
+
+ pModule = PyImport_Import(pName);
+ Py_DECREF(pName);
+
+ if (!pModule)
+ {
+ showError();
+ tell(0, "Failed to load '%s.py'", file);
+
+ return fail;
+ }
+
+ pFunc = PyObject_GetAttrString(pModule, function);
+
+ // pFunc is a new reference
+
+ if (!pFunc || !PyCallable_Check(pFunc))
+ {
+ if (PyErr_Occurred())
+ showError();
+
+ tell(0, "Cannot find function '%s'", function);
+
+ return fail;
+ }
+
+ return success;
+}
+
+int Python::exit()
+{
+ if (pFunc)
+ Py_XDECREF(pFunc);
+
+ if (pModule)
+ Py_DECREF(pModule);
+
+ Py_Finalize();
+
+ return success;
+}
+
+//***************************************************************************
+// Execute
+//***************************************************************************
+
+int Python::execute(cDbTable* eventsDb, int namingmode, const char* tmplExpression)
+{
+ PyObject* pValue;
+
+ free(result);
+ result = 0;
+
+ globalEventsDb = eventsDb;
+ globalNamingMode = namingmode;
+ globalTmplExpression = tmplExpression;
+
+ pValue = PyObject_CallObject(pFunc, 0);
+
+ if (!pValue)
+ {
+ showError();
+ tell(0, "Python: Call of function '%s()' failed", function);
+
+ return fail;
+ }
+
+#if PY_MAJOR_VERSION >= 3
+ // PyObject* strExc = PyObject_Repr(pValue);
+ // PyObject* pyStr = PyUnicode_AsEncodedString(strExc, "utf-8", "replace");
+ PyObject* pyStr = PyUnicode_AsEncodedString(pValue, "utf-8", "replace");
+ result = strdup(PyBytes_AsString(pyStr));
+ // Py_XDECREF(strExc);
+
+#else
+ result = strdup(PyString_AsString(pValue));
+#endif
+
+ tell(3, "Result of call: %s", result);
+
+ Py_DECREF(pValue);
+
+ return success;
+}
+
+
+//***************************************************************************
+// Dup Py String
+//***************************************************************************
+
+char* dupPyString(PyObject* pyObj)
+{
+ char* s;
+ PyObject* pyString;
+
+ if (!pyObj || !(pyString=PyObject_Str(pyObj)))
+ {
+ s = strdup("unknown error");
+ return s;
+ }
+
+#if PY_MAJOR_VERSION >= 3
+ PyObject* pyUni = PyObject_Repr(pyString); // Now a unicode object
+ PyObject* pyStr = PyUnicode_AsEncodedString(pyUni, "utf-8", "Error ~");
+ s = strdup(PyBytes_AsString(pyStr));
+ Py_XDECREF(pyUni);
+ Py_XDECREF(pyStr);
+#else
+ s = strdup(PyString_AsString(pyString));
+#endif
+
+ Py_DECREF(pyString);
+
+ return s;
+}
+
+//***************************************************************************
+// PY String From String
+//***************************************************************************
+
+PyObject* pyStringFromString(const char* s)
+{
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_FromString(s);
+#else
+ return PyString_FromString(s);
+#endif
+}
+
+//***************************************************************************
+// Show Error
+//***************************************************************************
+
+void Python::showError()
+{
+ char* error;
+ PyObject *ptype = 0, *pError = 0, *ptraceback = 0;
+ PyObject *moduleName, *pythModule;
+
+ PyErr_Fetch(&ptype, &pError, &ptraceback);
+
+ error = dupPyString(pError);
+ moduleName = pyStringFromString("traceback");
+
+ tell(0, "Python error was '%s'", error);
+
+ pythModule = PyImport_Import(moduleName);
+ Py_DECREF(moduleName);
+
+ // traceback
+
+ if (pythModule)
+ {
+ PyObject* pyFunc = PyObject_GetAttrString(pythModule, "format_exception");
+
+ if (pyFunc && PyCallable_Check(pyFunc))
+ {
+ PyObject *pythVal, *pystr;
+ char* s;
+
+ pythVal = PyObject_CallFunctionObjArgs(pyFunc, ptype, pError, ptraceback, 0);
+ if (pythVal)
+ {
+ s = dupPyString(pystr = PyObject_Str(pythVal));
+ tell(0, " %s", s);
+
+ free(s);
+ Py_DECREF(pystr);
+ Py_DECREF(pythVal);
+ }
+ }
+ }
+
+ free(error);
+ Py_DECREF(pError);
+ Py_DECREF(ptype);
+ Py_XDECREF(ptraceback);
+}
diff --git a/lib/python.h b/lib/python.h
new file mode 100644
index 0000000..eb50964
--- /dev/null
+++ b/lib/python.h
@@ -0,0 +1,78 @@
+/*
+ * python.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __PYTHON_H
+#define __PYTHON_H
+
+#include <Python.h>
+
+#include "db.h"
+
+//***************************************************************************
+// Class Python
+//***************************************************************************
+
+class Python
+{
+ public:
+
+ Python(const char* aFile, const char* aFunction);
+ ~Python();
+
+ int init(const char* modulePath = 0);
+ int exit();
+
+ int execute(cDbTable* eventsDb, int namingmode, const char* tmplExpression);
+
+ const char* getResult() { return result ? result : ""; }
+
+ protected:
+
+ static PyObject* eventTitle(PyObject* self, PyObject* args);
+ static PyObject* eventShortText(PyObject* self, PyObject* args);
+ static PyObject* eventStartTime(PyObject* self, PyObject* args);
+ static PyObject* eventYear(PyObject* self, PyObject* args);
+ static PyObject* eventCategory(PyObject* self, PyObject* args);
+
+ static PyObject* episodeName(PyObject* self, PyObject* args);
+ static PyObject* episodeShortName(PyObject* self, PyObject* args);
+ static PyObject* episodePartName(PyObject* self, PyObject* args);
+ static PyObject* extracol1(PyObject* self, PyObject* args);
+ static PyObject* extracol2(PyObject* self, PyObject* args);
+ static PyObject* extracol3(PyObject* self, PyObject* args);
+ static PyObject* episodeSeason(PyObject* self, PyObject* args);
+ static PyObject* episodePart(PyObject* self, PyObject* args);
+ static PyObject* episodeNumber(PyObject* self, PyObject* args);
+ static PyObject* namingMode(PyObject* self, PyObject* args);
+ static PyObject* tmplExpression(PyObject* self, PyObject* args);
+
+ void showError();
+
+ // data
+
+ PyObject* pModule;
+ PyObject* pFunc;
+
+ char* file;
+ char* function;
+ char* result;
+
+ // static stuff
+
+ static cDbTable* globalEventsDb;
+ static int globalNamingMode;
+ static const char* globalTmplExpression;
+ static PyMethodDef eventMethods[];
+
+#if PY_MAJOR_VERSION >= 3
+ static PyObject* PyInitEvent() { return PyModule_Create(&Python::moduledef); }
+ static PyModuleDef moduledef;
+#endif
+};
+
+//***************************************************************************
+#endif // __PYTHON_H
diff --git a/lib/pytst.c b/lib/pytst.c
new file mode 100644
index 0000000..a760318
--- /dev/null
+++ b/lib/pytst.c
@@ -0,0 +1,128 @@
+
+#include "python.h"
+
+
+#include "config.h"
+#include "common.h"
+#include "db.h"
+#include "epgservice.h"
+
+
+cDbTable* eventsDb = 0;
+cDbConnection* connection = 0;
+const char* logPrefix = "";
+
+//***************************************************************************
+// Init / Exit
+//***************************************************************************
+
+void initConnection()
+{
+ cDbConnection::init();
+
+ cDbConnection::setEncoding("utf8");
+ cDbConnection::setHost("localhost");
+
+ cDbConnection::setPort(3306);
+ cDbConnection::setName("epg2vdr");
+ cDbConnection::setUser("epg2vdr");
+ cDbConnection::setPass("epg");
+ cDbConnection::setConfPath("/etc/epgd/");
+
+ connection = new cDbConnection();
+}
+
+void exitConnection()
+{
+ cDbConnection::exit();
+
+ if (connection)
+ delete connection;
+}
+
+int init()
+{
+ eventsDb = new cDbTable(connection, "useevents");
+ if (eventsDb->open() != success) return fail;
+
+ return success;
+}
+
+int exit()
+{
+ delete eventsDb;
+ return done;
+}
+
+//***************************************************************************
+// Main
+//***************************************************************************
+
+int main(int argc, char** argv)
+{
+ cEpgConfig::logstdout = yes;
+ cEpgConfig::loglevel = 0;
+ int namingmode = tnmAuto;
+ const char* tmplExpression = "";
+
+ if (argc < 4)
+ {
+ tell(0, "Usage: pytst <cnt_source> <cnt_channnelid> <cn_eventid> [<namingmode>] [<template>]");
+ tell(0, " Get the data e.g. with:");
+ tell(0, " %s", "mysql -u epg2vdr -pepg -Depg2vdr --default-character-set=utf8' epgdb -e \"select cnt_source, cnt_eventid, cnt_channelid, sub_title, sub_shorttext from useevents where sub_title like '%whatever%';\"");
+ return 1;
+ }
+
+ if (argc >= 5)
+ namingmode = atoi(argv[4]);
+
+ if (argc >= 6)
+ tmplExpression = argv[5];
+
+ // at first allpy locale !!
+
+ setlocale(LC_CTYPE, "");
+
+ // read dictionary
+
+ if (dbDict.in("/etc/epgd/epg.dat") != success)
+ {
+ tell(0, "Invalid dictionary configuration, aborting!");
+ return 1;
+ }
+
+ initConnection();
+ init();
+
+ eventsDb->clear();
+ eventsDb->setValue("CNTSOURCE", argv[1]);
+ eventsDb->setValue("CHANNELID", argv[2]);
+ eventsDb->setBigintValue("CNTEVENTID", atol(argv[3]));
+
+ if (!eventsDb->find())
+ {
+ tell(0, "Event %s/%ld not found", argv[2], atol(argv[3]));
+ return 1;
+ }
+
+ tell(2, "Event '%s/%s' found",
+ eventsDb->getStrValue("TITLE"), eventsDb->getStrValue("SHORTTEXT"));
+
+ // Python stuff ..
+
+ Python py("recording", "name");
+
+ if (py.init("/etc/epgd") != success)
+ {
+ tell(0, "Init of python failed!");
+ return 1;
+ }
+
+ if (py.execute(eventsDb, namingmode, tmplExpression) == success)
+ tell(0, "Info: The recording name calculated by 'recording.py' (with namingmode %d) is '%s'", namingmode, py.getResult());
+
+ py.exit();
+ exitConnection();
+
+ return 0;
+}
diff --git a/lib/searchtimer.c b/lib/searchtimer.c
new file mode 100644
index 0000000..b516359
--- /dev/null
+++ b/lib/searchtimer.c
@@ -0,0 +1,1777 @@
+/*
+ * searchtimer.c
+ *
+ * See the README file for copyright information
+ *
+ */
+
+#include "python.h"
+#include "searchtimer.h"
+
+#include <list>
+
+//***************************************************************************
+//
+//***************************************************************************
+
+int cSearchTimer::searchField[] =
+{
+ sfTitle,
+ sfFolge,
+ sfDescription,
+ 0
+};
+
+const char* cSearchTimer::searchFieldName[] =
+{
+ "TITLE",
+ "SHORTTEXT",
+ "COMPLONGDESCRIPTION",
+ 0
+};
+
+int cSearchTimer::repeadCheckField[] =
+{
+ sfTitle, // 1
+ sfFolge, // 2
+ sfDescription, // 4
+
+ 0
+};
+
+const char* cSearchTimer::repeadCheckFieldName[] =
+{
+ "COMPTITLE", // <- "EPISODECOMPNAME" <- "EPISODECOMPSHORTNAME"
+ "COMPSHORTTEXT", // <- "EPISODECOMPPARTNAME"
+ "COMPLONGDESCRIPTION",
+
+ 0
+};
+
+//***************************************************************************
+// Class Search Timer
+//***************************************************************************
+
+cSearchTimer::cSearchTimer(cFrame* aParent)
+ : startValue("START", cDBS::ffInt, 10),
+ endValue("END", cDBS::ffInt, 10)
+{
+ parent = aParent;
+ connection = 0;
+ useeventsDb = 0;
+ searchtimerDb = 0;
+ timerDb = 0;
+ timersDoneDb = 0;
+ mapDb = 0;
+ vdrDb = 0;
+
+ selectChannelFromMap = 0;
+ selectDoneTimer = 0;
+ selectActiveSearchtimers = 0;
+ selectSearchtimerMaxModSp = 0;
+ selectAllTimer = 0;
+ selectRPTimerByEvent = 0;
+ selectTimerByEventId = 0;
+ selectConflictingTimers = 0;
+ selectFailedTimerByEvent = 0;
+ selectSearchTimerByName = 0;
+ selectSearchTimerById = 0;
+ selectEvent = 0;
+
+ lastSearchTimerUpdate = 0;
+
+ ptyRecName = new Python("recording", "name");
+}
+
+cSearchTimer::~cSearchTimer()
+{
+ delete ptyRecName;
+}
+
+int cSearchTimer::init(const char* confDir)
+{
+ if (ptyRecName->init(confDir) != success)
+ {
+ tell(0, "Error: Init of python script recording.py failed, aborting");
+ return fail;
+ }
+
+ return done;
+}
+
+int cSearchTimer::initDb()
+{
+ int status = success;
+
+ exitDb();
+
+ connection = new cDbConnection();
+
+ useeventsDb = new cDbTable(connection, "useevents");
+ if ((status = useeventsDb->open(yes) != success)) return status;
+
+ searchtimerDb = new cDbTable(connection, "searchtimers");
+ if (searchtimerDb->open(yes) != success) return fail;
+
+ timerDb = new cDbTable(connection, "timers");
+ if (timerDb->open(yes) != success) return fail;
+
+ timersDoneDb = new cDbTable(connection, "timersdone");
+ if (timersDoneDb->open(yes) != success) return fail;
+
+ mapDb = new cDbTable(connection, "channelmap");
+ if ((status = mapDb->open(yes)) != success) return status;
+
+ vdrDb = new cDbTable(connection, "vdrs");
+ if ((status = vdrDb->open(yes)) != success) return status;
+
+ // ----------
+ // select ...
+ // from searchtimers
+ // where state <> 'D' and active > 0
+ // and (type = 'R' or type is null)
+
+ selectActiveSearchtimers = new cDbStatement(searchtimerDb);
+
+ selectActiveSearchtimers->build("select ");
+ selectActiveSearchtimers->bindAllOut();
+ selectActiveSearchtimers->bind("UPDSP", cDBS::bndOut, ", ");
+ selectActiveSearchtimers->bind("INSSP", cDBS::bndOut, ", ");
+ selectActiveSearchtimers->build(" from %s where %s <> 'D' and %s > 0 and (%s = 'R' or %s is null)",
+ searchtimerDb->TableName(),
+ searchtimerDb->getField("STATE")->getDbName(),
+ searchtimerDb->getField("ACTIVE")->getDbName(),
+ searchtimerDb->getField("TYPE")->getDbName(),
+ searchtimerDb->getField("TYPE")->getDbName());
+
+ status += selectActiveSearchtimers->prepare();
+
+ // --------------------
+ // select max(modsp) from searchtimers
+
+ selectSearchtimerMaxModSp = new cDbStatement(searchtimerDb);
+
+ selectSearchtimerMaxModSp->build("select ");
+ selectSearchtimerMaxModSp->bind("MODSP", cDBS::bndOut, "max(");
+ selectSearchtimerMaxModSp->build(") from %s", searchtimerDb->TableName());
+
+ status += selectSearchtimerMaxModSp->prepare();
+
+ // ----------
+ // select * from searchtimers
+ // where state != 'D'
+ // and name = ?
+
+ selectSearchTimerByName = new cDbStatement(searchtimerDb);
+
+ selectSearchTimerByName->build("select ");
+ selectSearchTimerByName->bindAllOut();
+ selectSearchTimerByName->build(" from %s where %s != 'D'",
+ searchtimerDb->TableName(),
+ searchtimerDb->getField("STATE")->getDbName());
+ selectSearchTimerByName->bind("NAME", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectSearchTimerByName->prepare();
+
+ // ----------
+ // select * from searchtimers
+ // where state != 'D'
+ // and id = ?
+
+ selectSearchTimerById = new cDbStatement(searchtimerDb);
+
+ selectSearchTimerById->build("select ");
+ selectSearchTimerById->bindAllOut();
+ selectSearchTimerById->build(" from %s where %s != 'D'",
+ searchtimerDb->TableName(),
+ searchtimerDb->getField("STATE")->getDbName());
+ selectSearchTimerById->bind("ID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectSearchTimerById->prepare();
+
+ // ----------
+ // select id
+ // from timersdone
+ // ...
+
+ selectDoneTimer = new cDbStatement(timersDoneDb);
+
+ // selectDoneTimer - will build and prepared later at runtime ...
+
+ // ----------
+ // select channelname
+ // from channelmap
+
+ selectChannelFromMap = new cDbStatement(mapDb);
+
+ selectChannelFromMap->build("select ");
+ selectChannelFromMap->bind("CHANNELNAME", cDBS::bndOut);
+ selectChannelFromMap->build(" from %s where ", mapDb->TableName());
+ selectChannelFromMap->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet);
+
+ status += selectChannelFromMap->prepare();
+
+ // select t.*, v.name, v.tunercount
+ // from timers t, vdrs v
+ // where t.state in ('P','R')
+ // and t.vdruuid = v.uuid
+ // order by t.day, t.starttime
+
+ selectAllTimer = new cDbStatement(timerDb);
+
+ selectAllTimer->build("select ");
+ selectAllTimer->setBindPrefix("t.");
+ selectAllTimer->bindAllOut();
+ selectAllTimer->setBindPrefix("v.");
+ selectAllTimer->bind(vdrDb, "NAME", cDBS::bndOut, ", ");
+ selectAllTimer->bind(vdrDb, "TUNERCOUNT", cDBS::bndOut, ", ");
+ selectAllTimer->build(" from %s t, %s v ", timerDb->TableName(), vdrDb->TableName());
+ selectAllTimer->build(" where t.%s in ('P','R')", timerDb->getField("STATE")->getDbName());
+ selectAllTimer->build(" and t.VDRUUID = v.UUID");
+ selectAllTimer->build(" order by t.%s, t.%s",
+ timerDb->getField("DAY")->getDbName(),
+ timerDb->getField("STARTTIME")->getDbName());
+
+ status += selectAllTimer->prepare();
+
+ // select *
+ // from timers where
+ // eventid = ?
+
+ selectRPTimerByEvent = new cDbStatement(timerDb);
+
+ selectRPTimerByEvent->build("select ");
+ selectRPTimerByEvent->bindAllOut();
+ selectRPTimerByEvent->build(" from %s where ", timerDb->TableName());
+ selectRPTimerByEvent->bind("EVENTID", cDBS::bndIn | cDBS::bndSet);
+ status += selectRPTimerByEvent->prepare();
+
+ // select *
+ // from timers where
+ // state not in ('D','E')
+ // eventid = ? and channelid = ?
+
+ selectTimerByEventId = new cDbStatement(timerDb);
+
+ selectTimerByEventId->build("select ");
+ selectTimerByEventId->bindAllOut();
+ selectTimerByEventId->build(" from %s where ", timerDb->TableName());
+ selectTimerByEventId->build(" %s not in ('D', 'E')", timerDb->getField("STATE")->getDbName());
+ selectTimerByEventId->bind("EVENTID", cDBS::bndIn | cDBS::bndSet, " and ");
+ selectTimerByEventId->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectTimerByEventId->prepare();
+
+/*
+ // select * from timers
+ // where state in ('P','R')
+ // and active = 1
+ // and day + starttime div 100 * 60 * 60 + starttime % 100 * 60 >= ?
+ // and day + starttime div 100 * 60 * 60 + starttime % 100 * 60 <= ?
+ // and vdruuid = ?
+ // group by SUBSTRING_INDEX(channelid, '-', 3);
+
+ selectConflictingTimers = new cDbStatement(timerDb);
+
+ selectConflictingTimers->build("select ");
+ selectConflictingTimers->bindAllOut();
+ selectConflictingTimers->build(" from %s where %s in ('P','R')",
+ timerDb->TableName(), timerDb->getField("STATE")->getDbName());
+ selectConflictingTimers->build(" and active = 1");
+ selectConflictingTimers->bindText("day + starttime div 100 * 60 * 60 + starttime % 100 * 60", &startValue, ">=", " and ");
+ selectConflictingTimers->bindText("day + starttime div 100 * 60 * 60 + starttime % 100 * 60", &endValue, "<=", " and ");
+ selectConflictingTimers->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+ selectConflictingTimers->build(" group by SUBSTRING_INDEX(channelid, '-', 3)");
+
+ status += selectConflictingTimers->prepare();
+*/
+
+ // select * from timers
+ // where state in ('P','R')
+ // and active = 1
+ // and day + starttime div 100 * 60 * 60 + starttime % 100 * 60 >= ?
+ // and day + starttime div 100 * 60 * 60 + starttime % 100 * 60 <= ?
+ // and vdruuid = ?
+
+ selectConflictingTimers = new cDbStatement(timerDb);
+
+ selectConflictingTimers->build("select ");
+ selectConflictingTimers->bindAllOut();
+ selectConflictingTimers->build(" from %s where %s in ('P','R')",
+ timerDb->TableName(), timerDb->getField("STATE")->getDbName());
+ selectConflictingTimers->build(" and active = 1");
+ selectConflictingTimers->bindText("day + starttime div 100 * 60 * 60 + starttime % 100 * 60", &startValue, ">=", " and ");
+ selectConflictingTimers->bindText("day + starttime div 100 * 60 * 60 + starttime % 100 * 60", &endValue, "<=", " and ");
+ selectConflictingTimers->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectConflictingTimers->prepare();
+
+ // select * from timers
+ // where
+ // eventid = ?
+ // and channelid = ?
+ // and vdruuid = ?
+ // and state = ?
+
+ selectFailedTimerByEvent = new cDbStatement(timerDb);
+
+ selectFailedTimerByEvent->build("select ");
+ selectFailedTimerByEvent->bindAllOut();
+ selectFailedTimerByEvent->build(" from %s where ", timerDb->TableName());
+ selectFailedTimerByEvent->bind("EVENTID", cDBS::bndIn | cDBS::bndSet);
+ selectFailedTimerByEvent->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet, " and ");
+ selectFailedTimerByEvent->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+ selectFailedTimerByEvent->bind("STATE", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ selectFailedTimerByEvent->prepare();
+
+ // select *
+ // from eventsviewplain where
+ // useid = ?
+
+ selectEvent = new cDbStatement(useeventsDb);
+
+ selectEvent->build("select ");
+ selectEvent->bindAllOut();
+ selectEvent->build(" from eventsviewplain where ");
+ selectEvent->bind("USEID", cDBS::bndIn | cDBS::bndSet);
+
+ status += selectEvent->prepare();
+
+ // ----------
+
+ if (status != success)
+ {
+ tell(0, "Error: At least %d statements not prepared successfully", status*-1);
+ return status;
+ }
+
+ return success;
+}
+
+int cSearchTimer::exitDb()
+{
+ if (connection)
+ {
+ delete selectActiveSearchtimers; selectActiveSearchtimers = 0;
+ delete selectSearchtimerMaxModSp; selectSearchtimerMaxModSp = 0;
+ delete selectDoneTimer; selectDoneTimer = 0;
+ delete selectChannelFromMap; selectChannelFromMap = 0;
+ delete selectAllTimer; selectAllTimer = 0;
+ delete selectConflictingTimers; selectConflictingTimers = 0;
+ delete selectRPTimerByEvent; selectRPTimerByEvent = 0;
+ delete selectTimerByEventId; selectTimerByEventId = 0;
+ delete selectFailedTimerByEvent; selectFailedTimerByEvent = 0;
+ delete selectSearchTimerByName; selectSearchTimerByName = 0;
+ delete selectSearchTimerById; selectSearchTimerById = 0;
+ delete selectEvent; selectEvent = 0;
+
+ delete mapDb; mapDb = 0;
+ delete useeventsDb; useeventsDb = 0;
+ delete searchtimerDb; searchtimerDb = 0;
+ delete timerDb; timerDb = 0;
+ delete timersDoneDb; timersDoneDb = 0;
+ delete vdrDb; vdrDb = 0;
+
+ delete connection; connection = 0;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Check Connection
+//***************************************************************************
+
+int cSearchTimer::checkConnection()
+{
+ static int retry = 0;
+
+ // check connection
+
+ if (!dbConnected(yes))
+ {
+ // try to connect
+
+ tell(0, "Trying to re-connect to database!");
+ retry++;
+
+ if (initDb() != success)
+ {
+ tell(0, "Retry #%d failed!", retry);
+ exitDb();
+
+ return fail;
+ }
+
+ retry = 0;
+ tell(0, "Connection established successfull!");
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Any Search Timer Modified
+//***************************************************************************
+
+int cSearchTimer::modified()
+{
+ int modsp = 0;
+
+ if (selectSearchtimerMaxModSp->find())
+ modsp = searchtimerDb->getIntValue("MODSP");
+
+ selectSearchtimerMaxModSp->freeResult();
+
+ return modsp && modsp > lastSearchTimerUpdate;
+}
+
+//***************************************************************************
+// Prepare Search Statement
+//***************************************************************************
+
+cDbStatement* cSearchTimer::prepareSearchStatement(cDbRow* searchTimer)
+{
+ cDbTable* db = useeventsDb;
+
+ cDbStatement* select = new cDbStatement(db);
+
+ const char* searchOp = "=";
+ const char* expression = searchTimer->getStrValue("EXPRESSION");
+ const char* expression1 = searchTimer->getStrValue("EXPRESSION1");
+ const char* episodename = searchTimer->getStrValue("EPISODENAME");
+ const char* season = searchTimer->getStrValue("SEASON");
+ const char* seasonpart = searchTimer->getStrValue("SEASONPART");
+ const char* category = searchTimer->getStrValue("CATEGORY");
+ const char* genre = searchTimer->getStrValue("GENRE");
+ const char* tipp = searchTimer->getStrValue("TIPP");
+ const char* year = searchTimer->getStrValue("YEAR");
+ const char* chformat = searchTimer->getStrValue("CHFORMAT");
+
+ int noepgmatch = searchTimer->getIntValue("NOEPGMATCH");
+ int searchmode = searchTimer->getIntValue("SEARCHMODE");
+ int searchfields = searchTimer->getIntValue("SEARCHFIELDS");
+ int searchfields1 = searchTimer->getIntValue("SEARCHFIELDS1");
+ int casesensitiv = searchTimer->getIntValue("CASESENSITIV");
+ int weekdays = searchTimer->getValue("WEEKDAYS")->isNull() ? (int)na : searchTimer->getIntValue("WEEKDAYS");
+
+ switch (searchmode)
+ {
+ case smExact: searchOp = casesensitiv ? "= BINARY" : "="; break;
+ case smRegexp: searchOp = casesensitiv ? "regexp BINARY" : "regexp"; break;
+ case smLike: searchOp = casesensitiv ? "like BINARY" : "like"; break;
+ case smContained: searchOp = casesensitiv ? "like BINARY" : "like"; break;
+ }
+
+ select->build("select ");
+ select->bindAllOut();
+ select->setBindPrefix("c.");
+ select->bind(mapDb, "FORMAT", cDBS::bndOut, ", ");
+ select->clrBindPrefix();
+ select->build(" from eventsviewplain e, (select distinct channelid,channelname,format,ord,visible from %s) c where ",
+ mapDb->TableName());
+ select->build("e.%s = c.%s",
+ db->getField("CHANNELID")->getDbName(),
+ mapDb->getField("CHANNELID")->getDbName());
+ select->build(" and e.updflg in (%s)", cEventState::getVisible());
+ select->build(" and e.cnt_starttime >= unix_timestamp()-120"); // not more than 2 minutes running
+
+ // search fields 1
+
+ if (!isEmpty(expression) && strcmp(expression, "%") != 0 && strcmp(expression, "%%") != 0 && searchfields)
+ {
+ select->build(" and (");
+
+ for (int i = 0, n = 0; searchField[i]; i++)
+ {
+ if (!db->getField(searchFieldName[i]))
+ tell(0, "Fatal: Search field '%s' not known!", searchFieldName[i]);
+
+ else if (searchfields & searchField[i])
+ {
+ select->build("%s(%s %s '%s%s%s')", n++ ? " or " : "",
+ db->getField(searchFieldName[i])->getDbName(),
+ searchOp,
+ searchmode == smContained ? "%" : "",
+ connection->escapeSqlString(expression).c_str(),
+ searchmode == smContained ? "%" : "");
+ }
+ }
+
+ select->build(")");
+ }
+
+ // search fields 2
+
+ if (!isEmpty(expression1) && strcmp(expression1, "%") != 0 && strcmp(expression1, "%%") != 0 && searchfields1)
+ {
+ select->build(" and (");
+
+ for (int i = 0, n = 0; searchField[i]; i++)
+ {
+ if (!db->getField(searchFieldName[i]))
+ tell(0, "Fatal: Search field '%s' not known!", searchFieldName[i]);
+
+ else if (searchfields1 & searchField[i])
+ {
+ select->build("%s(%s %s '%s%s%s')", n++ ? " or " : "",
+ db->getField(searchFieldName[i])->getDbName(),
+ searchOp,
+ searchmode == smContained ? "%" : "",
+ connection->escapeSqlString(expression1).c_str(),
+ searchmode == smContained ? "%" : "");
+ }
+ }
+
+ select->build(")");
+ }
+
+ // Channel Format (CHFORMAT)
+
+ if (!isEmpty(chformat))
+ {
+ std::string format;
+
+ format = "'" + strReplace(",", "','", chformat) + "'";
+
+ select->build(" and (");
+
+ select->build(" c.%s in (%s) ",
+ mapDb->getField("FORMAT")->getDbName(),
+ format.c_str());
+
+ select->build(")");
+ }
+
+ // Kategorie 'Spielfilm','Serie' (CATEGORY)
+
+ if (!isEmpty(category))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("CATEGORY")->getDbName());
+
+ select->build(" %s in (%s) ",
+ db->getField("CATEGORY")->getDbName(),
+ category);
+
+ select->build(")");
+ }
+
+ // Genre 'Krimi','Action' (GENRE)
+
+ if (!isEmpty(genre))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("GENRE")->getDbName());
+
+ select->build(" %s in (%s) ",
+ db->getField("GENRE")->getDbName(),
+ genre);
+
+ select->build(")");
+ }
+
+ // Tipp (TIPP)
+
+ if (!isEmpty(tipp))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("TIPP")->getDbName());
+
+ select->build(" %s in (%s) ",
+ db->getField("TIPP")->getDbName(),
+ tipp);
+
+ select->build(")");
+ }
+
+ // Serien Titel (EPISODENAME)
+
+ if (!isEmpty(episodename))
+ {
+ select->build(" and (");
+
+ select->build(" %s = BINARY '%s' or (%s is null and %s = BINARY '%s')",
+ db->getField("EPISODENAME")->getDbName(),
+ connection->escapeSqlString(episodename).c_str(),
+ db->getField("EPISODENAME")->getDbName(),
+ db->getField("TITLE")->getDbName(),
+ connection->escapeSqlString(episodename).c_str());
+
+ select->build(")");
+ }
+
+ // Staffel like 3-5 (SEASON)
+
+ if (!isEmpty(season))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("EPISODESEASON")->getDbName());
+
+ select->build(" %s between %d and %d ",
+ db->getField("EPISODESEASON")->getDbName(),
+ rangeFrom(season), rangeTo(season));
+
+ select->build(")");
+ }
+
+ // Staffelfolge (SEASONPART)
+
+ if (!isEmpty(seasonpart))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("EPISODEPART")->getDbName());
+
+ select->build(" %s between %d and %d ",
+ db->getField("EPISODEPART")->getDbName(),
+ rangeFrom(seasonpart), rangeTo(seasonpart));
+
+ select->build(")");
+ }
+
+ // Jahr (YEAR)
+
+ if (!isEmpty(year))
+ {
+ select->build(" and (");
+
+ if (noepgmatch)
+ select->build("%s is null or ", db->getField("YEAR")->getDbName());
+
+ select->build(" %s between %d and %d ",
+ db->getField("YEAR")->getDbName(),
+ rangeFrom(year), rangeTo(year));
+
+ select->build(")");
+ }
+
+ if (weekdays > 0)
+ {
+ select->build(" and ");
+ select->build("(%d & (1 << weekday(from_unixtime(%s)))) <> 0",
+ weekdays, db->getField("STARTTIME")->getDbName());
+ }
+
+ select->build(" order by e.cnt_starttime, c.ord");
+
+ if (select->prepare() != success)
+ {
+ delete select;
+ select = 0;
+ tell(0, "Error: AUTOTIMER: Prepare of statement for searchtimer failed, skipping");
+ return 0;
+ }
+
+ const char* p = strstr(select->asText(), " from ");
+
+ tell(2, "AUTOTIMER: Search statement [%s;]", p ? p : select->asText());
+
+ return select;
+}
+
+//***************************************************************************
+// Check Timers
+// - check existing, pending timers against epg
+// - check existing, pending timers against searchtimers
+//***************************************************************************
+
+int cSearchTimer::checkTimers()
+{
+ int count = 0;
+
+ tell(1, "Checking timers against actual epg and serarchtimer settings");
+
+ if (checkConnection() != success)
+ return 0;
+
+ timerDb->clear();
+ vdrDb->clear();
+
+ for (int f = selectAllTimer->find(); f; f = selectAllTimer->fetch())
+ {
+ long aid = na;
+ int namingmode = timerDb->getIntValue("NAMINGMODE");
+ const char* tmplExpression = timerDb->getStrValue("TEMPLATE");
+
+ // check only active and pending timers with eventid
+
+ if (!timerDb->getIntValue("ACTIVE") || !timerDb->hasValue("STATE", "P") || !timerDb->getIntValue("EVENTID"))
+ continue;
+
+ if (timerDb->hasValue("SOURCE", "epgs")) // 'epgs' => epgsearch plugin
+ continue;
+
+ // -------------------
+ // lookups
+
+ if (timerDb->getIntValue("AUTOTIMERID"))
+ {
+ searchtimerDb->clear();
+ searchtimerDb->setValue("ID", timerDb->getIntValue("AUTOTIMERID"));
+
+ if (selectSearchTimerById->find())
+ {
+ aid = searchtimerDb->getIntValue("ID");
+ namingmode = searchtimerDb->getIntValue("NAMINGMODE");
+ tmplExpression = timerDb->getStrValue("TEMPLATE");
+ }
+ }
+
+ tell(2, "Checking timer(%ld) of event (%ld) against EPG",
+ timerDb->getIntValue("ID"), timerDb->getIntValue("EVENTID"));
+
+ useeventsDb->clear();
+ useeventsDb->setValue("USEID", timerDb->getIntValue("EVENTID"));
+
+ if (!selectEvent->find())
+ {
+ if (aid != na)
+ {
+ parent->message(0, 'W', "EPGD: Timer action", "Event (%ld) '%s' of timer (%ld) '%s' / autotimer (%ld) "
+ "not found; timer rejected!",
+ timerDb->getIntValue("EVENTID"), useeventsDb->getStrValue("TITLE"),
+ timerDb->getIntValue("ID"), timerDb->getStrValue("FILE"), aid);
+ modifyTimer(timerDb, taReject);
+ count++;
+ }
+ else
+ {
+ int warningCount = timerDb->getIntValue("WRNCOUNT");
+
+ if (!warningCount)
+ {
+ warningCount++;
+ parent->message(0, 'W', "EPGD: Timer action", "Event (%ld) '%s' of timer (%ld) '%s' not found."
+ "It's a manual timer, take your own solution!",
+ timerDb->getIntValue("EVENTID"), useeventsDb->getStrValue("TITLE"),
+ timerDb->getIntValue("ID"), timerDb->getStrValue("FILE"));
+
+ timerDb->setValue("WRNCOUNT", warningCount);
+ timerDb->update();
+ }
+ }
+
+ continue;
+ }
+
+ // ------------------------
+ // work - check timer data
+
+ // Check Start Time
+
+ if (!timerDb->hasValue("_STARTTIME", useeventsDb->getIntValue("STARTTIME")))
+ {
+ // if difference < 2 minutes adjust silent
+
+ if (abs(timerDb->getIntValue("_STARTTIME") - useeventsDb->getIntValue("STARTTIME")) > 2*tmeSecondsPerMinute)
+ parent->message(0, 'I', "EPGD: Timer action", "Info: Starttime of event (%ld) '%s' changed from '%s' to '%s', updating timer (%ld)",
+ timerDb->getIntValue("EVENTID"), useeventsDb->getStrValue("TITLE"),
+ l2pTime(timerDb->getIntValue("_STARTTIME")).c_str(), l2pTime(useeventsDb->getIntValue("STARTTIME")).c_str(),
+ timerDb->getIntValue("ID"));
+
+ modifyTimer(timerDb, taAdjust);
+ count++;
+ }
+
+ // Check recording name
+
+ if (namingmode != tnmDefault)
+ {
+ // execute python - calc recording name
+
+ if (ptyRecName->execute(useeventsDb, namingmode, tmplExpression) == success)
+ {
+ if (!timerDb->hasValue("FILE", ptyRecName->getResult()))
+ {
+ parent->message(0, 'I', "EPGD: Timer action", "Calculated name of event (%ld) changed from '%s' to '%s', updating timer (%ld)!",
+ timerDb->getIntValue("EVENTID"), timerDb->getStrValue("FILE"),
+ ptyRecName->getResult(), timerDb->getIntValue("ID"));
+
+ timerDb->setValue("FILE", ptyRecName->getResult());
+ modifyTimer(timerDb, taModify);
+ count++;
+ }
+ }
+ }
+
+ selectEvent->freeResult();
+ selectSearchTimerById->freeResult();
+ }
+
+ selectAllTimer->freeResult();
+
+ tell(1, "Timers check done");
+
+ return count;
+}
+
+//***************************************************************************
+// Modify Timer
+//***************************************************************************
+
+int cSearchTimer::modifyTimer(cDbTable* timerDb, TimerAction action)
+{
+ // create a 'M'odify request ...
+
+ timerDb->setCharValue("ACTION", action);
+ timerDb->getValue("STATE")->setNull();
+ timerDb->update();
+
+ tell(0, "Created '%s' request for timer (%ld) at vdr '%s'",
+ toName(action),
+ timerDb->getIntValue("ID"), timerDb->getStrValue("VDRUUID"));
+
+ // triggerVdrs("TIMERJOB", timerDb->getStrValue("VDRUUID"));
+
+ return success;
+}
+
+//***************************************************************************
+// Match Criterias
+//***************************************************************************
+
+int cSearchTimer::matchCriterias(cDbRow* searchTimer, cDbRow* event)
+{
+ const char* channelids = searchTimer->getStrValue("CHANNELIDS");
+ int chexclude = searchTimer->getIntValue("CHEXCLUDE");
+ int rangeStart = searchTimer->getValue("STARTTIME")->isNull() ? (int)na : searchTimer->getIntValue("STARTTIME");
+ int rangeEnd = searchTimer->getValue("ENDTIME")->isNull() ? (int)na : searchTimer->getIntValue("ENDTIME");
+ int nextDays = searchTimer->getValue("NEXTDAYS")->isNull() ? (int)na : searchTimer->getIntValue("NEXTDAYS");
+
+ const char* channelid = event->getStrValue("CHANNELID");
+ time_t starttime = event->getIntValue("STARTTIME");
+ int hhmm = l2hhmm(starttime);
+
+ // check if channel known to VDRs
+
+ mapDb->clear();
+ mapDb->setValue("CHANNELID", channelid);
+
+ if (!selectChannelFromMap->find() || mapDb->getIntValue("UNKNOWNATVDR") > 0)
+ {
+ mapDb->reset();
+ tell(3, "AUTOTIMER: Skipping hit, channelid '%s' is unknown at least on one VDR!",
+ event->getStrValue("CHANNELID"));
+ return no;
+ }
+
+ mapDb->reset();
+
+ // check channel matches
+
+ if (!isEmpty(channelids))
+ {
+ if (!chexclude && !strstr(channelids, channelid))
+ {
+ tell(3, "AUTOTIMER: Skipping hit due to channelid - '%s' not in '%s'",
+ event->getStrValue("CHANNELID"), channelids);
+ return no;
+ }
+ else if (chexclude && strstr(channelids, channelid))
+ {
+ tell(3, "AUTOTIMER: Skipping hit due to channelid - '%s' it in '%s'",
+ event->getStrValue("CHANNELID"), channelids);
+ return no;
+ }
+ }
+
+ // check start
+
+ if (rangeStart > 0 && hhmm < rangeStart)
+ {
+ tell(2, "AUTOTIMER: Skipping due to range (start before range)");
+ return no;
+ }
+
+ if (rangeEnd > 0 && hhmm > rangeEnd)
+ {
+ tell(3, "AUTOTIMER: Skipping due to range (start behind range)");
+ return no;
+ }
+
+ if (nextDays > 0 && event->getIntValue("STARTTIME") > time(0) + tmeSecondsPerDay * nextDays)
+ {
+ tell(3, "AUTOTIMER: Skipping event starting at '%s' due to nextdays of (%d) (start behind range)",
+ l2pTime(event->getIntValue("STARTTIME")).c_str(), nextDays);
+ return no;
+ }
+
+ // check range ... is start event in nextdays ...
+
+ return yes;
+}
+
+//***************************************************************************
+// Get Search Matches
+//***************************************************************************
+
+int cSearchTimer::getSearchMatches(cDbRow* searchTimer, json_t* obj, int start, int max)
+{
+ cDbStatement* select = 0;
+ long searchtimerid = searchTimer->getIntValue("ID");
+ const char* searchtimername = searchTimer->getStrValue("NAME");
+ int count = 0;
+ int end = max != na ? start + max : INT_MAX;
+
+ if (checkConnection() != success)
+ return fail;
+
+ searchtimerDb->clear();
+
+ if (searchtimerid > 0)
+ {
+ searchtimerDb->setValue("ID", searchtimerid);
+
+ if (!searchtimerDb->find())
+ {
+ tell(0, "Warning: Searchtimer %ld not found!", searchtimerid);
+ return fail;
+ }
+
+ searchTimer = searchtimerDb->getRow();
+ }
+ else if (!isEmpty(searchtimername))
+ {
+ searchtimerDb->setValue("NAME", searchtimername);
+
+ if (!selectSearchTimerByName->find())
+ {
+ tell(0, "Warning: Searchtimer '%s' not found!", searchtimername);
+ return fail;
+ }
+
+ searchTimer = searchtimerDb->getRow();
+ }
+
+ if (!(select = prepareSearchStatement(searchTimer)))
+ return fail;
+
+ json_t* oEvents = json_array();
+
+ useeventsDb->clear();
+
+ for (int res = select->find(); res; res = select->fetch())
+ {
+ useeventsDb->find(); // get all fields ..
+
+ if (!matchCriterias(searchTimer, useeventsDb->getRow()))
+ continue;
+
+ count++;
+
+ if (count < start || count >= end)
+ continue;
+
+ // add to json object
+
+ json_t* oData = json_object();
+
+ addFieldToJson(oData, useeventsDb, "USEID", no, "id");
+ addFieldToJson(oData, useeventsDb, "CHANNELID");
+ addFieldToJson(oData, useeventsDb, "STARTTIME");
+ addFieldToJson(oData, useeventsDb, "DURATION");
+ addFieldToJson(oData, useeventsDb, "CATEGORY");
+ addFieldToJson(oData, useeventsDb, "GENRE");
+ addFieldToJson(oData, useeventsDb, "TITLE");
+ addFieldToJson(oData, useeventsDb, "SHORTTEXT");
+ addFieldToJson(oData, useeventsDb, "SHORTDESCRIPTION");
+ addFieldToJson(oData, useeventsDb, "NUMRATING");
+ addFieldToJson(oData, useeventsDb, "TIPP");
+ addFieldToJson(oData, useeventsDb, "IMAGECOUNT");
+
+ isAlreadyDone(searchTimer->getIntValue("REPEATFIELDS"), oData, yes);
+
+ // timer for this Event pending?
+
+ timerDb->clear();
+ timerDb->setValue("EVENTID", useeventsDb->getIntValue("USEID"));
+ timerDb->setValue("CHANNELID", useeventsDb->getStrValue("CHANNELID"));
+
+ if (selectTimerByEventId->find())
+ addFieldToJson(oData, timerDb, "ID", no, "TIMERID");
+
+ selectTimerByEventId->freeResult();
+
+ json_array_append_new(oEvents, oData);
+ }
+
+ select->freeResult();
+ delete select;
+
+ searchtimerDb->reset();
+
+ json_object_set_new(obj, "start", json_integer(start));
+ json_object_set_new(obj, "count", json_integer(count));
+ json_object_set_new(obj, "events", oEvents);
+
+ return success;
+}
+
+//***************************************************************************
+// Get Done For Event by Searchtimer
+//***************************************************************************
+
+int cSearchTimer::getDoneFor(cDbRow* searchTimer, cDbRow* useevent, json_t* obj)
+{
+ if (checkConnection() != success)
+ return fail;
+
+ searchtimerDb->clear();
+ searchtimerDb->setValue("ID", searchTimer->getIntValue("ID"));
+
+ if (!searchtimerDb->find())
+ {
+ tell(0, "Warning: Searchtimer %ld not found", searchTimer->getIntValue("ID"));
+ return fail;
+ }
+
+ useeventsDb->clear();
+ useeventsDb->setBigintValue("CNTEVENTID", useevent->getBigintValue("CNTEVENTID"));
+ useeventsDb->setValue("CHANNELID", useevent->getStrValue("CHANNELID"));
+ useeventsDb->setValue("CNTSOURCE", useevent->getStrValue("CNTSOURCE"));
+
+ if (!useeventsDb->find())
+ {
+ tell(0, "Warning: Event '%s/%lu/%s' not found",
+ useevent->getStrValue("CHANNELID"),
+ useevent->getBigintValue("CNTEVENTID"),
+ useevent->getStrValue("CNTSOURCE"));
+
+ return fail;
+ }
+
+ isAlreadyDone(searchtimerDb->getIntValue("REPEATFIELDS"), obj, yes);
+
+ useeventsDb->reset();
+ searchtimerDb->reset();
+
+ return success;
+}
+
+//***************************************************************************
+// Update Search Timers
+//***************************************************************************
+
+int cSearchTimer::updateSearchTimers(int force, const char* reason)
+{
+ uint64_t start = cMyTimeMs::Now();
+ long total = 0;
+
+ tell(1, "AUTOTIMER: Updating searchtimers due to '%s' %s", reason, force ? "(force)" : "");
+
+ if (checkConnection() != success)
+ return fail;
+
+ searchtimerDb->clear();
+
+ for (int res = selectActiveSearchtimers->find(); res; res = selectActiveSearchtimers->fetch())
+ {
+ long hits = 0;
+ cDbStatement* select = 0;
+
+ // searchtimer updated after last run or force?
+
+ if (!force && searchtimerDb->getIntValue("MODSP") <= searchtimerDb->getIntValue("LASTRUN"))
+ continue;
+
+ select = prepareSearchStatement(searchtimerDb->getRow());
+
+ if (!select)
+ {
+ lastSearchTimerUpdate = time(0); // protect for infinite call on error
+ return 0;
+ }
+
+ useeventsDb->clear();
+
+ for (int res = select->find(); res; res = select->fetch())
+ {
+ time_t starttime = useeventsDb->getIntValue("STARTTIME");
+ int weekday = weekdayOf(starttime);
+
+ tell(3, "AUTOTIMER: Found event (%s) '%s' / '%s' (%ld/%s) at day %d",
+ l2pTime(starttime).c_str(),
+ useeventsDb->getStrValue("TITLE"),
+ useeventsDb->getStrValue("SHORTTEXT"),
+ useeventsDb->getIntValue("USEID"),
+ useeventsDb->getStrValue("CHANNELID"),
+ weekday);
+
+ useeventsDb->find(); // get all fields ..
+
+ // match
+
+ if (!matchCriterias(searchtimerDb->getRow(), useeventsDb->getRow()))
+ {
+ useeventsDb->reset();
+ continue;
+ }
+
+ // check if event already recorded or schedule for recording
+
+ if (!isAlreadyDone(searchtimerDb->getIntValue("REPEATFIELDS")))
+ {
+ timerDb->clear();
+ timerDb->setValue("EVENTID", useeventsDb->getIntValue("USEID"));
+
+ if (selectRPTimerByEvent->find())
+ {
+ tell(3, "AUTOTIMER: Timer for event (%ld) '%s/%s' already scheduled, skipping",
+ useeventsDb->getIntValue("USEID"),
+ useeventsDb->getStrValue("TITLE"),
+ useeventsDb->getStrValue("SHORTTEXT"));
+ }
+ else
+ {
+// #TODO time_t lEndTime = useeventsDb->getIntValue("DURATION");
+// #TODO int count = getUsedTransponderAt(useeventsDb->getIntValue("STARTTIME"), lEndTime);
+// #TODO if (count <= availableTransponders)
+ {
+ if (createTimer(searchtimerDb->getIntValue("ID")) == success)
+ hits++;
+ }
+// else
+// tell(1, "AUTOTIMER: Skipping due to");
+ }
+
+ selectRPTimerByEvent->freeResult();
+ useeventsDb->reset();
+ }
+ }
+
+ total += hits;
+
+ select->freeResult();
+ delete select;
+
+ searchtimerDb->find();
+ searchtimerDb->setValue("LASTRUN", time(0));
+
+ if (hits)
+ searchtimerDb->setValue("HITS", searchtimerDb->getIntValue("HITS") + hits);
+
+ searchtimerDb->update();
+ }
+
+ selectActiveSearchtimers->freeResult();
+ lastSearchTimerUpdate = time(0);
+
+ tell(1, "AUTOTIMER: Update done after %s, created (%ld) timers",
+ ms2Dur(cMyTimeMs::Now()-start).c_str(), total);
+
+ return total;
+}
+
+//***************************************************************************
+// Is Alreayd Done
+//***************************************************************************
+
+int cSearchTimer::isAlreadyDone(int repeatfields, json_t* obj, int silent)
+{
+ // std::string chkFields = "";
+
+ if (repeatfields <= 0)
+ return no;
+
+ tell(3, "Check if '%s/%s' already recorded by fields (%d)",
+ useeventsDb->getStrValue("TITLE"),
+ useeventsDb->getStrValue("SHORTTEXT"),
+ repeatfields);
+
+ // --------------------------------------
+ // check timersdone to avoid repeading
+
+ // prepare statement ...
+
+ selectDoneTimer->clear();
+ selectDoneTimer->build("select ");
+ selectDoneTimer->bind("ID", cDBS::bndOut);
+ selectDoneTimer->bind("STATE", cDBS::bndOut, ", ");
+ selectDoneTimer->build(" from %s where ", timersDoneDb->TableName());
+
+ // retry only 'F'ailed and re'J'ected timers, don't retry 'D'eleted timers sice they are deleted by user
+ /*
+ select id, state, title,comptitle, shorttext,compshorttext from timersdone where
+ state not in ('F','J')
+ and
+ (field('DERSTAATSANWALTDASLUDER', ifnull(comptitle,''),ifnull(episodecompshortname,'')) > 0
+ or field('',ifnull(comptitle,''),ifnull(episodecompshortname,'NoShortnameAvailable')) > 0)
+ and
+ (field('',ifnull(compshorttext,''),ifnull(episodecomppartname,'')) > 0
+ or field('',ifnull(compshorttext,''),ifnull(episodecomppartname,'')) > 0);
+ */
+
+ selectDoneTimer->build(" %s not in ('F','J')", // mysql ignoring case by default!
+ timersDoneDb->getField("STATE")->getDbName());
+
+ if (repeatfields & sfTitle)
+ {
+ selectDoneTimer->build(" and (field('%s', ifnull(comptitle,''),ifnull(episodecompshortname,'')) > 0"
+ " or field('%s',ifnull(comptitle,''),ifnull(episodecompshortname,'NoShortnameAvailable')) > 0)",
+ useeventsDb->getStrValue("COMPTITLE"), useeventsDb->getStrValue("EPISODECOMPSHORTNAME"));
+ }
+
+ if (repeatfields & sfFolge)
+ {
+ selectDoneTimer->build(" and (field('%s',ifnull(compshorttext,''),ifnull(episodecomppartname,'')) > 0"
+ " or field('%s',ifnull(compshorttext,''),ifnull(episodecomppartname,'')) > 0)",
+ useeventsDb->getStrValue("COMPSHORTTEXT"), useeventsDb->getStrValue("EPISODECOMPPARTNAME"));
+ }
+
+ if (selectDoneTimer->prepare() != success)
+ {
+ tell(0, "Error: AUTOTIMER: Prepare of statement for 'done' check failed, skipping");
+ return yes;
+ }
+
+ timersDoneDb->clear();
+
+ json_t* oDones = json_array();
+ int cnt = 0;
+
+ for (int f = selectDoneTimer->find(); f; f = selectDoneTimer->fetch())
+ {
+ json_t* oData = json_object();
+ long id = timersDoneDb->getIntValue("ID");
+ const char* state = timersDoneDb->getStrValue("STATE");
+
+ if (!silent)
+ tell(3, "AUTOTIMER: The timer/recording exists with timerdone "
+ "id %ld and state '%s', checked by '%s', skipping ...",
+ id, state, selectDoneTimer->asText());
+
+ if (!obj)
+ {
+ selectDoneTimer->freeResult();
+ return yes;
+ }
+
+ // add to json object
+
+ json_object_set_new(oData, "id", json_integer(id));
+ json_object_set_new(oData, "state", json_string(state));
+ json_array_append_new(oDones, oData);
+ cnt++;
+
+ selectDoneTimer->freeResult();
+ }
+
+ if (obj && cnt)
+ json_object_set_new(obj, "dones", oDones);
+
+ return no;
+}
+
+//***************************************************************************
+// Create Timer
+//
+// - searchtimerDb and useeventsDb has to be positioned and loaded
+//***************************************************************************
+
+int cSearchTimer::createTimer(int id)
+{
+ int status;
+ int timerid;
+ int retry = no;
+ int doneid = 0;
+
+ if (checkConnection() != success)
+ return fail;
+
+ const char* channelname = "";
+ int namingmode = searchtimerDb->getIntValue("NAMINGMODE");
+ const char* tmplExpression = searchtimerDb->getStrValue("TEMPLATE");
+
+ // ------------------------------------------
+ // lookup channel name to store in timersdone
+ // (just as a additional 'debug' info)
+
+ mapDb->clear();
+ mapDb->setValue("CHANNELID", useeventsDb->getStrValue("CHANNELID"));
+
+ if (selectChannelFromMap->find())
+ channelname = mapDb->getStrValue("CHANNELNAME");
+
+ if (isEmpty(channelname))
+ channelname = useeventsDb->getStrValue("CHANNELID");
+
+ selectChannelFromMap->freeResult();
+
+ // ------------------------------------------
+ // Create timer in timerdistribution ...
+
+ cDbRow timerRow("timers");
+
+ timerRow.clear();
+
+ timerRow.setValue("ID", na); // 'na' is the signal for modifyCreateTimer to create new timer
+ timerRow.setValue("ACTIVE", yes);
+ timerRow.setValue("TYPE", searchtimerDb->getStrValue("TYPE"));
+ timerRow.setValue("SOURCE", "epgd");
+ timerRow.setValue("EVENTID", useeventsDb->getIntValue("USEID"));
+ timerRow.setValue("_STARTTIME", useeventsDb->getIntValue("STARTTIME"));
+ timerRow.setValue("CHANNELID", useeventsDb->getStrValue("CHANNELID"));
+ timerRow.setValue("NAMINGMODE", namingmode);
+ timerRow.setValue("TEMPLATE", tmplExpression);
+ timerRow.setValue("CHILDLOCK", searchtimerDb->getIntValue("CHILDLOCK"));
+ timerRow.setValue("VDRUUID", searchtimerDb->getStrValue("VDRUUID"));
+ timerRow.setValue("VPS", searchtimerDb->getIntValue("VPS"));
+ timerRow.setValue("PRIORITY", searchtimerDb->getIntValue("PRIORITY"));
+ timerRow.setValue("LIFETIME", searchtimerDb->getIntValue("LIFETIME"));
+ timerRow.setValue("DIRECTORY", searchtimerDb->getStrValue("DIRECTORY"));
+
+ timerRow.setValue("AUTOTIMERID", searchtimerDb->getIntValue("ID"));
+ timerRow.setValue("AUTOTIMERNAME", searchtimerDb->getStrValue("NAME"));
+ timerRow.setValue("AUTOTIMERINSSP", searchtimerDb->getIntValue("INSSP"));
+ timerRow.setValue("EXPRESSION", searchtimerDb->getStrValue("EXPRESSION"));
+
+ if (namingmode != tnmDefault)
+ {
+ // execupe python - calc recording name
+
+ if (ptyRecName->execute(useeventsDb, namingmode, tmplExpression) == success)
+ {
+ tell(0, "Info: The recording name calculated by 'recording.py' is '%s'",
+ ptyRecName->getResult());
+
+ if (!isEmpty(ptyRecName->getResult()))
+ timerRow.setValue("FILE", ptyRecName->getResult());
+ }
+ }
+
+ // for 'event' based timers, check on failed attemps for this eventid
+
+ if (!timerRow.getValue("EVENTID")->isEmpty())
+ {
+ timerDb->clear();
+ timerDb->setValue("EVENTID", timerRow.getIntValue("EVENTID"));
+ timerDb->setValue("CHANNELID", timerRow.getStrValue("CHANNELID"));
+ timerDb->setValue("VDRUUID", timerRow.getStrValue("VDRUUID"));
+ timerDb->setCharValue("STATE", tsError);
+
+ // max three retrys ..
+
+ if (selectFailedTimerByEvent->find())
+ {
+ if (timerDb->getIntValue("RETRYS") > 3)
+ {
+ tell(0, "Error: AUTOTIMER: Aborting timer create for event %ld, already %ld attempts failed ",
+ timerDb->getIntValue("EVENTID"),
+ timerDb->getIntValue("RETRYS"));
+
+ selectFailedTimerByEvent->freeResult();
+
+ return fail;
+ }
+
+ retry = yes;
+ doneid = timerDb->getIntValue("DONEID");
+
+ timerRow.setValue("ID", timerDb->getIntValue("ID")); // signal for modifyCreateTimer to modify the existing timer
+ timerRow.setValue("RETRYS", timerDb->getIntValue("RETRYS")+1);
+ }
+
+ selectFailedTimerByEvent->freeResult();
+ }
+
+ status = modifyCreateTimer(&timerRow, timerid, /*resetState = */ retry);
+
+ // on scuccess add to timersdone ..
+
+ if (status == success)
+ {
+ timersDoneDb->clear();
+
+ if (doneid)
+ {
+ timersDoneDb->setValue("ID", doneid);
+
+ if (!timersDoneDb->find())
+ doneid = 0;
+ }
+
+ timersDoneDb->setCharValue("STATE", tdsTimerRequested);
+ timersDoneDb->setValue("SOURCE", "epgd");
+ timersDoneDb->setValue("CHANNELID", useeventsDb->getStrValue("CHANNELID"));
+ timersDoneDb->setValue("STARTTIME", useeventsDb->getIntValue("STARTTIME"));
+ timersDoneDb->setValue("DURATION", useeventsDb->getIntValue("DURATION"));
+ timersDoneDb->setValue("EXPRESSION", searchtimerDb->getStrValue("EXPRESSION"));
+ timersDoneDb->setValue("AUTOTIMERID", id);
+ timersDoneDb->setValue("AUTOTIMERNAME", searchtimerDb->getStrValue("NAME"));
+ timersDoneDb->setValue("TIMERID", timerid);
+ timersDoneDb->setValue("TITLE", useeventsDb->getStrValue("TITLE"));
+ timersDoneDb->setValue("COMPTITLE", useeventsDb->getStrValue("COMPTITLE"));
+
+ if (!useeventsDb->getValue("SHORTTEXT")->isEmpty())
+ timersDoneDb->setValue("SHORTTEXT", useeventsDb->getStrValue("SHORTTEXT"));
+ if (!useeventsDb->getValue("COMPSHORTTEXT")->isEmpty())
+ timersDoneDb->setValue("COMPSHORTTEXT", useeventsDb->getStrValue("COMPSHORTTEXT"));
+
+ if (!useeventsDb->getValue("LONGDESCRIPTION")->isEmpty())
+ timersDoneDb->setValue("LONGDESCRIPTION", useeventsDb->getStrValue("LONGDESCRIPTION"));
+ if (!useeventsDb->getValue("COMPLONGDESCRIPTION")->isEmpty())
+ timersDoneDb->setValue("COMPLONGDESCRIPTION", useeventsDb->getStrValue("COMPLONGDESCRIPTION"));
+
+ if (!useeventsDb->getValue("EPISODECOMPNAME")->isEmpty())
+ timersDoneDb->setValue("EPISODECOMPNAME", useeventsDb->getStrValue("EPISODECOMPNAME"));
+ if (!useeventsDb->getValue("EPISODECOMPSHORTNAME")->isEmpty())
+ timersDoneDb->setValue("EPISODECOMPSHORTNAME", useeventsDb->getStrValue("EPISODECOMPSHORTNAME"));
+
+ if (!useeventsDb->getValue("EPISODECOMPPARTNAME")->isEmpty())
+ timersDoneDb->setValue("EPISODECOMPPARTNAME", useeventsDb->getStrValue("EPISODECOMPPARTNAME"));
+ if (!useeventsDb->getValue("EPISODELANG")->isEmpty())
+ timersDoneDb->setValue("EPISODELANG", useeventsDb->getStrValue("EPISODELANG"));
+ if (!useeventsDb->getValue("EPISODESEASON")->isEmpty())
+ timersDoneDb->setValue("EPISODESEASON", useeventsDb->getIntValue("EPISODESEASON"));
+ if (!useeventsDb->getValue("EPISODEPART")->isEmpty())
+ timersDoneDb->setValue("EPISODEPART", useeventsDb->getIntValue("EPISODEPART"));
+
+ if (!isEmpty(channelname))
+ timersDoneDb->setValue("CHANNELNAME", channelname);
+
+ if (doneid)
+ {
+ timersDoneDb->update();
+ }
+ else
+ {
+ timersDoneDb->insert();
+ doneid = timersDoneDb->getLastInsertId();
+ }
+
+ parent->message(1, 'I', "EPGD: Timer action", "Created timer (%d) for event '%s - %s / %s on channel '%s' at '%s', doneid is (%d)",
+ timerid,
+ l2pTime(useeventsDb->getIntValue("STARTTIME")).c_str(),
+ useeventsDb->getStrValue("TITLE"), useeventsDb->getStrValue("SHORTTEXT"),
+ channelname, toWeekdayName(weekdayOf(useeventsDb->getIntValue("STARTTIME"))),
+ doneid);
+
+ timerDb->clear();
+ timerDb->setValue("ID", timerid);
+ timerDb->setValue("VDRUUID", searchtimerDb->getStrValue("VDRUUID"));
+
+ if (timerDb->find())
+ {
+ timerDb->setValue("DONEID", doneid);
+ timerDb->update();
+ }
+ }
+
+ return status;
+}
+
+//***************************************************************************
+// Modify Timer (copy paste from cMenuDb of epg2vdr/httpd)
+//
+// - timerRow contains the destination vdrUuid
+//***************************************************************************
+
+int cSearchTimer::modifyCreateTimer(cDbRow* timerRow, int& newid, int createRetry)
+{
+ int status = success;
+ int timerid = timerRow->getIntValue("ID");
+ int knownTimer = timerid != na;
+ int move = no;
+
+ newid = na;
+ connection->startTransaction();
+
+ timerDb->clear();
+
+ // lookup known (existing) timer
+
+ if (knownTimer)
+ {
+ timerDb->copyValues(timerRow, cDBS::ftPrimary);
+
+ if (!timerDb->find())
+ {
+ connection->commit();
+
+ tell(0, "Error: Timer (%d) at vdr '%s' not found, aborting modify request!",
+ timerid, timerDb->getStrValue("VDRUUID"));
+
+ return fail;
+ }
+
+ // found and all values are loaded!
+
+ // move to another vdr?
+
+ if (!timerDb->hasValue("VDRUUID", timerRow->getStrValue("VDRUUID")))
+ move = yes;
+ }
+ else
+ {
+ timerDb->setValue("VDRUUID", timerRow->getStrValue("VDRUUID"));
+ }
+
+ if (move)
+ {
+ // request 'D'elete of 'old' timer
+
+ timerDb->setCharValue("ACTION", taDelete);
+ timerDb->setValue("SOURCE", timerRow->getStrValue("SOURCE"));
+ status = timerDb->update();
+
+ // triggerVdrs("TIMERJOB", timerDb->getStrValue("VDRUUID"));
+
+ // create new on other vdr
+
+ timerDb->copyValues(timerRow, cDBS::ftData); // takeover all data (can be modified by user)
+ timerDb->setValue("ID", 0);
+ timerDb->setCharValue("ACTION", taCreate);
+ status += timerDb->insert();
+ newid = timerDb->getLastInsertId();
+
+ if (status == success)
+ tell(1, "Created 'move' request for timer (%d) at vdr '%s'",
+ timerid, timerDb->getStrValue("VDRUUID"));
+ }
+ else
+ {
+ // create 'C'reate oder 'M'odify request ...
+
+ timerDb->copyValues(timerRow, cDBS::ftData);
+
+ // createRetry ... verry special case ...
+
+ if (createRetry)
+ {
+ timerDb->getValue("STATE")->setNull();
+ timerDb->setCharValue("ACTION", taCreate);
+ }
+ else
+ {
+ timerDb->setCharValue("ACTION", knownTimer ? taModify : taCreate);
+ }
+
+ if (knownTimer)
+ {
+ status = timerDb->update();
+ newid = timerid;
+ }
+ else
+ {
+ status = timerDb->insert();
+ newid = timerDb->getLastInsertId();
+ }
+
+ if (status == success)
+ {
+ tell(1, "Created '%s' request for timer (%d), event (%ld) at vdr '%s' by autotimer (%ld)",
+ knownTimer ? "modify" : "create",
+ newid, timerDb->getIntValue("EVENTID"), timerDb->getStrValue("VDRUUID"),
+ timerDb->getIntValue("AUTOTIMERID"));
+ }
+ }
+
+ connection->commit();
+
+ // triggerVdrs("TIMERJOB", timerDb->getStrValue("VDRUUID"));
+
+ return status;
+}
+
+//***************************************************************************
+// Check Timer Conficts
+//***************************************************************************
+
+int cSearchTimer::checkTimerConflicts(std::string& mailBody)
+{
+ int conflicts = 0;
+
+ tell(1, "TCC: Starting timer conflict check");
+ mailBody = "";
+
+ if (checkConnection() != success)
+ return fail;
+
+ timerDb->clear();
+ vdrDb->clear();
+
+ for (int f = selectAllTimer->find(); f; f = selectAllTimer->fetch())
+ {
+ if (!timerDb->getIntValue("ACTIVE"))
+ continue;
+
+ if (timerDb->getIntValue("TCCMAILCNT") > 0)
+ continue;
+
+ tell(2, "TCC: Check conflicts for timer (%ld) '%s' on '%s'; channel '%s'",
+ timerDb->getIntValue("ID"), timerDb->getStrValue("FILE"),
+ vdrDb->getStrValue("NAME"), timerDb->getStrValue("CHANNELID"));
+
+ // calc 'start' and 'end' time of this timer ..
+
+ int sDay = timerDb->getIntValue("DAY");
+ int sTime = timerDb->getIntValue("STARTTIME");
+ int eDay = timerDb->getIntValue("DAY");
+ int eTime = timerDb->getIntValue("ENDTIME");
+
+ if (eTime < sTime)
+ eDay += tmeSecondsPerDay;
+
+ time_t lStartTime = sDay + sTime / 100 * tmeSecondsPerHour + sTime % 100 * tmeSecondsPerMinute;
+ time_t lEndTime = eDay + eTime / 100 * tmeSecondsPerHour + eTime % 100 * tmeSecondsPerMinute;
+
+ // check for conflicts
+
+ std::string mailPart;
+ int tunerCount = getUsedTransponderAt(lStartTime, lEndTime, mailPart);
+
+ if (tunerCount > vdrDb->getIntValue("TUNERCOUNT"))
+ {
+ conflicts++;
+
+ tell(1, "TCC: Timer (%ld) '%s' conflict at '%s - %s' needed %d transponders on '%s'",
+ timerDb->getIntValue("ID"),
+ timerDb->getStrValue("FILE"),
+ l2pTime(lStartTime).c_str(),
+ l2pTime(lEndTime).c_str(),
+ tunerCount,
+ vdrDb->getStrValue("NAME"));
+
+ mailBody +=
+ "<tbody>"
+ "<tr>"
+ "<th style=\"text-align:left;vertical-align:top;white-space:nowrap;padding:15px 0 5px 0;border-bottom: 1px solid #ccc;\" colspan=\"6\"> conflict #"
+ + num2Str(conflicts) + " on VDR '" + vdrDb->getStrValue("NAME")
+ + "' at " + l2pDate(lStartTime) + "</th>"
+ "</tr>";
+
+ mailBody += mailPart;
+
+ mailBody +=
+ "</tbody>";
+
+ timerDb->setValue("TCCMAILCNT", timerDb->getIntValue("TCCMAILCNT") + 1);
+ timerDb->update();
+
+ // #TODO - reject autotimer ... ?
+ // rejectTimer(timerDb->getRow());
+ }
+
+ else if (tunerCount <= 0) // DEBUG-tell - to be removed?
+ tell(1, "TCC: Fatal got 0 used transponders for timer (%ld) between %s (%ld) and %s (%ld)",
+ timerDb->getIntValue("ID"),
+ l2pTime(lStartTime).c_str(), lStartTime,
+ l2pTime(lEndTime).c_str(), lEndTime);
+ }
+
+ selectAllTimer->freeResult();
+
+ tell(1, "TCC: Finished timer conflict check with (%d) conflicts", conflicts);
+
+ return conflicts;
+}
+
+//***************************************************************************
+// Get Used Transponder At
+//***************************************************************************
+
+int cSearchTimer::getUsedTransponderAt(time_t lStartTime, time_t lEndTime, std::string& mailPart)
+{
+ char buf[1024+TB];
+
+ if (checkConnection() != success)
+ return fail;
+
+ startValue.setValue(lStartTime);
+ endValue.setValue(lEndTime);
+
+ std::map<std::string, cTccTransponder> transponders;
+ std::map<std::string, cTccTransponder>::iterator it;
+
+ for (int f = selectConflictingTimers->find(); f; f = selectConflictingTimers->fetch())
+ {
+ cTccTimerData timer;
+ std::string transponder = timerDb->getStrValue("CHANNELID");
+ size_t endpos = transponder.find_last_of("-");
+ const char* channelName = "unknown";
+
+ mapDb->clear();
+ mapDb->setValue("CHANNELID", timerDb->getStrValue("CHANNELID"));
+
+ if (selectChannelFromMap->find())
+ channelName = mapDb->getStrValue("CHANNELNAME");
+
+ selectChannelFromMap->freeResult();
+
+ if (endpos == std::string::npos)
+ continue;
+
+ transponder = transponder.substr(0, endpos); // build transponder name
+ transponders[transponder].count++;
+
+ timer.id = timerDb->getIntValue("ID");
+ timer.file = timerDb->getStrValue("FILE");
+ timer.begin = timerDb->getIntValue("STARTTIME");
+ timer.end = timerDb->getIntValue("ENDTIME");
+ timer.channel = channelName;
+ transponders[transponder].timers.push_back(timer);
+ }
+
+ selectConflictingTimers->freeResult();
+
+ for (it = transponders.begin(); it != transponders.end(); it++)
+ {
+ std::list<cTccTimerData>::iterator li;
+
+ for (li = it->second.timers.begin(); li != it->second.timers.end(); ++li)
+ {
+ tell(3, "TCC: found (%ld) '%s'", (*li).id, (*li).file.c_str());
+
+ sprintf(buf,
+ "<tr>"
+ "<td style=\"text-align:left;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%s</div></td>"
+ "<td style=\"text-align:left;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%s</div></td>"
+ "<td style=\"text-align:left;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%ld</div></td>"
+ "<td style=\"text-align:left;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%s</div></td>"
+ "<td style=\"text-align:right;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%s</div></td>"
+ "<td style=\"text-align:right;vertical-align:top;white-space:nowrap;padding:3px 2px;\"><div>%s</div></td>"
+ "</tr>",
+ it->first.c_str(),
+ (*li).channel.c_str(),
+ (*li).id,
+ (*li).file.c_str(),
+ hhmm2pTime((*li).begin).c_str(),
+ hhmm2pTime((*li).end).c_str());
+
+ mailPart += buf;
+ }
+ }
+
+ return transponders.size();
+}
+
+// //***************************************************************************
+// // Reject Timer
+// //***************************************************************************
+
+// int cSearchTimer::rejectTimer(cDbRow* timerRow)
+// {
+// tell(1, "Rejecting timer (%ld)", timerRow->getIntValue("ID"));
+
+// timerJobsDb->clear();
+// timerJobsDb->setValue("TIMERID", timerRow->getIntValue("ID"));
+// timerJobsDb->setValue("DONEID", timerRow->getIntValue("DONEID"));
+// timerJobsDb->setValue("STATE", "J");
+// timerJobsDb->setValue("ASSUMED", "N");
+// timerJobsDb->insert();
+
+// tell(1, "Created delete job for timer (%ld)", timerRow->getIntValue("ID"));
+
+// return success;
+// }
diff --git a/lib/searchtimer.h b/lib/searchtimer.h
new file mode 100644
index 0000000..f4c072c
--- /dev/null
+++ b/lib/searchtimer.h
@@ -0,0 +1,111 @@
+/*
+ * searchtimer.h
+ *
+ * See the README file for copyright information
+ *
+ */
+
+#ifndef __SEARCHTIMER_H
+#define __SEARCHTIMER_H
+
+#include "common.h"
+#include "configuration.h"
+#include "db.h"
+#include "epgservice.h"
+#include "json.h"
+
+class Python;
+
+//***************************************************************************
+// Search Timer
+//***************************************************************************
+
+class cSearchTimer
+{
+ public:
+
+ cSearchTimer(cFrame* aParent);
+ ~cSearchTimer();
+
+ int init(const char* confDir);
+ int initDb();
+ int exitDb();
+ int checkConnection();
+ int dbConnected(int force = no) { return connection && (!force || connection->check() == success); }
+
+ int modified(); // check if a search timer is modified by user
+ int updateSearchTimers(int force = yes, const char* reason = "");
+ int checkTimers();
+
+ int getSearchMatches(cDbRow* searchTimer, json_t* obj, int start = 0, int max = na);
+ int getDoneFor(cDbRow* searchTimer, cDbRow* useevent, json_t* obj);
+ int checkTimerConflicts(std::string& mailBody);
+ int getUsedTransponderAt(time_t lStartTime, time_t lEndTime, std::string& mailBody);
+
+ private:
+
+ struct cTccTimerData
+ {
+ long id;
+ int begin;
+ int end;
+ std::string channel;
+ std::string file;
+ };
+
+ struct cTccTransponder
+ {
+ int count;
+ std::list<cTccTimerData> timers;
+ };
+
+ cDbStatement* prepareSearchStatement(cDbRow* searchTimer);
+ int matchCriterias(cDbRow* searchTimer, cDbRow* event);
+ int isAlreadyDone(int repeatfields, json_t* obj = 0, int silent = no);
+ int createTimer(int id);
+ int modifyCreateTimer(cDbRow* timerRow, int& newid, int createRetry = no);
+ // int rejectTimer(cDbRow* timerRow);
+ int modifyTimer(cDbTable* timerDb, TimerAction action);
+
+ // data
+
+ Python* ptyRecName;
+
+ cDbConnection* connection;
+
+ cDbTable* searchtimerDb;
+ cDbTable* useeventsDb;
+ cDbTable* timersDoneDb;
+ cDbTable* timerDb;
+ cDbTable* mapDb;
+ cDbTable* vdrDb;
+ cDbTable* messageDb;
+
+ cDbStatement* selectChannelFromMap;
+ cDbStatement* selectDoneTimer;
+ cDbStatement* selectActiveSearchtimers;
+ cDbStatement* selectSearchtimerMaxModSp;
+ cDbStatement* selectSearchTimerByName;
+ cDbStatement* selectSearchTimerById;
+ cDbStatement* selectActiveVdrs;
+ cDbStatement* selectAllTimer;
+ cDbStatement* selectRPTimerByEvent;
+ cDbStatement* selectTimerByEventId;
+ cDbStatement* selectConflictingTimers;
+ cDbStatement* selectFailedTimerByEvent;
+ cDbStatement* selectEvent;
+
+ cDbValue startValue;
+ cDbValue endValue;
+
+ time_t lastSearchTimerUpdate;
+
+ static int searchField[];
+ static const char* searchFieldName[];
+ static int repeadCheckField[];
+ static const char* repeadCheckFieldName[];
+ cFrame* parent;
+};
+
+//***************************************************************************
+#endif // __SEARCHTIMER_H
diff --git a/lib/semtst.c b/lib/semtst.c
new file mode 100644
index 0000000..68a784c
--- /dev/null
+++ b/lib/semtst.c
@@ -0,0 +1,42 @@
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "common.h"
+
+const char* logPrefix = "";
+
+int main()
+{
+
+ Sem s(0x3db00001);
+
+ if (s.check() == success)
+ printf("free \n");
+ else
+ printf("locked \n");
+
+ printf("inc\n");
+
+ s.inc();
+
+ printf("inc done\n");
+ sleep(5);
+
+ s.inc();
+
+ printf("inc done\n");
+ sleep(5);
+
+ s.v();
+ s.v();
+
+ if (s.check() == success)
+ printf("free \n");
+ else
+ printf("locked \n");
+
+ return 0;
+}
diff --git a/lib/test.c b/lib/test.c
new file mode 100644
index 0000000..bdc5d0a
--- /dev/null
+++ b/lib/test.c
@@ -0,0 +1,738 @@
+/*
+ * test.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <stdint.h> // uint_64_t
+#include <sys/time.h>
+#include <time.h>
+
+#include <stdio.h>
+#include <string>
+
+#include "config.h"
+#include "common.h"
+#include "db.h"
+#include "epgservice.h"
+#include "dbdict.h"
+#include "wol.h"
+#include "curl.h"
+
+cDbConnection* connection = 0;
+const char* logPrefix = "";
+
+//***************************************************************************
+// Init Connection
+//***************************************************************************
+
+void initConnection()
+{
+ cDbConnection::init();
+
+ // cDbConnection::setHost("192.168.200.101");
+ cDbConnection::setHost("localhost");
+ cDbConnection::setPort(3306);
+
+ cDbConnection::setName("epg2vdr");
+ cDbConnection::setUser("epg2vdr");
+ cDbConnection::setPass("epg");
+
+ cDbConnection::setConfPath("/etc/epgd/");
+ cDbConnection::setEncoding("utf8");
+
+ connection = new cDbConnection();
+}
+
+void exitConnection()
+{
+ cDbConnection::exit();
+
+ if (connection)
+ delete connection;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+void chkCompress()
+{
+ std::string s = "_+*!#?=&%$< Hallo TEIL Hallo Folge ";
+
+ printf("'%s'\n", s.c_str());
+ prepareCompressed(s);
+ printf("'%s'\n", s.c_str());
+
+ s = "Place Vendôme - Heiße Diamanten";
+ printf("'%s'\n", s.c_str());
+ prepareCompressed(s);
+ printf("'%s'\n", s.c_str());
+
+ s = "Halöö älter";
+ printf("'%s'\n", s.c_str());
+ prepareCompressed(s);
+ printf("'%s'\n", s.c_str());
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+void chkStatement1()
+{
+ cDbTable* epgDb = new cDbTable(connection, "events");
+
+ if (epgDb->open() != success)
+ {
+ tell(0, "Could not access database '%s:%d' (%s)",
+ cDbConnection::getHost(), cDbConnection::getPort(), epgDb->TableName());
+
+ return ;
+ }
+
+ tell(0, "---------------------------------------------------");
+
+ // prepare statement to mark wasted DVB events
+
+ cDbValue* endTime = new cDbValue("starttime+duration", cDBS::ffInt, 10);
+ cDbStatement* updateDelFlg = new cDbStatement(epgDb);
+
+ // update events set delflg = ?, updsp = ?
+ // where channelid = ? and source = ?
+ // and starttime+duration > ?
+ // and starttime < ?
+ // and (tableid > ? or (tableid = ? and version <> ?))
+
+ updateDelFlg->build("update %s set ", epgDb->TableName());
+ updateDelFlg->bind(epgDb->getField("DelFlg"), cDBS::bndIn | cDBS::bndSet);
+ updateDelFlg->bind(epgDb->getField("UpdSp"), cDBS::bndIn | cDBS::bndSet, ", ");
+ updateDelFlg->build(" where ");
+ updateDelFlg->bind(epgDb->getField("ChannelId"), cDBS::bndIn | cDBS::bndSet);
+ updateDelFlg->bind(epgDb->getField("Source"), cDBS::bndIn | cDBS::bndSet, " and ");
+
+ updateDelFlg->bindCmp(0, endTime, ">", " and ");
+
+ updateDelFlg->bindCmp(0, epgDb->getField("StartTime"), 0, "<" , " and ");
+ updateDelFlg->bindCmp(0, epgDb->getField("TableId"), 0, ">" , " and (");
+ updateDelFlg->bindCmp(0, epgDb->getField("TableId"), 0, "=" , " or (");
+ updateDelFlg->bindCmp(0, epgDb->getField("Version"), 0, "<>" , " and ");
+ updateDelFlg->build("));");
+
+ updateDelFlg->prepare();
+
+ tell(0, "---------------------------------------------------");
+}
+
+// //***************************************************************************
+// //
+// //***************************************************************************
+
+// void chkStatement2()
+// {
+// cDbTable* imageRefDb = new cTableImageRefs(connection);
+// cDbTable* imageDb = new cTableImages(connection);
+
+// if (imageRefDb->open() != success)
+// return ;
+
+// if (imageDb->open() != success)
+// return ;
+
+// tell(0, "---------------------------------------------------");
+
+// cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+// cDbValue imageData;
+// imageData.setField(imageDb->getField(cTableImages::fiImage));
+
+// // select r.imagename, r.eventid, r.lfn, i.image from imagerefs r, images i
+// // where r.imagename = i.imagename and i.image is not null;
+
+// selectAllImages->build("select ");
+// selectAllImages->setBindPrefix("r.");
+// selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut);
+// selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+// selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+// selectAllImages->setBindPrefix("i.");
+// selectAllImages->bind(&imageData, cDBS::bndOut, ",");
+// selectAllImages->clrBindPrefix();
+// selectAllImages->build(" from %s r, %s i where ", imageRefDb->TableName(), imageDb->TableName());
+// selectAllImages->build("r.%s = i.%s and i.%s is not null;",
+// imageRefDb->getField(cTableImageRefs::fiImgName)->name,
+// imageDb->getField(cTableImages::fiImgName)->name,
+// imageDb->getField(cTableImages::fiImage)->name);
+
+// selectAllImages->prepare();
+
+
+// tell(0, "---------------------------------------------------");
+
+// //delete s;
+// delete imageRefDb;
+// delete imageDb;
+// }
+
+// //***************************************************************************
+// //
+// //***************************************************************************
+
+// void chkStatement3()
+// {
+// int count = 0;
+// int lcount = 0;
+
+// cDbTable* epgDb = new cTableEvents(connection);
+// cDbTable* mapDb = new cTableChannelMap(connection);
+
+// if (epgDb->open() != success)
+// return ;
+
+// if (mapDb->open() != success)
+// return ;
+
+// tell(0, "---------------------------------------------------");
+
+// cDbStatement* s = new cDbStatement(epgDb);
+
+// s->build("select ");
+// s->setBindPrefix("e.");
+// s->bind(cTableEvents::fiEventId, cDBS::bndOut);
+// s->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiSource, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiDelFlg, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiFileRef, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiTableId, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiVersion, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiTitle, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiShortText, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiStartTime, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiDuration, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiParentalRating, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiVps, cDBS::bndOut, ", ");
+// s->bind(cTableEvents::fiDescription, cDBS::bndOut, ", ");
+// s->clrBindPrefix();
+// s->build(" from eventsview e, %s m where ", mapDb->TableName());
+// s->build("e.%s = m.%s and e.%s = m.%s and ",
+// epgDb->getField(cTableEvents::fiChannelId)->name,
+// mapDb->getField(cTableChannelMap::fiChannelName)->name,
+// epgDb->getField(cTableEvents::fiSource)->name,
+// mapDb->getField(cTableChannelMap::fiSource)->name);
+// s->bindCmp("e", cTableEvents::fiUpdSp, 0, ">");
+// s->build(" order by m.%s;", mapDb->getField(cTableChannelMap::fiChannelName)->name);
+
+// s->prepare();
+
+// epgDb->clear();
+// epgDb->setValue(cTableEvents::fiUpdSp, (double)0);
+// epgDb->setValue(cTableEvents::fiSource, "vdr"); // used by selectUpdEventsByChannel
+// epgDb->setValue(cTableEvents::fiChannelId, "xxxxxxxxxxxxx"); // used by selectUpdEventsByChannel
+
+// int channels = 0;
+// char chan[100]; *chan = 0;
+
+// tell(0, "---------------------------------------------------");
+
+// for (int found = s->find(); found; found = s->fetch())
+// {
+// if (!*chan || strcmp(chan, epgDb->getStrValue(cTableEvents::fiChannelId)) != 0)
+// {
+// if (*chan)
+// tell(0, "processed %-20s with %d events", chan, count - lcount);
+
+// lcount = count;
+// channels++;
+// strcpy(chan, epgDb->getStrValue(cTableEvents::fiChannelId));
+
+// tell(0, "processing %-20s now", chan);
+// }
+
+// tell(0, "-> '%s' - (%ld)", epgDb->getStrValue(cTableEvents::fiChannelId),
+// epgDb->getIntValue(cTableEvents::fiEventId));
+
+
+// count++;
+// }
+
+// s->freeResult();
+
+// tell(0, "---------------------------------------------------");
+// tell(0, "updated %d channels and %d events", channels, count);
+// tell(0, "---------------------------------------------------");
+
+// delete s;
+// delete epgDb;
+// delete mapDb;
+// }
+
+// //***************************************************************************
+// //
+// //***************************************************************************
+
+// void chkStatement4()
+// {
+// cDbTable* eventDb = new cTableEvents(connection);
+// if (eventDb->open() != success) return;
+
+// cDbTable* imageRefDb = new cTableImageRefs(connection);
+// if (imageRefDb->open() != success) return;
+
+// cDbTable* imageDb = new cTableImages(connection);
+// if (imageDb->open() != success) return;
+
+// // select e.masterid, r.imagename, r.eventid, r.lfn, i.image
+// // from imagerefs r, images i, events e
+// // where r.imagename = i.imagename
+// // and e.eventid = r.eventid,
+// // and i.image is not null
+// // and (i.updsp > ? or r.updsp > ?);
+
+// cDBS::FieldDef masterFld = { "masterid", cDBS::ffUInt, 0, 999, cDBS::ftData };
+// cDbValue masterId;
+// cDbValue imageData;
+// cDbValue imageUpdSp;
+
+// masterId.setField(&masterFld);
+// imageData.setField(imageDb->getField(cTableImages::fiImage));
+// imageUpdSp.setField(imageDb->getField(cTableImages::fiUpdSp));
+
+// cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+// selectAllImages->build("select ");
+// selectAllImages->setBindPrefix("e.");
+// selectAllImages->bind(&masterId, cDBS::bndOut);
+// selectAllImages->setBindPrefix("r.");
+// selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut, ", ");
+// selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+// selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+// selectAllImages->setBindPrefix("i.");
+// selectAllImages->bind(&imageData, cDBS::bndOut, ", ");
+// selectAllImages->clrBindPrefix();
+// selectAllImages->build(" from %s r, %s i, %s e where ",
+// imageRefDb->TableName(), imageDb->TableName(), eventDb->TableName());
+// selectAllImages->build("e.%s = r.%s and i.%s = r.%s and i.%s is not null and (",
+// eventDb->getField(cTableEvents::fiEventId)->name,
+// imageRefDb->getField(cTableImageRefs::fiEventId)->name,
+// imageDb->getField(cTableImageRefs::fiImgName)->name,
+// imageRefDb->getField(cTableImageRefs::fiImgName)->name,
+// imageDb->getField(cTableImages::fiImage)->name);
+// selectAllImages->bindCmp("i", &imageUpdSp, ">");
+// selectAllImages->build(" or ");
+// selectAllImages->bindCmp("r", cTableImageRefs::fiUpdSp, 0, ">");
+// selectAllImages->build(");");
+
+// selectAllImages->prepare();
+
+// imageRefDb->clear();
+// imageRefDb->setValue(cTableImageRefs::fiUpdSp, 1377733333L);
+// imageUpdSp.setValue(1377733333L);
+
+// int count = 0;
+// for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+// {
+// count ++;
+// }
+// tell(0,"%d", count);
+// }
+
+//***************************************************************************
+// Content Of
+//***************************************************************************
+
+int contentOf(char* buf, const char* tag, const char* xml)
+{
+ std::string sTag = "<" + std::string(tag) + ">";
+ std::string eTag = "</" + std::string(tag) + ">";
+
+ const char* s;
+ const char* e;
+
+ *buf = 0;
+
+ if ((s = strstr(xml, sTag.c_str())) && (e = strstr(xml, eTag.c_str())))
+ {
+ s += strlen(sTag.c_str());
+
+ sprintf(buf, "%.*s", (int)(e-s), s);
+
+ return success;
+ }
+
+ return fail;
+}
+
+//***************************************************************************
+// Get Timer Id Of
+//***************************************************************************
+
+long getTimerIdOf(const char* aux)
+{
+ char epgaux[1000+TB];
+ char tid[100+TB];
+
+ if (isEmpty(aux))
+ return na;
+
+ if (contentOf(epgaux, "epgd", aux) != success)
+ return na;
+
+ if (contentOf(tid, "timerid", epgaux) != success)
+ return na;
+
+ return atol(tid);
+}
+
+//***************************************************************************
+// Remove Tag
+//***************************************************************************
+
+void removeTag(char* xml, const char* tag)
+{
+ std::string sTag = "<" + std::string(tag) + ">";
+ std::string eTag = "</" + std::string(tag) + ">";
+
+ const char* s;
+ const char* e;
+
+ if ((s = strstr(xml, sTag.c_str())) && (e = strstr(xml, eTag.c_str())))
+ {
+ char tmp[1000+TB];
+
+ e += strlen(eTag.c_str());
+
+ // sicher ist sicher ;)
+
+ if (e <= s)
+ return;
+
+ sprintf(tmp, "%.*s%s", int(s-xml), xml, e);
+
+ strcpy(xml, tmp);
+ }
+}
+
+//***************************************************************************
+// Insert Tag
+//***************************************************************************
+
+int insertTag(char* xml, const char* parent, const char* tag, int value)
+{
+ char tmp[1000+TB];
+ std::string sTag = "<" + std::string(parent) + ">";
+ const char* s;
+
+ if ((s = strstr(xml, sTag.c_str())))
+ {
+ s += strlen(sTag.c_str());
+ sprintf(tmp, "%.*s<%s>%d</%s>%s", int(s-xml), xml, tag, value, tag, s);
+ }
+ else
+ {
+ sprintf(tmp, "%s<%s><%s>%d</%s></%s>", xml, parent, tag, value, tag, parent);
+ }
+
+ strcpy(xml, tmp);
+
+ return success;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+void statementrecording()
+{
+ int insert;
+
+ tell(0, "---------------------------------");
+
+ cDbTable* recordingListDb = new cDbTable(connection, "recordinglist");
+ if (recordingListDb->open() != success) return ;
+
+ recordingListDb->clear();
+
+#ifdef USEMD5
+ md5Buf md5path;
+ createMd5("rec->FileName() dummy", md5path);
+ recordingListDb->setValue("MD5PATH", md5path);
+#else
+ recordingListDb->setValue("MD5PATH", "dummy");
+#endif
+
+ recordingListDb->setValue("OWNER", "me");
+ recordingListDb->setValue("STARTTIME", 12121212);
+
+ insert = !recordingListDb->find();
+ recordingListDb->clearChanged();
+
+ tell(0, "#1 %d changes", recordingListDb->getChanges());
+
+ // recordingListDb->setValue("STATE", "E");
+ recordingListDb->getValue("STATE")->setNull();
+ recordingListDb->setValue("PATH", "rec->FileName()");
+ recordingListDb->setValue("TITLE", "title");
+ recordingListDb->setValue("SHORTTEXT", "subTitle");
+ // recordingListDb->setValue("DESCRIPTION", "description");
+
+ recordingListDb->setValue("DURATION", 120*60);
+ recordingListDb->setValue("EVENTID", 1212);
+ recordingListDb->setValue("CHANNELID", "xxxxxx");
+
+ tell(0, "#2 %d changes", recordingListDb->getChanges());
+ recordingListDb->setValue("FSK", yes);
+ tell(0, "#3 %d changes", recordingListDb->getChanges());
+
+ // don't toggle uuid if already set!
+
+ if (recordingListDb->getValue("VDRUUID")->isNull())
+ recordingListDb->setValue("VDRUUID", "11111");
+
+ if (insert || recordingListDb->getChanges())
+ {
+ tell(0, "storing '%s' due to %d changes ", insert ? "insert" : "update", recordingListDb->getChanges());
+ recordingListDb->store();
+ }
+
+ recordingListDb->reset();
+
+ tell(0, "---------------------------------");
+
+ delete recordingListDb;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+void statementTimer()
+{
+ cDbValue timerState;
+ cDbFieldDef timerStateDef("STATE", "state", cDBS::ffAscii, 100, cDBS::ftData);
+
+ cEpgConfig::loglevel = 0;
+
+ cDbTable* timerDb = new cDbTable(connection, "timers");
+ if (timerDb->open() != success) return ;
+
+ cDbTable* useeventsDb = new cDbTable(connection, "useevents");
+ if (useeventsDb->open() != success) return ;
+
+ // select t.*,
+ // e.eventid, e.channelid, e.title, e.shorttext, e.shortdescription, e.category, e.genre, e.tipp
+ // from timers t left outer join events e
+ // on (t.eventid = e.masterid and e.updflg in (...))
+ // where
+ // t.state in (?)
+
+ timerState.setField(&timerStateDef);
+
+ cDbStatement* selectAllTimer = new cDbStatement(timerDb);
+
+ selectAllTimer->build("select ");
+ selectAllTimer->setBindPrefix("t.");
+ selectAllTimer->bindAllOut();
+ selectAllTimer->setBindPrefix("e.");
+ selectAllTimer->bind(useeventsDb, "USEID", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "CHANNELID", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "TITLE", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "SHORTTEXT", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "SHORTDESCRIPTION", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "CATEGORY", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "GENRE", cDBS::bndOut, ", ");
+ selectAllTimer->bind(useeventsDb, "TIPP", cDBS::bndOut, ", ");
+ selectAllTimer->clrBindPrefix();
+ selectAllTimer->build(" from %s t left outer join %s e",
+ timerDb->TableName(), "eventsviewplain");
+ selectAllTimer->build(" on (t.eventid = e.cnt_useid) and e.updflg in (%s)", cEventState::getVisible());
+
+ selectAllTimer->setBindPrefix("t.");
+ selectAllTimer->build(" where ");
+ selectAllTimer->bindInChar(0, "STATE", &timerState);
+
+ cEpgConfig::loglevel = 2;
+ selectAllTimer->prepare();
+
+ // ---------------------------------
+
+ timerDb->clear();
+ timerState.setValue("A,D,P");
+
+ tell(0, "---------------------------------");
+
+ for (int found = selectAllTimer->find(); found; found = selectAllTimer->fetch())
+ {
+ tell(0, "%ld) %s - %s",
+ timerDb->getIntValue("ID"),
+ timerDb->getStrValue("STATE"),
+ timerDb->getStrValue("FILE"));
+ }
+
+ tell(0, "---------------------------------");
+
+ delete selectAllTimer;
+ delete timerDb;
+}
+
+void statementVdrs()
+{
+ cDbTable* vdrDb = new cDbTable(connection, "vdrs");
+ if (vdrDb->open() != success) return ;
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", "10");
+ vdrDb->find();
+ vdrDb->setValue("VIDEOTOTAL", 1782579);
+ vdrDb->store();
+
+ delete vdrDb;
+}
+
+cDbFieldDef matchDensityTitleDef("MATCHDENSITYTITLE", "matchdensitytitle", cDBS::ffInt, 0, cDBS::ftData);
+cDbFieldDef matchDensityShorttextDef("MATCHDENSITYSHORTTEXT", "matchdensityshorttext", cDBS::ffInt, 0, cDBS::ftData);
+
+void chkStatement5()
+{
+ int count = 0;
+ cDbValue matchDensityTitle;
+ cDbValue matchDensityShorttext;
+
+ cDbTable* recordingListDb = new cDbTable(connection, "recordinglist");
+ if (recordingListDb->open() != success) return ;
+
+ matchDensityTitle.setField(&matchDensityTitleDef);
+ matchDensityShorttext.setField(&matchDensityShorttextDef);
+
+ recordingListDb->clear();
+
+ cDbStatement* selectRecordingForEventByLv = new cDbStatement(recordingListDb);
+
+ selectRecordingForEventByLv->build("select ");
+ selectRecordingForEventByLv->bind(recordingListDb->getField("TITLE"), cDBS::bndOut);
+ selectRecordingForEventByLv->bind(recordingListDb->getField("SHORTTEXT"), cDBS::bndOut, ", ");
+
+ selectRecordingForEventByLv->bindTextFree(", 100 - ifNull(epglvr(title, ?), 100)", &matchDensityTitle, cDBS::bndOut);
+ selectRecordingForEventByLv->appendBinding(recordingListDb->getValue("TITLE"), cDBS::bndIn);
+ selectRecordingForEventByLv->bindTextFree(", 100 - ifNull(epglvr(shorttext, ?), 100)", &matchDensityShorttext, cDBS::bndOut);
+ selectRecordingForEventByLv->appendBinding(recordingListDb->getValue("SHORTTEXT"), cDBS::bndIn);
+
+ selectRecordingForEventByLv->build(" from %s where", recordingListDb->TableName());
+ selectRecordingForEventByLv->build(" (%s <> 'D' or %s is null)",
+ recordingListDb->getField("STATE")->getDbName(),
+ recordingListDb->getField("STATE")->getDbName());
+ selectRecordingForEventByLv->bindTextFree("and epglvr(title, ?) < 47", recordingListDb->getValue("TITLE"), cDBS::bndIn);
+
+ if (selectRecordingForEventByLv->prepare() != success)
+ {
+ tell(0, "prepare failed");
+ return;
+ }
+
+ tell(0, "---------------------------------------------------");
+ const char* title = "Star Wars: Die Rache der Sith";
+ tell(0, "matches for '%s'", title);
+ tell(0, "---------------------------------------------------");
+
+ recordingListDb->clear();
+ recordingListDb->setValue("TITLE", title);
+ recordingListDb->setValue("SHORTTEXT", "Science-Fiction (USA 1979)");
+
+ for (int f = selectRecordingForEventByLv->find(); f; f = selectRecordingForEventByLv->fetch())
+ {
+ count++;
+ tell(2, "%03d) match density (%ld / %ld) for recording '%s' '%s'", count,
+ matchDensityTitle.getIntValue(), matchDensityShorttext.getIntValue(),
+ recordingListDb->getStrValue("TITLE"), recordingListDb->getStrValue("SHORTTEXT"));
+ }
+
+ tell(0, "---------------------------------------------------");
+ tell(2, "%d recordings", count);
+
+ selectRecordingForEventByLv->freeResult();
+}
+
+//***************************************************************************
+// Main
+//***************************************************************************
+
+int main(int argc, char** argv)
+{
+ cEpgConfig::logstdout = yes;
+ cEpgConfig::loglevel = 2;
+
+ if (argc > 1)
+ {
+ int size = 0;
+ char* url = 0;
+ MemoryStruct data;
+
+ data.clear();
+
+ asprintf(&url, "%s/eplist.cgi?action=show&file=%s",
+ "www.eplists.de", argv[1]);
+
+ tell(0, "try to download [%s]", url);
+
+ if (curl.downloadFile(url, size, &data) == success)
+ {
+ tell(0, "succeeded!");
+ tell(0, "Got: !");
+ tell(0, "%s", data.memory);
+ }
+ else
+ tell(0, "FAILED!");
+
+
+ free(url);
+
+ return 0;
+ }
+
+ setlocale(LC_CTYPE, "");
+ char* lang = setlocale(LC_CTYPE, 0);
+
+ if (lang)
+ {
+ tell(0, "Set locale to '%s'", lang);
+
+ if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0))
+ tell(0, "detected UTF-8");
+ else
+ tell(0, "no UTF-8");
+ }
+ else
+ {
+ tell(0, "Reseting locale for LC_CTYPE failed.");
+ }
+
+ // read dictionary
+
+// if (dbDict.in("demo.dat") != success)
+ if (dbDict.in("../configs/epg.dat") != success)
+ {
+ tell(0, "Invalid dictionary configuration, aborting!");
+ return 1;
+ }
+
+// dbDict.show();
+
+ initConnection();
+
+ chkStatement5();
+
+ // structure();
+
+// chkCompress();
+
+// tell(0, "duration was: '%s'", ms2Dur(2340).c_str());
+
+// statementVdrs();
+
+ // statementTimer();
+ // statementrecording();
+ // chkStatement2();
+ // chkStatement3();
+ // chkStatement4(); exitConnection();
+
+ return 0;
+}
diff --git a/lib/thread.c b/lib/thread.c
new file mode 100644
index 0000000..5938d75
--- /dev/null
+++ b/lib/thread.c
@@ -0,0 +1,342 @@
+/*
+ * thread.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <linux/unistd.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include "thread.h"
+
+//***************************************************************************
+// get abs time plus 'millisecondsFromNow'
+//***************************************************************************
+
+static bool absTime(struct timespec* abstime, int millisecondsFromNow)
+{
+ struct timeval now;
+
+ if (gettimeofday(&now, 0) == 0)
+ {
+ // get current time
+
+ now.tv_sec += millisecondsFromNow / 1000; // add full seconds
+ now.tv_usec += (millisecondsFromNow % 1000) * 1000; // add microseconds
+
+ // take care of an overflow
+
+ if (now.tv_usec >= 1000000)
+ {
+ now.tv_sec++;
+ now.tv_usec -= 1000000;
+ }
+
+ abstime->tv_sec = now.tv_sec; // seconds
+ abstime->tv_nsec = now.tv_usec * 1000; // nano seconds
+
+ return success;
+ }
+
+ return fail;
+}
+
+//***************************************************************************
+// Class cThread
+//***************************************************************************
+
+cThread::cThread(const char* Description, bool LowPriority)
+{
+ active = running = no;
+ childTid = 0;
+ childThreadId = 0;
+ description = 0;
+ silent = no;
+
+ if (Description)
+ SetDescription("%s", Description);
+
+ lowPriority = LowPriority;
+}
+
+cThread::~cThread()
+{
+ Cancel(); // just in case the derived class didn't call it
+ free(description);
+}
+
+void cThread::SetDescription(const char *Description, ...)
+{
+ free(description);
+ description = NULL;
+
+ if (Description)
+ {
+ va_list ap;
+ va_start(ap, Description);
+ vasprintf(&description, Description, ap);
+ va_end(ap);
+ }
+}
+
+void *cThread::StartThread(cThread *Thread)
+{
+ Thread->childThreadId = ThreadId();
+ if (Thread->description)
+ {
+ tell(Thread->silent ? 2 : 0, "'%s' thread started (pid=%d, tid=%d, prio=%s)", Thread->description, getpid(), Thread->childThreadId, Thread->lowPriority ? "low" : "high");
+#ifdef PR_SET_NAME
+ if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0)
+ tell(0, "%s thread naming failed (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
+#endif
+ }
+
+ if (Thread->lowPriority)
+ {
+ Thread->SetPriority(19);
+ Thread->SetIOPriority(7);
+ }
+
+ Thread->action();
+
+ if (Thread->description)
+ tell(Thread->silent ? 2 : 0, "'%s' thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
+
+ Thread->running = false;
+ Thread->active = false;
+
+ return NULL;
+}
+
+//***************************************************************************
+// Priority
+//***************************************************************************
+
+void cThread::SetPriority(int priority)
+{
+ if (setpriority(PRIO_PROCESS, 0, priority) < 0)
+ tell(0, "Error: Setting priority failed");
+}
+
+void cThread::SetIOPriority(int priority)
+{
+ if (syscall(SYS_ioprio_set, 1, 0, (priority & 0xff) | (3 << 13)) < 0) // idle class
+ tell(0, "Error: Setting io priority failed");
+}
+
+//***************************************************************************
+// Start
+//***************************************************************************
+
+#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it
+#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop
+
+bool cThread::Start(int s)
+{
+ silent = s;
+
+ if (!running)
+ {
+ if (active)
+ {
+ // Wait until the previous incarnation of this thread has completely ended
+ // before starting it newly:
+
+ cMyTimeMs RestartTimeout;
+
+ while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT)
+ cCondWait::SleepMs(THREAD_STOP_SLEEP);
+ }
+ if (!active)
+ {
+ active = running = true;
+
+ if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0)
+ {
+ pthread_detach(childTid); // auto-reap
+ }
+ else
+ {
+ tell(0, "Error: Thread won't start");
+ active = running = false;
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool cThread::Active(void)
+{
+ if (active)
+ {
+ int err;
+
+ if ((err = pthread_kill(childTid, 0)) != 0)
+ {
+ if (err != ESRCH)
+ tell(0, "Error: Thread ...");
+ childTid = 0;
+ active = running = false;
+ }
+ else
+ return true;
+ }
+
+ return false;
+}
+
+void cThread::Cancel(int WaitSeconds)
+{
+ running = false;
+
+ if (active && WaitSeconds > -1)
+ {
+ if (WaitSeconds > 0)
+ {
+ for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; )
+ {
+ if (!Active())
+ return;
+ cCondWait::SleepMs(10);
+ }
+
+ tell(0, "ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId, WaitSeconds);
+ }
+
+ pthread_cancel(childTid);
+ childTid = 0;
+ active = false;
+ }
+}
+
+pid_t cThread::ThreadId()
+{
+ return syscall(__NR_gettid);
+}
+
+//***************************************************************************
+// cCondWait
+//***************************************************************************
+
+cCondWait::cCondWait()
+{
+ signaled = false;
+ pthread_mutex_init(&mutex, NULL);
+ pthread_cond_init(&cond, NULL);
+}
+
+cCondWait::~cCondWait()
+{
+ pthread_cond_broadcast(&cond); // wake up any sleepers
+ pthread_cond_destroy(&cond);
+ pthread_mutex_destroy(&mutex);
+}
+
+void cCondWait::SleepMs(int TimeoutMs)
+{
+ cCondWait w;
+ w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait
+}
+
+bool cCondWait::Wait(int TimeoutMs)
+{
+ pthread_mutex_lock(&mutex);
+
+ if (!signaled)
+ {
+ if (TimeoutMs)
+ {
+ struct timespec abstime;
+
+ if (absTime(&abstime, TimeoutMs) == success)
+ {
+ while (!signaled)
+ {
+ if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT)
+ break;
+ }
+ }
+ }
+ else
+ pthread_cond_wait(&cond, &mutex);
+ }
+
+ bool r = signaled;
+ signaled = false;
+ pthread_mutex_unlock(&mutex);
+
+ return r;
+}
+
+void cCondWait::Signal()
+{
+ pthread_mutex_lock(&mutex);
+ signaled = true;
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&mutex);
+}
+
+//***************************************************************************
+// cCondVar
+//***************************************************************************
+
+cCondVar::cCondVar(void)
+{
+ pthread_cond_init(&cond, 0);
+}
+
+cCondVar::~cCondVar()
+{
+ pthread_cond_broadcast(&cond); // wake up any sleepers
+ pthread_cond_destroy(&cond);
+}
+
+void cCondVar::Wait(cMyMutex &Mutex)
+{
+ if (Mutex.locked)
+ {
+ int locked = Mutex.locked;
+ Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait
+ // does an implicit unlock of the mutex
+ pthread_cond_wait(&cond, &Mutex.mutex);
+ Mutex.locked = locked;
+ }
+}
+
+bool cCondVar::TimedWait(cMyMutex &Mutex, int TimeoutMs)
+{
+ bool r = true; // true = condition signaled, false = timeout
+
+ if (Mutex.locked)
+ {
+ struct timespec abstime;
+
+ if (absTime(&abstime, TimeoutMs) == success)
+ {
+ int locked = Mutex.locked;
+
+ // have to clear the locked count here, as pthread_cond_timedwait
+ // does an implicit unlock of the mutex.
+
+ Mutex.locked = 0;
+
+ if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT)
+ r = false;
+
+ Mutex.locked = locked;
+ }
+ }
+
+ return r;
+}
+
+void cCondVar::Broadcast(void)
+{
+ pthread_cond_broadcast(&cond);
+}
diff --git a/lib/thread.h b/lib/thread.h
new file mode 100644
index 0000000..1598ff2
--- /dev/null
+++ b/lib/thread.h
@@ -0,0 +1,92 @@
+/*
+ * thread.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __LIB_THREAD_H
+#define __LIB_THREAD_H
+
+#include "common.h"
+
+//***************************************************************************
+// Class cThread
+//***************************************************************************
+
+class cThread
+{
+ public:
+
+ cThread(const char *Description = NULL, bool LowPriority = false);
+ virtual ~cThread();
+
+ void SetDescription(const char* Description, ...) __attribute__ ((format (printf, 2, 3)));
+ bool Start(int s = no);
+ bool Active();
+ void SetPriority(int priority);
+ void SetIOPriority(int priority);
+
+ static pid_t ThreadId();
+
+ private:
+
+ static void* StartThread(cThread *Thread);
+
+ bool active;
+ bool running;
+ pthread_t childTid;
+ pid_t childThreadId;
+ cMyMutex mutex;
+ char* description;
+ bool lowPriority;
+ int silent;
+
+ protected:
+
+ virtual void action() = 0;
+
+ void Lock() { mutex.Lock(); }
+ void Unlock() { mutex.Unlock(); }
+ bool Running() { return running; }
+ void Cancel(int WaitSeconds = 0);
+};
+
+class cCondWait
+{
+ public:
+
+ cCondWait();
+ ~cCondWait();
+
+ bool Wait(int TimeoutMs = 0);
+ void Signal();
+
+ static void SleepMs(int TimeoutMs);
+
+ private:
+
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ bool signaled;
+};
+
+class cCondVar
+{
+ public:
+
+ cCondVar();
+ ~cCondVar();
+
+ void Wait(cMyMutex &Mutex);
+ bool TimedWait(cMyMutex &Mutex, int TimeoutMs);
+ void Broadcast();
+
+ private:
+
+ pthread_cond_t cond;
+};
+
+//***************************************************************************
+
+#endif // __LIB_THREAD_H
diff --git a/lib/wol.c b/lib/wol.c
new file mode 100644
index 0000000..2fb1eca
--- /dev/null
+++ b/lib/wol.c
@@ -0,0 +1,116 @@
+/*
+ * wol.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+#include "common.h"
+#include "wol.h"
+
+//***************************************************************************
+// Send WOL
+//***************************************************************************
+
+int sendWol(const char* mac, const char* device)
+{
+ struct sockaddr_in addr;
+ unsigned char packet[sizeWolPacket];
+ int sock;
+ int optval = 1;
+ cWolHeader wolHeader;
+ const char* bcast = "";
+
+ if (isEmpty(device))
+ device = getFirstInterface();
+
+ bcast = bcastAddressOf(getIpOf(device), getMaskOf(device));
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ {
+ tell(0, "Error: Cannot open socket '%s'", strerror(errno));
+ return fail;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&optval, sizeof(optval)) < 0)
+ {
+ tell(0, "Error: Cannot set socket options '%s'", strerror(errno));
+ return fail;
+ }
+
+ sprintf(wolHeader.remoteAddr, "%.*s", sizeAddr, bcast);
+
+ if (packMacAddr(mac, &wolHeader.macAddr) < 0)
+ {
+ tell(0, "Error: MAC Address '%s' ist not valid", mac);
+ return fail;
+ }
+
+ // .....
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(wolPort);
+
+ if (inet_aton(wolHeader.remoteAddr, &addr.sin_addr) == 0)
+ {
+ tell(0, "Error: Invalid remote ip address given '%s'", wolHeader.remoteAddr);
+ return fail;
+ }
+
+ for (int i = 0; i < 6; i++)
+ packet[i] = 0xFF;
+
+ for (int i = 1; i <= 16; i++)
+ for (int j = 0; j < 6; j++)
+ packet[i * 6 + j] = wolHeader.macAddr.macAddr[j];
+
+ if (sendto(sock, packet, sizeof(packet), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0)
+ {
+ tell(0, "Error: Cannot send data '%s'", strerror(errno));
+ return fail;
+ }
+
+ tell(0, "Info: Successful sent WOL magic packet to '%s' via bcast '%s'",
+ wolHeader.macAddr.macAddrStr, bcast);
+
+ close(sock);
+
+ return success;
+}
+
+//***************************************************************************
+// Pack MAC Address
+//***************************************************************************
+
+int packMacAddr(const char* mac, cMacAddr* packedMac)
+{
+ char* tmpMac = strdup(mac);
+ char* tok;
+
+ tok = strtok(tmpMac, ":");
+
+ for (int i = 0; i < macAddrTupel; i++)
+ {
+ if (!tok)
+ {
+ free(tmpMac);
+ return fail;
+ }
+
+ packedMac->macAddr[i] = (unsigned char)strtol(tok, 0, 16); // HEX
+ tok = strtok(0, ":");
+ }
+
+ strncpy(packedMac->macAddrStr, mac, sizeMacStr);
+ free(tmpMac);
+
+ return success;
+}
diff --git a/lib/wol.h b/lib/wol.h
new file mode 100644
index 0000000..05dd4cb
--- /dev/null
+++ b/lib/wol.h
@@ -0,0 +1,31 @@
+/*
+ * wol.h:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+enum wolMisc
+{
+ sizeWolPacket = 17*6,
+ sizeMacStr = 64,
+ sizeAddr = 16,
+
+ macAddrTupel = 6,
+ wolPort = 9
+};
+
+struct cMacAddr
+{
+ unsigned char macAddr[macAddrTupel];
+ char macAddrStr[sizeMacStr];
+};
+
+struct cWolHeader
+{
+ char remoteAddr[sizeAddr+TB];
+ cMacAddr macAddr;
+};
+
+int sendWol(const char* mac, const char* device = 0);
+int packMacAddr(const char* mac, cMacAddr* packedMac);