summaryrefslogtreecommitdiff
path: root/update.c
diff options
context:
space:
mode:
authorhorchi <vdr@jwendel.de>2017-03-05 14:51:57 +0100
committerhorchi <vdr@jwendel.de>2017-03-05 14:51:57 +0100
commit5eacf5bf36ddbac082a9e40a2bcdfd0f04fd3f9f (patch)
tree392875cb707b94aaba9d8941113eae35efaf2ec2 /update.c
downloadvdr-plugin-epg2vdr-5eacf5bf36ddbac082a9e40a2bcdfd0f04fd3f9f.tar.gz
vdr-plugin-epg2vdr-5eacf5bf36ddbac082a9e40a2bcdfd0f04fd3f9f.tar.bz2
commit of actual revision1.1.441.1.42
Diffstat (limited to 'update.c')
-rw-r--r--update.c1822
1 files changed, 1822 insertions, 0 deletions
diff --git a/update.c b/update.c
new file mode 100644
index 0000000..e789c8d
--- /dev/null
+++ b/update.c
@@ -0,0 +1,1822 @@
+/*
+ * update.c: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <locale.h>
+
+#include <vdr/videodir.h>
+#include <vdr/tools.h>
+
+#include "epg2vdr.h"
+#include "update.h"
+#include "handler.h"
+
+//***************************************************************************
+// ctor
+//***************************************************************************
+
+cUpdate::cUpdate(cPluginEPG2VDR* aPlugin)
+ : cThread("epg2vdr-update")
+{
+ // thread / update control
+
+ plugin = aPlugin;
+ connection = 0;
+ loopActive = no;
+ timerJobsUpdateTriggered = yes;
+ timerTableUpdateTriggered = yes;
+ recordingStateChangedTrigger = yes;
+ updateRecFolderOptionTrigger = no;
+
+ storeAllRecordingInfoFilesTrigger = no;
+ recordingFullReloadTrigger = no;
+ manualTrigger = no;
+ videoBasePath = 0;
+ dbReconnectTriggered = no;
+
+ fullreload = no;
+ epgdBusy = yes;
+ epgdState = cEpgdState::esUnknown;
+ mainActPending = no;
+ eventsPending = no;
+ nextEpgdUpdateAt = 0;
+
+ lastUpdateAt = 0;
+ lastEventsUpdateAt = 0;
+ lastRecordingCount = 0;
+ lastRecordingDeleteAt = 0;
+ timersTableMaxUpdsp = 0;
+
+ //
+
+ compDb = 0;
+ eventsDb = 0;
+ useeventsDb = 0;
+ fileDb = 0;
+ imageDb = 0;
+ imageRefDb = 0;
+ episodeDb = 0;
+ vdrDb = 0;
+ mapDb = 0;
+ timerDb = 0;
+ timerDoneDb = 0;
+ recordingDirDb = 0;
+ recordingListDb = 0;
+
+ selectMasterVdr = 0;
+ selectAllImages = 0;
+ selectUpdEvents = 0;
+ selectEventById = 0;
+ selectAllChannels = 0;
+ selectChannelById = 0;
+ markUnknownChannel = 0;
+ selectComponentsOf = 0;
+ deleteTimer = 0;
+ selectMyTimer = 0;
+ selectRecordings = 0;
+ selectRecForInfoUpdate = 0;
+ selectTimerByEvent = 0;
+ selectTimerById = 0;
+ selectTimerByDoneId = 0;
+ selectMaxUpdSp = 0;
+ selectPendingTimerActions = 0;
+
+ dvbDescription = 0;
+
+ //
+
+ epgimagedir = 0;
+ withutf8 = no;
+ handlerMaster = no;
+
+ // check/create uuid
+
+ if (isEmpty(Epg2VdrConfig.uuid))
+ {
+ sstrcpy(Epg2VdrConfig.uuid, getUniqueId(), sizeof(Epg2VdrConfig.uuid));
+ plugin->SetupStore("Uuid", Epg2VdrConfig.uuid);
+ Setup.Save();
+
+ tell(0, "Initially created uuid '%s'", Epg2VdrConfig.uuid);
+ }
+}
+
+//***************************************************************************
+// dtor
+//***************************************************************************
+
+cUpdate::~cUpdate()
+{
+ if (loopActive)
+ Stop();
+
+ free(epgimagedir);
+}
+
+//***************************************************************************
+// Init
+//***************************************************************************
+
+int cUpdate::init()
+{
+ char* dictPath = 0;
+ char* pdir;
+ char* lang;
+
+ lang = setlocale(LC_CTYPE, 0);
+
+ if (lang)
+ {
+ tell(0, "Set locale to '%s'", lang);
+
+ if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0))
+ {
+ tell(0, "detected UTF-8");
+ withutf8 = yes;
+ }
+ }
+ else
+ {
+ tell(0, "Reseting locale for LC_CTYPE failed.");
+ }
+
+ strcpy(imageExtension, "jpg");
+ asprintf(&epgimagedir, "%s/epgimages", EPG2VDR_DATA_DIR);
+ asprintf(&pdir, "%s/images", epgimagedir);
+
+ if (!(DirectoryOk(pdir) || MakeDirs(pdir, true)))
+ tell(0, "could not access or create Directory %s", pdir);
+
+ free(pdir);
+
+ // initialize the dictionary
+
+ asprintf(&dictPath, "%s/epg.dat", cPlugin::ConfigDirectory("epg2vdr/"));
+
+ if (dbDict.in(dictPath) != success)
+ {
+ tell(0, "Fatal: Dictionary not loaded, aborting!");
+ return fail;
+ }
+
+ tell(0, "Dictionary '%s' loaded", dictPath);
+ free(dictPath);
+
+ // init database ...
+
+ cDbConnection::setEncoding(withutf8 ? "utf8" : "latin1"); // mysql uses latin1 for ISO8851-1
+ cDbConnection::setHost(Epg2VdrConfig.dbHost);
+ cDbConnection::setPort(Epg2VdrConfig.dbPort);
+ cDbConnection::setName(Epg2VdrConfig.dbName);
+ cDbConnection::setUser(Epg2VdrConfig.dbUser);
+ cDbConnection::setPass(Epg2VdrConfig.dbPass);
+ cDbConnection::setConfPath(cPlugin::ConfigDirectory("epg2vdr/"));
+
+ videoBasePath = cVideoDirectory::Name();
+
+ return success;
+}
+
+//***************************************************************************
+// Exit
+//***************************************************************************
+
+int cUpdate::exit()
+{
+ exitDb();
+
+ return success;
+}
+
+cDbFieldDef imageSizeDef("image", "image", cDBS::ffUInt, 0, cDBS::ftData);
+
+//***************************************************************************
+// Init/Exit Database Connections
+//***************************************************************************
+
+int cUpdate::initDb()
+{
+ int status = success;
+
+ if (!connection)
+ connection = new cDbConnection();
+
+ vdrDb = new cDbTable(connection, "vdrs");
+ if (vdrDb->open() != success) return fail;
+
+ // DB-API check
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", EPGDNAME);
+
+ if (!vdrDb->find())
+ {
+ tell(0, "Can't lookup epgd information, start epgd to create the tables first! Aborting now.");
+ return fail;
+ }
+
+ vdrDb->reset();
+
+ if (vdrDb->getIntValue("DBAPI") != DB_API)
+ {
+ tell(0, "Found dbapi %d, expected %d, please alter the tables first! Aborting now.",
+ (int)vdrDb->getIntValue("DBAPI"), DB_API);
+ return fail;
+ }
+
+ // open tables ..
+
+ mapDb = new cDbTable(connection, "channelmap");
+ if (mapDb->open() != success) return fail;
+
+ fileDb = new cDbTable(connection, "fileref");
+ if (fileDb->open() != success) return fail;
+
+ imageDb = new cDbTable(connection, "images");
+ if (imageDb->open() != success) return fail;
+
+ imageRefDb = new cDbTable(connection, "imagerefs");
+ if (imageRefDb->open() != success) return fail;
+
+ episodeDb = new cDbTable(connection, "episodes");
+ if (episodeDb->open() != success) return fail;
+
+ eventsDb = new cDbTable(connection, "events");
+ if (eventsDb->open() != success) return fail;
+
+ useeventsDb = new cDbTable(connection, "useevents");
+ if (useeventsDb->open() != success) return fail;
+
+ compDb = new cDbTable(connection, "components");
+ if (compDb->open() != success) return fail;
+
+ timerDb = new cDbTable(connection, "timers");
+ if (timerDb->open() != success) return fail;
+
+ timerDoneDb = new cDbTable(connection, "timersdone");
+ if (timerDoneDb->open() != success) return fail;
+
+ recordingDirDb = new cDbTable(connection, "recordingdirs");
+ if (recordingDirDb->open() != success) return fail;
+
+ recordingListDb = new cDbTable(connection, "recordinglist");
+ if (recordingListDb->open() != success) return fail;
+
+ if ((status = cParameters::initDb(connection)) != success)
+ return status;
+
+ // -------------------------------------------
+ // init db values
+
+ dvbDescription = new cDbValue("description", cDBS::ffText, 50000);
+
+ // -------------------------------------------
+ // init statements
+
+ tell(2, "Prepare statements ...");
+
+ selectMasterVdr = new cDbStatement(vdrDb);
+
+ // select uuid, name from vdrs
+ // where upper(master) = 'Y' and uuid <> ?;
+
+ selectMasterVdr->build("select ");
+ selectMasterVdr->bind("UUID", cDBS::bndOut);
+ selectMasterVdr->bind("NAME", cDBS::bndOut, ", ");
+ selectMasterVdr->build(" from %s where upper(%s) = 'Y' and ",
+ vdrDb->TableName(), vdrDb->getField("MASTER")->getDbName());
+ selectMasterVdr->bindCmp(0, "Uuid", 0, "<>");
+
+ status += selectMasterVdr->prepare();
+
+ // all images
+
+ selectAllImages = new cDbStatement(imageRefDb);
+
+ // prepare fields
+
+ imageSize.setField(&imageSizeDef);
+ imageUpdSp.setField(imageDb->getField("UpdSp"));
+ masterId.setField(eventsDb->getField("MasterId"));
+
+ // select e.masterid, r.imagename, r.eventid, r.lfn, length(i.image)
+ // from imagerefs r, images i, events e
+ // where i.imagename = r.imagename
+ // and e.eventid = r.eventid
+ // and (i.updsp > ? or r.updsp > ?)
+
+ selectAllImages->build("select ");
+ selectAllImages->setBindPrefix("e.");
+ selectAllImages->bind(&masterId, cDBS::bndOut);
+ selectAllImages->setBindPrefix("r.");
+ selectAllImages->bind("ImgName", cDBS::bndOut, ", ");
+ selectAllImages->bind("EventId", cDBS::bndOut, ", ");
+ selectAllImages->bind("Lfn", cDBS::bndOut, ", ");
+ selectAllImages->setBindPrefix("i.");
+ selectAllImages->build(", length(");
+ selectAllImages->bind(&imageSize, cDBS::bndOut);
+ selectAllImages->build(")");
+ selectAllImages->clrBindPrefix();
+ selectAllImages->build(" from %s r, %s i, %s e where ",
+ imageRefDb->TableName(), imageDb->TableName(), eventsDb->TableName());
+ selectAllImages->build("e.%s = r.%s and i.%s = r.%s and (",
+ eventsDb->getField("EventId")->getDbName(),
+ imageRefDb->getField("EventId")->getDbName(),
+ imageDb->getField("ImgName")->getDbName(),
+ imageRefDb->getField("ImgName")->getDbName());
+ selectAllImages->bindCmp("i", &imageUpdSp, ">");
+ selectAllImages->build(" or ");
+ selectAllImages->bindCmp("r", "UpdSp", 0, ">");
+ selectAllImages->build(")");
+
+ status += selectAllImages->prepare();
+
+ // select distinct channelid, channelname
+ // from channelmap;
+
+ selectAllChannels = new cDbStatement(mapDb);
+
+ selectAllChannels->build("select distinct ");
+ selectAllChannels->bind("CHANNELID", cDBS::bndOut);
+ selectAllChannels->bind("CHANNELNAME", cDBS::bndOut, ", ");
+ selectAllChannels->build(" from %s", mapDb->TableName());
+
+ status += selectAllChannels->prepare();
+
+ // select distinct channelid, channelname
+ // from channelmap
+ // where channleid = ?;
+
+ selectChannelById = new cDbStatement(mapDb);
+
+ selectChannelById->build("select distinct ");
+ selectChannelById->bind("CHANNELID", cDBS::bndOut);
+ selectChannelById->bind("CHANNELNAME", cDBS::bndOut, ", ");
+ selectChannelById->build(" from %s where ", mapDb->TableName());
+ selectChannelById->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet);
+
+ status += selectChannelById->prepare();
+
+ // update channlemap
+ // set unknownatvdr = 1
+ // where channelid = ?
+
+ markUnknownChannel = new cDbStatement(mapDb);
+
+ markUnknownChannel->build("update %s set ", mapDb->TableName());
+ markUnknownChannel->bind("UNKNOWNATVDR", cDBS::bndIn | cDBS::bndSet);
+ markUnknownChannel->build(" where ");
+ markUnknownChannel->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet);
+
+ status += markUnknownChannel->prepare();
+
+ // select changed events
+
+ selectUpdEvents = new cDbStatement(eventsDb);
+
+ // select useid, eventid, source, delflg, updflg, fileref,
+ // tableid, version, title, shorttext, starttime,
+ // duration, parentalrating, vps, description
+ // from eventsview
+ // where
+ // channelid = ?
+ // and updsp > ?
+ // and updflg in (.....)
+
+ selectUpdEvents->build("select ");
+ selectUpdEvents->bind("USEID", cDBS::bndOut);
+ // selectUpdEvents->bind("MASTERID", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("EVENTID", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("SOURCE", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("DELFLG", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("UPDFLG", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("FILEREF", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("TABLEID", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("VERSION", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("TITLE", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("SHORTTEXT", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("STARTTIME", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("DURATION", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("PARENTALRATING", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("VPS", cDBS::bndOut, ", ");
+ selectUpdEvents->bind("CONTENTS", cDBS::bndOut, ", ");
+ selectUpdEvents->bind(dvbDescription, cDBS::bndOut, ", ");
+ selectUpdEvents->build(" from eventsview where ");
+ selectUpdEvents->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet);
+ selectUpdEvents->bindCmp(0, "UPDSP", 0, ">", " and ");
+ selectUpdEvents->build(" and UPDFLG in (%s)", Us::getNeeded());
+
+ status += selectUpdEvents->prepare();
+
+ // select event by useid
+
+ selectEventById = new cDbStatement(useeventsDb);
+
+ // select * from eventsview
+ // where useid = ?
+ // and updflg in (.....)
+
+ selectEventById->build("select ");
+ selectEventById->bindAllOut();
+ selectEventById->build(" from %s where ", useeventsDb->TableName());
+ selectEventById->bind("USEID", cDBS::bndIn | cDBS::bndSet);
+ selectEventById->build(" and %s in (%s)",
+ useeventsDb->getField("UPDFLG")->getDbName(),
+ Us::getNeeded());
+
+ status += selectEventById->prepare();
+
+ // ...
+
+ // select stream, type, lang, description
+ // from components where
+ // eventid = ?;
+ // channelid = ?;
+
+ selectComponentsOf = new cDbStatement(compDb);
+
+ selectComponentsOf->build("select ");
+ selectComponentsOf->bind("Stream", cDBS::bndOut);
+ selectComponentsOf->bind("Type", cDBS::bndOut, ", ");
+ selectComponentsOf->bind("Lang", cDBS::bndOut, ", ");
+ selectComponentsOf->bind("Description", cDBS::bndOut, ", ");
+ selectComponentsOf->build(" from %s where ", compDb->TableName());
+ selectComponentsOf->bind("EventId", cDBS::bndIn | cDBS::bndSet);
+ selectComponentsOf->bind("ChannelId", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectComponentsOf->prepare();
+
+ // select *
+ // from recordinglist where
+ // state <> 'D'
+
+ selectRecordings = new cDbStatement(recordingListDb);
+
+ selectRecordings->build("select ");
+ selectRecordings->bindAllOut();
+ selectRecordings->build(" from %s where ", recordingListDb->TableName());
+ selectRecordings->build(" (%s <> 'D' or %s is null)",
+ recordingListDb->getField("STATE")->getDbName(),
+ recordingListDb->getField("STATE")->getDbName());
+
+ status += selectRecordings->prepare();
+
+ // select srcmovieid, srcseriesid, scrseriesepisode
+ // from recordinglist where
+ // state <> 'D' or stete is null
+ // and (updsp > lastifoupd or lastifoupd is null)
+
+ selectRecForInfoUpdate = new cDbStatement(recordingListDb);
+
+ selectRecForInfoUpdate->build("select ");
+ selectRecForInfoUpdate->bindAllOut();
+ selectRecForInfoUpdate->build(" from %s where ", recordingListDb->TableName());
+ selectRecForInfoUpdate->build(" (%s <> 'D' or %s is null)",
+ recordingListDb->getField("STATE")->getDbName(),
+ recordingListDb->getField("STATE")->getDbName());
+ selectRecForInfoUpdate->build(" and (%s > %s or %s is null) ",
+ recordingListDb->getField("UPDSP")->getDbName(),
+ recordingListDb->getField("LASTIFOUPD")->getDbName(),
+ recordingListDb->getField("LASTIFOUPD")->getDbName());
+
+ status += selectRecForInfoUpdate->prepare();
+
+ // select *
+ // from timers where
+ // state <> 'D'
+ // and vdruuid = ?
+
+ selectMyTimer = new cDbStatement(timerDb);
+
+ selectMyTimer->build("select ");
+ selectMyTimer->bindAllOut();
+ selectMyTimer->build(" from %s where ", timerDb->TableName());
+ selectMyTimer->build(" %s <> 'D'", timerDb->getField("STATE")->getDbName());
+ selectMyTimer->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectMyTimer->prepare();
+
+ // select id, eventid, channelid, starttime, state, endtime
+ // from timers where
+ // eventid = ? and channelid = ? and vdruuid = ?
+
+ selectTimerByEvent = new cDbStatement(timerDb);
+
+ selectTimerByEvent->build("select ");
+ selectTimerByEvent->bind("ID", cDBS::bndOut);
+ selectTimerByEvent->bind("EVENTID", cDBS::bndOut, ", ");
+ selectTimerByEvent->bind("CHANNELID", cDBS::bndOut, ", ");
+ selectTimerByEvent->bind("STARTTIME", cDBS::bndOut, ", ");
+ selectTimerByEvent->bind("STATE", cDBS::bndOut, ", ");
+ selectTimerByEvent->bind("ENDTIME", cDBS::bndOut, ", ");
+ selectTimerByEvent->build(" from %s where ", timerDb->TableName());
+ selectTimerByEvent->bind("EVENTID", cDBS::bndIn | cDBS::bndSet);
+ selectTimerByEvent->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet, " and ");
+ selectTimerByEvent->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += selectTimerByEvent->prepare();
+
+ // select *
+ // from timers where
+ // id = ?
+
+ selectTimerById = new cDbStatement(timerDb);
+
+ selectTimerById->build("select ");
+ selectTimerById->bindAllOut();
+ selectTimerById->build(" from %s where ", timerDb->TableName());
+ selectTimerById->bind("ID", cDBS::bndIn | cDBS::bndSet);
+
+ status += selectTimerById->prepare();
+
+ // select *
+ // from timers where
+ // doneid = ?
+
+ selectTimerByDoneId = new cDbStatement(timerDb);
+
+ selectTimerByDoneId->build("select ");
+ selectTimerByDoneId->bindAllOut();
+ selectTimerByDoneId->build(" from %s where ", timerDb->TableName());
+ selectTimerByDoneId->bind("DONEID", cDBS::bndIn | cDBS::bndSet);
+
+ status += selectTimerByDoneId->prepare();
+
+ //-----------
+ // select
+ // max(updsp)
+ // from timers
+
+ selectMaxUpdSp = new cDbStatement(timerDb);
+
+ selectMaxUpdSp->build("select ");
+ selectMaxUpdSp->bind("UPDSP", cDBS::bndOut, "max(");
+ selectMaxUpdSp->build(") from %s", timerDb->TableName());
+
+ status += selectMaxUpdSp->prepare();
+
+ // select * from timers
+ // where
+ // action != 'A' and action != 'F' and action is not null // !taAssumed and !taFailed
+ // and (vdruuid = ? or vdruuid = 'any')
+
+ selectPendingTimerActions = new cDbStatement(timerDb);
+
+ selectPendingTimerActions->build("select ");
+ selectPendingTimerActions->bindAllOut();
+ selectPendingTimerActions->build(" from %s where ", timerDb->TableName());
+ selectPendingTimerActions->build("%s != 'A' and %s != 'F' and %s is not null",
+ timerDb->getField("ACTION")->getDbName(),
+ timerDb->getField("ACTION")->getDbName(),
+ timerDb->getField("ACTION")->getDbName());
+ selectPendingTimerActions->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and (");
+ selectPendingTimerActions->build(" or %s = 'any')", timerDb->getField("VDRUUID")->getDbName());
+
+ status += selectPendingTimerActions->prepare();
+
+ // delete from timers where
+ // eventid = ?
+
+ deleteTimer = new cDbStatement(timerDb);
+
+ deleteTimer->build("delete from %s where ", timerDb->TableName());
+ deleteTimer->bind("EVENTID", cDBS::bndIn | cDBS::bndSet);
+ deleteTimer->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet, " and ");
+ deleteTimer->bind("VDRUUID", cDBS::bndIn | cDBS::bndSet, " and ");
+
+ status += deleteTimer->prepare();
+
+ if (status == success)
+ {
+ // -------------------------------------------
+ // lookback -> get last stamp
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", Epg2VdrConfig.uuid);
+
+ if (vdrDb->find())
+ {
+ char buf[50+TB];
+
+ lastUpdateAt = vdrDb->getIntValue("LastUpdate");
+ lastEventsUpdateAt = lastUpdateAt;
+ getParameter("uuid", "lastEventsUpdateAt", lastEventsUpdateAt);
+
+ strftime(buf, 50, "%d.%m.%y %H:%M:%S", localtime(&lastUpdateAt));
+ tell(0, "Info: Last update was at '%s'", buf);
+ }
+
+ // register me to the vdrs table
+
+ int devCount = 0;
+ char* v;
+
+ for (int i = 0; i < cDevice::NumDevices(); i++)
+ {
+ const cDevice* device = cDevice::GetDevice(i);
+
+ if (device && device->NumProvidedSystems())
+ devCount ++;
+ }
+
+ asprintf(&v, "vdr %s epg2vdr %s (%s)", VDRVERSION, VERSION, VERSION_DATE);
+
+ vdrDb->setValue("UUID", Epg2VdrConfig.uuid);
+ vdrDb->setValue("IP", getIpOf(Epg2VdrConfig.netDevice));
+ vdrDb->setValue("MAC", getMacOf(Epg2VdrConfig.netDevice));
+ vdrDb->setValue("NAME", getHostName());
+ vdrDb->setValue("DBAPI", DB_API);
+ vdrDb->setValue("VERSION", v);
+ vdrDb->setValue("STATE", "attached");
+ vdrDb->setValue("MASTER", "n");
+ vdrDb->setValue("TUNERCOUNT", devCount);
+ vdrDb->setValue("SHAREINWEB", Epg2VdrConfig.shareInWeb);
+ vdrDb->setValue("USECOMMONRECFOLDER", Epg2VdrConfig.useCommonRecFolder);
+
+ // set svdrp port if uninitialized, we can't query ther actual port from VDR :(
+
+ if (vdrDb->getIntValue("SVDRP") == 0)
+ vdrDb->setValue("SVDRP", 6419); // #TODO -> plugin-setup?!
+
+ vdrDb->store();
+ updateVdrData();
+ free(v);
+ }
+
+ if (status == success)
+ status += cEpg2VdrEpgHandler::getSingleton()->updateExternalIdsMap(mapDb);
+
+ return status;
+}
+
+int cUpdate::exitDb()
+{
+ // de-register me at the vdrs table
+
+ if (vdrDb && vdrDb->isConnected())
+ {
+ vdrDb->setValue("Uuid", Epg2VdrConfig.uuid);
+ vdrDb->find();
+ vdrDb->setValue("Master", "n");
+ vdrDb->setValue("State", "detached");
+ vdrDb->store();
+ }
+
+ cParameters::exitDb();
+
+ delete selectAllImages; selectAllImages = 0;
+ delete selectUpdEvents; selectUpdEvents = 0;
+ delete selectEventById; selectEventById = 0;
+ delete selectAllChannels; selectAllChannels = 0;
+ delete selectChannelById; selectChannelById = 0;
+ delete markUnknownChannel; markUnknownChannel = 0;
+ delete selectComponentsOf; selectComponentsOf = 0;
+ delete selectMasterVdr; selectMasterVdr = 0;
+ delete deleteTimer; deleteTimer = 0;
+ delete selectMyTimer; selectMyTimer = 0;
+ delete selectRecordings; selectRecordings = 0;
+ delete selectRecForInfoUpdate; selectRecForInfoUpdate = 0;
+ delete selectTimerByEvent; selectTimerByEvent = 0;
+ delete selectTimerById; selectTimerById = 0;
+ delete selectTimerByDoneId; selectTimerByDoneId = 0;
+ delete selectMaxUpdSp; selectMaxUpdSp = 0;
+ delete selectPendingTimerActions; selectPendingTimerActions = 0;
+
+ delete vdrDb; vdrDb = 0;
+ delete mapDb; mapDb = 0;
+ delete fileDb; fileDb = 0;
+ delete imageDb; imageDb = 0;
+ delete imageRefDb; imageRefDb = 0;
+ delete episodeDb; episodeDb = 0;
+ delete eventsDb; eventsDb = 0;
+ delete useeventsDb; useeventsDb = 0;
+ delete compDb; compDb = 0;
+ delete timerDb; timerDb = 0;
+ delete timerDoneDb; timerDoneDb = 0;
+ delete recordingDirDb; recordingDirDb = 0;
+ delete recordingListDb; recordingListDb = 0;
+
+ delete dvbDescription; dvbDescription = 0;
+
+ delete connection; connection = 0;
+
+ return done;
+}
+
+//***************************************************************************
+// Check Connection
+//***************************************************************************
+
+int cUpdate::checkConnection(int& timeout)
+{
+ static int retryCount = 0;
+
+ timeout = retryCount < 5 ? 10 : 60;
+
+ // reconnect requested ?
+
+ if (dbReconnectTriggered)
+ exitDb();
+
+ dbReconnectTriggered = no;
+
+ // check connection
+
+ if (!dbConnected(yes))
+ {
+ // try to connect
+
+ tell(0, "Trying to re-connect to database!");
+ retryCount++;
+
+ if (initDb() != success)
+ {
+ tell(0, "Retry #%d failed, retrying in %d seconds!", retryCount, timeout);
+ exitDb();
+
+ return fail;
+ }
+
+ retryCount = 0;
+ tell(0, "Connection established successfull!");
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Is Handler Master
+//***************************************************************************
+
+int cUpdate::isHandlerMaster()
+{
+ static int initialized = no;
+
+ char flag = 0;
+
+/*
+ wenn no - handler ausschalten und db auf 'N'
+ wenn auto - aushandeln
+ wenn yes - handler anschalten und db auf 'Y'
+
+*/
+ if (!dbConnected())
+ return no;
+
+ // on first call detect state of epgd
+
+ if (!initialized)
+ {
+ epgdBusy = no;
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", EPGDNAME);
+
+ if (vdrDb->find())
+ {
+ nextEpgdUpdateAt = vdrDb->getIntValue("NEXTUPDATE");
+ epgdState = cEpgdState::toState(vdrDb->getStrValue("State"));
+
+ if (epgdState >= cEpgdState::esBusy && epgdState < cEpgdState::esBusyImages)
+ epgdBusy = yes;
+
+ tell(1, "Detected epgd state '%s' (%d)", vdrDb->getStrValue("State"), epgdState);
+ initialized = yes;
+ }
+ else
+ {
+ tell(0, "Info: Can't detect epgd state");
+ epgdBusy = yes;
+ }
+ }
+
+ // update handler role
+
+ if (Epg2VdrConfig.masterMode == mmAuto)
+ {
+ tell(3, "Auto check master role");
+
+ // select where "uuid <> ? and upper(master) = 'Y'"
+
+ vdrDb->clear();
+ vdrDb->setValue("Uuid", Epg2VdrConfig.uuid);
+
+ handlerMaster = !selectMasterVdr->find();
+
+ if (!handlerMaster)
+ tell(3, "Master found, uuid '%s' (%s)",
+ vdrDb->getStrValue("UUID"),
+ vdrDb->getStrValue("NAME"));
+
+ selectMasterVdr->freeResult();
+
+ flag = handlerMaster ? 'y' : 'n';
+ }
+ else if (Epg2VdrConfig.masterMode == mmYes)
+ {
+ flag = 'Y';
+ handlerMaster = yes;
+ }
+ else
+ {
+ flag = 'n';
+ handlerMaster = no;
+ }
+
+ // write again to force at least the updsp
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", Epg2VdrConfig.uuid);
+ vdrDb->find();
+
+ vdrDb->setValue("STATE", "attached");
+ vdrDb->setCharValue("MASTER", flag);
+ vdrDb->store();
+
+ // set handler state
+
+ int handlerState = !epgdBusy && handlerMaster;
+
+ if (cEpg2VdrEpgHandler::getSingleton()->getActive() != handlerState)
+ {
+ tell(1, "Change handler state to '%s'", handlerState ? "active" : "standby");
+ cEpg2VdrEpgHandler::getSingleton()->setActive(handlerState);
+ }
+
+ return handlerMaster;
+}
+
+// // ***************************************************************************
+// // Initially Init Epgd State
+// // ***************************************************************************
+
+// void cUpdate::updateEpgdState()
+// {
+// epgdBusy = no;
+
+// if (!dbConnected())
+// return;
+
+// vdrDb->clear();
+// vdrDb->setValue("UUID", EPGDNAME);
+
+// if (vdrDb->find())
+// {
+// nextEpgdUpdateAt = vdrDb->getIntValue("NEXTUPDATE");
+// epgdState = cEpgdState::toState(vdrDb->getStrValue("State"));
+
+// if (epgdState >= cEpgdState::esBusy && epgdState < cEpgdState::esBusyImages)
+// epgdBusy = yes;
+
+// tell(1, "Detected epgd state '%s' (%d)", vdrDb->getStrValue("State"), epgdState);
+// }
+// else
+// tell(0, "Info: Can't detect epgd state");
+
+// int handlerState = !epgdBusy && isHandlerMaster();
+
+// if (cEpg2VdrEpgHandler::getSingleton()->getActive() != handlerState)
+// tell(0, "Set handler state initially to '%s'", handlerState ? "active" : "standby");
+
+// cEpg2VdrEpgHandler::getSingleton()->setActive(handlerState);
+
+// vdrDb->reset();
+// }
+
+// ***************************************************************************
+// Update VDR Data
+// ***************************************************************************
+
+void cUpdate::updateVdrData()
+{
+ int usedMb = 0;
+ int freeMb = 0;
+
+ cVideoDirectory::VideoDiskSpace(&freeMb, &usedMb);
+
+ vdrDb->clear();
+ vdrDb->setValue("UUID", Epg2VdrConfig.uuid);
+ vdrDb->find();
+
+ vdrDb->setValue("VIDEODIR", videoBasePath);
+ vdrDb->setValue("VIDEOTOTAL", usedMb+freeMb);
+ vdrDb->setValue("VIDEOFREE", freeMb);
+
+ vdrDb->store();
+}
+
+//***************************************************************************
+// Update Rec Folder Option
+//***************************************************************************
+
+int cUpdate::updateRecFolderOption()
+{
+ vdrDb->clear();
+ vdrDb->setValue("UUID", Epg2VdrConfig.uuid);
+
+ if (vdrDb->find())
+ {
+ vdrDb->setValue("USECOMMONRECFOLDER", Epg2VdrConfig.useCommonRecFolder);
+ vdrDb->update();
+ }
+
+ updateRecFolderOptionTrigger = no;
+ recordingStateChangedTrigger = yes;
+ recordingFullReloadTrigger = yes;
+
+ return done;
+}
+
+//***************************************************************************
+// Trigger Update
+//***************************************************************************
+
+void cUpdate::triggerDbReconnect()
+{
+ dbReconnectTriggered = yes;
+ waitCondition.Broadcast();
+}
+
+//***************************************************************************
+// Trigger Update
+//***************************************************************************
+
+int cUpdate::triggerEpgUpdate(int reload)
+{
+ if (!loopActive)
+ {
+ tell(0, "Thread not running, try to recover ...");
+ Cancel(3);
+ Start();
+ }
+
+ fullreload = reload;
+ mainActPending = yes;
+ manualTrigger = yes;
+
+ if (epgdBusy)
+ {
+ Skins.QueueMessage(mtInfo, cString::sprintf(tr("Skipping '%s' request, epgd busy. Trying again later"),
+ reload ? tr("reload") : tr("update")));
+
+ return fail;
+ }
+
+ waitCondition.Broadcast(); // wakeup thread
+
+ return success;
+}
+
+//***************************************************************************
+// Trigger Recording Update
+//***************************************************************************
+
+int cUpdate::triggerRecUpdate()
+{
+ if (!loopActive)
+ {
+ tell(0, "Thread not running, try to recover ...");
+ Cancel(3);
+ Start();
+ }
+
+ recordingStateChangedTrigger = yes;
+ waitCondition.Broadcast(); // wakeup thread
+
+ return success;
+}
+
+int cUpdate::commonRecFolderOptionChanged()
+{
+ updateRecFolderOptionTrigger = yes;
+ waitCondition.Broadcast(); // wakeup thread
+
+ return done;
+}
+
+//***************************************************************************
+// Trigger Store Info Files
+//***************************************************************************
+
+int cUpdate::triggerStoreInfoFiles()
+{
+ if (!loopActive)
+ {
+ tell(0, "Thread not running, try to recover ...");
+ Cancel(3);
+ Start();
+ }
+
+ storeAllRecordingInfoFilesTrigger = yes;
+ waitCondition.Broadcast(); // wakeup thread
+
+ return success;
+}
+
+//***************************************************************************
+// Trigger Timer Jobs
+//***************************************************************************
+
+void cUpdate::triggerTimerJobs()
+{
+ if (!loopActive)
+ {
+ tell(0, "Thread not running, try to recover ...");
+ Cancel(3);
+ Start();
+ }
+
+ timerTableUpdateTriggered = yes;
+ timerJobsUpdateTriggered = yes;
+ waitCondition.Broadcast(); // wakeup thread
+}
+
+//***************************************************************************
+// Epgd State Change
+// - called via SVDRP
+//***************************************************************************
+
+void cUpdate::epgdStateChange(const char* state)
+{
+ // state control
+
+ if (!dbConnected())
+ return;
+
+ epgdState = cEpgdState::toState(state);
+ epgdBusy = epgdState >= cEpgdState::esBusy && epgdState < cEpgdState::esBusyImages;
+
+ tell(1, "Got epgd state '%s' (%d)", state, epgdState);
+
+ if (epgdState == Es::esBusyMatch)
+ eventsPending = yes; // events pending due to merge
+ else if (epgdState == Es::esBusyEvents)
+ mainActPending = yes; // main action pending
+
+ // epg handler control
+
+ int handlerState = !epgdBusy && handlerMaster;
+
+ if (cEpg2VdrEpgHandler::getSingleton()->getActive() != handlerState)
+ tell(1, "Change handler state to '%s'", handlerState ? "active" : "standby");
+
+ cEpg2VdrEpgHandler::getSingleton()->setActive(handlerState);
+
+ // check loop state
+
+ if (!loopActive)
+ {
+ tell(0, "Thread not running, try to recover ...");
+ Cancel(3);
+ Start();
+ }
+
+ if (!epgdBusy)
+ waitCondition.Broadcast(); // wakeup thread
+}
+
+//***************************************************************************
+// Stop Thread
+//***************************************************************************
+
+void cUpdate::Stop()
+{
+ loopActive = no;
+ waitCondition.Broadcast(); // wakeup thread
+
+ Cancel(10); // wait up to 10 seconds for thread was stopping
+}
+
+//***************************************************************************
+// Action
+//***************************************************************************
+
+void cUpdate::Action()
+{
+ // open tables - inside thread!
+
+ if (initDb() != success)
+ exitDb();
+
+ // mutex gets ONLY unlocked when sleeping
+
+ mutex.Lock();
+ loopActive = yes;
+
+ // main action loop ...
+
+ while (loopActive && Running())
+ {
+ int reconnectTimeout; // set by checkConnection()
+
+ // wait 1 minute
+
+ waitCondition.TimedWait(mutex, 60*1000);
+
+ // we pass here at least once per minute ...
+
+ if (checkConnection(reconnectTimeout) != success)
+ continue;
+
+ // recording stuff
+
+ if (dbConnected() && updateRecFolderOptionTrigger)
+ updateRecFolderOption();
+
+ if (dbConnected() && recordingStateChangedTrigger)
+ {
+ if (Epg2VdrConfig.shareInWeb)
+ recordingChanged(); // update timer state
+
+ updateVdrData(); // update video disk size/free, ...
+ updateRecordingTable(recordingFullReloadTrigger);
+
+ recordingFullReloadTrigger = no;
+ recordingStateChangedTrigger = no;
+ }
+ else if (dbConnected() && lastRecordingDeleteAt+5*tmeSecondsPerMinute < time(0))
+ {
+ cleanupDeletedRecordings();
+ updateRecordingInfoFiles();
+ lastRecordingDeleteAt = time(0);
+ }
+
+ if (dbConnected() && storeAllRecordingInfoFilesTrigger)
+ storeAllRecordingInfoFiles();
+
+ // check handler role
+
+ isHandlerMaster();
+
+ if (epgdBusy)
+ continue;
+
+ if (Epg2VdrConfig.shareInWeb)
+ {
+ // check timer distribution and take over 'my' timers
+
+ if (dbConnected() && timerJobsUpdateTriggered)
+ {
+ tell(2, "Updating EPG prior to 'check of timer request'");
+ refreshEpg(0, na); // refresh EPG before performing timer jobs!
+ performTimerJobs();
+ }
+
+ // update timer
+
+ if (dbConnected() && timerTableUpdateTriggered)
+ updateTimerTable();
+
+ if (dbConnected())
+ timerChanged();
+ }
+
+ // if triggered externally or updates pending
+
+ if (dbConnected(yes) && (mainActPending || eventsPending))
+ {
+ time_t lastUpdateStartAt = time(0);
+
+ tell(mainActPending ? 0 : 2, "--- EPG '%s' started ---", fullreload ? "full-reload" : mainActPending ? "update" : "refresh");
+
+ if (mainActPending)
+ {
+ // cleanup unreferenced image-files
+
+ if (cleanupPictures() != success)
+ continue;
+ }
+
+ if (refreshEpg() != success)
+ continue;
+
+ lastEventsUpdateAt = time(0);
+ setParameter("uuid", "lastEventsUpdateAt", lastEventsUpdateAt);
+ cSchedules::Cleanup(true); // force VDR to store of epg.data to filesystem
+
+ if (mainActPending)
+ {
+ // get pictures from database and copy to local FS
+
+ if (storePicturesToFs() != success)
+ continue;
+
+ lastUpdateAt = lastUpdateStartAt; // update lookback information (notice)
+
+ vdrDb->clear();
+ vdrDb->setValue("Uuid", Epg2VdrConfig.uuid);
+ vdrDb->find();
+ vdrDb->setValue("LastUpdate", lastUpdateAt);
+ vdrDb->store();
+ }
+
+ tell(mainActPending ? 0 : 2, "--- EPG %s finished ---", fullreload ? "reload" : mainActPending ? "update" : "refresh");
+
+ if (Epg2VdrConfig.loglevel > 2)
+ connection->showStat("update");
+
+ if (manualTrigger)
+ {
+ Skins.QueueMessage(mtInfo, cString::sprintf(tr("EPG '%s' done"), fullreload ? tr("reload") : tr("update")));
+ manualTrigger = no;
+ }
+
+ mainActPending = no;
+ eventsPending = no;
+ fullreload = no;
+ }
+ }
+
+ exit(); // don't call exit in dtor outside of thread!!
+
+ loopActive = no;
+
+ tell(0, "Update thread ended (tid=%i)", cThread::ThreadId());
+}
+
+//***************************************************************************
+// Clear Epg
+//***************************************************************************
+
+void clearEpg()
+{
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+
+ LOCK_TIMERS_WRITE;
+ LOCK_SCHEDULES_WRITE;
+
+ for (cTimer* timer = Timers->First(); timer; timer = Timers->Next(timer))
+ timer->SetEvent(0); // processing all timers here (local *and* remote)
+
+ for (cSchedule* schedule = Schedules->First(); schedule; schedule = Schedules->Next(schedule))
+ schedule->Cleanup(INT_MAX);
+
+ cEitFilter::SetDisableUntil(time(0) + 10);
+
+#else
+
+ while (!cSchedules::ClearAll())
+ tell(0, "Warning: Clear EPG failed, can't get lock. Retrying ...");
+
+#endif
+}
+
+//***************************************************************************
+// Refresh Epg
+//***************************************************************************
+
+int cUpdate::refreshEpg(const char* forChannelId, int maxTries)
+{
+ const cEvent* event;
+ int tries = 0;
+ int timerChanges = 0;
+ int total = 0;
+ int dels = 0;
+ int channels = 0;
+ uint64_t start = cTimeMs::Now();
+ cDbStatement* select = 0;
+
+ // lookback ...
+
+ getParameter("uuid", "lastEventsUpdateAt", lastEventsUpdateAt);
+
+ // full reload?
+
+ if (fullreload)
+ {
+ tell(1, "Removing all events from epg");
+
+ clearEpg();
+
+ lastUpdateAt = 0;
+ lastEventsUpdateAt = 0;
+ }
+
+ // iterate over all channels in channelmap
+
+ mapDb->clear();
+
+ if (forChannelId)
+ {
+ select = selectChannelById;
+ mapDb->setValue("CHANNELID", forChannelId);
+
+ tell(1, "Load EPG for channel '%s'", forChannelId);
+ }
+ else
+ {
+ select = selectAllChannels;
+
+ if (lastEventsUpdateAt)
+ tell(2, "Update EPG, loading changes since %s", l2pTime(lastEventsUpdateAt).c_str());
+ else
+ tell(2, "Update EPG, reloading all events");
+ }
+
+ for (int f = select->find(); f && dbConnected(yes); f = select->fetch())
+ {
+ int count = 0;
+ cSchedule* s = 0;
+ tChannelID channelId = tChannelID::FromString(mapDb->getStrValue("ChannelId"));
+
+ channels++;
+
+ eventsDb->clear();
+ eventsDb->setValue("UPDSP", forChannelId ? 0 : lastEventsUpdateAt);
+ eventsDb->setValue("CHANNELID", mapDb->getStrValue("CHANNELID"));
+
+ // get timers lock
+
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+ cStateKey timersKey;
+ tell(3, "-> Try to get timers lock");
+ cTimers* timers = cTimers::GetTimersWrite(timersKey, 500/*ms*/);
+#else
+ cTimers* timers = &Timers;
+#endif
+
+ // get schedules lock
+
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+ cStateKey schedulesKey;
+ tell(3, "-> Try to get schedules lock");
+ cSchedules* schedules = cSchedules::GetSchedulesWrite(schedulesKey, 500/*ms*/);
+#else
+ cSchedulesLock* schedulesLock = new cSchedulesLock(true, 500/*ms*/);
+ cSchedules* schedules = (cSchedules*)cSchedules::Schedules(*schedulesLock);
+ tell(3, "LOCK (refreshEpg)");
+#endif
+
+ if (!schedules || !timers)
+ {
+ tell(3, "Info: Can't get write lock on '%s'", !schedules ? "schedules" : "timers");
+
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+ if (schedules) schedulesKey.Remove();
+ if (timers) timersKey.Remove();
+#else
+ delete schedulesLock;
+#endif
+
+ if (tries++ > maxTries)
+ {
+ select->freeResult();
+ tell(3, "Warning: Aborting refresh after %d tries", tries);
+ break;
+ }
+
+ tell(3, "Retrying in 3 seconds");
+ sleep(3);
+
+ continue;
+ }
+
+ tries = 0;
+
+ // get schedule (channel)
+
+ if (!(s = (cSchedule*)schedules->GetSchedule(channelId)))
+ s = schedules->AddSchedule(channelId);
+
+ // -----------------------------------------
+ // iterate over all events of this channel
+
+ for (int found = selectUpdEvents->find(); found && dbConnected(); found = selectUpdEvents->fetch())
+ {
+ cTimer* timer = 0;
+ char updFlg = toupper(eventsDb->getStrValue("UPDFLG")[0]);
+
+ updFlg = updFlg == 0 ? 'P' : updFlg; // fix missing flag
+
+ // ignore unneded event rows ..
+
+ if (!Us::isNeeded(updFlg))
+ continue;
+
+ // get event / timer
+
+ if (event = s->GetEvent(eventsDb->getIntValue("USEID")))
+ {
+ if (Us::isRemove(updFlg))
+ tell(2, "Remove event %uld of channel '%s' due to updflg %c",
+ event->EventID(), (const char*)event->ChannelID().ToString(), updFlg);
+
+ if (event->HasTimer())
+ {
+ for (timer = timers->First(); timer; timer = timers->Next(timer))
+ if (timer->Event() == event)
+ break;
+ }
+
+ if (timer)
+ timer->SetEvent(0);
+
+ s->DelEvent((cEvent*)event);
+ }
+
+ if (!Us::isRemove(updFlg))
+ event = s->AddEvent(createEventFromRow(eventsDb->getRow()));
+ else if (event)
+ {
+ event = 0;
+ dels++;
+ }
+
+ if (timer && event)
+ {
+ timer->SetEvent(event);
+ timer->Matches(event);
+ timerChanges++;
+ }
+ else if (timer)
+ {
+ tell(0, "Info: Timer '%s', has no event anymore", *timer->ToDescr());
+ }
+
+ count++;
+ }
+
+ selectUpdEvents->freeResult();
+
+ // Kanal fertig machen ..
+
+ s->Sort();
+ s->SetModified();
+
+ // schedules lock freigeben
+
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+ schedulesKey.Remove();
+ tell(3, "-> Released schedules lock");
+ timersKey.Remove();
+ tell(3, "-> Released timers lock");
+#else
+ tell(3, "LOCK free (refreshEpg)");
+ delete schedulesLock;
+#endif
+
+ tell(2, "Processed channel '%s' - '%s' with %d updates",
+ eventsDb->getStrValue("ChannelId"),
+ mapDb->getStrValue("ChannelName"),
+ count);
+
+ total += count;
+ }
+
+ select->freeResult();
+
+ if (timerChanges)
+ {
+#if defined (APIVERSNUM) && (APIVERSNUM >= 20301)
+ LOCK_TIMERS_WRITE;
+ cTimers* timers = Timers;
+#else
+ cTimers* timers = &Timers;
+#endif
+ timers->SetModified();
+ }
+
+ if (lastEventsUpdateAt)
+ tell(2, "Updated changes since '%s'; %d channels, "
+ "%d events (%d deletions) in %s",
+ forChannelId ? "-" : l2pTime(lastEventsUpdateAt).c_str(),
+ channels, total, dels, ms2Dur(cTimeMs::Now()-start).c_str());
+ else
+ tell(2, "Updated all %d channels, %d events (%d deletions) in %s",
+ channels, total, dels, ms2Dur(cTimeMs::Now()-start).c_str());
+
+ return dbConnected(yes) ? success : fail;
+}
+
+//***************************************************************************
+// To/From Row
+//***************************************************************************
+
+cEvent* cUpdate::createEventFromRow(const cDbRow* row)
+{
+ cEvent* e = new cEvent(row->getIntValue("USEID"));
+
+ e->SetTableID(row->getIntValue("TABLEID"));
+ e->SetVersion(row->getIntValue("VERSION"));
+ e->SetTitle(row->getStrValue("TITLE"));
+ e->SetShortText(row->getStrValue("SHORTTEXT"));
+ e->SetStartTime(row->getIntValue("STARTTIME"));
+ e->SetDuration(row->getIntValue("DURATION"));
+ e->SetParentalRating(row->getIntValue("PARENTALRATING"));
+ e->SetVps(row->getIntValue("VPS"));
+ e->SetDescription(dvbDescription->getStrValue());
+ e->SetComponents(0);
+
+ // contents
+
+ uchar contents[MaxEventContents] = { 0 };
+ int numContents = 0;
+
+ for (const char* p = row->getStrValue("CONTENTS"); p && numContents < MaxEventContents; p = strchr(p, ','))
+ {
+ if (*p == ',') p++;
+
+ if (*p)
+ contents[numContents++] = strtol(p, 0, 0);
+ }
+
+ e->SetContents(contents);
+
+ // components
+
+ if (row->hasValue("SOURCE", "vdr"))
+ {
+ cComponents* components = new cComponents;
+
+ compDb->clear();
+ compDb->setBigintValue("EVENTID", row->getBigintValue("EVENTID"));
+ compDb->setValue("CHANNELID", row->getStrValue("CHANNELID"));
+
+ for (int f = selectComponentsOf->find(); f; f = selectComponentsOf->fetch())
+ {
+ components->SetComponent(components->NumComponents(),
+ compDb->getIntValue("STREAM"),
+ compDb->getIntValue("TYPE"),
+ compDb->getStrValue("LANG"),
+ compDb->getStrValue("DESCRIPTION"));
+ }
+
+ selectComponentsOf->freeResult();
+
+ if (components->NumComponents())
+ e->SetComponents(components); // event take ownership of components!
+ else
+ delete components;
+ }
+
+ return e;
+}
+
+//***************************************************************************
+// Store Pictures to local Filesystem
+//***************************************************************************
+
+int cUpdate::storePicturesToFs()
+{
+ int count = 0;
+ int updated = 0;
+ char* path = 0;
+ time_t start = time(0);
+
+ if (!Epg2VdrConfig.getepgimages)
+ return done;
+
+ asprintf(&path, "%s", epgimagedir);
+ chkDir(path);
+ free(path);
+ asprintf(&path, "%s/images", epgimagedir);
+ chkDir(path);
+ free(path);
+
+ tell(0, "Load images from database");
+
+ imageRefDb->clear();
+ imageRefDb->setValue("UpdSp", lastUpdateAt);
+ imageUpdSp.setValue(lastUpdateAt);
+
+ for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+ {
+ int eventid = masterId.getIntValue();
+ const char* imageName = imageRefDb->getStrValue("ImgName");
+ int lfn = imageRefDb->getIntValue("Lfn");
+ char* newpath;
+ char* linkdest = 0;
+ char* destfile = 0;
+ int forceLink = no;
+ int size = imageSize.getIntValue();
+
+ asprintf(&destfile, "%s/images/%s", epgimagedir, imageName);
+
+ // check target ... image changed?
+
+ if (!fileExists(destfile) || fileSize(destfile) != size)
+ {
+ // get image
+
+ imageDb->clear();
+ imageDb->setValue("ImgName", imageName);
+
+ if (imageDb->find() && !imageDb->getRow()->getValue("Image")->isNull())
+ {
+ count++;
+
+ // remove existing target
+
+ if (fileExists(destfile))
+ {
+ updated++;
+ removeFile(destfile);
+ }
+
+ forceLink = yes;
+ tell(2, "Store image '%s' with %d bytes", destfile, size);
+
+ if (FILE* fh1 = fopen(destfile, "w"))
+ {
+ fwrite(imageDb->getStrValue("Image"), 1, size, fh1);
+ fclose(fh1);
+ }
+ else
+ {
+ tell(1, "Can't write image to '%s', error was '%s'", destfile, strerror(errno));
+ }
+ }
+
+ imageDb->reset();
+ }
+
+ free(destfile);
+
+ // create links ...
+
+ asprintf(&linkdest, "./images/%s", imageName);
+
+#ifdef _IMG_LINK
+ if (!lfn)
+ {
+ // for lfn 0 create additional link without "_?"
+
+ asprintf(&newpath, "%s/%d.%s", epgimagedir, eventid, imageExtension);
+ createLink(newpath, linkdest, forceLink);
+ free(newpath);
+ }
+#endif
+
+ // create link with index
+
+ asprintf(&newpath, "%s/%d_%d.%s", epgimagedir, eventid, lfn, imageExtension);
+ createLink(newpath, linkdest, forceLink);
+ free(newpath);
+
+ // ...
+
+ free(linkdest);
+
+ if (!dbConnected())
+ break;
+ }
+
+ selectAllImages->freeResult();
+
+ tell(0, "Got %d images from database in %ld seconds (%d updates, %d new)",
+ count, time(0) - start, updated, count-updated);
+
+ return dbConnected(yes) ? success : fail;
+}
+
+//***************************************************************************
+// Remove Pictures
+//***************************************************************************
+
+int cUpdate::cleanupPictures()
+{
+ const char* ext = ".jpg";
+ struct dirent* dirent;
+ DIR* dir;
+ char* pdir;
+ int iCount = 0;
+ int lCount = 0;
+
+ imageRefDb->countWhere("", iCount);
+
+ if (iCount < 100) // less than 100 image lines are suspicious ;)
+ {
+ tell(0, "Exit image cleanup to avoid deleting of all images on empty imagerefs table");
+ return done;
+ }
+
+ // -----------------------
+ // remove unused images
+
+ tell(1, "Starting cleanup of images in '%s'", epgimagedir);
+
+ // -----------------------
+ // cleanup 'images' directory
+
+ cDbStatement* stmt = new cDbStatement(imageRefDb);
+
+ stmt->build("select ");
+ stmt->bind("FileRef", cDBS::bndOut);
+ stmt->build(" from %s where ", imageRefDb->TableName());
+ stmt->bind("ImgName", cDBS::bndIn | cDBS::bndSet);
+
+ if (stmt->prepare() != success)
+ {
+ delete stmt;
+ return fail;
+ }
+
+ iCount = 0;
+
+ // open directory
+
+ asprintf(&pdir, "%s/images", epgimagedir);
+
+ if (!(dir = opendir(pdir)))
+ {
+ tell(1, "Can't open directory '%s', '%s'", pdir, strerror(errno));
+
+ free(pdir);
+
+ return done;
+ }
+
+ free(pdir);
+
+ while (dbConnected() && (dirent = readdir(dir)))
+ {
+ // check extension
+
+ if (strncmp(dirent->d_name + strlen(dirent->d_name) - strlen(ext), ext, strlen(ext)) != 0)
+ continue;
+
+ imageRefDb->clear();
+ imageRefDb->setValue("ImgName", dirent->d_name);
+
+ if (!stmt->find())
+ {
+ asprintf(&pdir, "%s/images/%s", epgimagedir, dirent->d_name);
+
+ if (!removeFile(pdir))
+ iCount++;
+
+ free(pdir);
+ }
+
+ stmt->freeResult();
+ }
+
+ delete stmt;
+ closedir(dir);
+
+ if (!dbConnected(yes))
+ return fail;
+
+ // -----------------------
+ // remove wasted symlinks
+
+ if (!(dir = opendir(epgimagedir)))
+ {
+ tell(1, "Can't open directory '%s', '%s'", epgimagedir, strerror(errno));
+ return done;
+ }
+
+ tell(1, "Remove %s symlinks", fullreload ? "all" : "old");
+
+ while ((dirent = readdir(dir)))
+ {
+ // check extension
+
+ if (strncmp(dirent->d_name + strlen(dirent->d_name) - strlen(ext), ext, strlen(ext)) != 0)
+ continue;
+
+ asprintf(&pdir, "%s/%s", epgimagedir, dirent->d_name);
+
+ // fileExists use access() which dereference links!
+
+ if (isLink(pdir) && (fullreload || !fileExists(pdir)))
+ {
+ if (!removeFile(pdir))
+ lCount++;
+ }
+
+ free(pdir);
+ }
+
+ closedir(dir);
+ tell(1, "Cleanup finished, removed (%d) images and (%d) symlinks", iCount, lCount);
+
+ return success;
+}
+
+//***************************************************************************
+// Link Needed
+//***************************************************************************
+
+int cUpdate::pictureLinkNeeded(const char* linkName)
+{
+ int found;
+
+ if (!dbConnected())
+ return yes;
+
+ // we don't need to patch the linkname "123456_0.jpg"
+ // since atoi() stops at the first non numerical character ...
+
+ imageRefDb->clear();
+ imageRefDb->setValue("LFN", 0L);
+ imageRefDb->setBigintValue("EVENTID", atol(linkName));
+
+ found = imageRefDb->find();
+ imageRefDb->reset();
+
+ return found;
+}