add epgcache
authorAndreas Monzner <andreas.monzner@multimedia-labs.de>
Mon, 4 Jul 2005 17:49:04 +0000 (17:49 +0000)
committerAndreas Monzner <andreas.monzner@multimedia-labs.de>
Mon, 4 Jul 2005 17:49:04 +0000 (17:49 +0000)
lib/dvb/dvbtime.cpp
lib/dvb/dvbtime.h
lib/dvb/epgcache.cpp [new file with mode: 0644]
lib/dvb/epgcache.h [new file with mode: 0644]
lib/python/enigma_python.i
main/enigma.cpp

index 84a1ef4..5167a01 100644 (file)
@@ -39,15 +39,6 @@ time_t getRTC()
        return rtc_time != prev_time ? rtc_time : 0;
 }
 
-int fromBCD(int bcd)
-{
-       if ((bcd&0xF0)>=0xA0)
-               return -1;
-       if ((bcd&0xF)>=0xA)
-               return -1;
-       return ((bcd&0xF0)>>4)*10+(bcd&0xF);
-}
-
 time_t parseDVBtime(__u8 t1, __u8 t2, __u8 t3, __u8 t4, __u8 t5)
 {
        tm t;
index a1746b5..59a26c2 100644 (file)
@@ -7,6 +7,15 @@
 
 class eDVBChannel;
 
+inline int fromBCD(int bcd)
+{
+       if ((bcd&0xF0)>=0xA0)
+               return -1;
+       if ((bcd&0xF)>=0xA)
+               return -1;
+       return ((bcd&0xF0)>>4)*10+(bcd&0xF);
+}
+
 time_t parseDVBtime(__u8 t1, __u8 t2, __u8 t3, __u8 t4, __u8 t5);
 
 class TDT: public eGTable
@@ -45,8 +54,8 @@ public:
        PSignal0<void> m_timeUpdated;
        eDVBLocalTimeHandler();
        ~eDVBLocalTimeHandler();
-       bool ready() { return m_time_ready; }
-       int difference() { return m_time_difference; }
+       bool ready() const { return m_time_ready; }
+       int difference() const { return m_time_difference; }
        static eDVBLocalTimeHandler *getInstance() { return instance; }
 };
 
diff --git a/lib/dvb/epgcache.cpp b/lib/dvb/epgcache.cpp
new file mode 100644 (file)
index 0000000..41635b0
--- /dev/null
@@ -0,0 +1,957 @@
+#include <lib/dvb/epgcache.h>
+#include <lib/dvb/dvb.h>
+
+#undef EPG_DEBUG  
+
+#include <time.h>
+#include <unistd.h>  // for usleep
+#include <sys/vfs.h> // for statfs
+#include <libmd5sum.h>
+#include <lib/base/eerror.h>
+
+int eventData::CacheSize=0;
+
+eEPGCache* eEPGCache::instance;
+pthread_mutex_t eEPGCache::cache_lock=
+       PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+
+DEFINE_REF(eEPGCache)
+
+eEPGCache::eEPGCache()
+       :messages(this,1), back_messages(this,1) ,paused(0)
+       ,CleanTimer(this), zapTimer(this), abortTimer(this)
+{
+       eDebug("[EPGC] Initialized EPGCache");
+       isRunning=0;
+
+       CONNECT(messages.recv_msg, eEPGCache::gotMessage);
+       CONNECT(back_messages.recv_msg, eEPGCache::gotBackMessage);
+//     CONNECT(eDVB::getInstance()->switchedService, eEPGCache::enterService);
+//     CONNECT(eDVB::getInstance()->leaveService, eEPGCache::leaveService);
+       CONNECT(eDVBLocalTimeHandler::getInstance()->m_timeUpdated, eEPGCache::timeUpdated);
+       CONNECT(zapTimer.timeout, eEPGCache::startEPG);
+       CONNECT(CleanTimer.timeout, eEPGCache::cleanLoop);
+       CONNECT(abortTimer.timeout, eEPGCache::abortNonAvail);
+       instance=this;
+}
+
+void eEPGCache::timeUpdated()
+{
+       if ( !thread_running() )
+       {
+               eDebug("[EPGC] time updated.. start EPG Mainloop");
+               run();
+       }
+       else
+               messages.send(Message(Message::timeChanged));
+}
+
+int eEPGCache::sectionRead(const __u8 *data, int source)
+{
+       eit_t *eit = (eit_t*) data;
+
+       int len=HILO(eit->section_length)-1;//+3-4;
+       int ptr=EIT_SIZE;
+       if ( ptr >= len )
+               return 0;
+
+       //
+       // This fixed the EPG on the Multichoice irdeto systems
+       // the EIT packet is non-compliant.. their EIT packet stinks
+       if ( data[ptr-1] < 0x40 )
+               --ptr;
+
+       uniqueEPGKey service( HILO(eit->service_id), HILO(eit->original_network_id), HILO(eit->transport_stream_id) );
+       eit_event_struct* eit_event = (eit_event_struct*) (data+ptr);
+       int eit_event_size;
+       int duration;
+
+       time_t TM = parseDVBtime( eit_event->start_time_1, eit_event->start_time_2,     eit_event->start_time_3, eit_event->start_time_4, eit_event->start_time_5);
+// FIXME !!! TIME CORRECTION !
+       time_t now = time(0)+eDVBLocalTimeHandler::getInstance()->difference();
+
+       if ( TM != 3599 && TM > -1)
+       {
+               switch(source)
+               {
+               case NOWNEXT:
+                       haveData |= 2;
+                       break;
+               case SCHEDULE:
+                       haveData |= 1;
+                       break;
+               case SCHEDULE_OTHER:
+                       haveData |= 4;
+                       break;
+               }
+       }
+
+       Lock();
+       // hier wird immer eine eventMap zurück gegeben.. entweder eine vorhandene..
+       // oder eine durch [] erzeugte
+       std::pair<eventMap,timeMap> &servicemap = eventDB[service];
+       eventMap::iterator prevEventIt = servicemap.first.end();
+       timeMap::iterator prevTimeIt = servicemap.second.end();
+
+       while (ptr<len)
+       {
+               eit_event_size = HILO(eit_event->descriptors_loop_length)+EIT_LOOP_SIZE;
+
+               duration = fromBCD(eit_event->duration_1)*3600+fromBCD(eit_event->duration_2)*60+fromBCD(eit_event->duration_3);
+               TM = parseDVBtime(
+                       eit_event->start_time_1,
+                       eit_event->start_time_2,
+                       eit_event->start_time_3,
+                       eit_event->start_time_4,
+                       eit_event->start_time_5);
+
+               if ( TM == 3599 )
+                       goto next;
+
+               if ( TM != 3599 && (TM+duration < now || TM > now+14*24*60*60) )
+                       goto next;
+
+               if ( now <= (TM+duration) || TM == 3599 /*NVOD Service*/ )  // old events should not be cached
+               {
+                       __u16 event_id = HILO(eit_event->event_id);
+//                     eDebug("event_id is %d sid is %04x", event_id, service.sid);
+
+                       eventData *evt = 0;
+                       int ev_erase_count = 0;
+                       int tm_erase_count = 0;
+
+// search in eventmap
+                       eventMap::iterator ev_it =
+                               servicemap.first.find(event_id);
+
+                       // entry with this event_id is already exist ?
+                       if ( ev_it != servicemap.first.end() )
+                       {
+                               if ( source > ev_it->second->type )  // update needed ?
+                                       goto next; // when not.. the skip this entry
+// search this event in timemap
+                               timeMap::iterator tm_it_tmp = 
+                                       servicemap.second.find(ev_it->second->getStartTime());
+
+                               if ( tm_it_tmp != servicemap.second.end() )
+                               {
+                                       if ( tm_it_tmp->first == TM ) // correct eventData
+                                       {
+                                               // exempt memory
+                                               delete ev_it->second;
+                                               evt = new eventData(eit_event, eit_event_size, source);
+                                               ev_it->second=evt;
+                                               tm_it_tmp->second=evt;
+                                               goto next;
+                                       }
+                                       else
+                                       {
+                                               tm_erase_count++;
+                                               // delete the found record from timemap
+                                               servicemap.second.erase(tm_it_tmp);
+                                               prevTimeIt=servicemap.second.end();
+                                       }
+                               }
+                       }
+
+// search in timemap, for check of a case if new time has coincided with time of other event 
+// or event was is not found in eventmap
+                       timeMap::iterator tm_it =
+                               servicemap.second.find(TM);
+
+                       if ( tm_it != servicemap.second.end() )
+                       {
+
+// i think, if event is not found on eventmap, but found on timemap updating nevertheless demands
+#if 0
+                               if ( source > tm_it->second->type && tm_erase_count == 0 ) // update needed ?
+                                       goto next; // when not.. the skip this entry
+#endif
+
+// search this time in eventmap
+                               eventMap::iterator ev_it_tmp = 
+                                       servicemap.first.find(tm_it->second->getEventID());
+
+                               if ( ev_it_tmp != servicemap.first.end() )
+                               {
+                                       ev_erase_count++;                               
+                                       // delete the found record from eventmap
+                                       servicemap.first.erase(ev_it_tmp);
+                                       prevEventIt=servicemap.first.end();
+                               }
+                       }
+                       
+                       evt = new eventData(eit_event, eit_event_size, source);
+#if EPG_DEBUG
+                       bool consistencyCheck=true;
+#endif
+                       if (ev_erase_count > 0 && tm_erase_count > 0) // 2 different pairs have been removed
+                       {
+                               // exempt memory
+                               delete ev_it->second; 
+                               delete tm_it->second;
+                               ev_it->second=evt;
+                               tm_it->second=evt;
+                       }
+                       else if (ev_erase_count == 0 && tm_erase_count > 0) 
+                       {
+                               // exempt memory
+                               delete ev_it->second;
+                               tm_it=prevTimeIt=servicemap.second.insert( prevTimeIt, std::pair<const time_t, eventData*>( TM, evt ) );
+                               ev_it->second=evt;
+                       }
+                       else if (ev_erase_count > 0 && tm_erase_count == 0)
+                       {
+                               // exempt memory
+                               delete tm_it->second;
+                               ev_it=prevEventIt=servicemap.first.insert( prevEventIt, std::pair<const __u16, eventData*>( event_id, evt) );
+                               tm_it->second=evt;
+                       }
+                       else // added new eventData
+                       {
+#if EPG_DEBUG
+                               consistencyCheck=false;
+#endif
+                               prevEventIt=servicemap.first.insert( prevEventIt, std::pair<const __u16, eventData*>( event_id, evt) );
+                               prevTimeIt=servicemap.second.insert( prevTimeIt, std::pair<const time_t, eventData*>( TM, evt ) );
+                       }
+#if EPG_DEBUG
+                       if ( consistencyCheck )
+                       {
+                               if ( tm_it->second != evt || ev_it->second != evt )
+                                       eFatal("tm_it->second != ev_it->second");
+                               else if ( tm_it->second->getStartTime() != tm_it->first )
+                                       eFatal("event start_time(%d) non equal timemap key(%d)", 
+                                               tm_it->second->getStartTime(), tm_it->first );
+                               else if ( tm_it->first != TM )
+                                       eFatal("timemap key(%d) non equal TM(%d)", 
+                                               tm_it->first, TM);
+                               else if ( ev_it->second->getEventID() != ev_it->first )
+                                       eFatal("event_id (%d) non equal event_map key(%d)",
+                                               ev_it->second->getEventID(), ev_it->first);
+                               else if ( ev_it->first != event_id )
+                                       eFatal("eventmap key(%d) non equal event_id(%d)", 
+                                               ev_it->first, event_id );
+                       }
+#endif
+               }
+next:
+#if EPG_DEBUG
+               if ( servicemap.first.size() != servicemap.second.size() )
+               {
+                       FILE *f = fopen("/hdd/event_map.txt", "w+");
+                       int i=0;
+                       for (eventMap::iterator it(servicemap.first.begin())
+                               ; it != servicemap.first.end(); ++it )
+                               fprintf(f, "%d(key %d) -> time %d, event_id %d, data %p\n", 
+                                       i++, (int)it->first, (int)it->second->getStartTime(), (int)it->second->getEventID(), it->second );
+                       fclose(f);
+                       f = fopen("/hdd/time_map.txt", "w+");
+                       i=0;
+                       for (timeMap::iterator it(servicemap.second.begin())
+                               ; it != servicemap.second.end(); ++it )
+                       fprintf(f, "%d(key %d) -> time %d, event_id %d, data %p\n", 
+                               i++, (int)it->first, (int)it->second->getStartTime(), (int)it->second->getEventID(), it->second );
+                       fclose(f);
+
+                       eFatal("(1)map sizes not equal :( sid %04x tsid %04x onid %04x size %d size2 %d", 
+                               service.sid, service.tsid, service.onid, 
+                               servicemap.first.size(), servicemap.second.size() );
+               }
+#endif
+               ptr += eit_event_size;
+               eit_event=(eit_event_struct*)(((__u8*)eit_event)+eit_event_size);
+       }
+
+       tmpMap::iterator it = temp.find( service );
+       if ( it != temp.end() )
+       {
+               if ( source > it->second.second )
+               {
+                       it->second.first=now;
+                       it->second.second=source;
+               }
+       }
+       else
+               temp[service] = std::pair< time_t, int> (now, source);
+
+       Unlock();
+
+       return 0;
+}
+
+bool eEPGCache::finishEPG()
+{
+       if (!isRunning)  // epg ready
+       {
+               eDebug("[EPGC] stop caching events");
+               zapTimer.start(UPDATE_INTERVAL, 1);
+               eDebug("[EPGC] next update in %i min", UPDATE_INTERVAL / 60000);
+
+               singleLock l(cache_lock);
+               tmpMap::iterator It = temp.begin();
+               abortTimer.stop();
+
+               while (It != temp.end())
+               {
+//                     eDebug("sid = %02x, onid = %02x, type %d", It->first.sid, It->first.onid, It->second.second );
+                       if ( It->second.second == SCHEDULE
+                               || ( It->second.second == NOWNEXT && !(haveData&1) ) 
+                               )
+                       {
+//                             eDebug("ADD to last updated Map");
+                               serviceLastUpdated[It->first]=It->second.first;
+                       }
+                       if ( eventDB.find( It->first ) == eventDB.end() )
+                       {
+//                             eDebug("REMOVE from update Map");
+                               temp.erase(It++);
+                       }
+                       else
+                               It++;
+               }
+               if (!eventDB[current_service].first.empty())
+                       /*emit*/ EPGAvail(1);
+
+               /*emit*/ EPGUpdated();
+
+               return true;
+       }
+       return false;
+}
+
+void eEPGCache::flushEPG(const uniqueEPGKey & s)
+{
+       eDebug("[EPGC] flushEPG %d", (int)(bool)s);
+       Lock();
+       if (s)  // clear only this service
+       {
+               eventCache::iterator it = eventDB.find(s);
+               if ( it != eventDB.end() )
+               {
+                       eventMap &evMap = it->second.first;
+                       timeMap &tmMap = it->second.second;
+                       tmMap.clear();
+                       for (eventMap::iterator i = evMap.begin(); i != evMap.end(); ++i)
+                               delete i->second;
+                       evMap.clear();
+                       eventDB.erase(it);
+                       updateMap::iterator u = serviceLastUpdated.find(s);
+                       if ( u != serviceLastUpdated.end() )
+                               serviceLastUpdated.erase(u);
+                       startEPG();
+               }
+       }
+       else // clear complete EPG Cache
+       {
+               for (eventCache::iterator it(eventDB.begin());
+                       it != eventDB.end(); ++it)
+               {
+                       eventMap &evMap = it->second.first;
+                       timeMap &tmMap = it->second.second;
+                       for (eventMap::iterator i = evMap.begin(); i != evMap.end(); ++i)
+                               delete i->second;
+                       evMap.clear();
+                       tmMap.clear();
+               }
+               serviceLastUpdated.clear();
+               eventDB.clear();
+               startEPG();
+       }
+       eDebug("[EPGC] %i bytes for cache used", eventData::CacheSize);
+       Unlock();
+}
+
+void eEPGCache::cleanLoop()
+{
+       singleLock s(cache_lock);
+       if ( isRunning )
+       {
+               CleanTimer.start(5000,true);
+               eDebug("[EPGC] schedule cleanloop");
+               return;
+       }
+       if (!eventDB.empty() && !paused )
+       {
+               eDebug("[EPGC] start cleanloop");
+               const eit_event_struct* cur_event;
+               int duration;
+
+// FIXME !!! TIME_CORRECTION
+               time_t now = time(0)+eDVBLocalTimeHandler::getInstance()->difference();
+
+               for (eventCache::iterator DBIt = eventDB.begin(); DBIt != eventDB.end(); DBIt++)
+               {
+                       for (timeMap::iterator It = DBIt->second.second.begin(); It != DBIt->second.second.end() && It->first < now;)
+                       {
+                               cur_event = (*It->second).get();
+                               duration = fromBCD( cur_event->duration_1)*3600 + fromBCD(cur_event->duration_2)*60 + fromBCD(cur_event->duration_3);
+
+                               if ( now > (It->first+duration) )  // outdated normal entry (nvod references to)
+                               {
+                                       // remove entry from eventMap
+                                       eventMap::iterator b(DBIt->second.first.find(It->second->getEventID()));
+                                       if ( b != DBIt->second.first.end() )
+                                       {
+                                               // release Heap Memory for this entry   (new ....)
+//                                             eDebug("[EPGC] delete old event (evmap)");
+                                               DBIt->second.first.erase(b);
+                                       }
+
+                                       // remove entry from timeMap
+//                                     eDebug("[EPGC] release heap mem");
+                                       delete It->second;
+                                       DBIt->second.second.erase(It++);
+//                                     eDebug("[EPGC] delete old event (timeMap)");
+
+                                       // add this (changed) service to temp map...
+                                       if ( temp.find(DBIt->first) == temp.end() )
+                                               temp[DBIt->first]=std::pair<time_t, int>(now, NOWNEXT);
+                               }
+                               else
+                                       ++It;
+                       }
+                       if ( DBIt->second.second.size() < 2 )  
+                       // less than two events for this service in cache.. 
+                       {
+                               updateMap::iterator u = serviceLastUpdated.find(DBIt->first);
+                               if ( u != serviceLastUpdated.end() )
+                               {
+                                       // remove from lastupdated map.. 
+                                       serviceLastUpdated.erase(u);
+                                       // current service?
+                                       if ( DBIt->first == current_service )
+                                       {
+                                       // immediate .. after leave cleanloop 
+                                       // update epgdata for this service
+                                               zapTimer.start(0,true);
+                                       }
+                               }
+                       }
+               }
+
+               if (temp.size())
+                       /*emit*/ EPGUpdated();
+
+               eDebug("[EPGC] stop cleanloop");
+               eDebug("[EPGC] %i bytes for cache used", eventData::CacheSize);
+       }
+       CleanTimer.start(CLEAN_INTERVAL,true);
+}
+
+eEPGCache::~eEPGCache()
+{
+       messages.send(Message::quit);
+       kill(); // waiting for thread shutdown
+       Lock();
+       for (eventCache::iterator evIt = eventDB.begin(); evIt != eventDB.end(); evIt++)
+               for (eventMap::iterator It = evIt->second.first.begin(); It != evIt->second.first.end(); It++)
+                       delete It->second;
+       Unlock();
+}
+
+Event *eEPGCache::lookupEvent(const eServiceReferenceDVB &service, int event_id, bool plain)
+{
+       singleLock s(cache_lock);
+       uniqueEPGKey key( service );
+
+       eventCache::iterator It = eventDB.find( key );
+       if ( It != eventDB.end() && !It->second.first.empty() ) // entrys cached?
+       {
+               eventMap::iterator i( It->second.first.find( event_id ));
+               if ( i != It->second.first.end() )
+               {
+                       if ( service.getServiceType() == 4 ) // nvod ref
+                               return lookupEvent( service, i->second->getStartTime(), plain );
+                       else if ( plain )
+               // get plain data... not in Event Format !!!
+               // before use .. cast it to eit_event_struct*
+                               return (Event*) i->second->get();
+                       else
+                               return new Event( (uint8_t*)i->second->get() /*, (It->first.tsid<<16)|It->first.onid*/ );
+               }
+               else
+                       eDebug("event %04x not found in epgcache", event_id);
+       }
+       return 0;
+}
+
+Event *eEPGCache::lookupEvent(const eServiceReferenceDVB &service, time_t t, bool plain )
+// if t == 0 we search the current event...
+{
+       singleLock s(cache_lock);
+       uniqueEPGKey key(service);
+
+       // check if EPG for this service is ready...
+       eventCache::iterator It = eventDB.find( key );
+       if ( It != eventDB.end() && !It->second.first.empty() ) // entrys cached ?
+       {
+               if (!t)
+                       t = time(0)+eDVBLocalTimeHandler::getInstance()->difference();
+
+               timeMap::iterator i = It->second.second.lower_bound(t);
+               if ( i != It->second.second.end() )
+               {
+                       i--;
+                       if ( i != It->second.second.end() )
+                       {
+                               const eit_event_struct* eit_event = i->second->get();
+                               int duration = fromBCD(eit_event->duration_1)*3600+fromBCD(eit_event->duration_2)*60+fromBCD(eit_event->duration_3);
+                               if ( t <= i->first+duration )
+                               {
+                                       if ( plain )
+                                               // get plain data... not in Event Format !!!
+                                               // before use .. cast it to eit_event_struct*
+                                               return (Event*) i->second->get();
+                                       return new Event( (uint8_t*)i->second->get() /*, (It->first.tsid<<16)|It->first.onid */ );
+                               }
+                       }
+               }
+
+               for ( eventMap::iterator i( It->second.first.begin() ); i != It->second.first.end(); i++)
+               {
+                       const eit_event_struct* eit_event = i->second->get();
+                       int duration = fromBCD(eit_event->duration_1)*3600+fromBCD(eit_event->duration_2)*60+fromBCD(eit_event->duration_3);
+                       time_t begTime = parseDVBtime( eit_event->start_time_1, eit_event->start_time_2,        eit_event->start_time_3, eit_event->start_time_4,       eit_event->start_time_5);
+                       if ( t >= begTime && t <= begTime+duration) // then we have found
+                       {
+                               if ( plain )
+                                       // get plain data... not in Event Format !!!
+                                       // before use .. cast it to eit_event_struct*
+                                       return (Event*) i->second->get();
+                               return new Event( (uint8_t*)i->second->get()/*, (It->first.tsid<<16)|It->first.onid*/ );
+                       }
+               }
+       }
+       return 0;
+}
+
+void eEPGCache::pauseEPG()
+{
+       if (!paused)
+       {
+               abortEPG();
+               eDebug("[EPGC] paused]");
+               paused=1;
+       }
+}
+
+void eEPGCache::restartEPG()
+{
+       if (paused)
+       {
+               isRunning=0;
+               eDebug("[EPGC] restarted");
+               paused--;
+               if (paused)
+               {
+                       paused = 0;
+                       startEPG();   // updateEPG
+               }
+               cleanLoop();
+       }
+}
+
+void eEPGCache::startEPG()
+{
+       if (paused)  // called from the updateTimer during pause...
+       {
+               paused++;
+               return;
+       }
+       if (eDVBLocalTimeHandler::getInstance()->ready())
+       {
+               Lock();
+               temp.clear();
+               Unlock();
+               eDebug("[EPGC] start caching events");
+               state=0;
+               haveData=0;
+
+               eDVBSectionFilterMask mask;
+               memset(&mask, 0, sizeof(mask));
+               mask.pid = 0x12;
+               mask.flags = eDVBSectionFilterMask::rfCRC;
+
+               mask.data[0] = 0x4E;
+               mask.mask[0] = 0xFE;
+               m_NowNextReader->start(mask);
+               isRunning |= 1;
+
+               mask.data[0] = 0x50;
+               mask.mask[0] = 0xF0;
+               m_ScheduleReader->start(mask);
+               isRunning |= 2;
+
+               mask.data[0] = 0x60;
+               mask.mask[0] = 0xF0;
+               m_ScheduleOtherReader->start(mask);
+               isRunning |= 4;
+
+               abortTimer.start(5000,true);
+       }
+       else
+       {
+               eDebug("[EPGC] wait for clock update");
+               zapTimer.start(1000, 1); // restart Timer
+       }
+}
+
+void eEPGCache::abortNonAvail()
+{
+       if (!state)
+       {
+               if ( !(haveData&2) && (isRunning&2) )
+               {
+                       eDebug("[EPGC] abort non avail nownext reading");
+                       isRunning &= ~2;
+                       if ( m_NowNextReader )
+                               m_NowNextReader->stop();
+               }
+               if ( !(haveData&1) && (isRunning&1) )
+               {
+                       eDebug("[EPGC] abort non avail schedule reading");
+                       isRunning &= ~1;
+                       m_ScheduleReader->stop();
+               }
+               if ( !(haveData&4) && (isRunning&4) )
+               {
+                       eDebug("[EPGC] abort non avail schedule_other reading");
+                       isRunning &= ~4;
+                       m_ScheduleOtherReader->stop();
+               }
+               abortTimer.start(20000, true);
+       }
+       ++state;
+}
+
+void eEPGCache::startCache(const eServiceReferenceDVB& ref)
+{
+       if ( m_currentChannel )
+       {
+               next_service = ref;
+               leaveChannel(m_currentChannel);
+               return;
+       }
+       eDVBChannelID chid;
+       ref.getChannelID( chid );
+       ePtr<eDVBResourceManager> res_mgr;
+       if ( eDVBResourceManager::getInstance( res_mgr ) )
+               eDebug("[eEPGCache] no res manager!!");
+       else
+       {
+               ePtr<iDVBDemux> demux;
+               res_mgr->allocateChannel(chid, m_currentChannel);
+               if ( m_currentChannel->getDemux(demux) )
+               {
+                       eDebug("[eEPGCache] no demux!!");
+                       goto error4;
+               }
+               else
+               {
+                       RESULT res;
+                       m_NowNextReader = new eDVBSectionReader( demux, this, res );
+                       if ( res )
+                       {
+                               eDebug("[eEPGCache] couldnt initialize nownext reader!!");
+                               goto error3;
+                       }
+                       m_NowNextReader->connectRead(slot(*this, &eEPGCache::readNowNextData), m_NowNextConn);
+                       m_ScheduleReader = new eDVBSectionReader( demux, this, res );
+                       if ( res )
+                       {
+                               eDebug("[eEPGCache] couldnt initialize schedule reader!!");
+                               goto error2;
+                       }
+                       m_ScheduleReader->connectRead(slot(*this, &eEPGCache::readScheduleData), m_ScheduleConn);
+                       m_ScheduleOtherReader = new eDVBSectionReader( demux, this, res );
+                       if ( res )
+                       {
+                               eDebug("[eEPGCache] couldnt initialize schedule other reader!!");
+                               goto error1;
+                       }
+                       m_ScheduleOtherReader->connectRead(slot(*this, &eEPGCache::readScheduleOtherData), m_ScheduleOtherConn);
+                       messages.send(Message(Message::startService, ref));
+                       // -> gotMessage -> changedService
+               }
+       }
+       return;
+error1:
+       m_ScheduleOtherReader=0;
+       m_ScheduleOtherConn=0;
+error2:
+       m_ScheduleReader=0;
+       m_ScheduleConn=0;
+error3:
+       m_NowNextReader=0;
+       m_NowNextConn=0;
+error4:
+       m_currentChannel=0;
+}
+
+void eEPGCache::leaveChannel(iDVBChannel * chan)
+{
+       if ( chan && chan == m_currentChannel )
+       {
+               messages.send(Message(Message::leaveChannel, chan));
+       // -> gotMessage -> abortEPG
+       }
+}
+
+void eEPGCache::changedService(const uniqueEPGKey &service)
+{
+       current_service = service;
+       updateMap::iterator It = serviceLastUpdated.find( current_service );
+
+       int update;
+
+// check if this is a subservice and this is only a dbox2
+// then we dont start epgcache on subservice change..
+// ever and ever..
+
+//     if ( !err || err == -ENOCASYS )
+       {
+               update = ( It != serviceLastUpdated.end() ? ( UPDATE_INTERVAL - ( (time(0)+eDVBLocalTimeHandler::getInstance()->difference()-It->second) * 1000 ) ) : ZAP_DELAY );
+
+               if (update < ZAP_DELAY)
+                       update = ZAP_DELAY;
+
+               zapTimer.start(update, 1);
+               if (update >= 60000)
+                       eDebug("[EPGC] next update in %i min", update/60000);
+               else if (update >= 1000)
+                       eDebug("[EPGC] next update in %i sec", update/1000);
+       }
+
+       Lock();
+       bool empty=eventDB[current_service].first.empty();
+       Unlock();
+
+       if (!empty)
+       {
+               eDebug("[EPGC] yet cached");
+               /*emit*/ EPGAvail(1);
+       }
+       else
+       {
+               eDebug("[EPGC] not cached yet");
+               /*emit*/ EPGAvail(0);
+       }
+}
+
+void eEPGCache::abortEPG()
+{
+       abortTimer.stop();
+       zapTimer.stop();
+       if (isRunning)
+       {
+               if (isRunning & 1)
+               {
+                       isRunning &= ~1;
+                       if ( m_ScheduleReader )
+                               m_ScheduleReader->stop();
+               }
+               if (isRunning & 2)
+               {
+                       isRunning &= ~2;
+                       if ( m_NowNextReader )
+                               m_NowNextReader->stop();
+               }
+               if (isRunning & 4)
+               {
+                       isRunning &= ~4;
+                       if ( m_ScheduleOtherReader )
+                               m_ScheduleOtherReader->stop();
+               }
+               eDebug("[EPGC] abort caching events !!");
+               Lock();
+               temp.clear();
+               Unlock();
+       }
+}
+
+void eEPGCache::gotMessage( const Message &msg )
+{
+       switch (msg.type)
+       {
+               case Message::flush:
+                       flushEPG(msg.service);
+                       break;
+               case Message::startService:
+                       changedService(msg.service);
+                       break;
+               case Message::leaveChannel:
+                       abortEPG();
+                       back_messages.send(Message(Message::leaveChannelFinished));
+                       break;
+               case Message::pause:
+                       pauseEPG();
+                       break;
+               case Message::restart:
+                       restartEPG();
+                       break;
+               case Message::quit:
+                       quit(0);
+                       break;
+               case Message::timeChanged:
+                       cleanLoop();
+                       break;
+               default:
+                       eDebug("unhandled EPGCache Message!!");
+                       break;
+       }
+}
+
+void eEPGCache::gotBackMessage( const Message &msg )
+{
+       switch (msg.type)
+       {
+               case Message::leaveChannelFinished:
+                       m_ScheduleOtherReader=0;
+                       m_ScheduleOtherConn=0;
+                       m_ScheduleReader=0;
+                       m_ScheduleConn=0;
+                       m_NowNextReader=0;
+                       m_NowNextConn=0;
+                       m_currentChannel=0;
+                       eDebug("[eEPGC] channel leaved");
+                       if (next_service)
+                       {
+                               startCache(next_service);
+                               next_service = eServiceReferenceDVB();
+                       }
+                       break;
+               default:
+                       eDebug("unhandled EPGCache BackMessage!!");
+                       break;
+       }
+}
+
+void eEPGCache::thread()
+{
+       nice(4);
+       load();
+       cleanLoop();
+       exec();
+       save();
+}
+
+void eEPGCache::load()
+{
+       FILE *f = fopen("/hdd/epg.dat", "r");
+       if (f)
+       {
+               unsigned char md5_saved[16];
+               unsigned char md5[16];
+               int size=0;
+               int cnt=0;
+               bool md5ok=false;
+               if (!md5_file("/hdd/epg.dat", 1, md5))
+               {
+                       FILE *f = fopen("/hdd/epg.dat.md5", "r");
+                       if (f)
+                       {
+                               fread( md5_saved, 16, 1, f);
+                               fclose(f);
+                               if ( !memcmp(md5_saved, md5, 16) )
+                                       md5ok=true;
+                       }
+               }
+               if ( md5ok )
+               {
+                       fread( &size, sizeof(int), 1, f);
+                       while(size--)
+                       {
+                               uniqueEPGKey key;
+                               eventMap evMap;
+                               timeMap tmMap;
+                               int size=0;
+                               fread( &key, sizeof(uniqueEPGKey), 1, f);
+                               fread( &size, sizeof(int), 1, f);
+                               while(size--)
+                               {
+                                       int len=0;
+                                       int type=0;
+                                       eventData *event=0;
+                                       fread( &type, sizeof(int), 1, f);
+                                       fread( &len, sizeof(int), 1, f);
+                                       event = new eventData(0, len, type);
+                                       fread( event->EITdata, len, 1, f);
+                                       evMap[ event->getEventID() ]=event;
+                                       tmMap[ event->getStartTime() ]=event;
+                                       ++cnt;
+                               }
+                               eventDB[key]=std::pair<eventMap,timeMap>(evMap,tmMap);
+                       }
+                       eDebug("%d events read from /hdd/epg.dat.md5", cnt);
+               }
+               fclose(f);
+       }
+}
+
+void eEPGCache::save()
+{
+       struct statfs s;
+       off64_t tmp;
+       if (statfs("/hdd", &s)<0)
+               tmp=0;
+       else
+       {
+               tmp=s.f_blocks;
+               tmp*=s.f_bsize;
+       }
+
+       // prevent writes to builtin flash
+       if ( tmp < 1024*1024*50 ) // storage size < 50MB
+               return;
+
+       // check for enough free space on storage
+       tmp=s.f_bfree;
+       tmp*=s.f_bsize;
+       if ( tmp < (eventData::CacheSize*12)/10 ) // 20% overhead
+               return;
+
+       FILE *f = fopen("/hdd/epg.dat", "w");
+       int cnt=0;
+       if ( f )
+       {
+               int size = eventDB.size();
+               fwrite( &size, sizeof(int), 1, f );
+               for (eventCache::iterator service_it(eventDB.begin()); service_it != eventDB.end(); ++service_it)
+               {
+                       timeMap &timemap = service_it->second.second;
+                       fwrite( &service_it->first, sizeof(uniqueEPGKey), 1, f);
+                       size = timemap.size();
+                       fwrite( &size, sizeof(int), 1, f);
+                       for (timeMap::iterator time_it(timemap.begin()); time_it != timemap.end(); ++time_it)
+                       {
+                               int len = time_it->second->ByteSize;
+                               fwrite( &time_it->second->type, sizeof(int), 1, f );
+                               fwrite( &len, sizeof(int), 1, f);
+                               fwrite( time_it->second->EITdata, len, 1, f);
+                               ++cnt;
+                       }
+               }
+               eDebug("%d events written to /hdd/epg.dat", cnt);
+               fclose(f);
+               unsigned char md5[16];
+               if (!md5_file("/hdd/epg.dat", 1, md5))
+               {
+                       FILE *f = fopen("/hdd/epg.dat.md5", "w");
+                       if (f)
+                       {
+                               fwrite( md5, 16, 1, f);
+                               fclose(f);
+                       }
+               }
+       }
+}
+
+RESULT eEPGCache::getInstance(ePtr<eEPGCache> &ptr)
+{
+       ptr = instance;
+       if (!ptr)
+               return -1;
+       return 0;
+}
+
diff --git a/lib/dvb/epgcache.h b/lib/dvb/epgcache.h
new file mode 100644 (file)
index 0000000..bc25692
--- /dev/null
@@ -0,0 +1,308 @@
+#ifndef __epgcache_h_
+#define __epgcache_h_
+
+#ifndef SWIG
+
+#include <vector>
+#include <list>
+#include <ext/hash_map>
+
+// check if gcc version >= 3.4
+#if defined(__GNUC__) && ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ == 4 )
+#else
+#include <ext/stl_hash_fun.h>
+#endif
+#include <errno.h>
+
+#include <lib/dvb/eit.h>
+#include <lib/dvb/lowlevel/eit.h>
+#include <lib/dvb/idvb.h>
+#include <lib/dvb/demux.h>
+#include <lib/dvb/dvbtime.h>
+#include <lib/base/ebase.h>
+#include <lib/base/thread.h>
+#include <lib/base/message.h>
+
+#define CLEAN_INTERVAL 60000    //  1 min
+#define UPDATE_INTERVAL 3600000  // 60 min
+#define ZAP_DELAY 2000          // 2 sek
+
+#define HILO(x) (x##_hi << 8 | x##_lo)
+
+class eventData;
+class eServiceReferenceDVB;
+
+struct uniqueEPGKey
+{
+       int sid, onid, tsid;
+       uniqueEPGKey( const eServiceReferenceDVB &ref )
+               :sid( ref.type != eServiceReference::idInvalid ? ref.getServiceID().get() : -1 )
+               ,onid( ref.type != eServiceReference::idInvalid ? ref.getOriginalNetworkID().get() : -1 )
+               ,tsid( ref.type != eServiceReference::idInvalid ? ref.getTransportStreamID().get() : -1 )
+       {
+       }
+       uniqueEPGKey()
+               :sid(-1), onid(-1), tsid(-1)
+       {
+       }
+       uniqueEPGKey( int sid, int onid, int tsid )
+               :sid(sid), onid(onid), tsid(tsid)
+       {
+       }
+       bool operator <(const uniqueEPGKey &a) const
+       {
+               return memcmp( &sid, &a.sid, sizeof(int)*3)<0;
+       }
+       operator bool() const
+       { 
+               return !(sid == -1 && onid == -1 && tsid == -1); 
+       }
+       bool operator==(const uniqueEPGKey &a) const
+       {
+               return !memcmp( &sid, &a.sid, sizeof(int)*3);
+       }
+       struct equal
+       {
+               bool operator()(const uniqueEPGKey &a, const uniqueEPGKey &b) const
+               {
+                       return !memcmp( &a.sid, &b.sid, sizeof(int)*3);
+               }
+       };
+};
+
+//eventMap is sorted by event_id
+#define eventMap std::map<__u16, eventData*>
+//timeMap is sorted by beginTime
+#define timeMap std::map<time_t, eventData*>
+
+#define tmpMap std::map<uniqueEPGKey, std::pair<time_t, int> >
+
+#if defined(__GNUC__) && ((__GNUC__ == 3 && __GNUC_MINOR__ >= 1) || __GNUC__ == 4 )  // check if gcc version >= 3.1
+       #define eventCache __gnu_cxx::hash_map<uniqueEPGKey, std::pair<eventMap, timeMap>, __gnu_cxx::hash<uniqueEPGKey>, uniqueEPGKey::equal>
+       #define updateMap __gnu_cxx::hash_map<uniqueEPGKey, time_t, __gnu_cxx::hash<uniqueEPGKey>, uniqueEPGKey::equal >
+       namespace __gnu_cxx
+#else // for older gcc use following
+       #define eventCache std::hash_map<uniqueEPGKey, std::pair<eventMap, timeMap>, std::hash<uniqueEPGKey>, uniqueEPGKey::equal >
+       #define updateMap std::hash_map<uniqueEPGKey, time_t, std::hash<uniqueEPGKey>, uniqueEPGKey::equal >
+       namespace std
+#endif
+{
+template<> struct hash<uniqueEPGKey>
+{
+       inline size_t operator()( const uniqueEPGKey &x) const
+       {
+               int v=(x.onid^x.sid);
+               v^=v>>8;
+               return v&0xFF;
+       }
+};
+}
+
+class eventData
+{
+       friend class eEPGCache;
+private:
+       __u8* EITdata;
+       int ByteSize;
+public:
+       int type;
+       static int CacheSize;
+       eventData(const eit_event_struct* e, int size, int type)
+       :ByteSize(size), type(type)
+       {
+               CacheSize+=size;
+               EITdata = new __u8[size];
+               if (e)
+                       memcpy(EITdata, (__u8*) e, size);
+       }
+       ~eventData()
+       {
+               CacheSize-=ByteSize;
+               delete [] EITdata;
+       }
+       operator const eit_event_struct*() const
+       {
+               return (const eit_event_struct*) EITdata;
+       }
+       const eit_event_struct* get() const
+       {
+               return (const eit_event_struct*) EITdata;
+       }
+       int getEventID()
+       {
+               return HILO( ((const eit_event_struct*) EITdata)->event_id );
+       }
+       time_t getStartTime()
+       {
+               return parseDVBtime(
+                       EITdata[2], EITdata[3],
+                       EITdata[4], EITdata[5], EITdata[6]);
+       }
+};
+#else
+       #include <lib/base/smartptr.h>
+#endif
+
+class eEPGCache: public eMainloop, private eThread, public Object
+{
+       DECLARE_REF(eEPGCache)
+public:
+#ifndef SWIG
+       enum {NOWNEXT, SCHEDULE, SCHEDULE_OTHER};
+       struct Message
+       {
+               enum
+               {
+                       flush,
+                       startService,
+                       leaveChannel,
+                       pause,
+                       restart,
+                       updated,
+                       isavail,
+                       quit,
+                       timeChanged,
+                       leaveChannelFinished
+               };
+               int type;
+               uniqueEPGKey service;
+               union {
+                       int err;
+                       time_t time;
+                       bool avail;
+               };
+               Message()
+                       :type(0), time(0) {}
+               Message(int type)
+                       :type(type) {}
+               Message(int type, bool b)
+                       :type(type), avail(b) {}
+               Message(int type, const eServiceReferenceDVB& service, int err=0)
+                       :type(type), service(service), err(err) {}
+               Message(int type, time_t time)
+                       :type(type), time(time) {}
+       };
+       eFixedMessagePump<Message> messages;
+       eFixedMessagePump<Message> back_messages;
+private:
+       static pthread_mutex_t cache_lock;
+       eServiceReferenceDVB next_service;
+       uniqueEPGKey current_service;
+       int paused;
+
+       int state;
+       __u8 isRunning, haveData;
+
+       ePtr<iDVBChannel> m_currentChannel;
+       ePtr<eConnection> m_NowNextConn, m_ScheduleConn, m_ScheduleOtherConn;
+       ePtr<iDVBSectionReader> m_NowNextReader, m_ScheduleReader, m_ScheduleOtherReader;
+
+       int sectionRead(const __u8 *data, int source);
+       void readNowNextData(const __u8 *data);
+       void readScheduleData(const __u8 *data);
+       void readScheduleOtherData(const __u8 *data);
+
+       static eEPGCache *instance;
+
+       eventCache eventDB;
+       updateMap serviceLastUpdated;
+       tmpMap temp;
+       eTimer CleanTimer;
+       eTimer zapTimer;
+       eTimer abortTimer;
+       bool finishEPG();
+       void abortNonAvail();
+       void flushEPG(const uniqueEPGKey & s=uniqueEPGKey());
+       void startEPG();
+
+       void changedService(const uniqueEPGKey &);
+       void abortEPG();
+
+       void cleanLoop();
+       void pauseEPG();
+       void restartEPG();
+       void thread();
+       void gotMessage(const Message &message);
+       void gotBackMessage(const Message &message);
+       void timeUpdated();
+       void save();
+       void load();
+       void leaveChannel(iDVBChannel *);
+#endif
+public:
+       static RESULT getInstance(ePtr<eEPGCache> &ptr);
+       // called from other thread context !!
+       void startCache(const eServiceReferenceDVB &);
+       Event *lookupEvent(const eServiceReferenceDVB &service, int event_id, bool plain=false );
+       Event *lookupEvent(const eServiceReferenceDVB &service, time_t=0, bool plain=false );
+#ifndef SWIG
+       eEPGCache();
+       ~eEPGCache();
+
+       inline void Lock();
+       inline void Unlock();
+
+       const eventMap* getEventMap(const eServiceReferenceDVB &service);
+       const timeMap* getTimeMap(const eServiceReferenceDVB &service);
+       tmpMap* getUpdatedMap() { return &temp; }
+
+       Signal1<void, bool> EPGAvail;
+       Signal0<void> EPGUpdated;
+#endif
+};
+
+TEMPLATE_TYPEDEF(ePtr<eEPGCache>,eEPGCachePtr);
+
+inline const eventMap* eEPGCache::getEventMap(const eServiceReferenceDVB &service)
+{
+       eventCache::iterator It = eventDB.find( service );
+       if ( It != eventDB.end() && It->second.first.size() )
+               return &(It->second.first);
+       else
+               return 0;
+}
+
+inline const timeMap* eEPGCache::getTimeMap(const eServiceReferenceDVB &service)
+{
+       eventCache::iterator It = eventDB.find( service );
+       if ( It != eventDB.end() && It->second.second.size() )
+               return &(It->second.second);
+       else
+               return 0;
+}
+
+inline void eEPGCache::readNowNextData( const __u8 *data)
+{
+       if ( !data || state == 2 )
+               m_NowNextReader->stop();
+       else
+               sectionRead(data, eEPGCache::NOWNEXT);
+}
+
+inline void eEPGCache::readScheduleData(const __u8 *data)
+{
+       if ( !data || state == 2 )
+               m_ScheduleReader->stop();
+       else
+               sectionRead(data, eEPGCache::SCHEDULE);
+}
+
+inline void eEPGCache::readScheduleOtherData(const __u8 *data)
+{
+       if ( !data || state == 2 )
+               m_ScheduleOtherReader->stop();
+       else
+               sectionRead(data, eEPGCache::SCHEDULE_OTHER);
+}
+
+inline void eEPGCache::Lock()
+{
+       pthread_mutex_lock(&cache_lock);
+}
+
+inline void eEPGCache::Unlock()
+{
+       pthread_mutex_unlock(&cache_lock);
+}
+
+#endif
index ae8bf92..477e1bf 100644 (file)
@@ -67,6 +67,7 @@ is usually caused by not marking PSignals as immutable.
 #include <lib/actions/action.h>
 #include <lib/gdi/gfont.h>
 #include <lib/gdi/epng.h>
+#include <lib/dvb/epgcache.h>
 
 extern void runMainloop();
 extern void quitMainloop();
@@ -123,6 +124,7 @@ extern PSignal1<void,int> &keyPressedSignal();
 %include <lib/actions/action.h>
 %include <lib/gdi/gfont.h>
 %include <lib/gdi/epng.h>
+%include <lib/dvb/epgcache.h>
 
 %include <lib/gdi/gpixmap.h>
 /**************  eptr  **************/
index 21c1d30..9647dea 100644 (file)
@@ -91,6 +91,7 @@ void keyEvent(const eRCKey &key)
 #include <lib/dvb/dvb.h>
 #include <lib/dvb/db.h>
 #include <lib/dvb/dvbtime.h>
+#include <lib/dvb/epgcache.h>
 
 class eMain: public eApplication, public Object
 {
@@ -100,6 +101,7 @@ class eMain: public eApplication, public Object
        ePtr<eDVBDB> m_dvbdb;
        ePtr<eDVBLocalTimeHandler> m_locale_time_handler;
        ePtr<eComponentScan> m_scan;
+       ePtr<eEPGCache> m_epgcache;
 
 public:
        eMain()
@@ -110,6 +112,7 @@ public:
                m_dvbdb = new eDVBDB();
                m_mgr = new eDVBResourceManager();
                m_locale_time_handler = new eDVBLocalTimeHandler();
+               m_epgcache = new eEPGCache();
                m_mgr->setChannelList(m_dvbdb);
                
 //             m_scan = new eComponentScan();