diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile | 7 | ||||
-rw-r--r-- | lib/common.c | 812 | ||||
-rw-r--r-- | lib/common.h | 179 | ||||
-rw-r--r-- | lib/config.c | 63 | ||||
-rw-r--r-- | lib/config.h | 73 | ||||
-rw-r--r-- | lib/db.c | 1086 | ||||
-rw-r--r-- | lib/db.h | 1030 | ||||
-rw-r--r-- | lib/tabledef.c | 856 | ||||
-rw-r--r-- | lib/tabledef.h | 832 |
9 files changed, 4938 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..f27782b --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,7 @@ + + +all: + g++ -ggdb -DPLGDIR='"."' test.c common.c config.c db.c tabledef.c -lrt -lz -lmysqlclient -o t + +clean: + rm -f *.o *.a *~ core diff --git a/lib/common.c b/lib/common.c new file mode 100644 index 0000000..f3a0436 --- /dev/null +++ b/lib/common.c @@ -0,0 +1,812 @@ +/* + * common.c: + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <sys/stat.h> + +#ifdef USEUUID +# include <uuid/uuid.h> +#endif + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <zlib.h> + +#ifdef USELIBARCHIVE +# include <archive.h> +# include <archive_entry.h> +#endif + +#ifdef VDR_PLUGIN +# include <vdr/thread.h> +#endif + +#include "common.h" +#include "config.h" + +#ifdef VDR_PLUGIN + cMutex logMutex; +#endif + +//*************************************************************************** +// Debug +//*************************************************************************** + +void tell(int eloquence, const char* format, ...) +{ + if (EPG2VDRConfig.loglevel < eloquence) + return ; + + const int sizeBuffer = 100000; + char t[sizeBuffer+100]; *t = 0; + va_list ap; + +#ifdef VDR_PLUGIN + cMutexLock lock(&logMutex); +#endif + + va_start(ap, format); + +#ifdef VDR_PLUGIN + snprintf(t, sizeBuffer, "scraper2vdr: "); +#endif + + vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap); + + if (EPG2VDRConfig.logstdout) + { + char buf[50+TB]; + time_t now; + time(&now); + strftime(buf, 50, "%y.%m.%d %H:%M:%S", localtime(&now)); + printf("%s %s\n", buf, t); + } + else + syslog(LOG_ERR, "%s", t); + + va_end(ap); +} + +//*************************************************************************** +// 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 = 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); +} + +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 = max(mblen(&s[ps], lenSrc-ps), 1); + + for (int pi = 0; pi < lenIgn; pi += csIgn) + { + csIgn = 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; + + csSrc = max(mblen(&s[ps], lenSrc-ps), 1); + + for (int pi = 0; pi < lenIgn; pi += csIgn) + { + csIgn = 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); +} + +//*************************************************************************** +// 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)); +} + +//*************************************************************************** +// 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) +{ + char txt[30]; + tm* tmp = localtime(&t); + + strftime(txt, sizeof(txt), "%d.%m.%Y %T", tmp); + + return std::string(txt); +} + +//*************************************************************************** +// MS to Duration +//*************************************************************************** + +std::string ms2Dur(uint64_t t) +{ + char txt[30]; + + int s = t / 1000; + int ms = t % 1000; + + snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms); + + return std::string(txt); +} + +//*************************************************************************** +// Char to Char-String +//*************************************************************************** + +const char* c2s(char c, char* buf) +{ + sprintf(buf, "%c", c); + + return buf; +} + +//*************************************************************************** +// TOOLS +//*************************************************************************** + +int isEmpty(const char* str) +{ + return !str || !*str; +} + +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 '%m'", path); + + return false; +} + +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 '%m'", path); + + return false; +} + + +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 '%m'", link); + 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', '%m'", filename); + + 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 '%m'"); + 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 + +//*************************************************************************** +// 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; +} + +//************************************************************************* +// 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 unzip file%s!\n", op); return; + case Z_BUF_ERROR: tell(0, "Error: Couldn't 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 unzip data for unknown reason (%6d)%s!\n", errorCode, op); return; + } +} + +//************************************************************************* +// 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() +{ + struct ifaddrs *ifaddr, *ifa; + static char host[NI_MAXHOST] = ""; + + if (getifaddrs(&ifaddr) == -1) + { + tell(0, "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, "getnameinfo() failed: %s", gai_strerror(res)); + return ""; + } + + // skip loopback interface + + if (strcmp(host, "127.0.0.1") == 0) + continue; + + tell(5, "%-8s %-15s %s", ifa->ifa_name, host, + ifa->ifa_addr->sa_family == AF_INET ? " (AF_INET)" : + ifa->ifa_addr->sa_family == AF_INET6 ? " (AF_INET6)" : ""); + } + } + + freeifaddrs(ifaddr); + + return host; +} + +#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 - %m", file); + 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 + +//*************************************************************************** +// Class LogDuration +//*************************************************************************** + +#ifdef VDR_PLUGIN + +# include <vdr/plugin.h> + +LogDuration::LogDuration(const char* aMessage, int aLogLevel) +{ + logLevel = aLogLevel; + strcpy(message, aMessage); + + // at last ! + + durationStart = cTimeMs::Now(); +} + +LogDuration::~LogDuration() +{ + tell(logLevel, "duration '%s' was (%dms)", + message, cTimeMs::Now() - durationStart); +} + +void LogDuration::show(const char* label) +{ + tell(logLevel, "elapsed '%s' at '%s' was (%dms)", + message, label, cTimeMs::Now() - durationStart); +} + +#endif + +//*************************************************************************** +// 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: Can't access '%s'; %m", file); + 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 diff --git a/lib/common.h b/lib/common.h new file mode 100644 index 0000000..614dfe6 --- /dev/null +++ b/lib/common.h @@ -0,0 +1,179 @@ +/* + * common.h: EPG2VDR plugin for the Video Disk Recorder + * + * 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 <stdlib.h> +#include <string> + +#include <openssl/md5.h> // MD5_* + +#ifdef VDR_PLUGIN +# include <vdr/tools.h> +#endif + +#ifdef USELIBXML +# include <libxslt/transform.h> +# include <libxslt/xsltutils.h> +# include <libexslt/exslt.h> +#endif + +//*************************************************************************** +// +//*************************************************************************** + +#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; } +#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, + + sizeMd5 = 2 * MD5_DIGEST_LENGTH, + sizeUuid = 36, + + tmeSecondsPerMinute = 60, + tmeSecondsPerHour = tmeSecondsPerMinute * 60, + tmeSecondsPerDay = 24 * tmeSecondsPerHour +}; + +//*************************************************************************** +// Tell +//*************************************************************************** + +void tell(int eloquence, const char* format, ...); + +//*************************************************************************** +// MemoryStruct for curl callbacks +//*************************************************************************** + +struct MemoryStruct +{ + MemoryStruct() { memory = 0; clear(); } + ~MemoryStruct() { clear(); } + + // data + + char* memory; + size_t size; + + // tag attribute + + char tag[100]; // the tag to be compared + char name[100]; // content name (filename) + int headerOnly; + + int isEmpty() { return memory == 0; } + + void clear() + { + free(memory); + memory = 0; + size = 0; + *tag = 0; + *name = 0; + headerOnly = no; + } +}; + +//*************************************************************************** +// Tools +//*************************************************************************** + +unsigned int getHostId(); +const char* getHostName(); +const char* getFirstIp(); + +#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); + +char* rTrim(char* buf); +char* lTrim(char* buf); +char* allTrim(char* buf); +char* sstrcpy(char* dest, const char* src, int max); +std::string num2Str(int num); +std::string l2pTime(time_t t); +std::string ms2Dur(uint64_t t); +const char* c2s(char c, char* buf); + +int fileExists(const char* path); +int fileSize(const char* path); +int createLink(const char* link, const char* dest, int force); +int isLink(const char* path); +int isEmpty(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 +//*************************************************************************** + +int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData); +void tellZipError(int errorCode, const char* op, const char* msg); + +#ifdef USELIBARCHIVE +int unzip(const char* file, const char* filter, char*& buffer, + int& size, char* entryName); +#endif + +#ifdef VDR_PLUGIN + +//*************************************************************************** +// 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; +}; +#endif + +//*************************************************************************** +#endif //___COMMON_H diff --git a/lib/config.c b/lib/config.c new file mode 100644 index 0000000..9ca25f1 --- /dev/null +++ b/lib/config.c @@ -0,0 +1,63 @@ +/* + * config.c: + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <string.h> + +#include "common.h" +#include "config.h" + +cEPG2VDRConfig EPG2VDRConfig; + +cEPG2VDRConfig::cEPG2VDRConfig(void) +{ + mainmenuVisible = yes; + mainmenuFullupdate = 0; + + useproxy = no; + sstrcpy(httpproxy, "127.0.0.1:8000", sizeof(httpproxy)); + sstrcpy(username, "", sizeof(username)); + sstrcpy(password, "", sizeof(password)); + + checkInitial = yes; + updatetime = 6; // hours + days = 8; + upddays = 2; + storeXmlToFs = no; + blacklist = no; + masterMode = 0; + + getepgimages = yes; + maximagesperevent = 1; + epgImageSize = 2; + + seriesEnabled = yes; + sstrcpy(seriesUrl, "eplists.constabel.net", sizeof(seriesUrl)); + seriesPort = 2006; + storeSeriesToFs = no; + +#ifdef VDR_PLUGIN + activeOnEpgd = no; + scheduleBoot = no; +#else + sstrcpy(cachePath, "/var/cache/epgd", sizeof(cachePath)); + sstrcpy(pluginPath, PLGDIR, sizeof(pluginPath)); + sstrcpy(epgView, "eventsview.sql", sizeof(epgView)); + updateThreshold = 200; + maintanance = no; +#endif + + sstrcpy(dbHost, "localhost", sizeof(dbHost)); + dbPort = 3306; + sstrcpy(dbName, "epg2vdr", sizeof(dbName)); + sstrcpy(dbUser, "epg2vdr", sizeof(dbUser)); + sstrcpy(dbPass, "epg", sizeof(dbPass)); + + logstdout = no; + loglevel = 1; + + uuid[0] = 0; +} diff --git a/lib/config.h b/lib/config.h new file mode 100644 index 0000000..e0c729c --- /dev/null +++ b/lib/config.h @@ -0,0 +1,73 @@ +/* + * config.h: + * + * See the README file for copyright information and how to reach the author. + * + * $Id: config.h,v 1.2 2012/10/26 08:44:13 wendel Exp $ + */ + +#ifndef __EPG2VDR_CONFIG_H +#define __EPG2VDR_CONFIG_H + +#include "common.h" + +//*************************************************************************** +// Config +//*************************************************************************** + +struct cEPG2VDRConfig +{ + public: + + cEPG2VDRConfig(void); + + int useproxy; + char httpproxy[256+TB]; + char username[100+TB]; + char password[100+TB]; + + int checkInitial; + int updatetime; + int days; + int upddays; + int storeXmlToFs; + int blacklist; // to enable noepg feature + + int getepgimages; + int maximagesperevent; + int epgImageSize; + + int seriesEnabled; + char seriesUrl[500+TB]; + int seriesPort; + int storeSeriesToFs; + +#ifdef VDR_PLUGIN + int activeOnEpgd; + int scheduleBoot; +#else + char cachePath[256+TB]; + char pluginPath[256+TB]; + char epgView[100+TB]; + int updateThreshold; + int maintanance; +#endif + + char dbHost[100+TB]; + int dbPort; + char dbName[100+TB]; + char dbUser[100+TB]; + char dbPass[100+TB]; + + int logstdout; + int loglevel; + + int mainmenuVisible; + int mainmenuFullupdate; + int masterMode; + char uuid[sizeUuid+TB]; +}; + +extern cEPG2VDRConfig EPG2VDRConfig; + +#endif // __EPG2VDR_CONFIG_H diff --git a/lib/db.c b/lib/db.c new file mode 100644 index 0000000..17e679c --- /dev/null +++ b/lib/db.c @@ -0,0 +1,1086 @@ +/* + * db.c + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <stdio.h> + +#include <mysql/errmsg.h> + +#include "db.h" + +// #define DEB_HANDLER + +//*************************************************************************** +// DB Statement +//*************************************************************************** + +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; +} + +cDbStatement::cDbStatement(cDbConnection* aConnection, const char* stmt) +{ + table = 0; + connection = aConnection; + stmtTxt = stmt; + stmt = 0; + inCount = 0; + outCount = 0; + inBind = 0; + outBind = 0; + affected = 0; + metaResult = 0; + bindPrefix = 0; +} + +//*************************************************************************** +// Execute +//*************************************************************************** + +int cDbStatement::execute(int noResult) +{ + affected = 0; + + if (!connection || !connection->getMySql()) + return fail; + + if (!stmt) + return connection->errorSql(connection, "execute(missing statement)"); + + // tell(0, "execute %d [%s]", stmt, stmtTxt.c_str()); + + if (mysql_stmt_execute(stmt)) + return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str()); + + // 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::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(int field, int mode, const char* delim) +{ + return bind(table->getRow()->getValue(field), mode, delim); +} + +int cDbStatement::bind(cDbValue* value, int mode, const char* delim) +{ + if (!value || !value->getField()) + return fail; + + if (delim) + stmtTxt += delim; + + if (bindPrefix) + stmtTxt += bindPrefix; + + if (mode & bndIn) + { + if (mode & bndSet) + stmtTxt += value->getName() + string(" ="); + + stmtTxt += " ?"; + appendBinding(value, bndIn); + } + else if (mode & bndOut) + { + stmtTxt += value->getName(); + appendBinding(value, bndOut); + } + + return success; +} + +int cDbStatement::bindCmp(const char* ctable, cDbValue* value, + const char* comp, const char* delim) +{ + if (ctable) + build("%s.", ctable); + + build("%s%s %s ?", delim ? delim : "", value->getName(), comp); + + appendBinding(value, bndIn); + + return success; +} + +int cDbStatement::bindCmp(const char* ctable, int field, cDbValue* value, + const char* comp, const char* delim) +{ + cDbValue* vf = table->getRow()->getValue(field); + cDbValue* vv = value ? value : vf; + + if (ctable) + build("%s.", ctable); + + build("%s%s %s ?", delim ? delim : "", vf->getName(), comp); + + 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*)realloc(*bindings, count * sizeof(MYSQL_BIND)); + + newBinding = &((*bindings)[count-1]); + + if (value->getField()->format == ffAscii || value->getField()->format == ffText) + { + newBinding->buffer_type = MYSQL_TYPE_STRING; + newBinding->buffer = value->getStrValueRef(); + newBinding->buffer_length = value->getField()->size; + newBinding->length = value->getStrValueSizeRef(); + + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else if (value->getField()->format == ffMlob) + { + newBinding->buffer_type = MYSQL_TYPE_BLOB; + newBinding->buffer = value->getStrValueRef(); + newBinding->buffer_length = value->getField()->size; + newBinding->length = value->getStrValueSizeRef(); + + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else if (value->getField()->format == 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()->format == 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 + { + newBinding->buffer_type = MYSQL_TYPE_LONG; + newBinding->buffer = value->getIntValueRef(); + newBinding->is_unsigned = (value->getField()->format == ffUInt); + + newBinding->length = 0; + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + + return success; +} + +//*************************************************************************** +// Prepare Statement +//*************************************************************************** + +int cDbStatement::prepare() +{ + if (!stmtTxt.length() || !connection->getMySql()) + 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(2, "Statement '%s' with (%d) in parameters and (%d) out bindings prepared", + stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount); + + return success; +} + +//*************************************************************************** +// cDbService +//*************************************************************************** + +const char* cDbService::formats[] = +{ + "INT", + "INT", + "VARCHAR", + "TEXT", + "MEDIUMBLOB", + "FLOAT", + "DATETIME", + + 0 +}; + +const char* cDbService::toString(FieldFormat t) +{ + return formats[t]; +} + +//*************************************************************************** +// Class cDbTable +//*************************************************************************** + +char* cDbTable::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; + +//*************************************************************************** +// Object +//*************************************************************************** + +cDbTable::cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i) +{ + connection = aConnection; + row = new cDbRow(f); + holdInMemory = no; + + stmtSelect = 0; + stmtInsert = 0; + stmtUpdate = 0; + + indices = i; +} + +cDbTable::~cDbTable() +{ + close(); + + delete row; +} + +//*************************************************************************** +// Open / Close +//*************************************************************************** + +int cDbTable::open() +{ + if (connection->attachConnection() != success) + { + tell(0, "Could not access database '%s:%d' (tried to open %s)", + connection->getHost(), connection->getPort(), TableName()); + + return fail; + } + + return init(); +} + +int cDbTable::close() +{ + if (stmtSelect) { delete stmtSelect; stmtSelect = 0; } + if (stmtInsert) { delete stmtInsert; stmtInsert = 0; } + if (stmtUpdate) { delete stmtUpdate; stmtUpdate = 0; } + + connection->detachConnection(); + + return success; +} + +//*************************************************************************** +// Init +//*************************************************************************** + +int cDbTable::init() +{ + string str; + + if (!isConnected()) return fail; + + // check/create table ... + + if (createTable() != success) + return fail; + + // ------------------------------ + // prepare BASIC statements + // ------------------------------ + + // select by primary key ... + + stmtSelect = new cDbStatement(this); + + stmtSelect->build("select "); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (row->getField(i)->type & ftCalc) + continue; + + stmtSelect->bind(i, bndOut, n++ ? ", " : ""); + } + + stmtSelect->build(" from %s where ", TableName()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (!(row->getField(i)->type & ftPrimary)) + continue; + + stmtSelect->bind(i, bndIn | bndSet, n++ ? " and " : ""); + } + + stmtSelect->build(";"); + + if (stmtSelect->prepare() != success) + return fail; + + // ----------------------------------------- + // insert + + stmtInsert = new cDbStatement(this); + + stmtInsert->build("insert into %s set ", TableName()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + // don't insert autoinc and calculated fields + + if (row->getField(i)->type & ftCalc || row->getField(i)->type & ftAutoinc) + continue; + + stmtInsert->bind(i, 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()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + // don't update PKey, autoinc and calculated fields + + if (row->getField(i)->type & ftPrimary || + row->getField(i)->type & ftCalc || + row->getField(i)->type & ftAutoinc) + continue; + + if (strcmp(row->getField(i)->name, "inssp") == 0) // don't update the insert stamp + continue; + + stmtUpdate->bind(i, bndIn | bndSet, n++ ? ", " : ""); + } + + stmtUpdate->build(" where "); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (!(row->getField(i)->type & ftPrimary)) + continue; + + stmtUpdate->bind(i, bndIn | bndSet, n++ ? " and " : ""); + } + + stmtUpdate->build(";"); + + if (stmtUpdate->prepare() != success) + return fail; + + return success; +} + +//*************************************************************************** +// Check Table +//*************************************************************************** + +int cDbTable::exist(const char* name) +{ + if (!name) + name = TableName(); + + MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name); + MYSQL_ROW tabRow = mysql_fetch_row(result); + mysql_free_result(result); + + return tabRow ? yes : no; +} + +//*************************************************************************** +// Create Table +//*************************************************************************** + +int cDbTable::createTable() +{ + string statement; + string aKey; + + if (!isConnected()) + { + if (connection->attachConnection() != success) + { + tell(0, "Could not access database '%s:%d' (tried to create %s)", + connection->getHost(), connection->getPort(), TableName()); + + return fail; + } + } + + // table exists -> nothing to do + + if (exist()) + return done; + + tell(0, "Initialy creating table '%s'", TableName()); + + // build 'create' statement ... + + statement = string("create table ") + TableName() + string("("); + + for (int i = 0; getField(i)->name; i++) + { + int size = getField(i)->size; + char num[10]; + + if (getField(i)->type & ftCalc) + continue; + + if (i) statement += string(", "); + statement += string(getField(i)->name) + " " + string(toString(getField(i)->format)); + + if (getField(i)->format != ffMlob) + { + if (!size) size = getField(i)->format == ffAscii || getField(i)->format == ffText ? 100 : 11; + + if (getField(i)->format != ffFloat) + sprintf(num, "%d", size); + else + sprintf(num, "%d,%d", size/10, size%10); + + statement += "(" + string(num) + ")"; + + if (getField(i)->format == ffUInt) + statement += " unsigned"; + + if (getField(i)->type & ftAutoinc) + statement += " not null auto_increment"; + else if (getField(i)->type & ftDef0) + statement += " default '0'"; + } + } + + aKey = ""; + + for (int i = 0, n = 0; getField(i)->name; i++) + { + if (getField(i)->type & ftPrimary) + { + if (n++) aKey += string(", "); + aKey += string(getField(i)->name) + " DESC"; + } + } + + if (aKey.length()) + { + statement += string(", PRIMARY KEY("); + statement += aKey; + statement += ")"; + } + + aKey = ""; + + for (int i = 0, n = 0; getField(i)->name; i++) + { + if (getField(i)->type & ftAutoinc && !(getField(i)->type & ftPrimary)) + { + if (n++) aKey += string(", "); + aKey += string(getField(i)->name) + " DESC"; + } + } + + if (aKey.length()) + { + statement += string(", KEY("); + statement += aKey; + statement += ")"; + } + + // statement += string(") ENGINE MYISAM;"); + statement += string(") ENGINE InnoDB;"); + + tell(1, "%s", statement.c_str()); + + if (connection->query(statement.c_str())) + return connection->errorSql(getConnection(), "createTable()", + 0, statement.c_str()); + + // create indices + + createIndices(); + + return success; +} + +//*************************************************************************** +// Create Indices +//*************************************************************************** + +int cDbTable::createIndices() +{ + string statement; + + tell(5, "Initialy checking indices for '%s'", TableName()); + + // check/create indexes + + if (!indices) + return done; + + for (int i = 0; getIndex(i)->name; i++) + { + IndexDef* index = getIndex(i); + int fCount; + string idxName; + int expectCount = 0; + + for (; index->fields[expectCount] != na; expectCount++) ; + + if (!expectCount) + continue; + + // check + + idxName = "idx" + string(index->name); + + checkIndex(idxName.c_str(), fCount); + + if (fCount != expectCount) + { + // create index + + statement = "create index " + idxName; + statement += " on " + string(TableName()) + "("; + + int n = 0; + + for (int f = 0; index->fields[f] != na; f++) + { + FieldDef* fld = getField(index->fields[f]); + + if (fld && !(fld->type & ftCalc)) + { + if (n++) statement += string(", "); + statement += fld->name; + } + } + + if (!n) continue; + + statement += ");"; + tell(1, "%s", statement.c_str()); + + if (connection->query(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) +{ + for (int i = 0; i < fieldCount(); i++) + { + if (getField(i)->format == ffAscii || getField(i)->format == ffText) + row->setValue(i, r->getStrValue(i)); + else + row->setValue(i, r->getIntValue(i)); + } +} + +//*************************************************************************** +// 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_SERVER_LOST_EXTENDED) + 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) +{ + string tmp; + + if (!connection || !connection->getMySql()) + return fail; + + tmp = "delete from " + string(TableName()) + " where " + string(where); + + if (connection->query(tmp.c_str())) + return connection->errorSql(connection, "deleteWhere()", 0, tmp.c_str()); + + return success; +} + +//*************************************************************************** +// Coiunt Where +//*************************************************************************** + +int cDbTable::countWhere(const char* where, int& count, const char* what) +{ + string tmp; + MYSQL_RES* res; + MYSQL_ROW data; + + count = 0; + + if (isEmpty(what)) + what = "count(1)"; + + if (!isEmpty(where)) + tmp = "select " + string(what) + " from " + string(TableName()) + " where " + string(where); + else + tmp = "select " + string(what) + " from " + string(TableName()); + + if (connection->query(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() +{ + string tmp; + + tmp = "delete from " + string(TableName()); + + if (connection->query(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() +{ + if (!stmtInsert) + { + tell(0, "Fatal missing insert statement\n"); + return fail; + } + + for (int i = 0; getField(i)->name; i++) + { + if (strcmp(getField(i)->name, "updsp") == 0 || strcmp(getField(i)->name, "inssp") == 0) + setValue(getField(i)->index, time(0)); + } + +#ifdef DEB_HANDLER + + if (strcmp(TableName(), "events") == 0) + tell(1, "inserting vdr event %d for '%s', starttime = %ld, updflg = '%s'", + getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6)); +#endif + + if (stmtInsert->execute()) + return fail; + + return stmtInsert->getAffected() == 1 ? success : fail; +} + +//*************************************************************************** +// Update +//*************************************************************************** + +int cDbTable::update() +{ + if (!stmtUpdate) + { + tell(0, "Fatal missing update statement\n"); + return fail; + } + + for (int i = 0; getField(i)->name; i++) + { + if (strcmp(getField(i)->name, "updsp") == 0) + { + setValue(getField(i)->index, time(0)); + break; + } + } + +#ifdef DEB_HANDLER + if (strcmp(TableName(), "events") == 0) + tell(1, "updating vdr event %d for '%s', starttime = %ld, updflg = '%s'", + getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6)); +#endif + + 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..522226e --- /dev/null +++ b/lib/db.h @@ -0,0 +1,1030 @@ +/* + * db.h + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __DB_H +#define __DB_H + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include <sstream> + +#include <mysql/mysql.h> + +#include "common.h" + +class cDbTable; +class cDbConnection; + +using namespace std; + +//*************************************************************************** +// cDbService +//*************************************************************************** + +class cDbService +{ + public: + + enum Misc + { + maxIndexFields = 20 + }; + + enum FieldFormat + { + ffInt, + ffUInt, + ffAscii, // -> VARCHAR + ffText, + ffMlob, // -> MEDIUMBLOB + ffFloat, + ffDateTime, + ffCount + }; + + enum FieldType + { + ftData = 1, + ftPrimary = 2, + ftMeta = 4, + ftCalc = 8, + ftAutoinc = 16, + ftDef0 = 32 + }; + + struct FieldDef + { + const char* name; + FieldFormat format; + int size; + int index; + int type; + }; + + enum BindType + { + bndIn = 0x001, + bndOut = 0x002, + bndSet = 0x004 + }; + + enum ProcType + { + ptProcedure, + ptFunction + }; + + struct IndexDef + { + const char* name; + int fields[maxIndexFields+1]; + int order; // not implemented yet + }; + + static const char* toString(FieldFormat t); + static const char* formats[]; +}; + +typedef cDbService cDBS; + +//*************************************************************************** +// cDbValue +//*************************************************************************** + +class cDbValue : public cDbService +{ + public: + + cDbValue(FieldDef* f = 0) + { + field = 0; + strValue = 0; + ownField = 0; + + if (f) setField(f); + } + + cDbValue(const char* name, FieldFormat format, int size) + { + strValue = 0; + + ownField = new FieldDef; + ownField->name = strdup(name); + ownField->format = format; + ownField->size = size; + ownField->type = ftData; + + field = ownField; + + clear(); + } + + virtual ~cDbValue() + { + free(); + } + + void free() + { + clear(); + ::free(strValue); + strValue = 0; + + if (ownField) + { + ::free((char*)ownField->name); // böser cast ;) + delete ownField; + ownField = 0; + } + + field = 0; + } + + void clear() + { + if (strValue) + *strValue = 0; + + strValueSize = 0; + numValue = 0; + floatValue = 0; + memset(&timeValue, 0, sizeof(timeValue)); + + nullValue = 1; + initialized = no; + } + + virtual void setField(FieldDef* f) + { + free(); + field = f; + + if (field) + strValue = (char*)calloc(field->size+TB, sizeof(char)); + } + + virtual FieldDef* getField() { return field; } + virtual const char* getName() { return field->name; } + + void setValue(const char* value, int size = 0) + { + clear(); + + if (field->format != ffAscii && field->format != ffText && field->format != ffMlob) + { + tell(0, "Setting invalid field format for '%s', expected ASCII or MLOB", field->name); + return; + } + + if (field->format == ffMlob && !size) + { + tell(0, "Missing size for MLOB field '%s'", field->name); + return; + } + + if (value && size) + { + if (size > field->size) + { + tell(0, "Warning, size of %d for '%s' exeeded, got %d bytes!", + field->size, field->name, size); + + size = field->size; + } + + memcpy(strValue, value, size); + strValue[size] = 0; + strValueSize = size; + nullValue = 0; + } + + else if (value) + { + if (strlen(value) > (size_t)field->size) + tell(0, "Warning, size of %d for '%s' exeeded [%s]", + field->size, field->name, value); + + sprintf(strValue, "%.*s", field->size, value); + strValueSize = strlen(strValue); + nullValue = 0; + } + } + + 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->format == ffInt || field->format == ffUInt) + { + numValue = value; + nullValue = 0; + } + else if (field->format == ffDateTime) + { + struct tm tm; + time_t v = value; + + 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; + } + else + { + tell(0, "Setting invalid field format for '%s'", field->name); + } + } + + void setValue(double value) + { + if (field->format == ffInt || field->format == ffUInt) + { + numValue = value; + nullValue = 0; + } + else if (field->format == ffFloat) + { + floatValue = value; + nullValue = 0; + } + else + { + tell(0, "Setting invalid field format for '%s'", field->name); + } + } + + int hasValue(long value) + { + if (field->format == ffInt || field->format == ffUInt) + return numValue == value; + + if (field->format == ffDateTime) + return no; // to be implemented! + + tell(0, "Setting invalid field format for '%s'", field->name); + + return no; + } + + int hasValue(double value) + { + if (field->format == ffInt || field->format == ffUInt) + return numValue == value; + + if (field->format == ffFloat) + return floatValue == value; + + tell(0, "Setting invalid field format for '%s'", field->name); + + return no; + } + +// int hasValue(float value) +// { +// if (field->format != ffFloat) +// { +// tell(0, "Checking invalid field format for '%s', expected FLOAT", field->name); +// return no; +// } + +// return floatValue == value; +// } + +// int hasValue(long value) +// { +// if (field->format != ffInt && field->format != ffUInt) +// { +// tell(0, "Checking invalid field format for '%s', expected INT", field->name); +// return no; +// } + +// return numValue == value; +// } + + int hasValue(const char* value) + { + if (!value) + value = ""; + + if (field->format != ffAscii && field->format != ffText) + { + tell(0, "Checking invalid field format for '%s', expected ASCII or MLOB", field->name); + return no; + } + + return strcmp(getStrValue(), value) == 0; + } + + time_t getTimeValue() + { + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + 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 : ""; } + long getIntValue() { return !isNull() ? numValue : 0; } + float getFloatValue() { return !isNull() ? floatValue : 0; } + int isNull() { return nullValue; } + + char* getStrValueRef() { return strValue; } + long* getIntValueRef() { return &numValue; } + MYSQL_TIME* getTimeValueRef() { return &timeValue; } + float* getFloatValueRef() { return &floatValue; } + my_bool* getNullRef() { return &nullValue; } + + private: + + FieldDef* ownField; + FieldDef* field; + long numValue; + float floatValue; + MYSQL_TIME timeValue; + char* strValue; + unsigned long strValueSize; + my_bool nullValue; + int initialized; +}; + +//*************************************************************************** +// cDbStatement +//*************************************************************************** + +class cDbStatement : public cDbService +{ + public: + + cDbStatement(cDbTable* aTable); + cDbStatement(cDbConnection* aConnection, const char* stmt = ""); + + virtual ~cDbStatement() { clear(); } + + int execute(int noResult = no); + int find(); + int fetch(); + int freeResult(); + + // interface + + int build(const char* format, ...); + + void setBindPrefix(const char* p) { bindPrefix = p; } + void clrBindPrefix() { bindPrefix = 0; } + int bind(cDbValue* value, int mode, const char* delim = 0); + int bind(int field, int mode, const char* delim = 0); + + int bindCmp(const char* table, cDbValue* value, + const char* comp, const char* delim = 0); + int bindCmp(const char* table, int field, cDbValue* value, + const char* comp, const char* delim = 0); + + // .. + + int prepare(); + int getAffected() { return affected; } + int getResultCount(); + const char* asText() { return stmtTxt.c_str(); } + + private: + + void clear(); + int appendBinding(cDbValue* value, BindType bt); + + 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; +}; + +//*************************************************************************** +// Class Database Row +//*************************************************************************** + +class cDbRow : public cDbService +{ + public: + + cDbRow(FieldDef* f) + { + count = 0; + fieldDef = 0; + useFields(f); + + dbValues = new cDbValue[count]; + + for (int f = 0; f < count; f++) + dbValues[f].setField(getField(f)); + } + + virtual ~cDbRow() { delete[] dbValues; } + + void clear() + { + for (int f = 0; f < count; f++) + dbValues[f].clear(); + } + + virtual FieldDef* getField(int f) { return f < 0 ? 0 : fieldDef+f; } + virtual int fieldCount() { return count; } + + void setValue(int f, const char* value, + int size = 0) { dbValues[f].setValue(value, size); } + void setValue(int f, int value) { dbValues[f].setValue(value); } + void setValue(int f, long value) { dbValues[f].setValue(value); } + void setValue(int f, double value) { dbValues[f].setValue(value); } + void setCharValue(int f, char value) { dbValues[f].setCharValue(value); } + + int hasValue(int f, const char* value) const { return dbValues[f].hasValue(value); } + int hasValue(int f, long value) const { return dbValues[f].hasValue(value); } + int hasValue(int f, double value) const { return dbValues[f].hasValue(value); } + + cDbValue* getValue(int f) { return &dbValues[f]; } + + const char* getStrValue(int f) const { return dbValues[f].getStrValue(); } + long getIntValue(int f) const { return dbValues[f].getIntValue(); } + float getFloatValue(int f) const { return dbValues[f].getFloatValue(); } + int isNull(int f) const { return dbValues[f].isNull(); } + + protected: + + virtual void useFields(FieldDef* f) { fieldDef = f; for (count = 0; (fieldDef+count)->name; count++); } + + int count; // field count + FieldDef* fieldDef; + cDbValue* dbValues; +}; + +//*************************************************************************** +// Connection +//*************************************************************************** + +class cDbConnection +{ + public: + + cDbConnection() + { + mysql = 0; + attached = 0; + inTact = no; + connectDropped = yes; + } + + virtual ~cDbConnection() + { + if (mysql) + { + mysql_close(mysql); + mysql_thread_end(); + } + } + + int attachConnection() + { + static int first = yes; + + if (!mysql) + { + connectDropped = yes; + + if (!(mysql = mysql_init(0))) + return errorSql(this, "attachConnection(init)"); + + if (!mysql_real_connect(mysql, dbHost, + dbUser, dbPass, dbName, dbPort, 0, 0)) + { + mysql_close(mysql); + mysql = 0; + tell(0, "Error, connecting to database at '%s' on port (%d) failed", + dbHost, dbPort); + + 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) + { + mysql_close(mysql); + mysql_thread_end(); + mysql = 0; + } + } + + int isConnected() { return getMySql() > 0; } + + int check() + { + if (!isConnected()) + return fail; + + query("SELECT SYSDATE();"); + queryReset(); + + return isConnected() ? success : fail; + } + + virtual int query(const char* format, ...) + { + int status = 1; + MYSQL* h = getMySql(); + + if (h && format) + { + char* stmt; + + va_list more; + va_start(more, format); + 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); + } + } + + 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 access '%s'; %m", file); + return fail; + } + + buffer = (char*)malloc(size+1); + + while (res = fread(buffer+nread, 1, 1000, f)) + { + nread += res; + size += 1000; + buffer = (char*)realloc(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 && mysql) + { + mysql_close(mysql); + mysql_thread_end(); + mysql = 0; + attached = 0; + } + + return mysql; + } + + int getAttachedCount() { return attached; } + + // -------------- + // 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; } + + int errorSql(cDbConnection* mysql, const char* prefix, MYSQL_STMT* stmt = 0, const char* stmtTxt = 0); + + static int init() + { + if (mysql_library_init(0, 0, 0)) + { + tell(0, "Error: mysql_library_init failed"); + return fail; // return errorSql(0, "init(library_init)"); + } + + return success; + } + + static int exit() + { + mysql_library_end(); + free(dbHost); + free(dbUser); + free(dbPass); + free(dbName); + free(encoding); + + return done; + } + + MYSQL* mysql; + + private: + + int initialized; + int attached; + int inTact; + int connectDropped; + + static char* encoding; + + // 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, FieldDef* f, IndexDef* i = 0); + virtual ~cDbTable(); + + virtual const char* TableName() = 0; + + virtual int open(); + virtual int close(); + + 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(); + virtual int update(); + virtual int store(); + + virtual int 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 setValue(int f, const char* value, int size = 0) { row->setValue(f, value, size); } + void setValue(int f, int value) { row->setValue(f, value); } + void setValue(int f, long value) { row->setValue(f, value); } + void setValue(int f, double value) { row->setValue(f, value); } + void setCharValue(int f, char value) { row->setCharValue(f, value); } + + int hasValue(int f, const char* value) { return row->hasValue(f, value); } + int hasValue(int f, long value) { return row->hasValue(f, value); } + int hasValue(int f, double value) { return row->hasValue(f, value); } + + const char* getStrValue(int f) const { return row->getStrValue(f); } + long getIntValue(int f) const { return row->getIntValue(f); } + float getFloatValue(int f) const { return row->getFloatValue(f); } + int isNull(int f) const { return row->isNull(f); } + + FieldDef* getField(int f) { return row->getField(f); } + int fieldCount() { return row->fieldCount(); } + cDbRow* getRow() { return row; } + + cDbConnection* getConnection() { return connection; } + MYSQL* getMySql() { return connection->getMySql(); } + int isConnected() { return connection && connection->getMySql(); } + + virtual IndexDef* getIndex(int i) { return indices+i; } + virtual int exist(const char* name = 0); + virtual int createTable(); + + // static stuff + + static void setConfPath(const char* cpath) { free(confPath); confPath = strdup(cpath); } + + protected: + + virtual int init(); + virtual int createIndices(); + virtual int checkIndex(const char* idxName, int& fieldCount); + + virtual void copyValues(cDbRow* r); + + // data + + cDbRow* row; + int holdInMemory; // hold table additionally in memory (not implemented yet) + + IndexDef* indices; + + // basic statements + + cDbStatement* stmtSelect; + cDbStatement* stmtInsert; + cDbStatement* stmtUpdate; + cDbConnection* connection; + + // statics + + static char* confPath; +}; + +//*************************************************************************** +// 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/tabledef.c b/lib/tabledef.c new file mode 100644 index 0000000..d4bb6a9 --- /dev/null +++ b/lib/tabledef.c @@ -0,0 +1,856 @@ +/* + * tabledef.c + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "tabledef.h" + +//*************************************************************************** +// cEpgdState +//*************************************************************************** + +const char* cEpgdState::states[] = +{ + "init", + "standby", + "stopped", + + "busy (events)", + "busy (match)", + "busy (images)", + "busy (scraping)", + + 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; +} + +//*************************************************************************** +// Event Fields +//*************************************************************************** +//*************************************************************************** +// Fields +//*************************************************************************** + +cDbService::FieldDef cTableEvents::fields[] = +{ + // name format size index type + + // primary key + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + + { "masterid", ffUInt, 0, fiMasterId, ftAutoinc }, + { "useid", ffUInt, 0, fiUseId, ftData }, + + // meta + + { "source", ffAscii, 10, fiSource, ftMeta }, + { "fileref", ffAscii, 50, fiFileRef, ftMeta }, + { "inssp", ffInt, 10, fiInsSp, ftMeta }, + { "updsp", ffInt, 10, fiUpdSp, ftMeta }, + { "updflg", ffAscii, 1, fiUpdFlg, ftMeta }, + { "delflg", ffAscii, 1, fiDelFlg, ftMeta }, + + // vdr event data + + { "tableid", ffInt, 2, fiTableId, ftData }, + { "version", ffInt, 3, fiVersion, ftData }, + { "title", ffAscii, 200, fiTitle, ftData }, + { "comptitle", ffAscii, 200, fiCompTitle, ftData }, + { "shorttext", ffAscii, 300, fiShortText, ftData }, + { "compshorttext", ffAscii, 300, fiCompShortText, ftData }, + { "longdescription", ffText, 25000, fiLongDescription, ftData }, + { "starttime", ffInt, 10, fiStartTime, ftData }, + { "duration", ffInt, 5, fiDuration, ftData }, + { "parentalrating", ffInt, 2, fiParentalRating, ftData }, + { "vps", ffInt, 10, fiVps, ftData }, + + { "description", ffText, 50000, fiDescription, ftCalc }, + + // additional external data + + { "shortdescription", ffAscii, 3000, fiShortDescription, ftData }, + { "actor", ffAscii, 3000, fiActor, ftData }, + { "audio", ffAscii, 50, fiAudio, ftData }, + { "category", ffAscii, 50, fiCategory, ftData }, + { "country", ffAscii, 50, fiCountry, ftData }, + { "director", ffAscii, 250, fiDirector, ftData }, + { "flags", ffAscii, 100, fiFlags, ftData }, + { "genre", ffAscii, 100, fiGenre, ftData }, + { "info", ffText, 10000, fiInfo, ftData }, + { "music", ffAscii, 250, fiMusic, ftData }, + { "producer", ffText, 1000, fiProducer, ftData }, + { "screenplay", ffAscii, 500, fiScreenplay, ftData }, + { "shortreview", ffAscii, 500, fiShortreview, ftData }, + { "tipp", ffAscii, 250, fiTipp, ftData }, + { "topic", ffAscii, 500, fiTopic, ftData }, + { "year", ffAscii, 10, fiYear, ftData }, + { "rating", ffAscii, 250, fiRating, ftData }, + { "fsk", ffAscii, 2, fiFsk, ftData }, + { "movieid", ffAscii, 20, fiMovieid, ftData }, + { "moderator", ffAscii, 250, fiModerator, ftData }, + { "other", ffText, 2000, fiOther, ftData }, + { "guest", ffText, 1000, fiGuest, ftData }, + { "camera", ffText, 1000, fiCamera, ftData }, + + { "extepnum", ffInt, 4, fiExtEpNum, ftData }, + { "imagecount", ffInt, 2, fiImageCount, ftData }, + + // episodes (constable) + + { "episode", ffAscii, 250, fiEpisode, ftData }, + { "episodepart", ffAscii, 250, fiEpisodePart, ftData }, + { "episodelang", ffAscii, 3, fiEpisodeLang, ftData }, + + // tv scraper + + { "scrseriesid", ffInt, 11, fiScrSeriesId, ftData }, + { "scrseriesepisode", ffInt, 11, fiScrSeriesEpisode, ftData }, + { "scrmovieid", ffInt, 11, fiScrMovieId, ftData }, + { "scrsp", ffInt, 11, fiScrSp, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableEvents::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableEvents::indices[] = +{ + // index fields + + { "comptitle", { fiCompTitle, na }, 0 }, + { "source", { fiSource, na }, 0 }, + { "FilerefSource", { fiFileRef, fiSource, na }, 0 }, + { "channelid", { fiChannelId, na }, 0 }, + { "useid", { fiUseId, na }, 0 }, + { "useidchannelid", { fiUseId, fiChannelId, na }, 0 }, + { "updflgupdsp", { fiUpdFlg, fiUpdSp, na }, 0 }, + { "idxsourcechannelid", { fiSource, fiChannelId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Components +//*************************************************************************** + +cDbService::FieldDef cTableComponents::fields[] = +{ + // name format size index type + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "stream", ffInt, 3, fiStream, ftPrimary }, + { "type", ffInt, 3, fiType, ftPrimary }, + { "lang", ffAscii, 8, fiLang, ftPrimary }, + { "description", ffAscii, 100, fiDescription, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { 0 } +}; + +//*************************************************************************** +// File References +//*************************************************************************** + +cDbService::FieldDef cTableFileRefs::fields[] = +{ + // name format size index type + + { "name", ffAscii, 100, fiName, ftPrimary }, + { "source", ffAscii, 10, fiSource, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "extid", ffAscii, 10, fiExternalId, ftData }, + { "fileref", ffAscii, 100, fiFileRef, ftData }, // name + '-' + tag + { "tag", ffAscii, 100, fiTag, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableFileRefs::indices[] = +{ + // index fields + + { "SourceFileref", { fiSource, fiFileRef, na }, 0 }, + { "Fileref", { fiFileRef, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Image Ref Fields +//*************************************************************************** + +cDbService::FieldDef cTableImageRefs::fields[] = +{ + // name format size index type + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "lfn", ffInt, 0, fiLfn, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "source", ffAscii, 10, fiSource, ftMeta }, + + { "fileref", ffAscii, 100, fiFileRef, ftData }, + { "imagename", ffAscii, 100, fiImgName, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableImageRefs::indices[] = +{ + // index fields + + { "lfn", { fiLfn, na }, 0 }, + { "name", { fiImgName, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Image Fields +//*************************************************************************** + +cDbService::FieldDef cTableImages::fields[] = +{ + // name format size index type + + { "imagename", ffAscii, 100, fiImgName, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "image", ffMlob, 200000, fiImage, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Series Episode Fields +//*************************************************************************** + +cDbService::FieldDef cTableEpisodes::fields[] = +{ + // name format size index type + + // primary key + + { "compname", ffAscii, 100, fiCompName, ftPrimary }, // episode name compressed + { "comppartname", ffAscii, 200, fiCompPartName, ftPrimary }, // part name compressed + { "lang", ffAscii, 10, fiLang, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "link", ffInt, 0, fiLink, ftData }, + + // episode data + + { "shortname", ffAscii, 100, fiShortName, ftData }, + { "episodename", ffAscii, 100, fiEpisodeName, ftData }, // episode name + + // part data + + { "partname", ffAscii, 300, fiPartName, ftData }, // part name + { "season", ffInt, 0, fiSeason, ftData }, + { "part", ffInt, 0, fiPart, ftData }, + { "parts", ffInt, 0, fiParts, ftData }, + { "number", ffInt, 0, fiNumber, ftData }, + + { "extracol1", ffAscii, 250, fiExtraCol1, ftData }, + { "extracol2", ffAscii, 250, fiExtraCol2, ftData }, + { "extracol3", ffAscii, 250, fiExtraCol3, ftData }, + + { "comment", ffAscii, 250, fiComment, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableEpisodes::indices[] = +{ + // index fields + + { "updsp", { fiUpdSp, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Channel Map Fields +//*************************************************************************** + +cDbService::FieldDef cTableChannelMap::fields[] = +{ + // name format size index type + + { "extid", ffAscii, 10, fiExternalId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "source", ffAscii, 20, fiSource, ftPrimary }, + + { "channelname", ffAscii, 100, fiChannelName, ftData }, + + { "vps", ffInt, 0, fiVps, ftData }, + { "merge", ffInt, 0, fiMerge, ftData }, + { "mergesp", ffInt, 0, fiMergeSp, ftData }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "updflg", ffAscii, 1, fiUpdFlg, ftMeta }, + + { 0 } +}; + +cDbService::IndexDef cTableChannelMap::indices[] = +{ + // index fields + + { "sourceExtid", { fiSource, fiExternalId, na }, 0 }, + { "source", { fiSource, na }, 0 }, + { "updflg", { fiUpdFlg, na }, 0 }, + { "idxsourcechannelid", { fiSource, fiChannelId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// VDRs Fields +//*************************************************************************** + +cDbService::FieldDef cTableVdrs::fields[] = +{ + // name format size index type + + { "uuid", ffAscii, 40, fiUuid, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "name", ffAscii, 100, fiName, ftData }, + { "version", ffAscii, 100, fiVersion, ftData }, + { "dbapi", ffUInt, 0, fiDbApi, ftData }, + { "lastupd", ffInt, 0, fiLastUpdate, ftData }, + { "nextupd", ffInt, 0, fiNextUpdate, ftData }, + { "state", ffAscii, 20, fiState, ftData }, + { "master", ffAscii, 1, fiMaster, ftData }, + { "ip", ffAscii, 20, fiIp, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Parameter Fields +//*************************************************************************** + +cDbService::FieldDef cTableParameters::fields[] = +{ + // name format size index type + + { "owner", ffAscii, 40, fiOwner, ftPrimary }, + { "name", ffAscii, 40, fiName, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "value", ffAscii, 100, fiValue, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Analyse +//*************************************************************************** + +cDbService::FieldDef cTableAnalyse::fields[] = +{ + // name format size index type + + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "vdr_masterid", ffUInt, 0, fiVdrMasterId, ftData }, + { "vdr_eventid", ffUInt, 0, fiVdrEventId, ftPrimary }, + + { "vdr_starttime", ffInt, 10, fiVdrStartTime, ftData }, + { "vdr_duration", ffInt, 5, fiVdrDuration, ftData }, + { "vdr_title", ffAscii, 200, fiVdrTitle, ftData }, + { "vdr_shorttext", ffAscii, 300, fiVdrShortText, ftData }, + + { "ext_masterid", ffUInt, 0, fiExtMasterId, ftData }, + { "ext_eventid", ffUInt, 0, fiExtEventId, ftData }, + { "ext_starttime", ffInt, 10, fiExtStartTime, ftData }, + { "ext_duration", ffInt, 5, fiExtDuration, ftData }, + { "ext_title", ffAscii, 200, fiExtTitle, ftData }, + { "ext_shorttext", ffAscii, 300, fiExtShortText, ftData }, + { "ext_episode", ffAscii, 1, fiExtEpisode, ftData }, + { "ext_merge", ffInt, 11, fiExtMerge, ftData }, + { "ext_images", ffAscii, 1, fiExiImages, ftData }, + + { "lvmin", ffInt, 3, fiLvMin, ftData }, + { "rank", ffInt, 5, fiRank, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableAnalyse::indices[] = +{ + // index fields + + { "vdr_masterid", { fiVdrMasterId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Snapshot +//*************************************************************************** + +cDbService::FieldDef cTableSnapshot::fields[] = +{ + // name format size index type + + { "channelid", ffAscii, 50, fiChannelId, ftData }, + { "source", ffAscii, 10, fiSource, ftData }, + + { "masterid", ffUInt, 0, fiVdrMasterId, ftData }, + { "eventid", ffUInt, 0, fiEventId, ftData }, + { "useid", ffUInt, 0, fiUseId, ftData }, + + { "starttime", ffInt, 10, fiStartTime, ftData }, + { "duration", ffInt, 5, fiDuration, ftData }, + { "title", ffAscii, 200, fiTitle, ftData }, + { "comptitle", ffAscii, 200, fiCompTitle, ftData }, + { "shorttext", ffAscii, 300, fiShortText, ftData }, + { "compshorttext", ffAscii, 300, fiCompShortText, ftData }, + + { "updsp", ffInt, 10, fiUpdsp, ftData }, + + { "episode", ffAscii, 1, fiEpisode, ftData }, + { "merge", ffInt, 0, fiMerge, ftData }, + { "images", ffAscii, 1, fiImages, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableSnapshot::indices[] = +{ + // index fields + + { "channelid", { fiChannelId, na }, 0 }, + { "starttimeSource", { fiStartTime, fiSource, na }, 0 }, + + { 0 } +}; + + +//*************************************************************************** +// Series Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeries::fields[] = +{ + // name format size index type + + // primary key + { "series_id", ffUInt, 0, fiSeriesId, ftPrimary }, + //Data + { "series_name", ffAscii, 200, fiSeriesName, ftData }, + { "series_last_scraped", ffUInt, 0, fiSeriesLastScraped, ftData }, + { "series_last_updated", ffUInt, 0, fiSeriesLastUpdated, ftData }, + { "series_overview", ffText, 10000, fiSeriesOverview, ftData }, + { "series_firstaired", ffAscii, 50, fiSeriesFirstAired, ftData }, + { "series_network", ffAscii, 100, fiSeriesNetwork, ftData }, + { "series_imdb_id", ffAscii, 20, fiSeriesIMDBId, ftData }, + { "series_genre", ffAscii, 100, fiSeriesGenre, ftData }, + { "series_rating", ffFloat, 31, fiSeriesRating, ftData }, + { "series_status", ffAscii, 50, fiSeriesStatus, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeries::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeries::indices[] = +{ + // index fields + + { 0 } +}; + + +//*************************************************************************** +// SeriesEpisode Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesEpisode::fields[] = +{ + // name format size index type + + // primary key + { "episode_id", ffUInt, 0, fiEpisodeId, ftPrimary }, + //Data + { "episode_number", ffUInt, 0, fiEpisodeNumber, ftData }, + { "season_number", ffUInt, 0, fiSeasonNumber, ftData }, + { "episode_name", ffAscii, 300, fiEpisodeName, ftData }, + { "episode_overview", ffText, 10000, fiEpisodeOverview, ftData }, + { "episode_firstaired", ffAscii, 20, fiEpisodeFirstAired, ftData }, + { "episode_gueststars", ffAscii, 1000, fiEpisodeGuestStars, ftData }, + { "episode_rating", ffFloat, 31, fiEpisodeRating, ftData }, + { "episode_last_updated", ffUInt, 0, fiEpisodeLastUpdated, ftData }, + { "series_id", ffUInt, 0, fiSeriesId, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesEpisode::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesEpisode::indices[] = +{ + // index fields + { "series_id", { fiSeriesId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// SeriesMedia Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesMedia::fields[] = +{ + // name format size index type + + // primary key + { "series_id", ffUInt, 0, fiSeriesId, ftPrimary }, + { "season_number", ffUInt, 0, fiSeasonNumber, ftPrimary }, + { "episode_id", ffUInt, 0, fiEpisodeId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + { "media_type", ffUInt, 0, fiMediaType, ftPrimary }, + //Data + { "media_url", ffAscii, 100, fiMediaUrl, ftData }, + { "media_width", ffUInt, 0, fiMediaWidth, ftData }, + { "media_height", ffUInt, 0, fiMediaHeight, ftData }, + { "media_rating", ffFloat, 31, fiMediaRating, ftData }, + { "media_content", ffMlob, 1000000, fiMediaContent, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesMedia::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesMedia::indices[] = +{ + // index fields + { "series_id", { fiSeriesId, na }, 0 }, + { "season_number", { fiSeasonNumber, na }, 0 }, + { "episode_id", { fiEpisodeId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// SeriesActor Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesActor::fields[] = +{ + // name format size index type + + // primary key + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_name", ffAscii, 100, fiActorName, ftData }, + { "actor_role", ffAscii, 500, fiActorRole, ftData }, + { "actor_sortorder", ffUInt, 0, fiSortOrder, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesActor::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesActor::indices[] = +{ + // index fields + + { 0 } +}; + +//*************************************************************************** +// Movie Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovies::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + //Data + { "movie_title", ffAscii, 300, fiTitle, ftData }, + { "movie_original_title", ffAscii, 300, fiOriginalTitle, ftData }, + { "movie_tagline", ffAscii, 1000, fiTagline, ftData }, + { "movie_overview", ffText, 5000, fiOverview, ftData }, + { "movie_adult", ffUInt, 0, fiIsAdult, ftData }, + { "movie_collection_id", ffUInt, 0, fiCollectionId, ftData }, + { "movie_collection_name", ffAscii, 300, fiCollectionName, ftData }, + { "movie_budget", ffUInt, 0, fiBudget, ftData }, + { "movie_revenue", ffUInt, 0, fiRevenue, ftData }, + { "movie_genres", ffAscii, 500, fiGenres, ftData }, + { "movie_homepage", ffAscii, 300, fiHomepage, ftData }, + { "movie_release_date", ffAscii, 20, fiReleaaseDate, ftData }, + { "movie_runtime", ffUInt, 0, fiRuntime, ftData }, + { "movie_popularity", ffFloat, 31, fiPopularity, ftData }, + { "movie_vote_average", ffFloat, 31, fiVoteAverage, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovies::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovies::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// MovieActor Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieActor::fields[] = +{ + // name format size index type + + // primary key + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_name", ffAscii, 300, fiActorName, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieActor::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieActor::indices[] = +{ + // index fields + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// MovieActors Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieActors::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_role", ffAscii, 300, fiRole, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieActors::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieActors::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// cTableMovieMedia Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieMedia::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + { "media_type", ffUInt, 0, fiMediaType, ftPrimary }, + //Data + { "media_url", ffAscii, 100, fiMediaUrl, ftData }, + { "media_width", ffUInt, 0, fiMediaWidth, ftData }, + { "media_height", ffUInt, 0, fiMediaHeight, ftData }, + { "media_content", ffMlob, 1000000, fiMediaContent, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieMedia::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieMedia::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// cTableRecordings Fields +//*************************************************************************** + +cDbService::FieldDef cTableRecordings::fields[] = +{ + // name format size index type + + // primary key + { "uuid", ffAscii, 40, fiUuid, ftPrimary }, + { "rec_path", ffAscii, 200, fiRecPath, ftPrimary }, + { "rec_start", ffUInt, 0, fiRecStart, ftPrimary }, + + //Data + { "event_id", ffUInt, 0, fiEventId, ftData }, + { "channel_id", ffAscii, 50, fiChannelId, ftData }, + { "scrapinfo_movie_id", ffUInt, 0, fiScrapInfoMovieId, ftData }, + { "scrapinfo_series_id", ffUInt, 0, fiScrapInfoSeriesId, ftData }, + { "scrapinfo_episode_id", ffUInt, 0, fiScrapInfoEpisodeId, ftData }, + { "scrap_new", ffUInt, 0, fiScrapNew, ftData }, + { "rec_title", ffAscii, 200, fiRecTitle, ftData }, + { "rec_subtitle", ffAscii, 500, fiRecSubTitle, ftData }, + { "rec_duration", ffUInt, 0, fiRecDuration, ftData }, + { "movie_id", ffUInt, 0, fiMovieId, ftData }, + { "series_id", ffUInt, 0, fiSeriesId, ftData }, + { "episode_id", ffUInt, 0, fiEpisodeId, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableRecordings::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableRecordings::indices[] = +{ + // index fields + { "uuid", { fiUuid, na }, 0 }, + { "rec_path", { fiRecPath, na }, 0 }, + { "rec_start", { fiRecStart, na }, 0 }, + + { 0 } +};
\ No newline at end of file diff --git a/lib/tabledef.h b/lib/tabledef.h new file mode 100644 index 0000000..bd2b043 --- /dev/null +++ b/lib/tabledef.h @@ -0,0 +1,832 @@ +/* + * tabledef.h + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __TABLEDEF_H +#define __TABLEDEF_H + +#include "db.h" + +//*************************************************************************** +// cEpgdState +//*************************************************************************** + +class cEpgdState +{ + public: + + enum State + { + esUnknown = na, + + esInit, + esStandby, + esStopped, + + esBusy, + esBusyEvents = esBusy, + esBusyMatch, + + esBusyImages, + + esBusyScraping, + + 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; + +//*************************************************************************** +// cUpdateState +//*************************************************************************** + +class cUpdateState +{ + public: + + enum State + { + // add to VDRs EPG + + usActive = 'A', + usLink = 'L', + usPassthrough = 'P', + + // remove from VDRs EPG + + usChanged = 'C', + usDelete = 'D', + usRemove = 'R', + + // don't care for VDRs EPG + + usInactive = 'I', + usTarget = 'T' + }; + + // get lists for SQL 'in' statements + + static const char* getDeletable() { return "'A','L','P','R','I'"; } + static const char* getNeeded() { return "'A','L','P','C','D','R'"; } + + // checks fpr c++ code + + static int isNeeded(char c) { return strchr("ALPCDR", c) != 0; } + static int isRemove(char c) { return strchr("CDR", c) != 0; } + +}; + +typedef cUpdateState Us; + +//*************************************************************************** +// class cTableFileRef +//*************************************************************************** + +class cTableFileRefs : public cDbTable +{ + public: + + cTableFileRefs(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "fileref"; } + + enum FieldIndex + { + fiName, + fiSource, + + fiInsSp, + fiUpdSp, + + fiExternalId, + fiFileRef, + fiTag, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableImageRef +//*************************************************************************** + +class cTableImageRefs : public cDbTable +{ + public: + + cTableImageRefs(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "imagerefs"; } + + enum FieldIndex + { + fiEventId, + fiLfn, + + fiInsSp, + fiUpdSp, + fiSource, + fiFileRef, + + fiImgName, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableImage +//*************************************************************************** + +class cTableImages : public cDbTable +{ + public: + + cTableImages(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "images"; } + + enum FieldIndex + { + fiImgName, + + fiInsSp, + fiUpdSp, + fiImage, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableEvent +//*************************************************************************** + +class cTableEvents : public cDbTable +{ + public: + + cTableEvents(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "events"; } + + enum FieldIndex + { + fiEventId, + fiChannelId, + + fiMasterId, + fiUseId, + + fiSource, + fiFileRef, + fiInsSp, + fiUpdSp, + fiUpdFlg, // update flag + fiDelFlg, // deletion flag + + fiTableId, + fiVersion, + fiTitle, + fiCompTitle, // compressed (without whitespace and special characters) + fiShortText, + fiCompShortText, // compressed (without whitespace and special characters) + fiLongDescription, + fiStartTime, + fiDuration, + fiParentalRating, + fiVps, + fiDescription, // view field, not stored! + + fiShortDescription, + fiActor, + fiAudio, + fiCategory, + fiCountry, + fiDirector, + fiFlags, + fiGenre, + fiInfo, + fiMusic, + fiProducer, + fiScreenplay, + fiShortreview, + fiTipp, + fiTopic, + fiYear, + fiRating, + fiFsk, + fiMovieid, + fiModerator, + fiOther, + fiGuest, + fiCamera, + + fiExtEpNum, + fiImageCount, + + fiEpisode, + fiEpisodePart, + fiEpisodeLang, + + fiScrSeriesId, + fiScrSeriesEpisode, + fiScrMovieId, + fiScrSp, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableComponent +//*************************************************************************** + +class cTableComponents : public cDbTable +{ + public: + + cTableComponents(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "components"; } + + enum FieldIndex + { + fiEventId, + fiChannelId, + fiStream, + fiType, + fiLang, + fiDescription, + + fiInsSp, + fiUpdSp + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableEpisode +//*************************************************************************** + +class cTableEpisodes : public cDbTable +{ + public: + + cTableEpisodes(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "episodes"; } + + + enum FieldIndex + { + // primary key + + fiCompName, // compressed name (without whitespace and special characters) + fiCompPartName, // " " " + fiLang, // "de", "en", ... + + fiInsSp, + fiUpdSp, + fiLink, + + // episode data + + fiShortName, + fiEpisodeName, // episode name (fielname without path and suffix) + + // part data + + fiPartName, // part name + fiSeason, + fiPart, + fiParts, + fiNumber, + + fiExtraCol1, + fiExtraCol2, + fiExtraCol3, + fiComment, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableChannelMap +//*************************************************************************** + +class cTableChannelMap : public cDbTable +{ + public: + + cTableChannelMap(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "channelmap"; } + + enum FieldIndex + { + fiExternalId, // + fiChannelId, // + fiSource, + + fiChannelName, + + fiVps, + fiMerge, + fiMergeSp, + + fiInsSp, + fiUpdSp, + fiUpdFlg, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableVdr +//*************************************************************************** + +class cTableVdrs : public cDbTable +{ + public: + + cTableVdrs(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "vdrs"; } + + enum FieldIndex + { + fiUuid, + + fiInsSp, + fiUpdSp, + + fiName, + fiVersion, + fiDbApi, + fiLastUpdate, + fiNextUpdate, + fiState, + fiMaster, + fiIp, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableParameters +//*************************************************************************** + +class cTableParameters : public cDbTable +{ + public: + + cTableParameters(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "parameters"; } + + enum FieldIndex + { + fiOwner, + fiName, + + fiInsSp, + fiUpdSp, + + fiValue, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// cTableAnalyse +//*************************************************************************** + +class cTableAnalyse : public cDbTable +{ + public: + + cTableAnalyse(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "analyse"; } + + enum FieldIndex + { + fiChannelId, + fiVdrMasterId, + fiVdrEventId, + + fiVdrStartTime, + fiVdrDuration, + fiVdrTitle, + fiVdrShortText, + + fiExtMasterId, + fiExtEventId, + fiExtStartTime, + fiExtDuration, + fiExtTitle, + fiExtShortText, + fiExtEpisode, + fiExtMerge, + fiExiImages, + + fiLvMin, + fiRank, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// cTableSnapshot +//*************************************************************************** + +class cTableSnapshot : public cDbTable +{ + public: + + cTableSnapshot(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "snapshot"; } + + enum FieldIndex + { + fiChannelId, + fiSource, + fiVdrMasterId, + fiEventId, + fiUseId, + fiStartTime, + fiDuration, + fiTitle, + fiCompTitle, + fiShortText, + fiCompShortText, + fiUpdsp, + fiEpisode, + fiMerge, + fiImages, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeries +//*************************************************************************** + +class cTableSeries : public cDbTable +{ + public: + + cTableSeries(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series"; } + + enum FieldIndex + { + fiSeriesId, + + fiSeriesName, + fiSeriesLastScraped, + fiSeriesLastUpdated, + fiSeriesOverview, + fiSeriesFirstAired, + fiSeriesNetwork, + fiSeriesIMDBId, + fiSeriesGenre, + fiSeriesRating, + fiSeriesStatus, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesEpisode +//*************************************************************************** + +class cTableSeriesEpisode : public cDbTable +{ + public: + + cTableSeriesEpisode(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_episode"; } + + enum FieldIndex + { + fiEpisodeId, + + fiEpisodeNumber, + fiSeasonNumber, + fiEpisodeName, + fiEpisodeOverview, + fiEpisodeFirstAired, + fiEpisodeGuestStars, + fiEpisodeRating, + fiEpisodeLastUpdated, + fiSeriesId, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesMedia +//*************************************************************************** + +class cTableSeriesMedia : public cDbTable +{ + public: + + cTableSeriesMedia(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_media"; } + + enum FieldIndex + { + fiSeriesId, + fiSeasonNumber, + fiEpisodeId, + fiActorId, + fiMediaType, + + fiMediaUrl, + fiMediaWidth, + fiMediaHeight, + fiMediaRating, + fiMediaContent, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesActor +//*************************************************************************** + +class cTableSeriesActor : public cDbTable +{ + public: + + cTableSeriesActor(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_actor"; } + + enum FieldIndex + { + fiActorId, + + fiActorName, + fiActorRole, + fiSortOrder, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovies +//*************************************************************************** + +class cTableMovies : public cDbTable +{ + public: + + cTableMovies(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie"; } + + enum FieldIndex + { + fiMovieId, + + fiTitle, + fiOriginalTitle, + fiTagline, + fiOverview, + fiIsAdult, + fiCollectionId, + fiCollectionName, + fiBudget, + fiRevenue, + fiGenres, + fiHomepage, + fiReleaaseDate, + fiRuntime, + fiPopularity, + fiVoteAverage, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieActor +//*************************************************************************** + +class cTableMovieActor : public cDbTable +{ + public: + + cTableMovieActor(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_actor"; } + + enum FieldIndex + { + fiActorId, + + fiActorName, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieActors +//*************************************************************************** + +class cTableMovieActors : public cDbTable +{ + public: + + cTableMovieActors(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_actors"; } + + enum FieldIndex + { + fiMovieId, + fiActorId, + + fiRole, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieMedia +//*************************************************************************** + +class cTableMovieMedia : public cDbTable +{ + public: + + cTableMovieMedia(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_media"; } + + enum FieldIndex + { + fiMovieId, + fiActorId, + fiMediaType, + + fiMediaUrl, + fiMediaWidth, + fiMediaHeight, + fiMediaContent, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableRecordings +//*************************************************************************** + +class cTableRecordings : public cDbTable +{ + public: + + cTableRecordings(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "recordings"; } + + enum FieldIndex + { + fiUuid, + fiRecPath, + fiRecStart, + + fiEventId, + fiChannelId, + fiScrapInfoMovieId, + fiScrapInfoSeriesId, + fiScrapInfoEpisodeId, + fiScrapNew, + fiRecTitle, + fiRecSubTitle, + fiRecDuration, + fiMovieId, + fiSeriesId, + fiEpisodeId, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +#endif //__TABLEDEF_H |