MythTV  0.26-pre
eitcache.cpp
Go to the documentation of this file.
00001 // -*- Mode: c++ -*-
00002 /*
00003  * Copyright 2006 (C) Stuart Auchterlonie <stuarta at squashedfrog.net>
00004  * Copyright 2006 (C) Janne Grunau <janne-mythtv at grunau.be>
00005  * License: GPL v2
00006  */
00007 
00008 #include <QDateTime>
00009 
00010 #include "eitcache.h"
00011 #include "mythcontext.h"
00012 #include "mythdb.h"
00013 #include "mythlogging.h"
00014 
00015 #define LOC QString("EITCache: ")
00016 
00017 // Highest version number. version is 5bits
00018 const uint EITCache::kVersionMax = 31;
00019 
00020 EITCache::EITCache()
00021     : accessCnt(0), hitCnt(0),   tblChgCnt(0),   verChgCnt(0),
00022       entryCnt(0), pruneCnt(0), prunedHitCnt(0), wrongChannelHitCnt(0)
00023 {
00024     // 24 hours ago
00025     lastPruneTime = QDateTime::currentDateTime().toUTC().toTime_t() - 86400;
00026 }
00027 
00028 EITCache::~EITCache()
00029 {
00030     WriteToDB();
00031 }
00032 
00033 void EITCache::ResetStatistics(void)
00034 {
00035     accessCnt = 0;
00036     hitCnt    = 0;
00037     tblChgCnt = 0;
00038     verChgCnt = 0;
00039     entryCnt  = 0;
00040     pruneCnt  = 0;
00041     prunedHitCnt = 0;
00042     wrongChannelHitCnt = 0;
00043 }
00044 
00045 QString EITCache::GetStatistics(void) const
00046 {
00047     QMutexLocker locker(&eventMapLock);
00048     return QString(
00049         "EITCache::statistics: Accesses: %1, Hits: %2, "
00050         "Table Upgrades %3, New Versions: %4, Entries: %5 "
00051         "Pruned entries: %6, pruned Hits: %7 Discard channel Hit %8 "
00052         "Hit Ratio %9.")
00053         .arg(accessCnt).arg(hitCnt).arg(tblChgCnt).arg(verChgCnt)
00054         .arg(entryCnt).arg(pruneCnt).arg(prunedHitCnt)
00055         .arg(wrongChannelHitCnt)
00056         .arg((hitCnt+prunedHitCnt+wrongChannelHitCnt)/(double)accessCnt);
00057 }
00058 
00059 static inline uint64_t construct_sig(uint tableid, uint version,
00060                                      uint endtime, bool modified)
00061 {
00062     return (((uint64_t) modified  << 63) | ((uint64_t) tableid   << 40) |
00063             ((uint64_t) version   << 32) | ((uint64_t) endtime));
00064 }
00065 
00066 static inline uint extract_table_id(uint64_t sig)
00067 {
00068     return (sig >> 40) & 0xff;
00069 }
00070 
00071 static inline uint extract_version(uint64_t sig)
00072 {
00073     return (sig >> 32) & 0x1f;
00074 }
00075 
00076 static inline uint extract_endtime(uint64_t sig)
00077 {
00078     return sig & 0xffffffff;
00079 }
00080 
00081 static inline bool modified(uint64_t sig)
00082 {
00083     return sig >> 63;
00084 }
00085 
00086 static void replace_in_db(int chanid, uint eventid, uint64_t sig)
00087 {
00088 
00089     MSqlQuery query(MSqlQuery::InitCon());
00090 
00091     QString qstr =
00092         "REPLACE INTO eit_cache "
00093         "       ( chanid,  eventid,  tableid,  version,  endtime) "
00094         "VALUES (:CHANID, :EVENTID, :TABLEID, :VERSION, :ENDTIME)";
00095 
00096     query.prepare(qstr);
00097     query.bindValue(":CHANID",   chanid);
00098     query.bindValue(":EVENTID",  eventid);
00099     query.bindValue(":TABLEID",  extract_table_id(sig));
00100     query.bindValue(":VERSION",  extract_version(sig));
00101     query.bindValue(":ENDTIME",  extract_endtime(sig));
00102 
00103     if (!query.exec())
00104         MythDB::DBError("Error updating eitcache", query);
00105 
00106     return;
00107 }
00108 
00109 static void delete_in_db(uint endtime)
00110 {
00111     LOG(VB_EIT, LOG_INFO, LOC + "Deleting old cache entries from the database");
00112     MSqlQuery query(MSqlQuery::InitCon());
00113 
00114     QString qstr =
00115         "DELETE FROM eit_cache "
00116         "WHERE endtime < :ENDTIME";
00117 
00118     query.prepare(qstr);
00119     query.bindValue(":ENDTIME", endtime);
00120 
00121     if (!query.exec())
00122         MythDB::DBError("Error deleting old eitcache entries.", query);
00123 
00124     return;
00125 }
00126 
00127 #define EITDATA      0
00128 #define CHANNEL_LOCK 1
00129 #define STATISTIC    2
00130 
00131 static bool lock_channel(int chanid, uint lastPruneTime)
00132 {
00133     int lock = 1;
00134     MSqlQuery query(MSqlQuery::InitCon());
00135 
00136     QString qstr = "SELECT COUNT(*) "
00137                    "FROM eit_cache "
00138                    "WHERE chanid  = :CHANID   AND "
00139                    "      endtime > :ENDTIME  AND "
00140                    "      status  = :STATUS";
00141 
00142     query.prepare(qstr);
00143     query.bindValue(":CHANID",   chanid);
00144     query.bindValue(":ENDTIME",  lastPruneTime);
00145     query.bindValue(":STATUS",   CHANNEL_LOCK);
00146 
00147     if (!query.exec() || !query.isActive())
00148     {
00149         MythDB::DBError("Error checking for channel lock", query);
00150         return false;
00151     }
00152 
00153     if (query.next())
00154         lock = query.value(0).toInt();
00155 
00156     if (lock)
00157     {
00158         LOG(VB_EIT, LOG_INFO,
00159             LOC + QString("Ignoring channel %1 since it is locked.")
00160                 .arg(chanid));
00161         return false;
00162     }
00163     else
00164     {
00165         uint now = QDateTime::currentDateTime().toTime_t();
00166         qstr = "INSERT INTO eit_cache "
00167                "       ( chanid,  endtime,  status) "
00168                "VALUES (:CHANID, :ENDTIME, :STATUS)";
00169 
00170         query.prepare(qstr);
00171         query.bindValue(":CHANID",   chanid);
00172         query.bindValue(":ENDTIME",  now);
00173         query.bindValue(":STATUS",   CHANNEL_LOCK);
00174 
00175         if (!query.exec())
00176         {
00177             MythDB::DBError("Error inserting channel lock", query);
00178             return false;
00179         }
00180     }
00181 
00182     return true;
00183 }
00184 
00185 static void unlock_channel(int chanid, uint updated)
00186 {
00187     MSqlQuery query(MSqlQuery::InitCon());
00188 
00189     QString qstr =
00190         "DELETE FROM eit_cache "
00191         "WHERE chanid  = :CHANID   AND "
00192         "      status  = :STATUS";
00193 
00194     query.prepare(qstr);
00195     query.bindValue(":CHANID",  chanid);
00196     query.bindValue(":STATUS",  CHANNEL_LOCK);
00197 
00198     if (!query.exec())
00199         MythDB::DBError("Error deleting channel lock", query);
00200 
00201     // inserting statistics
00202     uint now = QDateTime::currentDateTime().toTime_t();
00203     qstr = "REPLACE INTO eit_cache "
00204            "       ( chanid,  eventid,  endtime,  status) "
00205            "VALUES (:CHANID, :EVENTID, :ENDTIME, :STATUS)";
00206 
00207     query.prepare(qstr);
00208     query.bindValue(":CHANID",   chanid);
00209     query.bindValue(":EVENTID",  updated);
00210     query.bindValue(":ENDTIME",  now);
00211     query.bindValue(":STATUS",   STATISTIC);
00212 
00213     if (!query.exec())
00214         MythDB::DBError("Error inserting eit statistics", query);
00215 }
00216 
00217 
00218 event_map_t * EITCache::LoadChannel(uint chanid)
00219 {
00220     if (!lock_channel(chanid, lastPruneTime))
00221         return NULL;
00222 
00223     MSqlQuery query(MSqlQuery::InitCon());
00224 
00225     QString qstr =
00226         "SELECT eventid,tableid,version,endtime "
00227         "FROM eit_cache "
00228         "WHERE chanid        = :CHANID   AND "
00229         "      endtime       > :ENDTIME  AND "
00230         "      status        = :STATUS";
00231 
00232     query.prepare(qstr);
00233     query.bindValue(":CHANID",   chanid);
00234     query.bindValue(":ENDTIME",  lastPruneTime);
00235     query.bindValue(":STATUS",   EITDATA);
00236 
00237 
00238     if (!query.exec() || !query.isActive())
00239     {
00240         MythDB::DBError("Error loading eitcache", query);
00241         return NULL;
00242     }
00243 
00244     event_map_t * eventMap = new event_map_t();
00245 
00246     while (query.next())
00247     {
00248         uint eventid = query.value(0).toUInt();
00249         uint tableid = query.value(1).toUInt();
00250         uint version = query.value(2).toUInt();
00251         uint endtime = query.value(3).toUInt();
00252 
00253         (*eventMap)[eventid] = construct_sig(tableid, version, endtime, false);
00254     }
00255 
00256     if (eventMap->size())
00257         LOG(VB_EIT, LOG_INFO, LOC + QString("Loaded %1 entries for channel %2")
00258                 .arg(eventMap->size()).arg(chanid));
00259 
00260     entryCnt += eventMap->size();
00261     return eventMap;
00262 }
00263 
00264 void EITCache::WriteChannelToDB(uint chanid)
00265 {
00266     event_map_t * eventMap = channelMap[chanid];
00267 
00268     if (!eventMap)
00269     {
00270         channelMap.remove(chanid);
00271         return;
00272     }
00273 
00274     uint size    = eventMap->size();
00275     uint updated = 0;
00276 
00277     event_map_t::iterator it = eventMap->begin();
00278     while (it != eventMap->end())
00279     {
00280         if (modified(*it) && extract_endtime(*it) > lastPruneTime)
00281         {
00282             replace_in_db(chanid, it.key(), *it);
00283             updated++;
00284             *it &= ~(uint64_t)0 >> 1; // mark as synced
00285         }
00286         ++it;
00287     }
00288     unlock_channel(chanid, updated);
00289 
00290     if (updated)
00291         LOG(VB_EIT, LOG_INFO, LOC + QString("Wrote %1 modified entries of %2 "
00292                                       "for channel %3 to database.")
00293                 .arg(updated).arg(size).arg(chanid));
00294 }
00295 
00296 void EITCache::WriteToDB(void)
00297 {
00298     QMutexLocker locker(&eventMapLock);
00299 
00300     key_map_t::iterator it = channelMap.begin();
00301     while (it != channelMap.end())
00302     {
00303         WriteChannelToDB(it.key());
00304         ++it;
00305     }
00306 }
00307 
00308 
00309 
00310 bool EITCache::IsNewEIT(uint chanid,  uint tableid,   uint version,
00311                         uint eventid, uint endtime)
00312 {
00313     accessCnt++;
00314 
00315     if (accessCnt % 500000 == 50000)
00316     {
00317         LOG(VB_EIT, LOG_INFO, GetStatistics());
00318         WriteToDB();
00319     }
00320 
00321     // don't readd pruned entries
00322     if (endtime < lastPruneTime)
00323     {
00324         prunedHitCnt++;
00325         return false;
00326     }
00327     // validity check, reject events with endtime over 7 weeks in the future
00328     if (endtime > lastPruneTime + 50 * 86400)
00329         return false;
00330 
00331     QMutexLocker locker(&eventMapLock);
00332     if (!channelMap.contains(chanid))
00333     {
00334         channelMap[chanid] = LoadChannel(chanid);
00335     }
00336 
00337     if (!channelMap[chanid])
00338     {
00339         wrongChannelHitCnt++;
00340         return false;
00341     }
00342 
00343     event_map_t * eventMap = channelMap[chanid];
00344     event_map_t::iterator it = eventMap->find(eventid);
00345     if (it != eventMap->end())
00346     {
00347         if (extract_table_id(*it) > tableid)
00348         {
00349             // EIT from lower (ie. better) table number
00350             tblChgCnt++;
00351         }
00352         else if ((extract_table_id(*it) == tableid) &&
00353                  ((extract_version(*it) < version) ||
00354                   ((extract_version(*it) == kVersionMax) &&
00355                    version < kVersionMax)))
00356         {
00357             // EIT updated version on current table
00358             verChgCnt++;
00359         }
00360         else
00361         {
00362             // EIT data previously seen
00363             hitCnt++;
00364             return false;
00365         }
00366     }
00367 
00368     eventMap->insert(eventid, construct_sig(tableid, version, endtime, true));
00369     entryCnt++;
00370 
00371     return true;
00372 }
00373 
00378 uint EITCache::PruneOldEntries(uint timestamp)
00379 {
00380     if (VERBOSE_LEVEL_CHECK(VB_EIT, LOG_INFO))
00381     {
00382         QDateTime tmptime;
00383         tmptime.setTime_t(timestamp);
00384         LOG(VB_EIT, LOG_INFO,
00385             LOC + "Pruning all entries that ended before UTC " +
00386             tmptime.toString(Qt::ISODate));
00387     }
00388 
00389     lastPruneTime  = timestamp;
00390 
00391     // Write all modified entries to DB and start with a clean cache
00392     WriteToDB();
00393 
00394     // Prune old entries in the DB
00395     delete_in_db(timestamp);
00396 
00397     return 0;
00398 }
00399 
00400 
00404 void EITCache::ClearChannelLocks(void)
00405 {
00406     MSqlQuery query(MSqlQuery::InitCon());
00407 
00408     QString qstr =
00409         "DELETE FROM eit_cache "
00410         "WHERE status  = :STATUS";
00411 
00412     query.prepare(qstr);
00413     query.bindValue(":STATUS",  CHANNEL_LOCK);
00414 
00415     if (!query.exec())
00416         MythDB::DBError("Error clearing channel locks", query);
00417 }
00418 
00419 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends