From 20f69c200ec5703e958a6b95abfcfd5a108e5939 Mon Sep 17 00:00:00 2001 From: Andreas Monzner Date: Mon, 4 Jul 2005 17:49:04 +0000 Subject: [PATCH] add epgcache --- lib/dvb/dvbtime.cpp | 9 - lib/dvb/dvbtime.h | 13 +- lib/dvb/epgcache.cpp | 957 +++++++++++++++++++++++++++++++++++++ lib/dvb/epgcache.h | 308 ++++++++++++ lib/python/enigma_python.i | 2 + main/enigma.cpp | 3 + 6 files changed, 1281 insertions(+), 11 deletions(-) create mode 100644 lib/dvb/epgcache.cpp create mode 100644 lib/dvb/epgcache.h diff --git a/lib/dvb/dvbtime.cpp b/lib/dvb/dvbtime.cpp index 84a1ef4b..5167a01e 100644 --- a/lib/dvb/dvbtime.cpp +++ b/lib/dvb/dvbtime.cpp @@ -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; diff --git a/lib/dvb/dvbtime.h b/lib/dvb/dvbtime.h index a1746b5e..59a26c21 100644 --- a/lib/dvb/dvbtime.h +++ b/lib/dvb/dvbtime.h @@ -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 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 index 00000000..41635b03 --- /dev/null +++ b/lib/dvb/epgcache.cpp @@ -0,0 +1,957 @@ +#include +#include + +#undef EPG_DEBUG + +#include +#include // for usleep +#include // for statfs +#include +#include + +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 &servicemap = eventDB[service]; + eventMap::iterator prevEventIt = servicemap.first.end(); + timeMap::iterator prevTimeIt = servicemap.second.end(); + + while (ptrdescriptors_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( 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( event_id, evt) ); + tm_it->second=evt; + } + else // added new eventData + { +#if EPG_DEBUG + consistencyCheck=false; +#endif + prevEventIt=servicemap.first.insert( prevEventIt, std::pair( event_id, evt) ); + prevTimeIt=servicemap.second.insert( prevTimeIt, std::pair( 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(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 res_mgr; + if ( eDVBResourceManager::getInstance( res_mgr ) ) + eDebug("[eEPGCache] no res manager!!"); + else + { + ePtr 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(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 &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 index 00000000..bc25692e --- /dev/null +++ b/lib/dvb/epgcache.h @@ -0,0 +1,308 @@ +#ifndef __epgcache_h_ +#define __epgcache_h_ + +#ifndef SWIG + +#include +#include +#include + +// check if gcc version >= 3.4 +#if defined(__GNUC__) && ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ == 4 ) +#else +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#define tmpMap std::map > + +#if defined(__GNUC__) && ((__GNUC__ == 3 && __GNUC_MINOR__ >= 1) || __GNUC__ == 4 ) // check if gcc version >= 3.1 + #define eventCache __gnu_cxx::hash_map, __gnu_cxx::hash, uniqueEPGKey::equal> + #define updateMap __gnu_cxx::hash_map, uniqueEPGKey::equal > + namespace __gnu_cxx +#else // for older gcc use following + #define eventCache std::hash_map, std::hash, uniqueEPGKey::equal > + #define updateMap std::hash_map, uniqueEPGKey::equal > + namespace std +#endif +{ +template<> struct hash +{ + 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 +#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 messages; + eFixedMessagePump back_messages; +private: + static pthread_mutex_t cache_lock; + eServiceReferenceDVB next_service; + uniqueEPGKey current_service; + int paused; + + int state; + __u8 isRunning, haveData; + + ePtr m_currentChannel; + ePtr m_NowNextConn, m_ScheduleConn, m_ScheduleOtherConn; + ePtr 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 &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 EPGAvail; + Signal0 EPGUpdated; +#endif +}; + +TEMPLATE_TYPEDEF(ePtr,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 diff --git a/lib/python/enigma_python.i b/lib/python/enigma_python.i index ae8bf92d..477e1bf7 100644 --- a/lib/python/enigma_python.i +++ b/lib/python/enigma_python.i @@ -67,6 +67,7 @@ is usually caused by not marking PSignals as immutable. #include #include #include +#include extern void runMainloop(); extern void quitMainloop(); @@ -123,6 +124,7 @@ extern PSignal1 &keyPressedSignal(); %include %include %include +%include %include /************** eptr **************/ diff --git a/main/enigma.cpp b/main/enigma.cpp index 21c1d30a..9647dea9 100644 --- a/main/enigma.cpp +++ b/main/enigma.cpp @@ -91,6 +91,7 @@ void keyEvent(const eRCKey &key) #include #include #include +#include class eMain: public eApplication, public Object { @@ -100,6 +101,7 @@ class eMain: public eApplication, public Object ePtr m_dvbdb; ePtr m_locale_time_handler; ePtr m_scan; + ePtr 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(); -- 2.30.2