/* * update.h: TVM2VDR plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * */ #ifndef __UPDATE_H #define __UPDATE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "config.h" #include "channelmap.h" #include "series.h" #include "levenshtein.h" #define TVM2VDR_USERAGENT "libcurl-agent/1.0" #define sizeMd5String 32 class cTvmEpgHandler; class cDbRow; const char* createMd5(cDbRow* row, char* md5); struct MemMap { int version; int tableid; }; //*************************************************************************** // MemoryStruct for curl callbacks //*************************************************************************** struct CurlMemoryStruct { CurlMemoryStruct() { memory = 0; clear(); } ~CurlMemoryStruct() { clear(); } // data char* memory; size_t size; // tag attribute int match; char tag[100]; // the tag to be compared char ignoreTag[100]; void clear() { free(memory); memory = 0; size = 0; match = 0; *tag = 0; *ignoreTag = 0; } }; //*************************************************************************** // //*************************************************************************** class cDbService { public: enum Misc { maxViewFields = 50 }; enum FieldFormat { ffInt, ffAscii, ffCount }; enum FieldType { ftData = 1, ftPrimary = 2, ftMeta = 4, ftCalculated = 8, ftOrderAsc = 16, // not implemented yet ftOrderDesc = 32 // not implemented yet }; enum ViewType { vtDbView, // will created as a database view vtSelect // logical view only a select statement will be prepared }; struct FieldDef { const char* name; FieldFormat format; int index; int type; const char* viewStmt; }; struct Criteria { int fieldIndex; const char* op; }; struct ViewDef { const char* name; ViewType type; const char* from; Criteria crit[maxViewFields+1]; int critCount; int fields[maxViewFields+1]; int fieldCount; sqlite3_stmt* stmtSelect; }; static const char* toString(FieldFormat t); static const char* formats[]; }; //*************************************************************************** // Class Database Row //*************************************************************************** class cDbRow : public cDbService { public: cDbRow(FieldDef* f, ViewDef* v = 0) { count = 0; viewDef = 0; fieldDef = 0; useFields(f); useView(v); iValues = (long*)calloc(count, sizeof(long)); sValues = (string**)calloc(count, sizeof(string*)); } ~cDbRow() { clear(); free(iValues); free(sValues); } void clear() { for (int i = 0; i < count; i++) { if (sValues[i]) delete sValues[i]; sValues[i] = 0; iValues[i] = 0; } } virtual int cCount(cDbRow* row, int cmpOnlySet = no) { int c = 0; for (int f = 0; f < count; f++) { if (getField(f)->type != ftData) continue; if (cmpOnlySet && !fieldIsSet(f)) continue; if (getField(f)->format == ffAscii) { if (strcmp(getStrValue(f), row->getStrValue(f)) != 0) c++; } else { if (iValues[f] != row->getIntValue(f)) c++; } } return c; } virtual int fieldIsSet(int f) { if (getField(f)->format == ffAscii) return sValues[f] != 0; else return iValues[f] != 0; } virtual FieldDef* getField(int f) { return fieldDef+f; } virtual int fieldCount() { return count; } virtual ViewDef* getView(int f) { return viewDef+f; } void setValue(int f, const char* value) { if (sValues[f]) { delete sValues[f]; sValues[f] = 0; } if (getField(f)->format != ffAscii) tell(0, "TVM2VDR: Setting invalid field format for '%s', expected INT", getField(f)->name); else if (value) sValues[f] = new string(value); else sValues[f] = new string(""); } void setValue(int f, int value) { if (getField(f)->format != ffInt) tell(0, "TVM2VDR: Setting invalid field format for '%s', expected ASCII", getField(f)->name); else iValues[f] = value; } const char* getStrValue(int f) const { return sValues[f] ? sValues[f]->c_str() : ""; } long getIntValue(int f) const { return iValues[f]; } const char* getIntValueAsStr(int f) const { stringstream ss; ss << iValues[f]; sValues[f] = new string(ss.str()); return sValues[f]->c_str(); } protected: virtual void useFields(FieldDef* f) { fieldDef = f; for (count = 0; (fieldDef+count)->name; count++); } virtual void useView(ViewDef* v) { viewDef = v; } int count; FieldDef* fieldDef; ViewDef* viewDef; string** sValues; long* iValues; }; //*************************************************************************** // class cTvmFields //*************************************************************************** class cTvmFields : public cDbService { public: enum FieldIndex { fiName, fiInsSp, fiUpdSp, fiFileRef, fiTag, fiCount }; static FieldDef fields[]; }; //*************************************************************************** // class cImageFields //*************************************************************************** class cImageFields : public cDbService { public: enum FieldIndex { fiEventId, fiLfn, fiInsSp, fiUpdSp, fiName, fiCount }; enum ViewIndex { viAllLessLfn, viAllByName, }; static FieldDef fields[]; static ViewDef views[]; }; //*************************************************************************** // class cTvmFields //*************************************************************************** class cEventFields : public cDbService { public: enum FieldIndex { fiEventId, fiChannelId, fiSource, fiFileRef, fiInsSp, fiUpdSp, fiUpdFlg, // update 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, fiOrigtitle, fiScreenplay, fiShortreview, fiTipp, fiTopic, fiYear, fiRating, fiFsk, fiMovieid, fiModerator, fiTeam, fiGuest, fiEpisode, fiEpisodePart, fiEpisodeLang, fiCount }; enum ViewIndex { viVdr, viByChannel, viByCompTitle, viByUpdFlag, viAll, viAllBySource }; static FieldDef* toField(const char* name); static FieldDef fields[]; static ViewDef views[]; }; //*************************************************************************** // class cEpisodeFields //*************************************************************************** class cEpisodeFields : public cDbService { public: enum FieldIndex { // primary key fiCompName, // compressed name (without whitespace and special characters) fiCompPartName, // " " " fiLang, // "de", "en", ... fiDistCompName, fiInsSp, fiUpdSp, fiMaxUpdSp, fiLink, // episode data fiShortName, fiEpisodeName, // episode name (fielname without path and suffix) // part data fiPartName, // part name fiSeason, fiPart, fiNumber, fiCount }; enum ViewIndex { viMaxUpdSp, viAllDistinct, viByCompNames, viByCompName }; static FieldDef fields[]; static ViewDef views[]; }; //*************************************************************************** // class cEpisodeFields //*************************************************************************** class cTvmMapFields : public cDbService { public: enum FieldIndex { fiTvmId, // fiChannelName, // fiSource, fiInsSp, fiUpdSp, fiCount }; static FieldDef fields[]; }; //*************************************************************************** // Table //*************************************************************************** class Table : cDbService { public: Table(const char* name, FieldDef* f, ViewDef* v = 0); ~Table(); const char* TableName() { return tableName; } virtual int open(); virtual int close(); virtual int find(cDbRow* r = 0, int copy = yes); virtual int find(int viewIndex, cDbRow* r = 0); virtual int fetch(int viewIndex, cDbRow* r = 0); virtual void resetFetch(int viewIndex); virtual int prepareWhere(const char* where, sqlite3_stmt*& stmt); virtual int bindWhereStr(sqlite3_stmt* stmt, int index, const char* value); virtual int bindWhereInt(sqlite3_stmt* stmt, int index, int value); virtual int findWhere(sqlite3_stmt*& stmt, cDbRow* r = 0); virtual int fetch(sqlite3_stmt*& stmt, cDbRow* r = 0); virtual void resetFetch(sqlite3_stmt* stmt); virtual void finalizeFetch(sqlite3_stmt*& stmt); virtual int insert(cDbRow* r = 0); virtual int update(cDbRow* r = 0); virtual int store(cDbRow* r = 0); virtual int deleteWhere(const char* where); virtual int truncate(); // interface to cDbRow void clear() { row->clear(); } void setValue(int f, const char* value) { row->setValue(f, value); } void setValue(int f, int value) { row->setValue(f, value); } const char* getStrValue(int f) const { return row->getStrValue(f); } long getIntValue(int f) const { return row->getIntValue(f); } FieldDef* getField(int f) { return row->getField(f); } int fieldCount() { return row->fieldCount(); } int cCount(cDbRow* aRow, int cmpOnlySet = no) { return row->cCount(aRow, cmpOnlySet); } cDbRow* getRow() { return row; } // more void setHoldInMem(int aFlag) { holdInMemory = aFlag; } // static stuff static void setFileName(const char* name) { free(fileName); fileName = strdup(name); } static void setConfPath(const char* cpath) { free(confPath); confPath = strdup(cpath); } static void setEncoding(const char* enc) { free(encoding); encoding = strdup(enc); } static const char* getFileName() { return fileName; } static const char* getEncoding() { return encoding; } static void setExtensionFile(const char* name) { free(extensionFile); extensionFile = strdup(name); } static const char* getExtensionFile() { return extensionFile; } protected: virtual int init(); virtual int createTable(); virtual int createView(ViewDef* view); int bindField(sqlite3_stmt* stmt, FieldDef* field, cDbRow* r); int prepareStmt(const char* aStatement, sqlite3_stmt*& stmt); int errorSqlite(const char* prefix); virtual void storeValues(sqlite3_stmt* stmt, ViewDef* view = 0); virtual void copyValues(cDbRow* r); // data const char* tableName; cDbRow* row; int holdInMemory; // hold table additionally in memory (not implemented yet) // basic statements sqlite3_stmt* stmtSelect; sqlite3_stmt* stmtInsert; sqlite3_stmt* stmtUpdate; sqlite3_stmt* stmtTruncate; // statics static int initialized; static sqlite3* handle; static char* fileName; static char* confPath; static char* encoding; static char* extensionFile; static int attached; static cMutex mutex; static int walMode; }; //*************************************************************************** // Update //*************************************************************************** class cUpdate : public cThread { public: cUpdate(); ~cUpdate(); // interface void Stop(); int isUpdateActive() { return performUpdate; } void scheduleAutoUpdate(int wait = 0); void triggerUpdate(int full = false, int reload = false); int reloadMaps(); void loadXSLT(); int updateTvmMap(const char* dbpath); int loglevel; static cEvent* createEventFromRow(const cDbRow* row); private: // functions void Action(void); int downloadFile(const char* url, int& size, const char* tag = ""); int update(void); int dropVdrsEpg(); int refreshEpg(); void cleanup(); cTimer* getTimerOf(const cEvent* event) const; // TVM void unscramble(); int getPictures(); void cleanupEvents(const char* path); void cleanupPictures(); int pictureLinkNeeded(const char* linkName); int getImageUrl(int imgSize, char*& url, const char* image); int processTvmFile(const int tvmid, const char* outfilename, const char* fileRef); xmlDocPtr transformXml(const char* xmlfile, xsltStylesheetPtr pxsltStylesheet); int parseEvent(cDbRow* event, xmlNode* node); int updateVdrEpg(const int tvmid, const char* fileRef); int storeImages(int evtId, const char* images, const char* ext); // Series int evaluateEpisodes(); int downloadEpisodes(); // data int loopActive; CurlMemoryStruct data; cChannelMap* chanmap; char* xmldir; char* epgimagedir; char* episodesdir; xsltStylesheetPtr pxsltStylesheet; int withutf8; cCondVar waitCondition; cMutex mutex; time_t nextAutoUpdateAt; int performUpdate; int fullupdate; int fullreload; char imageExtension[3+TB]; Table* epgDb; Table* fileDb; Table* imageDb; Table* episodeDb; cTvmEpgHandler* epgHandler; cMutex mutexEpisodes; map evtMemList; }; //*************************************************************************** // EPG Handler //*************************************************************************** #if VDRVERSNUM < 10726 class cEpgHandler : public cListObject { public: cEpgHandler(void) {} virtual ~cEpgHandler() {} virtual bool IgnoreChannel(const cChannel *Channel) { return false; } virtual bool HandleEvent(cEvent* event) { return false; } virtual bool HandledExternally(const cChannel *Channel) { return false; } virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) { return false; } }; #endif class cTvmEpgHandler : public cEpgHandler { public: cTvmEpgHandler(cChannelMap* cm, map* aEvtMemList) { chanmap = cm; evtMemList = aEvtMemList; // epgDb = new Table("events", cEventFields::fields); epgDb = new Table("events", cEventFields::fields, cEventFields::views); if (epgDb->open() != success) tell(0, "TVM2VDR: Could not access sqlite database %s for table %s", epgDb->getFileName(), epgDb->TableName()); } ~cTvmEpgHandler() { epgDb->close(); delete epgDb; } //*************************************************************************** // Is Update //*************************************************************************** virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) { // Die VDR Events fuer welche wir TVM verwenden auf updatedays begrenzen // daher 'false' zurück geben und somit ein NON-update vorgaukeln ;) if (chanmap->GetTvmidOfChannel(channleId.ToString()) > 0) if (StartTime > time(0) + TVM2VDRConfig.upddays*24*60*60) return false; char evtKey[100]; sprintf(evtKey, "%d%s", EventID, (const char*)channleId.ToString()); if (evtMemList->find(evtKey) == evtMemList->end()) { tell(3, "TVM2VDR: Handle event %d for channel '%s' (insert case)", EventID, (const char*)channleId.ToString()); return true; } uchar oldTableId = ::max(uchar((*evtMemList)[evtKey].tableid), uchar(0x4E)); // skip if old tid is already lower if (oldTableId < TableID) return false; // skip if version an tid identical // if (uchar((*evtMemList)[evtKey].tableid) == TableID && (*evtMemList)[evtKey].version == Version) if (oldTableId == TableID && (*evtMemList)[evtKey].version == Version) return false; if (TVM2VDRConfig.loglevel > 2) tell(0, "TVM2VDR: Handle update of event %d [%s] %d/%d - %d/%d", EventID, evtKey, Version, TableID, (*evtMemList)[evtKey].version, (*evtMemList)[evtKey].tableid); return true; } //*************************************************************************** // Handle Event //*************************************************************************** virtual bool HandleEvent(cEvent* event) { if (!event || !channleId.Valid()) return false; // TVM-ID: // na -> kanal nicht konfiguriert -> wird ignoriert // = 0 -> wird vom Sender genommen -> und in der DB abgelegt // > 0 -> echte tvmid -> wird von TVM ins epg übertragen, // das Sender EPG wird nur (zusätzlich) in der DB gehalten // Events der Kanaele welche nicht in der map zu finden sind ignorieren if (chanmap->GetTvmidOfChannel(channleId.ToString()) == na) return false; // create new row .. cDbRow row(cEventFields::fields); string comp; row.clear(); row.setValue(cEventFields::fiEventId, event->EventID()); row.setValue(cEventFields::fiChannelId, channleId.ToString()); row.setValue(cEventFields::fiSource, "vdr"); row.setValue(cEventFields::fiTableId, event->TableID()); row.setValue(cEventFields::fiVersion, event->Version()); row.setValue(cEventFields::fiTitle, event->Title()); row.setValue(cEventFields::fiShortText, event->ShortText()); row.setValue(cEventFields::fiLongDescription, event->Description()); row.setValue(cEventFields::fiStartTime, event->StartTime()); row.setValue(cEventFields::fiDuration, event->Duration()); row.setValue(cEventFields::fiParentalRating, event->ParentalRating()); row.setValue(cEventFields::fiVps, event->Vps()); // compressed .. if (event->Title()) { comp = event->Title(); prepareCompressed(comp); row.setValue(cEventFields::fiCompTitle, comp.c_str()); } if (event->ShortText()) { comp = event->ShortText(); prepareCompressed(comp); row.setValue(cEventFields::fiCompShortText, comp.c_str()); } epgDb->store(&row); // build hash key char evtKey[100]; sprintf(evtKey, "%d%s", event->EventID(), (const char*)channleId.ToString()); (*evtMemList)[evtKey].version = event->Version(); (*evtMemList)[evtKey].tableid = event->TableID(); return true; } //*************************************************************************** // Set Description //*************************************************************************** virtual bool SetDescription(cEvent* event, const char* Description) { if (chanmap->GetTvmidOfChannel(channleId.ToString()) != 0) return false; epgDb->clear(); epgDb->setValue(cEventFields::fiEventId, event->EventID()); epgDb->setValue(cEventFields::fiChannelId, channleId.ToString()); if (!epgDb->find(cEventFields::viVdr)) // search and load view fields { epgDb->resetFetch(cEventFields::viVdr); return false; } tell(3, "TVM2VDR: Pimp description of channel '%s'; event '%s'", (const char*)channleId.ToString(), event->Title()); event->SetDescription(epgDb->getStrValue(cEventFields::fiDescription)); epgDb->resetFetch(cEventFields::viVdr); return true; } //*************************************************************************** // Handled Externally //*************************************************************************** virtual bool HandledExternally(const cChannel* Channel) { channleId = Channel->GetChannelID(); return chanmap->GetTvmidOfChannel(channleId.ToString()) > 0; } //*************************************************************************** // NOEPG feature - so we don't need the noepg plug anymore //*************************************************************************** virtual bool IgnoreChannel(const cChannel* Channel) { if (TVM2VDRConfig.blacklist && chanmap->GetTvmidOfChannel(Channel->GetChannelID().ToString()) == na) return true; return false; } private: map* evtMemList; cChannelMap* chanmap; tChannelID channleId; Table* epgDb; }; //*************************************************************************** #endif //__UPDATE_H