diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile | 119 | ||||
-rw-r--r-- | lib/common.c | 1921 | ||||
-rw-r--r-- | lib/common.h | 560 | ||||
-rw-r--r-- | lib/config.c | 59 | ||||
-rw-r--r-- | lib/config.h | 47 | ||||
-rw-r--r-- | lib/configuration.c | 238 | ||||
-rw-r--r-- | lib/configuration.h | 164 | ||||
-rw-r--r-- | lib/curl.c | 454 | ||||
-rw-r--r-- | lib/curl.h | 77 | ||||
-rw-r--r-- | lib/db.c | 1646 | ||||
-rw-r--r-- | lib/db.h | 1367 | ||||
-rw-r--r-- | lib/dbdict.c | 527 | ||||
-rw-r--r-- | lib/dbdict.h | 471 | ||||
-rw-r--r-- | lib/demo.c | 531 | ||||
-rw-r--r-- | lib/demo.dat | 17 | ||||
-rw-r--r-- | lib/epgservice.c | 121 | ||||
-rw-r--r-- | lib/epgservice.h | 469 | ||||
-rw-r--r-- | lib/imgtools.c | 217 | ||||
-rw-r--r-- | lib/imgtools.h | 31 | ||||
-rw-r--r-- | lib/json.c | 168 | ||||
-rw-r--r-- | lib/json.h | 36 | ||||
-rw-r--r-- | lib/parameters.c | 257 | ||||
-rw-r--r-- | lib/parameters.h | 59 | ||||
-rw-r--r-- | lib/python.c | 421 | ||||
-rw-r--r-- | lib/python.h | 78 | ||||
-rw-r--r-- | lib/pytst.c | 128 | ||||
-rw-r--r-- | lib/searchtimer.c | 1777 | ||||
-rw-r--r-- | lib/searchtimer.h | 111 | ||||
-rw-r--r-- | lib/semtst.c | 42 | ||||
-rw-r--r-- | lib/test.c | 738 | ||||
-rw-r--r-- | lib/thread.c | 342 | ||||
-rw-r--r-- | lib/thread.h | 92 | ||||
-rw-r--r-- | lib/wol.c | 116 | ||||
-rw-r--r-- | lib/wol.h | 31 |
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( ®, expression, opt) != 0) + return fail; + + // Suchen des ersten Vorkommens von reg in string + + status = regexec(®, string, 1, &rm, 0); + regfree(®); + + 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 ¶meters[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); |