X-Git-Url: https://git.cweiske.de/enigma2.git/blobdiff_plain/aa3d1162bef321bd2d0effdafce6eb080660a7ca..2b557e7ef4b0518736c5162a501cd9bc743930b3:/lib/dvb/epgcache.cpp diff --git a/lib/dvb/epgcache.cpp b/lib/dvb/epgcache.cpp index 634aead3..47da4101 100644 --- a/lib/dvb/epgcache.cpp +++ b/lib/dvb/epgcache.cpp @@ -3,59 +3,423 @@ #undef EPG_DEBUG +#ifdef EPG_DEBUG +#include +#endif + #include #include // for usleep #include // for statfs -#include +// #include #include +#include +#include +#include int eventData::CacheSize=0; +descriptorMap eventData::descriptors; +__u8 eventData::data[4108]; +extern const uint32_t crc32_table[256]; + +const eServiceReference &handleGroup(const eServiceReference &ref) +{ + if (ref.flags & eServiceReference::isGroup) + { + ePtr res; + if (!eDVBResourceManager::getInstance(res)) + { + ePtr db; + if (!res->getChannelList(db)) + { + eBouquet *bouquet=0; + if (!db->getBouquet(ref, bouquet)) + { + std::list::iterator it(bouquet->m_services.begin()); + if (it != bouquet->m_services.end()) + return *it; + } + } + } + } + return ref; +} + + +eventData::eventData(const eit_event_struct* e, int size, int type) + :ByteSize(size&0xFF), type(type&0xFF) +{ + if (!e) + return; + + __u32 descr[65]; + __u32 *pdescr=descr; + + __u8 *data = (__u8*)e; + int ptr=10; + int descriptors_length = (data[ptr++]&0x0F) << 8; + descriptors_length |= data[ptr++]; + while ( descriptors_length > 0 ) + { + __u8 *descr = data+ptr; + int descr_len = descr[1]+2; + + __u32 crc = 0; + int cnt=0; + while(cnt++ < descr_len) + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ data[ptr++]) & 0xFF]; + + descriptorMap::iterator it = + descriptors.find(crc); + if ( it == descriptors.end() ) + { + CacheSize+=descr_len; + __u8 *d = new __u8[descr_len]; + memcpy(d, descr, descr_len); + descriptors[crc] = descriptorPair(1, d); + } + else + ++it->second.first; + + *pdescr++=crc; + descriptors_length -= descr_len; + } + ByteSize = 12+((pdescr-descr)*4); + EITdata = new __u8[ByteSize]; + CacheSize+=ByteSize; + memcpy(EITdata, (__u8*) e, 12); + memcpy(EITdata+12, descr, ByteSize-12); +} + +const eit_event_struct* eventData::get() const +{ + int pos = 12; + int tmp = ByteSize-12; + memcpy(data, EITdata, 12); + __u32 *p = (__u32*)(EITdata+12); + while(tmp>0) + { + descriptorMap::iterator it = + descriptors.find(*p++); + if ( it != descriptors.end() ) + { + int b = it->second.second[1]+2; + memcpy(data+pos, it->second.second, b ); + pos += b; + } + tmp-=4; + } + + return (const eit_event_struct*)data; +} + +eventData::~eventData() +{ + if ( ByteSize ) + { + CacheSize-=ByteSize; + ByteSize-=12; + __u32 *d = (__u32*)(EITdata+12); + while(ByteSize) + { + descriptorMap::iterator it = + descriptors.find(*d++); + if ( it != descriptors.end() ) + { + descriptorPair &p = it->second; + if (!--p.first) // no more used descriptor + { + CacheSize -= it->second.second[1]; + delete [] it->second.second; // free descriptor memory + descriptors.erase(it); // remove entry from descriptor map + } + } + ByteSize-=4; + } + delete [] EITdata; + } +} + +void eventData::load(FILE *f) +{ + int size=0; + int id=0; + __u8 header[2]; + descriptorPair p; + fread(&size, sizeof(int), 1, f); + while(size) + { + fread(&id, sizeof(__u32), 1, f); + fread(&p.first, sizeof(int), 1, f); + fread(header, 2, 1, f); + int bytes = header[1]+2; + p.second = new __u8[bytes]; + p.second[0] = header[0]; + p.second[1] = header[1]; + fread(p.second+2, bytes-2, 1, f); + descriptors[id]=p; + --size; + CacheSize+=bytes; + } +} + +void eventData::save(FILE *f) +{ + int size=descriptors.size(); + descriptorMap::iterator it(descriptors.begin()); + fwrite(&size, sizeof(int), 1, f); + while(size) + { + fwrite(&it->first, sizeof(__u32), 1, f); + fwrite(&it->second.first, sizeof(int), 1, f); + fwrite(it->second.second, it->second.second[1]+2, 1, f); + ++it; + --size; + } +} eEPGCache* eEPGCache::instance; pthread_mutex_t eEPGCache::cache_lock= PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +pthread_mutex_t eEPGCache::channel_map_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) + :messages(this,1), cleanTimer(this)//, paused(0) { 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); + CONNECT(cleanTimer.timeout, eEPGCache::cleanLoop); + + ePtr res_mgr; + eDVBResourceManager::getInstance(res_mgr); + if (!res_mgr) + eDebug("[eEPGCache] no resource manager !!!!!!!"); + else + res_mgr->connectChannelAdded(slot(*this,&eEPGCache::DVBChannelAdded), m_chanAddedConn); instance=this; } void eEPGCache::timeUpdated() { - if ( !thread_running() ) + if (!sync()) { eDebug("[EPGC] time updated.. start EPG Mainloop"); run(); + } else + messages.send(Message(Message::timeChanged)); +} + +void eEPGCache::DVBChannelAdded(eDVBChannel *chan) +{ + if ( chan ) + { +// eDebug("[eEPGCache] add channel %p", chan); + channel_data *data = new channel_data(this); + data->channel = chan; + data->prevChannelState = -1; +#ifdef ENABLE_PRIVATE_EPG + data->m_PrivatePid = -1; +#endif + singleLock s(channel_map_lock); + m_knownChannels.insert( std::pair(chan, data) ); + chan->connectStateChange(slot(*this, &eEPGCache::DVBChannelStateChanged), data->m_stateChangedConn); } +} + +void eEPGCache::DVBChannelRunning(iDVBChannel *chan) +{ + channelMapIterator it = + m_knownChannels.find(chan); + if ( it == m_knownChannels.end() ) + eDebug("[eEPGCache] will start non existing channel %p !!!", chan); else - messages.send(Message(Message::timeChanged)); + { + channel_data &data = *it->second; + ePtr res_mgr; + if ( eDVBResourceManager::getInstance( res_mgr ) ) + eDebug("[eEPGCache] no res manager!!"); + else + { + ePtr demux; + if ( data.channel->getDemux(demux, 0) ) + { + eDebug("[eEPGCache] no demux!!"); + return; + } + else + { + RESULT res = demux->createSectionReader( this, data.m_NowNextReader ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize nownext reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_ScheduleReader ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize schedule reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_ScheduleOtherReader ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize schedule other reader!!"); + return; + } +#ifdef ENABLE_PRIVATE_EPG + res = demux->createSectionReader( this, data.m_PrivateReader ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize private reader!!"); + return; + } +#endif +#ifdef ENABLE_MHW_EPG + res = demux->createSectionReader( this, data.m_MHWReader ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize mhw reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_MHWReader2 ); + if ( res ) + { + eDebug("[eEPGCache] couldnt initialize mhw reader!!"); + return; + } +#endif + messages.send(Message(Message::startChannel, chan)); + // -> gotMessage -> changedService + } + } + } +} + +void eEPGCache::DVBChannelStateChanged(iDVBChannel *chan) +{ + channelMapIterator it = + m_knownChannels.find(chan); + if ( it != m_knownChannels.end() ) + { + int state=0; + chan->getState(state); + if ( it->second->prevChannelState != state ) + { + switch (state) + { + case iDVBChannel::state_ok: + { + eDebug("[eEPGCache] channel %p running", chan); + DVBChannelRunning(chan); + break; + } + case iDVBChannel::state_release: + { + eDebug("[eEPGCache] remove channel %p", chan); + messages.send(Message(Message::leaveChannel, chan)); + pthread_mutex_lock(&it->second->channel_active); + singleLock s(channel_map_lock); + m_knownChannels.erase(it); + pthread_mutex_unlock(&it->second->channel_active); + delete it->second; + it->second=0; + // -> gotMessage -> abortEPG + break; + } + default: // ignore all other events + return; + } + if (it->second) + it->second->prevChannelState = state; + } + } +} + +void eEPGCache::FixOverlapping(std::pair &servicemap, time_t TM, int duration, const timeMap::iterator &tm_it, const uniqueEPGKey &service) +{ + timeMap::iterator tmp = tm_it; + while ((tmp->first+tmp->second->getDuration()-300) > TM) + { + if(tmp->first != TM +#ifdef ENABLE_PRIVATE_EPG + && tmp->second->type != PRIVATE +#endif +#ifdef ENABLE_MHW + && tmp->second->type != MHW +#endif + ) + { + __u16 event_id = tmp->second->getEventID(); + servicemap.first.erase(event_id); +#ifdef EPG_DEBUG + Event evt((uint8_t*)tmp->second->get()); + eServiceEvent event; + event.parseFrom(&evt, service.sid<<16|service.onid); + eDebug("(1)erase no more used event %04x %d\n%s %s\n%s", + service.sid, event_id, + event.getBeginTimeString().c_str(), + event.getEventName().c_str(), + event.getExtendedDescription().c_str()); +#endif + delete tmp->second; + if (tmp == servicemap.second.begin()) + { + servicemap.second.erase(tmp); + break; + } + else + servicemap.second.erase(tmp--); + } + else + { + if (tmp == servicemap.second.begin()) + break; + --tmp; + } + } + + tmp = tm_it; + while(tmp->first < (TM+duration-300)) + { + if (tmp->first != TM && tmp->second->type != PRIVATE) + { + __u16 event_id = tmp->second->getEventID(); + servicemap.first.erase(event_id); +#ifdef EPG_DEBUG + Event evt((uint8_t*)tmp->second->get()); + eServiceEvent event; + event.parseFrom(&evt, service.sid<<16|service.onid); + eDebug("(2)erase no more used event %04x %d\n%s %s\n%s", + service.sid, event_id, + event.getBeginTimeString().c_str(), + event.getEventName().c_str(), + event.getExtendedDescription().c_str()); +#endif + delete tmp->second; + servicemap.second.erase(tmp++); + } + else + ++tmp; + if (tmp == servicemap.second.end()) + break; + } } -int eEPGCache::sectionRead(const __u8 *data, int source) +void eEPGCache::sectionRead(const __u8 *data, int source, channel_data *channel) { eit_t *eit = (eit_t*) data; int len=HILO(eit->section_length)-1;//+3-4; int ptr=EIT_SIZE; if ( ptr >= len ) - return 0; + return; - // // This fixed the EPG on the Multichoice irdeto systems // the EIT packet is non-compliant.. their EIT packet stinks if ( data[ptr-1] < 0x40 ) @@ -67,26 +431,12 @@ int eEPGCache::sectionRead(const __u8 *data, int source) 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(); + time_t now = eDVBLocalTimeHandler::getInstance()->nowTime(); if ( TM != 3599 && TM > -1) - { - switch(source) - { - case NOWNEXT: - haveData |= 2; - break; - case SCHEDULE: - haveData |= 1; - break; - case SCHEDULE_OTHER: - haveData |= 4; - break; - } - } + channel->haveData |= source; - Lock(); + singleLock s(cache_lock); // hier wird immer eine eventMap zurück gegeben.. entweder eine vorhandene.. // oder eine durch [] erzeugte std::pair &servicemap = eventDB[service]; @@ -120,7 +470,7 @@ int eEPGCache::sectionRead(const __u8 *data, int source) int ev_erase_count = 0; int tm_erase_count = 0; -// search in eventmap + // search in eventmap eventMap::iterator ev_it = servicemap.first.find(event_id); @@ -128,23 +478,24 @@ int eEPGCache::sectionRead(const __u8 *data, int source) 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 = + goto next; // when not.. then 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 + if ( tm_it_tmp->first == TM ) // just update 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; + ev_it->second = tm_it_tmp->second = + new eventData(eit_event, eit_event_size, source); + FixOverlapping(servicemap, TM, duration, tm_it_tmp, service); goto next; } - else + else // event has new event begin time { tm_erase_count++; // delete the found record from timemap @@ -154,46 +505,44 @@ int eEPGCache::sectionRead(const __u8 *data, int source) } } -// 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 + // 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() ) { + // event with same start time but another event_id... + if ( source > tm_it->second->type && + ev_it == servicemap.first.end() ) + goto next; // when not.. then skip this entry -// 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 = + // 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++; + 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 +#ifdef 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 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) + else if (ev_erase_count == 0 && tm_erase_count > 0) { // exempt memory delete ev_it->second; @@ -209,34 +558,37 @@ int eEPGCache::sectionRead(const __u8 *data, int source) } else // added new eventData { -#if EPG_DEBUG +#ifdef EPG_DEBUG consistencyCheck=false; #endif - prevEventIt=servicemap.first.insert( prevEventIt, std::pair( event_id, evt) ); - prevTimeIt=servicemap.second.insert( prevTimeIt, std::pair( TM, evt ) ); + ev_it=prevEventIt=servicemap.first.insert( prevEventIt, std::pair( event_id, evt) ); + tm_it=prevTimeIt=servicemap.second.insert( prevTimeIt, std::pair( TM, evt ) ); } -#if EPG_DEBUG + + FixOverlapping(servicemap, TM, duration, tm_it, service); + +#ifdef 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)", + 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)", + 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)", + eFatal("eventmap key(%d) non equal event_id(%d)", ev_it->first, event_id ); } #endif } next: -#if EPG_DEBUG +#ifdef EPG_DEBUG if ( servicemap.first.size() != servicemap.second.size() ) { FILE *f = fopen("/hdd/event_map.txt", "w+"); @@ -250,8 +602,8 @@ next: 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 ); + 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", @@ -262,68 +614,12 @@ next: 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(); + singleLock l(cache_lock); if (s) // clear only this service { eventCache::iterator it = eventDB.find(s); @@ -336,10 +632,17 @@ void eEPGCache::flushEPG(const uniqueEPGKey & s) delete i->second; evMap.clear(); eventDB.erase(it); - updateMap::iterator u = serviceLastUpdated.find(s); - if ( u != serviceLastUpdated.end() ) - serviceLastUpdated.erase(u); - startEPG(); + + // TODO .. search corresponding channel for removed service and remove this channel from lastupdated map +#ifdef ENABLE_PRIVATE_EPG + contentMaps::iterator it = + content_time_tables.find(s); + if ( it != content_time_tables.end() ) + { + it->second.clear(); + content_time_tables.erase(it); + } +#endif } } else // clear complete EPG Cache @@ -354,40 +657,33 @@ void eEPGCache::flushEPG(const uniqueEPGKey & s) evMap.clear(); tmMap.clear(); } - serviceLastUpdated.clear(); eventDB.clear(); - startEPG(); +#ifdef ENABLE_PRIVATE_EPG + content_time_tables.clear(); +#endif + channelLastUpdated.clear(); + singleLock m(channel_map_lock); + for (channelMapIterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + it->second->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 ) + if (!eventDB.empty()) { eDebug("[EPGC] start cleanloop"); - const eit_event_struct* cur_event; - int duration; -// FIXME !!! TIME_CORRECTION - time_t now = time(0)+eDVBLocalTimeHandler::getInstance()->difference(); + time_t now = eDVBLocalTimeHandler::getInstance()->nowTime(); for (eventCache::iterator DBIt = eventDB.begin(); DBIt != eventDB.end(); DBIt++) { + bool updated = false; 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) + if ( now > (It->first+It->second->getDuration()) ) // outdated normal entry (nvod references to) { // remove entry from eventMap eventMap::iterator b(DBIt->second.first.find(It->second->getEventID())); @@ -403,555 +699,2563 @@ void eEPGCache::cleanLoop() 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); + updated = true; } else ++It; } - if ( DBIt->second.second.size() < 2 ) - // less than two events for this service in cache.. +#ifdef ENABLE_PRIVATE_EPG + if ( updated ) { - updateMap::iterator u = serviceLastUpdated.find(DBIt->first); - if ( u != serviceLastUpdated.end() ) + contentMaps::iterator x = + content_time_tables.find( DBIt->first ); + if ( x != content_time_tables.end() ) { - // remove from lastupdated map.. - serviceLastUpdated.erase(u); - // current service? - if ( DBIt->first == current_service ) + timeMap &tmMap = eventDB[DBIt->first].second; + for ( contentMap::iterator i = x->second.begin(); i != x->second.end(); ) { - // immediate .. after leave cleanloop - // update epgdata for this service - zapTimer.start(0,true); + for ( contentTimeMap::iterator it(i->second.begin()); + it != i->second.end(); ) + { + if ( tmMap.find(it->second.first) == tmMap.end() ) + i->second.erase(it++); + else + ++it; + } + if ( i->second.size() ) + ++i; + else + x->second.erase(i++); } } } +#endif } - - if (temp.size()) - /*emit*/ EPGUpdated(); - eDebug("[EPGC] stop cleanloop"); eDebug("[EPGC] %i bytes for cache used", eventData::CacheSize); } - CleanTimer.start(CLEAN_INTERVAL,true); + cleanTimer.start(CLEAN_INTERVAL,true); } eEPGCache::~eEPGCache() { messages.send(Message::quit); kill(); // waiting for thread shutdown - Lock(); + singleLock s(cache_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) +void eEPGCache::gotMessage( const Message &msg ) { - singleLock s(cache_lock); - uniqueEPGKey key( service ); - - eventCache::iterator It = eventDB.find( key ); - if ( It != eventDB.end() && !It->second.first.empty() ) // entrys cached? + switch (msg.type) { - eventMap::iterator i( It->second.first.find( event_id )); - if ( i != It->second.first.end() ) + case Message::flush: + flushEPG(msg.service); + break; + case Message::startChannel: { - 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*/ ); + singleLock s(channel_map_lock); + channelMapIterator channel = + m_knownChannels.find(msg.channel); + if ( channel != m_knownChannels.end() ) + channel->second->startChannel(); + break; } - 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() ) + case Message::leaveChannel: { - 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 */ ); - } - } + singleLock s(channel_map_lock); + channelMapIterator channel = + m_knownChannels.find(msg.channel); + if ( channel != m_knownChannels.end() ) + channel->second->abortEPG(); + break; } - - for ( eventMap::iterator i( It->second.first.begin() ); i != It->second.first.end(); i++) + case Message::quit: + quit(0); + break; +#ifdef ENABLE_PRIVATE_EPG + case Message::got_private_pid: { - 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 + for (channelMapIterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) { - 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*/ ); + eDVBChannel *channel = (eDVBChannel*) it->first; + channel_data *data = it->second; + eDVBChannelID chid = channel->getChannelID(); + if ( chid.transport_stream_id.get() == msg.service.tsid && + chid.original_network_id.get() == msg.service.onid && + data->m_PrivatePid == -1 ) + { + data->m_PrevVersion = -1; + data->m_PrivatePid = msg.pid; + data->m_PrivateService = msg.service; + int onid = chid.original_network_id.get(); + onid |= 0x80000000; // we use highest bit as private epg indicator + chid.original_network_id = onid; + updateMap::iterator It = channelLastUpdated.find( chid ); + int update = ( It != channelLastUpdated.end() ? ( UPDATE_INTERVAL - ( (eDVBLocalTimeHandler::getInstance()->nowTime()-It->second) * 1000 ) ) : ZAP_DELAY ); + if (update < ZAP_DELAY) + update = ZAP_DELAY; + data->startPrivateTimer.start(update, 1); + if (update >= 60000) + eDebug("[EPGC] next private update in %i min", update/60000); + else if (update >= 1000) + eDebug("[EPGC] next private update in %i sec", update/1000); + break; + } } + break; } +#endif + case Message::timeChanged: + cleanLoop(); + break; + default: + eDebug("unhandled EPGCache Message!!"); + break; } - return 0; } -void eEPGCache::pauseEPG() +void eEPGCache::thread() { - if (!paused) - { - abortEPG(); - eDebug("[EPGC] paused]"); - paused=1; - } + hasStarted(); + nice(4); + load(); + cleanLoop(); + runLoop(); + save(); } -void eEPGCache::restartEPG() +void eEPGCache::load() { - if (paused) + singleLock s(cache_lock); + FILE *f = fopen("/hdd/epg.dat", "r"); + if (f) { - isRunning=0; - eDebug("[EPGC] restarted"); - paused--; - if (paused) + int size=0; + int cnt=0; +#if 0 + unsigned char md5_saved[16]; + unsigned char md5[16]; + 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 ) +#endif { - paused = 0; - startEPG(); // updateEPG + unsigned int magic=0; + fread( &magic, sizeof(int), 1, f); + if (magic != 0x98765432) + { + eDebug("[EPGC] epg file has incorrect byte order.. dont read it"); + fclose(f); + return; + } + char text1[13]; + fread( text1, 13, 1, f); + if ( !strncmp( text1, "ENIGMA_EPG_V5", 13) ) + { + 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--) + { + __u8 len=0; + __u8 type=0; + eventData *event=0; + fread( &type, sizeof(__u8), 1, f); + fread( &len, sizeof(__u8), 1, f); + event = new eventData(0, len, type); + event->EITdata = new __u8[len]; + eventData::CacheSize+=len; + fread( event->EITdata, len, 1, f); + evMap[ event->getEventID() ]=event; + tmMap[ event->getStartTime() ]=event; + ++cnt; + } + eventDB[key]=std::pair(evMap,tmMap); + } + eventData::load(f); + eDebug("[EPGC] %d events read from /hdd/epg.dat", cnt); +#ifdef ENABLE_PRIVATE_EPG + char text2[11]; + fread( text2, 11, 1, f); + if ( !strncmp( text2, "PRIVATE_EPG", 11) ) + { + size=0; + fread( &size, sizeof(int), 1, f); + while(size--) + { + int size=0; + uniqueEPGKey key; + fread( &key, sizeof(uniqueEPGKey), 1, f); + eventMap &evMap=eventDB[key].first; + fread( &size, sizeof(int), 1, f); + while(size--) + { + int size; + int content_id; + fread( &content_id, sizeof(int), 1, f); + fread( &size, sizeof(int), 1, f); + while(size--) + { + time_t time1, time2; + __u16 event_id; + fread( &time1, sizeof(time_t), 1, f); + fread( &time2, sizeof(time_t), 1, f); + fread( &event_id, sizeof(__u16), 1, f); + content_time_tables[key][content_id][time1]=std::pair(time2, event_id); + eventMap::iterator it = + evMap.find(event_id); + if (it != evMap.end()) + it->second->type = PRIVATE; + } + } + } + } +#endif // ENABLE_PRIVATE_EPG + } + else + eDebug("[EPGC] don't read old epg database"); + fclose(f); } - cleanLoop(); } } -void eEPGCache::startEPG() +void eEPGCache::save() { - if (paused) // called from the updateTimer during pause... + struct statfs s; + off64_t tmp; + if (statfs("/hdd", &s)<0) + tmp=0; + else { - paused++; - return; + tmp=s.f_blocks; + tmp*=s.f_bsize; } - 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; + // prevent writes to builtin flash + if ( tmp < 1024*1024*50 ) // storage size < 50MB + return; - mask.data[0] = 0x4E; - mask.mask[0] = 0xFE; - m_NowNextReader->start(mask); - isRunning |= 1; + // check for enough free space on storage + tmp=s.f_bfree; + tmp*=s.f_bsize; + if ( tmp < (eventData::CacheSize*12)/10 ) // 20% overhead + return; - mask.data[0] = 0x50; - mask.mask[0] = 0xF0; - m_ScheduleReader->start(mask); - isRunning |= 2; + FILE *f = fopen("/hdd/epg.dat", "w"); + int cnt=0; + if ( f ) + { + unsigned int magic = 0x98765432; + fwrite( &magic, sizeof(int), 1, f); + const char *text = "ENIGMA_EPG_V5"; + fwrite( text, 13, 1, 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) + { + __u8 len = time_it->second->ByteSize; + fwrite( &time_it->second->type, sizeof(__u8), 1, f ); + fwrite( &len, sizeof(__u8), 1, f); + fwrite( time_it->second->EITdata, len, 1, f); + ++cnt; + } + } + eDebug("[EPGC] %d events written to /hdd/epg.dat", cnt); + eventData::save(f); +#ifdef ENABLE_PRIVATE_EPG + const char* text3 = "PRIVATE_EPG"; + fwrite( text3, 11, 1, f ); + size = content_time_tables.size(); + fwrite( &size, sizeof(int), 1, f); + for (contentMaps::iterator a = content_time_tables.begin(); a != content_time_tables.end(); ++a) + { + contentMap &content_time_table = a->second; + fwrite( &a->first, sizeof(uniqueEPGKey), 1, f); + int size = content_time_table.size(); + fwrite( &size, sizeof(int), 1, f); + for (contentMap::iterator i = content_time_table.begin(); i != content_time_table.end(); ++i ) + { + int size = i->second.size(); + fwrite( &i->first, sizeof(int), 1, f); + fwrite( &size, sizeof(int), 1, f); + for ( contentTimeMap::iterator it(i->second.begin()); + it != i->second.end(); ++it ) + { + fwrite( &it->first, sizeof(time_t), 1, f); + fwrite( &it->second.first, sizeof(time_t), 1, f); + fwrite( &it->second.second, sizeof(__u16), 1, f); + } + } + } +#endif + fclose(f); +#if 0 + 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); + } + } +#endif + } +} - mask.data[0] = 0x60; - mask.mask[0] = 0xF0; - m_ScheduleOtherReader->start(mask); - isRunning |= 4; +eEPGCache::channel_data::channel_data(eEPGCache *ml) + :cache(ml) + ,abortTimer(ml), zapTimer(ml), state(0) + ,isRunning(0), haveData(0) +#ifdef ENABLE_PRIVATE_EPG + ,startPrivateTimer(ml) +#endif +#ifdef ENABLE_MHW_EPG + ,m_MHWTimeoutTimer(ml) +#endif +{ +#ifdef ENABLE_MHW_EPG + CONNECT(m_MHWTimeoutTimer.timeout, eEPGCache::channel_data::MHWTimeout); +#endif + CONNECT(zapTimer.timeout, eEPGCache::channel_data::startEPG); + CONNECT(abortTimer.timeout, eEPGCache::channel_data::abortNonAvail); +#ifdef ENABLE_PRIVATE_EPG + CONNECT(startPrivateTimer.timeout, eEPGCache::channel_data::startPrivateReader); +#endif + pthread_mutex_init(&channel_active, 0); +} - abortTimer.start(5000,true); +bool eEPGCache::channel_data::finishEPG() +{ + if (!isRunning) // epg ready + { + eDebug("[EPGC] stop caching events(%ld)", eDVBLocalTimeHandler::getInstance()->nowTime()); + zapTimer.start(UPDATE_INTERVAL, 1); + eDebug("[EPGC] next update in %i min", UPDATE_INTERVAL / 60000); + for (int i=0; i < 3; ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } + singleLock l(cache->cache_lock); + cache->channelLastUpdated[channel->getChannelID()] = eDVBLocalTimeHandler::getInstance()->nowTime(); +#ifdef ENABLE_MHW_EPG + cleanup(); +#endif + return true; } - else + return false; +} + +void eEPGCache::channel_data::startEPG() +{ + eDebug("[EPGC] start caching events(%ld)", eDVBLocalTimeHandler::getInstance()->nowTime()); + state=0; + haveData=0; + for (int i=0; i < 3; ++i) { - eDebug("[EPGC] wait for clock update"); - zapTimer.start(1000, 1); // restart Timer + seenSections[i].clear(); + calcedSections[i].clear(); } + + eDVBSectionFilterMask mask; + memset(&mask, 0, sizeof(mask)); + +#ifdef ENABLE_MHW_EPG + mask.pid = 0xD3; + mask.data[0] = 0x91; + mask.mask[0] = 0xFF; + m_MHWReader->connectRead(slot(*this, &eEPGCache::channel_data::readMHWData), m_MHWConn); + m_MHWReader->start(mask); + isRunning |= MHW; + memcpy(&m_MHWFilterMask, &mask, sizeof(eDVBSectionFilterMask)); + + mask.pid = 0x231; + mask.data[0] = 0xC8; + mask.mask[0] = 0xFF; + mask.data[1] = 0; + mask.mask[1] = 0xFF; + m_MHWReader2->connectRead(slot(*this, &eEPGCache::channel_data::readMHWData2), m_MHWConn2); + m_MHWReader2->start(mask); + isRunning |= MHW; + memcpy(&m_MHWFilterMask2, &mask, sizeof(eDVBSectionFilterMask)); + mask.data[1] = 0; + mask.mask[1] = 0; +#endif + + mask.pid = 0x12; + mask.flags = eDVBSectionFilterMask::rfCRC; + + mask.data[0] = 0x4E; + mask.mask[0] = 0xFE; + m_NowNextReader->connectRead(slot(*this, &eEPGCache::channel_data::readData), m_NowNextConn); + m_NowNextReader->start(mask); + isRunning |= NOWNEXT; + + mask.data[0] = 0x50; + mask.mask[0] = 0xF0; + m_ScheduleReader->connectRead(slot(*this, &eEPGCache::channel_data::readData), m_ScheduleConn); + m_ScheduleReader->start(mask); + isRunning |= SCHEDULE; + + mask.data[0] = 0x60; + m_ScheduleOtherReader->connectRead(slot(*this, &eEPGCache::channel_data::readData), m_ScheduleOtherConn); + m_ScheduleOtherReader->start(mask); + isRunning |= SCHEDULE_OTHER; + + abortTimer.start(7000,true); } -void eEPGCache::abortNonAvail() +void eEPGCache::channel_data::abortNonAvail() { if (!state) { - if ( !(haveData&2) && (isRunning&2) ) + if ( !(haveData&NOWNEXT) && (isRunning&NOWNEXT) ) { eDebug("[EPGC] abort non avail nownext reading"); - isRunning &= ~2; - if ( m_NowNextReader ) - m_NowNextReader->stop(); + isRunning &= ~NOWNEXT; + m_NowNextReader->stop(); + m_NowNextConn=0; } - if ( !(haveData&1) && (isRunning&1) ) + if ( !(haveData&SCHEDULE) && (isRunning&SCHEDULE) ) { eDebug("[EPGC] abort non avail schedule reading"); - isRunning &= ~1; + isRunning &= ~SCHEDULE; m_ScheduleReader->stop(); + m_ScheduleConn=0; } - if ( !(haveData&4) && (isRunning&4) ) + if ( !(haveData&SCHEDULE_OTHER) && (isRunning&SCHEDULE_OTHER) ) { eDebug("[EPGC] abort non avail schedule_other reading"); - isRunning &= ~4; + isRunning &= ~SCHEDULE_OTHER; m_ScheduleOtherReader->stop(); + m_ScheduleOtherConn=0; } - 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) ) +#ifdef ENABLE_MHW_EPG + if ( !(haveData&MHW) && (isRunning&MHW) ) { - eDebug("[eEPGCache] no demux!!"); - goto error4; + eDebug("[EPGC] abort non avail mhw reading"); + isRunning &= ~MHW; + m_MHWReader->stop(); + m_MHWConn=0; + m_MHWReader2->stop(); + m_MHWConn2=0; } +#endif + if ( isRunning ) + abortTimer.start(90000, true); 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 ) + ++state; + for (int i=0; i < 3; ++i) { - eDebug("[eEPGCache] couldnt initialize schedule other reader!!"); - goto error1; + seenSections[i].clear(); + calcedSections[i].clear(); } - 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 - } + ++state; } -void eEPGCache::changedService(const uniqueEPGKey &service) +void eEPGCache::channel_data::startChannel() { - current_service = service; - updateMap::iterator It = serviceLastUpdated.find( current_service ); - - int update; + pthread_mutex_lock(&channel_active); + updateMap::iterator It = cache->channelLastUpdated.find( channel->getChannelID() ); -// check if this is a subservice and this is only a dbox2 -// then we dont start epgcache on subservice change.. -// ever and ever.. + int update = ( It != cache->channelLastUpdated.end() ? ( UPDATE_INTERVAL - ( (eDVBLocalTimeHandler::getInstance()->nowTime()-It->second) * 1000 ) ) : ZAP_DELAY ); -// 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); - } + if (update < ZAP_DELAY) + update = ZAP_DELAY; - 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); - } + 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); } -void eEPGCache::abortEPG() +void eEPGCache::channel_data::abortEPG() { + for (int i=0; i < 3; ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } abortTimer.stop(); zapTimer.stop(); if (isRunning) { - if (isRunning & 1) + eDebug("[EPGC] abort caching events !!"); + if (isRunning & SCHEDULE) + { + isRunning &= ~SCHEDULE; + m_ScheduleReader->stop(); + m_ScheduleConn=0; + } + if (isRunning & NOWNEXT) { - isRunning &= ~1; - if ( m_ScheduleReader ) - m_ScheduleReader->stop(); + isRunning &= ~NOWNEXT; + m_NowNextReader->stop(); + m_NowNextConn=0; } - if (isRunning & 2) + if (isRunning & SCHEDULE_OTHER) { - isRunning &= ~2; - if ( m_NowNextReader ) - m_NowNextReader->stop(); + isRunning &= ~SCHEDULE_OTHER; + m_ScheduleOtherReader->stop(); + m_ScheduleOtherConn=0; } - if (isRunning & 4) +#ifdef ENABLE_MHW_EPG + if (isRunning & MHW) { - isRunning &= ~4; - if ( m_ScheduleOtherReader ) - m_ScheduleOtherReader->stop(); + isRunning &= ~MHW; + m_MHWReader->stop(); + m_MHWConn=0; + m_MHWReader2->stop(); + m_MHWConn2=0; } - eDebug("[EPGC] abort caching events !!"); - Lock(); - temp.clear(); - Unlock(); +#endif } +#ifdef ENABLE_PRIVATE_EPG + if (m_PrivateReader) + m_PrivateReader->stop(); + if (m_PrivateConn) + m_PrivateConn=0; +#endif + pthread_mutex_unlock(&channel_active); } -void eEPGCache::gotMessage( const Message &msg ) +void eEPGCache::channel_data::readData( const __u8 *data) { - switch (msg.type) + int source; + int map; + iDVBSectionReader *reader=NULL; + switch(data[0]) { - 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(); + case 0x4E ... 0x4F: + reader=m_NowNextReader; + source=NOWNEXT; + map=0; break; - case Message::quit: - quit(0); + case 0x50 ... 0x5F: + reader=m_ScheduleReader; + source=SCHEDULE; + map=1; break; - case Message::timeChanged: - cleanLoop(); + case 0x60 ... 0x6F: + reader=m_ScheduleOtherReader; + source=SCHEDULE_OTHER; + map=2; break; default: - eDebug("unhandled EPGCache Message!!"); - break; + eDebug("[EPGC] unknown table_id !!!"); + return; } -} - -void eEPGCache::gotBackMessage( const Message &msg ) -{ - switch (msg.type) + tidMap &seenSections = this->seenSections[map]; + tidMap &calcedSections = this->calcedSections[map]; + if ( state == 1 && calcedSections == seenSections || state > 1 ) { - 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) + eDebugNoNewLine("[EPGC] "); + switch (source) + { + case NOWNEXT: + m_NowNextConn=0; + eDebugNoNewLine("nownext"); + break; + case SCHEDULE: + m_ScheduleConn=0; + eDebugNoNewLine("schedule"); + break; + case SCHEDULE_OTHER: + m_ScheduleOtherConn=0; + eDebugNoNewLine("schedule other"); + break; + default: eDebugNoNewLine("unknown");break; + } + eDebug(" finished(%ld)", eDVBLocalTimeHandler::getInstance()->nowTime()); + if ( reader ) + reader->stop(); + isRunning &= ~source; + if (!isRunning) + finishEPG(); + } + else + { + eit_t *eit = (eit_t*) data; + __u32 sectionNo = data[0] << 24; + sectionNo |= data[3] << 16; + sectionNo |= data[4] << 8; + sectionNo |= eit->section_number; + + tidMap::iterator it = + seenSections.find(sectionNo); + + if ( it == seenSections.end() ) + { + seenSections.insert(sectionNo); + calcedSections.insert(sectionNo); + __u32 tmpval = sectionNo & 0xFFFFFF00; + __u8 incr = source == NOWNEXT ? 1 : 8; + for ( int i = 0; i <= eit->last_section_number; i+=incr ) { - startCache(next_service); - next_service = eServiceReferenceDVB(); + if ( i == eit->section_number ) + { + for (int x=i; x <= eit->segment_last_section_number; ++x) + calcedSections.insert(tmpval|(x&0xFF)); + } + else + calcedSections.insert(tmpval|(i&0xFF)); } - break; - default: - eDebug("unhandled EPGCache BackMessage!!"); - break; + cache->sectionRead(data, source, this); + } } } -void eEPGCache::thread() +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, const eventData *&result, int direction) +// if t == -1 we search the current event... { - nice(4); - load(); - cleanLoop(); - exec(); - save(); -} + singleLock s(cache_lock); + uniqueEPGKey key(handleGroup(service)); -void eEPGCache::load() -{ - FILE *f = fopen("/hdd/epg.dat", "r"); - if (f) + // check if EPG for this service is ready... + eventCache::iterator It = eventDB.find( key ); + if ( It != eventDB.end() && !It->second.first.empty() ) // entrys cached ? { - 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 ) + if (t==-1) + t = eDVBLocalTimeHandler::getInstance()->nowTime(); + timeMap::iterator i = direction <= 0 ? It->second.second.lower_bound(t) : // find > or equal + It->second.second.upper_bound(t); // just > + if ( i != It->second.second.end() ) { - fread( &size, sizeof(int), 1, f); - while(size--) + if ( direction < 0 || (direction == 0 && i->second->getStartTime() > t) ) { - uniqueEPGKey key; - eventMap evMap; - timeMap tmMap; - int size=0; - fread( &key, sizeof(uniqueEPGKey), 1, f); - fread( &size, sizeof(int), 1, f); - while(size--) + timeMap::iterator x = i; + --x; + if ( x != It->second.second.end() ) { - 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; + time_t start_time = x->second->getStartTime(); + if (direction >= 0) + { + if (t < start_time) + return -1; + if (t > (start_time+x->second->getDuration())) + return -1; + } + i = x; } - eventDB[key]=std::pair(evMap,tmMap); + else + return -1; } - eDebug("%d events read from /hdd/epg.dat.md5", cnt); + result = i->second; + return 0; } - fclose(f); } + return -1; } -void eEPGCache::save() +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, const eit_event_struct *&result, int direction) { - struct statfs s; - off64_t tmp; - if (statfs("/hdd", &s)<0) - tmp=0; - else + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventTime(service, t, data, direction); + if ( !ret && data ) + result = data->get(); + return ret; +} + +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, Event *& result, int direction) +{ + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventTime(service, t, data, direction); + if ( !ret && data ) + result = new Event((uint8_t*)data->get()); + return ret; +} + +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, ePtr &result, int direction) +{ + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventTime(service, t, data, direction); + if ( !ret && data ) + { + Event ev((uint8_t*)data->get()); + result = new eServiceEvent(); + const eServiceReferenceDVB &ref = (const eServiceReferenceDVB&)service; + ret = result->parseFrom(&ev, (ref.getTransportStreamID().get()<<16)|ref.getOriginalNetworkID().get()); + } + return ret; +} + +RESULT eEPGCache::lookupEventId(const eServiceReference &service, int event_id, const eventData *&result ) +{ + singleLock s(cache_lock); + uniqueEPGKey key(handleGroup(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() ) + { + result = i->second; + return 0; + } + else + { + result = 0; + eDebug("[EPGC] event %04x not found in epgcache", event_id); + } + } + return -1; +} + +RESULT eEPGCache::lookupEventId(const eServiceReference &service, int event_id, const eit_event_struct *&result) +{ + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventId(service, event_id, data); + if ( !ret && data ) + result = data->get(); + return ret; +} + +RESULT eEPGCache::lookupEventId(const eServiceReference &service, int event_id, Event *& result) +{ + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventId(service, event_id, data); + if ( !ret && data ) + result = new Event((uint8_t*)data->get()); + return ret; +} + +RESULT eEPGCache::lookupEventId(const eServiceReference &service, int event_id, ePtr &result) +{ + singleLock s(cache_lock); + const eventData *data=0; + RESULT ret = lookupEventId(service, event_id, data); + if ( !ret && data ) + { + Event ev((uint8_t*)data->get()); + result = new eServiceEvent(); + const eServiceReferenceDVB &ref = (const eServiceReferenceDVB&)service; + ret = result->parseFrom(&ev, (ref.getTransportStreamID().get()<<16)|ref.getOriginalNetworkID().get()); + } + return ret; +} + +RESULT eEPGCache::startTimeQuery(const eServiceReference &service, time_t begin, int minutes) +{ + eventCache::iterator It = eventDB.find(handleGroup(service)); + if ( It != eventDB.end() && It->second.second.size() ) + { + m_timemap_end = minutes != -1 ? It->second.second.upper_bound(begin+minutes*60) : It->second.second.end(); + if ( begin != -1 ) + { + m_timemap_cursor = It->second.second.lower_bound(begin); + if ( m_timemap_cursor != It->second.second.end() ) + { + if ( m_timemap_cursor->second->getStartTime() != begin ) + { + timeMap::iterator x = m_timemap_cursor; + --x; + if ( x != It->second.second.end() ) + { + time_t start_time = x->second->getStartTime(); + if ( begin > start_time && begin < (start_time+x->second->getDuration())) + m_timemap_cursor = x; + } + } + } + } + else + m_timemap_cursor = It->second.second.begin(); + const eServiceReferenceDVB &ref = (const eServiceReferenceDVB&)handleGroup(service); + currentQueryTsidOnid = (ref.getTransportStreamID().get()<<16) | ref.getOriginalNetworkID().get(); + return 0; + } + return -1; +} + +RESULT eEPGCache::getNextTimeEntry(const eventData *& result) +{ + if ( m_timemap_cursor != m_timemap_end ) + { + result = m_timemap_cursor++->second; + return 0; + } + return -1; +} + +RESULT eEPGCache::getNextTimeEntry(const eit_event_struct *&result) +{ + if ( m_timemap_cursor != m_timemap_end ) + { + result = m_timemap_cursor++->second->get(); + return 0; + } + return -1; +} + +RESULT eEPGCache::getNextTimeEntry(Event *&result) +{ + if ( m_timemap_cursor != m_timemap_end ) + { + result = new Event((uint8_t*)m_timemap_cursor++->second->get()); + return 0; + } + return -1; +} + +RESULT eEPGCache::getNextTimeEntry(ePtr &result) +{ + if ( m_timemap_cursor != m_timemap_end ) + { + Event ev((uint8_t*)m_timemap_cursor++->second->get()); + result = new eServiceEvent(); + return result->parseFrom(&ev, currentQueryTsidOnid); + } + return -1; +} + +void fillTuple(ePyObject tuple, char *argstring, int argcount, ePyObject service, ePtr &ptr, ePyObject nowTime, ePyObject service_name ) +{ + ePyObject tmp; + int pos=0; + while(pos < argcount) + { + bool inc_refcount=false; + switch(argstring[pos]) + { + case '0': // PyLong 0 + tmp = PyLong_FromLong(0); + break; + case 'I': // Event Id + tmp = ptr ? PyLong_FromLong(ptr->getEventId()) : ePyObject(); + break; + case 'B': // Event Begin Time + tmp = ptr ? PyLong_FromLong(ptr->getBeginTime()) : ePyObject(); + break; + case 'D': // Event Duration + tmp = ptr ? PyLong_FromLong(ptr->getDuration()) : ePyObject(); + break; + case 'T': // Event Title + tmp = ptr ? PyString_FromString(ptr->getEventName().c_str()) : ePyObject(); + break; + case 'S': // Event Short Description + tmp = ptr ? PyString_FromString(ptr->getShortDescription().c_str()) : ePyObject(); + break; + case 'E': // Event Extended Description + tmp = ptr ? PyString_FromString(ptr->getExtendedDescription().c_str()) : ePyObject(); + break; + case 'C': // Current Time + tmp = nowTime; + inc_refcount = true; + break; + case 'R': // service reference string + tmp = service; + inc_refcount = true; + break; + case 'N': // service name + tmp = service_name; + inc_refcount = true; + } + if (!tmp) + { + tmp = Py_None; + inc_refcount = true; + } + if (inc_refcount) + Py_INCREF(tmp); + PyTuple_SET_ITEM(tuple, pos++, tmp); + } +} + +int handleEvent(ePtr &ptr, ePyObject dest_list, char* argstring, int argcount, ePyObject service, ePyObject nowTime, ePyObject service_name, ePyObject convertFunc, ePyObject convertFuncArgs) +{ + if (convertFunc) + { + fillTuple(convertFuncArgs, argstring, argcount, service, ptr, nowTime, service_name); + ePyObject result = PyObject_CallObject(convertFunc, convertFuncArgs); + if (result) + { + if (service_name) + Py_DECREF(service_name); + if (nowTime) + Py_DECREF(nowTime); + Py_DECREF(convertFuncArgs); + Py_DECREF(dest_list); + PyErr_SetString(PyExc_StandardError, + "error in convertFunc execute"); + eDebug("error in convertFunc execute"); + return -1; + } + PyList_Append(dest_list, result); + Py_DECREF(result); + } + else + { + ePyObject tuple = PyTuple_New(argcount); + fillTuple(tuple, argstring, argcount, service, ptr, nowTime, service_name); + PyList_Append(dest_list, tuple); + Py_DECREF(tuple); + } + return 0; +} + +// here we get a python list +// the first entry in the list is a python string to specify the format of the returned tuples (in a list) +// 0 = PyLong(0) +// I = Event Id +// B = Event Begin Time +// D = Event Duration +// T = Event Title +// S = Event Short Description +// E = Event Extended Description +// C = Current Time +// R = Service Reference +// N = Service Name +// then for each service follows a tuple +// first tuple entry is the servicereference (as string... use the ref.toString() function) +// the second is the type of query +// 2 = event_id +// -1 = event before given start_time +// 0 = event intersects given start_time +// +1 = event after given start_time +// the third +// when type is eventid it is the event_id +// when type is time then it is the start_time ( 0 for now_time ) +// the fourth is the end_time .. ( optional .. for query all events in time range) + +PyObject *eEPGCache::lookupEvent(ePyObject list, ePyObject convertFunc) +{ + ePyObject convertFuncArgs; + int argcount=0; + char *argstring=NULL; + if (!PyList_Check(list)) + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("no list"); + return NULL; + } + int listIt=0; + int listSize=PyList_Size(list); + if (!listSize) + { + PyErr_SetString(PyExc_StandardError, + "not params given"); + eDebug("not params given"); + return NULL; + } + else + { + ePyObject argv=PyList_GET_ITEM(list, 0); // borrowed reference! + if (PyString_Check(argv)) + { + argstring = PyString_AS_STRING(argv); + ++listIt; + } + else + argstring = "I"; // just event id as default + argcount = strlen(argstring); +// eDebug("have %d args('%s')", argcount, argstring); + } + if (convertFunc) + { + if (!PyCallable_Check(convertFunc)) + { + PyErr_SetString(PyExc_StandardError, + "convertFunc must be callable"); + eDebug("convertFunc is not callable"); + return NULL; + } + convertFuncArgs = PyTuple_New(argcount); + } + + ePyObject nowTime = strchr(argstring, 'C') ? + PyLong_FromLong(eDVBLocalTimeHandler::getInstance()->nowTime()) : + ePyObject(); + + bool must_get_service_name = strchr(argstring, 'N') ? true : false; + + // create dest list + ePyObject dest_list=PyList_New(0); + while(listSize > listIt) + { + ePyObject item=PyList_GET_ITEM(list, listIt++); // borrowed reference! + if (PyTuple_Check(item)) + { + bool service_changed=false; + int type=0; + long event_id=-1; + time_t stime=-1; + int minutes=0; + int tupleSize=PyTuple_Size(item); + int tupleIt=0; + ePyObject service; + while(tupleSize > tupleIt) // parse query args + { + ePyObject entry=PyTuple_GET_ITEM(item, tupleIt); // borrowed reference! + switch(tupleIt++) + { + case 0: + { + if (!PyString_Check(entry)) + { + eDebug("tuple entry 0 is no a string"); + goto skip_entry; + } + service = entry; + break; + } + case 1: + type=PyInt_AsLong(entry); + if (type < -1 || type > 2) + { + eDebug("unknown type %d", type); + goto skip_entry; + } + break; + case 2: + event_id=stime=PyInt_AsLong(entry); + break; + case 3: + minutes=PyInt_AsLong(entry); + break; + default: + eDebug("unneeded extra argument"); + break; + } + } + eServiceReference ref(handleGroup(eServiceReference(PyString_AS_STRING(service)))); + if (ref.type != eServiceReference::idDVB) + { + eDebug("service reference for epg query is not valid"); + continue; + } + + // redirect subservice querys to parent service + eServiceReferenceDVB &dvb_ref = (eServiceReferenceDVB&)ref; + if (dvb_ref.getParentTransportStreamID().get()) // linkage subservice + { + eServiceCenterPtr service_center; + if (!eServiceCenter::getPrivInstance(service_center)) + { + dvb_ref.setTransportStreamID( dvb_ref.getParentTransportStreamID() ); + dvb_ref.setServiceID( dvb_ref.getParentServiceID() ); + dvb_ref.setParentTransportStreamID(eTransportStreamID(0)); + dvb_ref.setParentServiceID(eServiceID(0)); + dvb_ref.name=""; + service = PyString_FromString(dvb_ref.toString().c_str()); + service_changed = true; + } + } + + ePyObject service_name; + if (must_get_service_name) + { + ePtr sptr; + eServiceCenterPtr service_center; + eServiceCenter::getPrivInstance(service_center); + if (service_center) + { + service_center->info(ref, sptr); + if (sptr) + { + std::string name; + sptr->getName(ref, name); + if (name.length()) + service_name = PyString_FromString(name.c_str()); + } + } + if (!service_name) + service_name = PyString_FromString(""); + } + if (minutes) + { + Lock(); + if (!startTimeQuery(ref, stime, minutes)) + { + ePtr ptr; + while (!getNextTimeEntry(ptr)) + { + if (handleEvent(ptr, dest_list, argstring, argcount, service, nowTime, service_name, convertFunc, convertFuncArgs)) + { + Unlock(); + return 0; // error + } + } + } + Unlock(); + } + else + { + ePtr ptr; + if (stime) + { + if (type == 2) + lookupEventId(ref, event_id, ptr); + else + lookupEventTime(ref, stime, ptr, type); + } + if (handleEvent(ptr, dest_list, argstring, argcount, service, nowTime, service_name, convertFunc, convertFuncArgs)) + return 0; // error + } + if (service_changed) + Py_DECREF(service); + if (service_name) + Py_DECREF(service_name); + } +skip_entry: + ; + } + if (convertFuncArgs) + Py_DECREF(convertFuncArgs); + if (nowTime) + Py_DECREF(nowTime); + return dest_list; +} + +void fillTuple2(ePyObject tuple, const char *argstring, int argcount, eventData *evData, ePtr &ptr, ePyObject service_name, ePyObject service_reference) +{ + ePyObject tmp; + int pos=0; + while(pos < argcount) + { + bool inc_refcount=false; + switch(argstring[pos]) + { + case '0': // PyLong 0 + tmp = PyLong_FromLong(0); + break; + case 'I': // Event Id + tmp = PyLong_FromLong(evData->getEventID()); + break; + case 'B': // Event Begin Time + if (ptr) + tmp = ptr ? PyLong_FromLong(ptr->getBeginTime()) : ePyObject(); + else + tmp = PyLong_FromLong(evData->getStartTime()); + break; + case 'D': // Event Duration + if (ptr) + tmp = ptr ? PyLong_FromLong(ptr->getDuration()) : ePyObject(); + else + tmp = PyLong_FromLong(evData->getDuration()); + break; + case 'T': // Event Title + tmp = ptr ? PyString_FromString(ptr->getEventName().c_str()) : ePyObject(); + break; + case 'S': // Event Short Description + tmp = ptr ? PyString_FromString(ptr->getShortDescription().c_str()) : ePyObject(); + break; + case 'E': // Event Extended Description + tmp = ptr ? PyString_FromString(ptr->getExtendedDescription().c_str()) : ePyObject(); + break; + case 'R': // service reference string + tmp = service_reference; + inc_refcount = true; + break; + case 'N': // service name + tmp = service_name; + inc_refcount = true; + break; + } + if (!tmp) + { + tmp = Py_None; + inc_refcount = true; + } + if (inc_refcount) + Py_INCREF(tmp); + PyTuple_SET_ITEM(tuple, pos++, tmp); + } +} + +// here we get a python tuple +// the first entry in the tuple is a python string to specify the format of the returned tuples (in a list) +// I = Event Id +// B = Event Begin Time +// D = Event Duration +// T = Event Title +// S = Event Short Description +// E = Event Extended Description +// R = Service Reference +// N = Service Name +// the second tuple entry is the MAX matches value +// the third tuple entry is the type of query +// 0 = search for similar broadcastings (SIMILAR_BROADCASTINGS_SEARCH) +// 1 = search events with exactly title name (EXAKT_TITLE_SEARCH) +// 2 = search events with text in title name (PARTIAL_TITLE_SEARCH) +// when type is 0 (SIMILAR_BROADCASTINGS_SEARCH) +// the fourth is the servicereference string +// the fifth is the eventid +// when type is 1 or 2 (EXAKT_TITLE_SEARCH or PARTIAL_TITLE_SEARCH) +// the fourth is the search text +// the fifth is +// 0 = case sensitive (CASE_CHECK) +// 1 = case insensitive (NO_CASECHECK) + +PyObject *eEPGCache::search(ePyObject arg) +{ + ePyObject ret; + int descridx = -1; + __u32 descr[512]; + int eventid = -1; + const char *argstring=0; + char *refstr=0; + int argcount=0; + int querytype=-1; + bool needServiceEvent=false; + int maxmatches=0; + + if (PyTuple_Check(arg)) + { + int tuplesize=PyTuple_Size(arg); + if (tuplesize > 0) + { + ePyObject obj = PyTuple_GET_ITEM(arg,0); + if (PyString_Check(obj)) + { + argcount = PyString_GET_SIZE(obj); + argstring = PyString_AS_STRING(obj); + for (int i=0; i < argcount; ++i) + switch(argstring[i]) + { + case 'S': + case 'E': + case 'T': + needServiceEvent=true; + default: + break; + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("tuple arg 0 is not a string"); + return NULL; + } + } + if (tuplesize > 1) + maxmatches = PyLong_AsLong(PyTuple_GET_ITEM(arg, 1)); + if (tuplesize > 2) + { + querytype = PyLong_AsLong(PyTuple_GET_ITEM(arg, 2)); + if (tuplesize > 4 && querytype == 0) + { + ePyObject obj = PyTuple_GET_ITEM(arg, 3); + if (PyString_Check(obj)) + { + refstr = PyString_AS_STRING(obj); + eServiceReferenceDVB ref(refstr); + if (ref.valid()) + { + eventid = PyLong_AsLong(PyTuple_GET_ITEM(arg, 4)); + singleLock s(cache_lock); + const eventData *evData = 0; + lookupEventId(ref, eventid, evData); + if (evData) + { + __u8 *data = evData->EITdata; + int tmp = evData->ByteSize-12; + __u32 *p = (__u32*)(data+12); + // search short and extended event descriptors + while(tmp>0) + { + __u32 crc = *p++; + descriptorMap::iterator it = + eventData::descriptors.find(crc); + if (it != eventData::descriptors.end()) + { + __u8 *descr_data = it->second.second; + switch(descr_data[0]) + { + case 0x4D ... 0x4E: + descr[++descridx]=crc; + default: + break; + } + } + tmp-=4; + } + } + if (descridx<0) + eDebug("event not found"); + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("tuple arg 4 is not a valid service reference string"); + return NULL; + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("tuple arg 4 is not a string"); + return NULL; + } + } + else if (tuplesize > 4 && (querytype == 1 || querytype == 2) ) + { + ePyObject obj = PyTuple_GET_ITEM(arg, 3); + if (PyString_Check(obj)) + { + int casetype = PyLong_AsLong(PyTuple_GET_ITEM(arg, 4)); + const char *str = PyString_AS_STRING(obj); + int textlen = PyString_GET_SIZE(obj); + if (querytype == 1) + eDebug("lookup for events with '%s' as title(%s)", str, casetype?"ignore case":"case sensitive"); + else + eDebug("lookup for events with '%s' in title(%s)", str, casetype?"ignore case":"case sensitive"); + singleLock s(cache_lock); + for (descriptorMap::iterator it(eventData::descriptors.begin()); + it != eventData::descriptors.end() && descridx < 511; ++it) + { + __u8 *data = it->second.second; + if ( data[0] == 0x4D ) // short event descriptor + { + int title_len = data[5]; + if ( querytype == 1 ) + { + if (title_len > textlen) + continue; + else if (title_len < textlen) + continue; + if ( casetype ) + { + if ( !strncasecmp((const char*)data+6, str, title_len) ) + { +// std::string s((const char*)data+6, title_len); +// eDebug("match1 %s %s", str, s.c_str() ); + descr[++descridx] = it->first; + } + } + else if ( !strncmp((const char*)data+6, str, title_len) ) + { +// std::string s((const char*)data+6, title_len); +// eDebug("match2 %s %s", str, s.c_str() ); + descr[++descridx] = it->first; + } + } + else + { + int idx=0; + while((title_len-idx) >= textlen) + { + if (casetype) + { + if (!strncasecmp((const char*)data+6+idx, str, textlen) ) + { + descr[++descridx] = it->first; +// std::string s((const char*)data+6, title_len); +// eDebug("match 3 %s %s", str, s.c_str() ); + break; + } + else if (!strncmp((const char*)data+6+idx, str, textlen) ) + { + descr[++descridx] = it->first; +// std::string s((const char*)data+6, title_len); +// eDebug("match 4 %s %s", str, s.c_str() ); + break; + } + } + ++idx; + } + } + } + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("tuple arg 4 is not a string"); + return NULL; + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("tuple arg 3(%d) is not a known querytype(0, 1, 2)", querytype); + return NULL; + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("not enough args in tuple"); + return NULL; + } + } + else + { + PyErr_SetString(PyExc_StandardError, + "type error"); + eDebug("arg 0 is not a tuple"); + return NULL; + } + + if (descridx > -1) + { + int maxcount=maxmatches; + eServiceReferenceDVB ref(refstr?(const eServiceReferenceDVB&)handleGroup(eServiceReference(refstr)):eServiceReferenceDVB("")); + // ref is only valid in SIMILAR_BROADCASTING_SEARCH + // in this case we start searching with the base service + bool first = ref.valid() ? true : false; + singleLock s(cache_lock); + eventCache::iterator cit(ref.valid() ? eventDB.find(ref) : eventDB.begin()); + while(cit != eventDB.end() && maxcount) + { + if ( ref.valid() && !first && cit->first == ref ) + { + // do not scan base service twice ( only in SIMILAR BROADCASTING SEARCH ) + ++cit; + continue; + } + ePyObject service_name; + ePyObject service_reference; + timeMap &evmap = cit->second.second; + // check all events + for (timeMap::iterator evit(evmap.begin()); evit != evmap.end() && maxcount; ++evit) + { + int evid = evit->second->getEventID(); + if ( evid == eventid) + continue; + __u8 *data = evit->second->EITdata; + int tmp = evit->second->ByteSize-12; + __u32 *p = (__u32*)(data+12); + // check if any of our descriptor used by this event + int cnt=-1; + while(tmp>0) + { + __u32 crc32 = *p++; + for ( int i=0; i <= descridx; ++i) + { + if (descr[i] == crc32) // found... + ++cnt; + } + tmp-=4; + } + if ( (querytype == 0 && cnt == descridx) || + ((querytype == 1 || querytype == 2) && cnt != -1) ) + { + const uniqueEPGKey &service = cit->first; + eServiceReference ref = + eDVBDB::getInstance()->searchReference(service.tsid, service.onid, service.sid); + if (ref.valid()) + { + // create servive event + ePtr ptr; + if (needServiceEvent) + { + lookupEventId(ref, evid, ptr); + if (!ptr) + eDebug("event not found !!!!!!!!!!!"); + } + // create service name + if (!service_name && strchr(argstring,'N')) + { + ePtr sptr; + eServiceCenterPtr service_center; + eServiceCenter::getPrivInstance(service_center); + if (service_center) + { + service_center->info(ref, sptr); + if (sptr) + { + std::string name; + sptr->getName(ref, name); + if (name.length()) + service_name = PyString_FromString(name.c_str()); + } + } + if (!service_name) + service_name = PyString_FromString(""); + } + // create servicereference string + if (!service_reference && strchr(argstring,'R')) + service_reference = PyString_FromString(ref.toString().c_str()); + // create list + if (!ret) + ret = PyList_New(0); + // create tuple + ePyObject tuple = PyTuple_New(argcount); + // fill tuple + fillTuple2(tuple, argstring, argcount, evit->second, ptr, service_name, service_reference); + PyList_Append(ret, tuple); + Py_DECREF(tuple); + --maxcount; + } + } + } + if (service_name) + Py_DECREF(service_name); + if (service_reference) + Py_DECREF(service_reference); + if (first) + { + // now start at first service in epgcache database ( only in SIMILAR BROADCASTING SEARCH ) + first=false; + cit=eventDB.begin(); + } + else + ++cit; + } + } + + if (!ret) + { + Py_INCREF(Py_None); + ret=Py_None; + } + + return ret; +} + +#ifdef ENABLE_PRIVATE_EPG +#include +#include +#include + +void eEPGCache::PMTready(eDVBServicePMTHandler *pmthandler) +{ + ePtr > ptr; + if (!pmthandler->getPMT(ptr) && ptr) + { + std::vector::const_iterator i; + for (i = ptr->getSections().begin(); i != ptr->getSections().end(); ++i) + { + const ProgramMapSection &pmt = **i; + + ElementaryStreamInfoConstIterator es; + for (es = pmt.getEsInfo()->begin(); es != pmt.getEsInfo()->end(); ++es) + { + int tmp=0; + switch ((*es)->getType()) + { + case 0x05: // private + for (DescriptorConstIterator desc = (*es)->getDescriptors()->begin(); + desc != (*es)->getDescriptors()->end(); ++desc) + { + switch ((*desc)->getTag()) + { + case PRIVATE_DATA_SPECIFIER_DESCRIPTOR: + if (((PrivateDataSpecifierDescriptor*)(*desc))->getPrivateDataSpecifier() == 190) + tmp |= 1; + break; + case 0x90: + { + UnknownDescriptor *descr = (UnknownDescriptor*)*desc; + int descr_len = descr->getLength(); + if (descr_len == 4) + { + uint8_t data[descr_len+2]; + descr->writeToBuffer(data); + if ( !data[2] && !data[3] && data[4] == 0xFF && data[5] == 0xFF ) + tmp |= 2; + } + break; + } + default: + break; + } + } + default: + break; + } + if (tmp==3) + { + eServiceReferenceDVB ref; + if (!pmthandler->getServiceReference(ref)) + { + int pid = (*es)->getPid(); + messages.send(Message(Message::got_private_pid, ref, pid)); + return; + } + } + } + } + } + else + eDebug("PMTready but no pmt!!"); +} + +struct date_time +{ + __u8 data[5]; + time_t tm; + date_time( const date_time &a ) + { + memcpy(data, a.data, 5); + tm = a.tm; + } + date_time( const __u8 data[5]) + { + memcpy(this->data, data, 5); + tm = parseDVBtime(data[0], data[1], data[2], data[3], data[4]); + } + date_time() { - tmp=s.f_blocks; - tmp*=s.f_bsize; } + const __u8& operator[](int pos) const + { + return data[pos]; + } +}; - // prevent writes to builtin flash - if ( tmp < 1024*1024*50 ) // storage size < 50MB - return; +struct less_datetime +{ + bool operator()( const date_time &a, const date_time &b ) const + { + return abs(a.tm-b.tm) < 360 ? false : a.tm < b.tm; + } +}; - // check for enough free space on storage - tmp=s.f_bfree; - tmp*=s.f_bsize; - if ( tmp < (eventData::CacheSize*12)/10 ) // 20% overhead - return; +void eEPGCache::privateSectionRead(const uniqueEPGKey ¤t_service, const __u8 *data) +{ + contentMap &content_time_table = content_time_tables[current_service]; + singleLock s(cache_lock); + std::map< date_time, std::list, less_datetime > start_times; + eventMap &evMap = eventDB[current_service].first; + timeMap &tmMap = eventDB[current_service].second; + int ptr=8; + int content_id = data[ptr++] << 24; + content_id |= data[ptr++] << 16; + content_id |= data[ptr++] << 8; + content_id |= data[ptr++]; + + contentTimeMap &time_event_map = + content_time_table[content_id]; + for ( contentTimeMap::iterator it( time_event_map.begin() ); + it != time_event_map.end(); ++it ) + { + eventMap::iterator evIt( evMap.find(it->second.second) ); + if ( evIt != evMap.end() ) + { + delete evIt->second; + evMap.erase(evIt); + } + tmMap.erase(it->second.first); + } + time_event_map.clear(); - FILE *f = fopen("/hdd/epg.dat", "w"); - int cnt=0; - if ( f ) + __u8 duration[3]; + memcpy(duration, data+ptr, 3); + ptr+=3; + int duration_sec = + fromBCD(duration[0])*3600+fromBCD(duration[1])*60+fromBCD(duration[2]); + + const __u8 *descriptors[65]; + const __u8 **pdescr = descriptors; + + int descriptors_length = (data[ptr++]&0x0F) << 8; + descriptors_length |= data[ptr++]; + while ( descriptors_length > 0 ) { - int size = eventDB.size(); - fwrite( &size, sizeof(int), 1, f ); - for (eventCache::iterator service_it(eventDB.begin()); service_it != eventDB.end(); ++service_it) + int descr_type = data[ptr]; + int descr_len = data[ptr+1]; + descriptors_length -= (descr_len+2); + if ( descr_type == 0xf2 ) { - 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) + ptr+=2; + int tsid = data[ptr++] << 8; + tsid |= data[ptr++]; + int onid = data[ptr++] << 8; + onid |= data[ptr++]; + int sid = data[ptr++] << 8; + sid |= data[ptr++]; + +// WORKAROUND for wrong transmitted epg data (01.08.2006) + if ( onid == 0x85 ) { - 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; + switch( (tsid << 16) | sid ) + { + case 0x01030b: sid = 0x1b; tsid = 4; break; // Premiere Win + case 0x0300f0: sid = 0xe0; tsid = 2; break; + case 0x0300f1: sid = 0xe1; tsid = 2; break; + case 0x0300f5: sid = 0xdc; break; + case 0x0400d2: sid = 0xe2; tsid = 0x11; break; + case 0x1100d3: sid = 0xe3; break; + } + } +//////////////////////////////////////////// + + uniqueEPGKey service( sid, onid, tsid ); + descr_len -= 6; + while( descr_len > 0 ) + { + __u8 datetime[5]; + datetime[0] = data[ptr++]; + datetime[1] = data[ptr++]; + int tmp_len = data[ptr++]; + descr_len -= 3; + while( tmp_len > 0 ) + { + memcpy(datetime+2, data+ptr, 3); + ptr+=3; + descr_len -= 3; + tmp_len -= 3; + start_times[datetime].push_back(service); + } } } - eDebug("%d events written to /hdd/epg.dat", cnt); - fclose(f); - unsigned char md5[16]; - if (!md5_file("/hdd/epg.dat", 1, md5)) + else { - FILE *f = fopen("/hdd/epg.dat.md5", "w"); - if (f) + *pdescr++=data+ptr; + ptr += 2; + ptr += descr_len; + } + } + __u8 event[4098]; + eit_event_struct *ev_struct = (eit_event_struct*) event; + ev_struct->running_status = 0; + ev_struct->free_CA_mode = 1; + memcpy(event+7, duration, 3); + ptr = 12; + const __u8 **d=descriptors; + while ( d < pdescr ) + { + memcpy(event+ptr, *d, ((*d)[1])+2); + ptr+=(*d++)[1]; + ptr+=2; + } + for ( std::map< date_time, std::list >::iterator it(start_times.begin()); it != start_times.end(); ++it ) + { + time_t now = eDVBLocalTimeHandler::getInstance()->nowTime(); + if ( (it->first.tm + duration_sec) < now ) + continue; + memcpy(event+2, it->first.data, 5); + int bptr = ptr; + int cnt=0; + for (std::list::iterator i(it->second.begin()); i != it->second.end(); ++i) + { + event[bptr++] = 0x4A; + __u8 *len = event+(bptr++); + event[bptr++] = (i->tsid & 0xFF00) >> 8; + event[bptr++] = (i->tsid & 0xFF); + event[bptr++] = (i->onid & 0xFF00) >> 8; + event[bptr++] = (i->onid & 0xFF); + event[bptr++] = (i->sid & 0xFF00) >> 8; + event[bptr++] = (i->sid & 0xFF); + event[bptr++] = 0xB0; + bptr += sprintf((char*)(event+bptr), "Option %d", ++cnt); + *len = ((event+bptr) - len)-1; + } + int llen = bptr - 12; + ev_struct->descriptors_loop_length_hi = (llen & 0xF00) >> 8; + ev_struct->descriptors_loop_length_lo = (llen & 0xFF); + + time_t stime = it->first.tm; + while( tmMap.find(stime) != tmMap.end() ) + ++stime; + event[6] += (stime - it->first.tm); + __u16 event_id = 0; + while( evMap.find(event_id) != evMap.end() ) + ++event_id; + event[0] = (event_id & 0xFF00) >> 8; + event[1] = (event_id & 0xFF); + time_event_map[it->first.tm]=std::pair(stime, event_id); + eventData *d = new eventData( ev_struct, bptr, PRIVATE ); + evMap[event_id] = d; + tmMap[stime] = d; + } +} + +void eEPGCache::channel_data::startPrivateReader() +{ + eDVBSectionFilterMask mask; + memset(&mask, 0, sizeof(mask)); + mask.pid = m_PrivatePid; + mask.flags = eDVBSectionFilterMask::rfCRC; + mask.data[0] = 0xA0; + mask.mask[0] = 0xFF; + eDebug("[EPGC] start privatefilter for pid %04x and version %d", m_PrivatePid, m_PrevVersion); + if (m_PrevVersion != -1) + { + mask.data[3] = m_PrevVersion << 1; + mask.mask[3] = 0x3E; + mask.mode[3] = 0x3E; + } + seenPrivateSections.clear(); + if (!m_PrivateConn) + m_PrivateReader->connectRead(slot(*this, &eEPGCache::channel_data::readPrivateData), m_PrivateConn); + m_PrivateReader->start(mask); +} + +void eEPGCache::channel_data::readPrivateData( const __u8 *data) +{ + if ( seenPrivateSections.find( data[6] ) == seenPrivateSections.end() ) + { + cache->privateSectionRead(m_PrivateService, data); + seenPrivateSections.insert(data[6]); + } + if ( seenPrivateSections.size() == (unsigned int)(data[7] + 1) ) + { + eDebug("[EPGC] private finished"); + eDVBChannelID chid = channel->getChannelID(); + int tmp = chid.original_network_id.get(); + tmp |= 0x80000000; // we use highest bit as private epg indicator + chid.original_network_id = tmp; + cache->channelLastUpdated[chid] = eDVBLocalTimeHandler::getInstance()->nowTime(); + m_PrevVersion = (data[5] & 0x3E) >> 1; + startPrivateReader(); + } +} + +#endif // ENABLE_PRIVATE_EPG + +#ifdef ENABLE_MHW_EPG +void eEPGCache::channel_data::cleanup() +{ + m_channels.clear(); + m_themes.clear(); + m_titles.clear(); + m_program_ids.clear(); +} + +__u8 *eEPGCache::channel_data::delimitName( __u8 *in, __u8 *out, int len_in ) +{ + // Names in mhw structs are not strings as they are not '\0' terminated. + // This function converts the mhw name into a string. + // Constraint: "length of out" = "length of in" + 1. + int i; + for ( i=0; i < len_in; i++ ) + out[i] = in[i]; + + i = len_in - 1; + while ( ( i >=0 ) && ( out[i] == 0x20 ) ) + i--; + + out[i+1] = 0; + return out; +} + +void eEPGCache::channel_data::timeMHW2DVB( u_char hours, u_char minutes, u_char *return_time) +// For time of day +{ + return_time[0] = toBCD( hours ); + return_time[1] = toBCD( minutes ); + return_time[2] = 0; +} + +void eEPGCache::channel_data::timeMHW2DVB( int minutes, u_char *return_time) +{ + timeMHW2DVB( int(minutes/60), minutes%60, return_time ); +} + +void eEPGCache::channel_data::timeMHW2DVB( u_char day, u_char hours, u_char minutes, u_char *return_time) +// For date plus time of day +{ + // Remove offset in mhw time. + __u8 local_hours = hours; + if ( hours >= 16 ) + local_hours -= 4; + else if ( hours >= 8 ) + local_hours -= 2; + + // As far as we know all mhw time data is sent in central Europe time zone. + // So, temporarily set timezone to western europe + time_t dt = eDVBLocalTimeHandler::getInstance()->nowTime(); + + char *old_tz = getenv( "TZ" ); + putenv("TZ=CET-1CEST,M3.5.0/2,M10.5.0/3"); + tzset(); + + tm *localnow = localtime( &dt ); + + if (day == 7) + day = 0; + if ( day + 1 < localnow->tm_wday ) // day + 1 to prevent old events to show for next week. + day += 7; + if (local_hours <= 5) + day++; + + dt += 3600*24*(day - localnow->tm_wday); // Shift dt to the recording date (local time zone). + dt += 3600*(local_hours - localnow->tm_hour); // Shift dt to the recording hour. + + tm *recdate = gmtime( &dt ); // This will also take care of DST. + + if ( old_tz == NULL ) + unsetenv( "TZ" ); + else + putenv( old_tz ); + tzset(); + + // Calculate MJD according to annex in ETSI EN 300 468 + int l=0; + if ( recdate->tm_mon <= 1 ) // Jan or Feb + l=1; + int mjd = 14956 + recdate->tm_mday + int( (recdate->tm_year - l) * 365.25) + + int( (recdate->tm_mon + 2 + l * 12) * 30.6001); + + return_time[0] = (mjd & 0xFF00)>>8; + return_time[1] = mjd & 0xFF; + + timeMHW2DVB( recdate->tm_hour, minutes, return_time+2 ); +} + +void eEPGCache::channel_data::storeTitle(std::map<__u32, mhw_title_t>::iterator itTitle, std::string sumText, const __u8 *data) +// data is borrowed from calling proc to save memory space. +{ + __u8 name[34]; + // For each title a separate EIT packet will be sent to eEPGCache::sectionRead() + bool isMHW2 = itTitle->second.mhw2_mjd_hi || itTitle->second.mhw2_mjd_lo || + itTitle->second.mhw2_duration_hi || itTitle->second.mhw2_duration_lo; + + eit_t *packet = (eit_t *) data; + packet->table_id = 0x50; + packet->section_syntax_indicator = 1; + packet->service_id_hi = m_channels[ itTitle->second.channel_id - 1 ].channel_id_hi; + packet->service_id_lo = m_channels[ itTitle->second.channel_id - 1 ].channel_id_lo; + packet->version_number = 0; // eEPGCache::sectionRead() will dig this for the moment + packet->current_next_indicator = 0; + packet->section_number = 0; // eEPGCache::sectionRead() will dig this for the moment + packet->last_section_number = 0; // eEPGCache::sectionRead() will dig this for the moment + packet->transport_stream_id_hi = m_channels[ itTitle->second.channel_id - 1 ].transport_stream_id_hi; + packet->transport_stream_id_lo = m_channels[ itTitle->second.channel_id - 1 ].transport_stream_id_lo; + packet->original_network_id_hi = m_channels[ itTitle->second.channel_id - 1 ].network_id_hi; + packet->original_network_id_lo = m_channels[ itTitle->second.channel_id - 1 ].network_id_lo; + packet->segment_last_section_number = 0; // eEPGCache::sectionRead() will dig this for the moment + packet->segment_last_table_id = 0x50; + + __u8 *title = isMHW2 ? ((__u8*)(itTitle->second.title))-4 : (__u8*)itTitle->second.title; + std::string prog_title = (char *) delimitName( title, name, isMHW2 ? 33 : 23 ); + int prog_title_length = prog_title.length(); + + int packet_length = EIT_SIZE + EIT_LOOP_SIZE + EIT_SHORT_EVENT_DESCRIPTOR_SIZE + + prog_title_length + 1; + + eit_event_t *event_data = (eit_event_t *) (data + EIT_SIZE); + event_data->event_id_hi = (( itTitle->first ) >> 8 ) & 0xFF; + event_data->event_id_lo = ( itTitle->first ) & 0xFF; + + if (isMHW2) + { + u_char *data = (u_char*) event_data; + data[2] = itTitle->second.mhw2_mjd_hi; + data[3] = itTitle->second.mhw2_mjd_lo; + data[4] = itTitle->second.mhw2_hours; + data[5] = itTitle->second.mhw2_minutes; + data[6] = itTitle->second.mhw2_seconds; + timeMHW2DVB( HILO(itTitle->second.mhw2_duration), data+7 ); + } + else + { + timeMHW2DVB( itTitle->second.dh.day, itTitle->second.dh.hours, itTitle->second.ms.minutes, + (u_char *) event_data + 2 ); + timeMHW2DVB( HILO(itTitle->second.duration), (u_char *) event_data+7 ); + } + + event_data->running_status = 0; + event_data->free_CA_mode = 0; + int descr_ll = EIT_SHORT_EVENT_DESCRIPTOR_SIZE + 1 + prog_title_length; + + eit_short_event_descriptor_struct *short_event_descriptor = + (eit_short_event_descriptor_struct *) ( (u_char *) event_data + EIT_LOOP_SIZE); + short_event_descriptor->descriptor_tag = EIT_SHORT_EVENT_DESCRIPTOR; + short_event_descriptor->descriptor_length = EIT_SHORT_EVENT_DESCRIPTOR_SIZE + + prog_title_length - 1; + short_event_descriptor->language_code_1 = 'e'; + short_event_descriptor->language_code_2 = 'n'; + short_event_descriptor->language_code_3 = 'g'; + short_event_descriptor->event_name_length = prog_title_length; + u_char *event_name = (u_char *) short_event_descriptor + EIT_SHORT_EVENT_DESCRIPTOR_SIZE; + memcpy(event_name, prog_title.c_str(), prog_title_length); + + // Set text length + event_name[prog_title_length] = 0; + + if ( sumText.length() > 0 ) + // There is summary info + { + unsigned int sum_length = sumText.length(); + if ( sum_length + short_event_descriptor->descriptor_length <= 0xff ) + // Store summary in short event descriptor + { + // Increase all relevant lengths + event_name[prog_title_length] = sum_length; + short_event_descriptor->descriptor_length += sum_length; + packet_length += sum_length; + descr_ll += sum_length; + sumText.copy( (char *) event_name+prog_title_length+1, sum_length ); + } + else + // Store summary in extended event descriptors + { + int remaining_sum_length = sumText.length(); + int nbr_descr = int(remaining_sum_length/247) + 1; + for ( int i=0; i < nbr_descr; i++) + // Loop once per extended event descriptor { - fwrite( md5, 16, 1, f); - fclose(f); + eit_extended_descriptor_struct *ext_event_descriptor = (eit_extended_descriptor_struct *) (data + packet_length); + sum_length = remaining_sum_length > 247 ? 247 : remaining_sum_length; + remaining_sum_length -= sum_length; + packet_length += 8 + sum_length; + descr_ll += 8 + sum_length; + + ext_event_descriptor->descriptor_tag = EIT_EXTENDED_EVENT_DESCRIPOR; + ext_event_descriptor->descriptor_length = sum_length + 6; + ext_event_descriptor->descriptor_number = i; + ext_event_descriptor->last_descriptor_number = nbr_descr - 1; + ext_event_descriptor->iso_639_2_language_code_1 = 'e'; + ext_event_descriptor->iso_639_2_language_code_2 = 'n'; + ext_event_descriptor->iso_639_2_language_code_3 = 'g'; + u_char *the_text = (u_char *) ext_event_descriptor + 8; + the_text[-2] = 0; + the_text[-1] = sum_length; + sumText.copy( (char *) the_text, sum_length, sumText.length() - sum_length - remaining_sum_length ); } } } + + if (!isMHW2) + { + // Add content descriptor + u_char *descriptor = (u_char *) data + packet_length; + packet_length += 4; + descr_ll += 4; + + int content_id = 0; + std::string content_descr = (char *) delimitName( m_themes[itTitle->second.theme_id].name, name, 15 ); + if ( content_descr.find( "FILM" ) != std::string::npos ) + content_id = 0x10; + else if ( content_descr.find( "SPORT" ) != std::string::npos ) + content_id = 0x40; + + descriptor[0] = 0x54; + descriptor[1] = 2; + descriptor[2] = content_id; + descriptor[3] = 0; + } + + event_data->descriptors_loop_length_hi = (descr_ll & 0xf00)>>8; + event_data->descriptors_loop_length_lo = (descr_ll & 0xff); + + packet->section_length_hi = ((packet_length - 3)&0xf00)>>8; + packet->section_length_lo = (packet_length - 3)&0xff; + + // Feed the data to eEPGCache::sectionRead() + cache->sectionRead( data, MHW, this ); } -RESULT eEPGCache::getInstance(ePtr &ptr) +void eEPGCache::channel_data::startTimeout(int msec) { - ptr = instance; - if (!ptr) - return -1; - return 0; + m_MHWTimeoutTimer.start(msec,true); + m_MHWTimeoutet=false; +} + +void eEPGCache::channel_data::startMHWReader(__u16 pid, __u8 tid) +{ + m_MHWFilterMask.pid = pid; + m_MHWFilterMask.data[0] = tid; + m_MHWReader->start(m_MHWFilterMask); +// eDebug("start 0x%02x 0x%02x", pid, tid); +} + +void eEPGCache::channel_data::startMHWReader2(__u16 pid, __u8 tid, int ext) +{ + m_MHWFilterMask2.pid = pid; + m_MHWFilterMask2.data[0] = tid; + if (ext != -1) + { + m_MHWFilterMask2.data[1] = ext; + m_MHWFilterMask2.mask[1] = 0xFF; +// eDebug("start 0x%03x 0x%02x 0x%02x", pid, tid, ext); + } + else + { + m_MHWFilterMask2.data[1] = 0; + m_MHWFilterMask2.mask[1] = 0; +// eDebug("start 0x%02x 0x%02x", pid, tid); + } + m_MHWReader2->start(m_MHWFilterMask2); +} + +void eEPGCache::channel_data::readMHWData(const __u8 *data) +{ + if ( m_MHWReader2 ) + m_MHWReader2->stop(); + + if ( state > 1 || // aborted + // have si data.. so we dont read mhw data + (haveData & (SCHEDULE|SCHEDULE_OTHER)) ) + { + eDebug("[EPGC] mhw aborted %d", state); + } + else if (m_MHWFilterMask.pid == 0xD3 && m_MHWFilterMask.data[0] == 0x91) + // Channels table + { + int len = ((data[1]&0xf)<<8) + data[2] - 1; + int record_size = sizeof( mhw_channel_name_t ); + int nbr_records = int (len/record_size); + + for ( int i = 0; i < nbr_records; i++ ) + { + mhw_channel_name_t *channel = (mhw_channel_name_t*) &data[4 + i*record_size]; + m_channels.push_back( *channel ); + } + haveData |= MHW; + + eDebug("[EPGC] mhw %d channels found", m_channels.size()); + + // Channels table has been read, start reading the themes table. + startMHWReader(0xD3, 0x92); + return; + } + else if (m_MHWFilterMask.pid == 0xD3 && m_MHWFilterMask.data[0] == 0x92) + // Themes table + { + int len = ((data[1]&0xf)<<8) + data[2] - 16; + int record_size = sizeof( mhw_theme_name_t ); + int nbr_records = int (len/record_size); + int idx_ptr = 0; + __u8 next_idx = (__u8) *(data + 3 + idx_ptr); + __u8 idx = 0; + __u8 sub_idx = 0; + for ( int i = 0; i < nbr_records; i++ ) + { + mhw_theme_name_t *theme = (mhw_theme_name_t*) &data[19 + i*record_size]; + if ( i >= next_idx ) + { + idx = (idx_ptr<<4); + idx_ptr++; + next_idx = (__u8) *(data + 3 + idx_ptr); + sub_idx = 0; + } + else + sub_idx++; + + m_themes[idx+sub_idx] = *theme; + } + eDebug("[EPGC] mhw %d themes found", m_themes.size()); + // Themes table has been read, start reading the titles table. + startMHWReader(0xD2, 0x90); + startTimeout(4000); + return; + } + else if (m_MHWFilterMask.pid == 0xD2 && m_MHWFilterMask.data[0] == 0x90) + // Titles table + { + mhw_title_t *title = (mhw_title_t*) data; + + if ( title->channel_id == 0xFF ) // Separator + return; // Continue reading of the current table. + else + { + // Create unique key per title + __u32 title_id = ((title->channel_id)<<16)|((title->dh.day)<<13)|((title->dh.hours)<<8)| + (title->ms.minutes); + __u32 program_id = ((title->program_id_hi)<<24)|((title->program_id_mh)<<16)| + ((title->program_id_ml)<<8)|(title->program_id_lo); + + if ( m_titles.find( title_id ) == m_titles.end() ) + { + startTimeout(4000); + title->mhw2_mjd_hi = 0; + title->mhw2_mjd_lo = 0; + title->mhw2_duration_hi = 0; + title->mhw2_duration_lo = 0; + m_titles[ title_id ] = *title; + if ( (title->ms.summary_available) && (m_program_ids.find(program_id) == m_program_ids.end()) ) + // program_ids will be used to gather summaries. + m_program_ids[ program_id ] = title_id; + return; // Continue reading of the current table. + } + else if (!checkTimeout()) + return; + } + if ( !m_program_ids.empty()) + { + // Titles table has been read, there are summaries to read. + // Start reading summaries, store corresponding titles on the fly. + startMHWReader(0xD3, 0x90); + eDebug("[EPGC] mhw %d titles(%d with summary) found", + m_titles.size(), + m_program_ids.size()); + startTimeout(4000); + return; + } + } + else if (m_MHWFilterMask.pid == 0xD3 && m_MHWFilterMask.data[0] == 0x90) + // Summaries table + { + mhw_summary_t *summary = (mhw_summary_t*) data; + + // Create unique key per record + __u32 program_id = ((summary->program_id_hi)<<24)|((summary->program_id_mh)<<16)| + ((summary->program_id_ml)<<8)|(summary->program_id_lo); + int len = ((data[1]&0xf)<<8) + data[2]; + + // ugly workaround to convert const __u8* to char* + char *tmp=0; + memcpy(&tmp, &data, sizeof(void*)); + tmp[len+3] = 0; // Terminate as a string. + + std::map<__u32, __u32>::iterator itProgid( m_program_ids.find( program_id ) ); + if ( itProgid == m_program_ids.end() ) + { /* This part is to prevent to looping forever if some summaries are not received yet. + There is a timeout of 4 sec. after the last successfully read summary. */ + if (!m_program_ids.empty() && !checkTimeout()) + return; // Continue reading of the current table. + } + else + { + std::string the_text = (char *) (data + 11 + summary->nb_replays * 7); + + unsigned int pos=0; + while((pos = the_text.find("\r\n")) != std::string::npos) + the_text.replace(pos, 2, " "); + + // Find corresponding title, store title and summary in epgcache. + std::map<__u32, mhw_title_t>::iterator itTitle( m_titles.find( itProgid->second ) ); + if ( itTitle != m_titles.end() ) + { + startTimeout(4000); + storeTitle( itTitle, the_text, data ); + m_titles.erase( itTitle ); + } + m_program_ids.erase( itProgid ); + if ( !m_program_ids.empty() ) + return; // Continue reading of the current table. + } + } + eDebug("[EPGC] mhw finished(%ld) %d summaries not found", + eDVBLocalTimeHandler::getInstance()->nowTime(), + m_program_ids.size()); + // Summaries have been read, titles that have summaries have been stored. + // Now store titles that do not have summaries. + for (std::map<__u32, mhw_title_t>::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) + storeTitle( itTitle, "", data ); + isRunning &= ~MHW; + m_MHWConn=0; + if ( m_MHWReader ) + m_MHWReader->stop(); + if (haveData) + finishEPG(); } +void eEPGCache::channel_data::readMHWData2(const __u8 *data) +{ + int dataLen = (((data[1]&0xf) << 8) | data[2]) + 3; + + if ( m_MHWReader ) + m_MHWReader->stop(); + + if ( state > 1 || // aborted + // have si data.. so we dont read mhw data + (haveData & (eEPGCache::SCHEDULE|eEPGCache::SCHEDULE_OTHER)) ) + { + eDebug("[EPGC] mhw2 aborted %d", state); + } + else if (m_MHWFilterMask2.pid == 0x231 && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) + // Channels table + { + int num_channels = data[120]; + if(dataLen > 120) + { + int ptr = 121 + 6 * num_channels; + if( dataLen > ptr ) + { + for( int chid = 0; chid < num_channels; ++chid ) + { + ptr += ( data[ptr] & 0x0f ) + 1; + if( dataLen < ptr ) + goto abort; + } + } + else + goto abort; + } + else + goto abort; + // data seems consistent... + const __u8 *tmp = data+121; + for (int i=0; i < num_channels; ++i) + { + mhw_channel_name_t channel; + channel.transport_stream_id_hi = *(tmp++); + channel.transport_stream_id_lo = *(tmp++); + channel.channel_id_hi = *(tmp++); + channel.channel_id_lo = *(tmp++); +#warning FIXME hardcoded network_id in mhw2 epg + channel.network_id_hi = 0; // hardcoded astra 19.2 + channel.network_id_lo = 1; + m_channels.push_back(channel); + tmp+=2; + } + for (int i=0; i < num_channels; ++i) + { + mhw_channel_name_t &channel = m_channels[i]; + int channel_name_len=*(tmp++)&0x0f; + int x=0; + for (; x < channel_name_len; ++x) + channel.name[x]=*(tmp++); + channel.name[x+1]=0; + } + haveData |= MHW; + eDebug("[EPGC] mhw2 %d channels found", m_channels.size()); + } + else if (m_MHWFilterMask2.pid == 0x231 && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) + { + // Themes table + eDebug("[EPGC] mhw2 themes nyi"); + } + else if (m_MHWFilterMask2.pid == 0x234 && m_MHWFilterMask2.data[0] == 0xe6) + // Titles table + { + int pos=18; + bool valid=true; + int len = ((data[1]&0xf)<<8) + data[2] - 16; + bool finish=false; + if(data[dataLen-1] != 0xff) + return; + while( pos < dataLen ) + { + valid = false; + pos += 7; + if( pos < dataLen ) + { + pos += 3; + if( pos < dataLen ) + { + if( data[pos] > 0xc0 ) + { + pos += ( data[pos] - 0xc0 ); + pos += 4; + if( pos < dataLen ) + { + if( data[pos] == 0xff ) + { + ++pos; + valid = true; + } + } + } + } + } + if( !valid ) + { + if (checkTimeout()) + goto start_summary; + return; + } + } + // data seems consistent... + mhw_title_t title; + pos = 18; + while (pos < len) + { + title.channel_id = data[pos]+1; + title.program_id_ml = data[pos+1]; + title.program_id_lo = data[pos+2]; + title.mhw2_mjd_hi = data[pos+3]; + title.mhw2_mjd_lo = data[pos+4]; + title.mhw2_hours = data[pos+5]; + title.mhw2_minutes = data[pos+6]; + title.mhw2_seconds = data[pos+7]; + int duration = ((data[pos+8] << 8)|data[pos+9]) >> 4; + title.mhw2_duration_hi = (duration&0xFF00) >> 8; + title.mhw2_duration_lo = duration&0xFF; + __u8 slen = data[pos+10] & 0x3f; + __u8 *dest = ((__u8*)title.title)-4; + memcpy(dest, &data[pos+11], slen>33 ? 33 : slen); + memset(dest+slen, 0x20, 33-slen); + pos += 11 + slen; +// not used theme id (data[7] & 0x3f) + (data[pos] & 0x3f); + __u32 summary_id = (data[pos+1] << 8) | data[pos+2]; + + // Create unique key per title + __u32 title_id = (title.channel_id<<16) | (title.program_id_ml<<8) | title.program_id_lo; + +// eDebug("program_id: %08x, %s", program_id, +// std::string((const char *)title.title, (int)(slen > 23 ? 23 : slen)).c_str()); + + pos += 4; + + if ( m_titles.find( title_id ) == m_titles.end() ) + { + startTimeout(4000); + m_titles[ title_id ] = title; + if (summary_id != 0xFFFF && // no summary avail + m_program_ids.find(summary_id) == m_program_ids.end()) + { + m_program_ids[ summary_id ] = title_id; + } + } + else + { + if ( !checkTimeout() ) + continue; // Continue reading of the current table. + finish=true; + break; + } + } +start_summary: + if (finish) + { + eDebug("[EPGC] mhw2 %d titles(%d with summary) found", m_titles.size(), m_program_ids.size()); + if (!m_program_ids.empty()) + { + // Titles table has been read, there are summaries to read. + // Start reading summaries, store corresponding titles on the fly. + startMHWReader2(0x236, 0x96); + startTimeout(4000); + return; + } + } + else + return; + } + else if (m_MHWFilterMask2.pid == 0x236 && m_MHWFilterMask2.data[0] == 0x96) + // Summaries table + { + int len, loop, pos, lenline; + bool valid; + valid = true; + if( dataLen > 18 ) + { + loop = data[12]; + pos = 13 + loop; + if( dataLen > pos ) + { + loop = data[pos] & 0x0f; + pos += 1; + if( dataLen > pos ) + { + len = 0; + for( ; loop > 0; --loop ) + { + if( dataLen > (pos+len) ) + { + lenline = data[pos+len]; + len += lenline + 1; + } + else + valid=false; + } + } + } + } + else if (!checkTimeout()) + return; // continue reading + if (valid && !checkTimeout()) + { + // data seems consistent... + __u32 summary_id = (data[3]<<8)|data[4]; + + // ugly workaround to convert const __u8* to char* + char *tmp=0; + memcpy(&tmp, &data, sizeof(void*)); + + len = 0; + loop = data[12]; + pos = 13 + loop; + loop = tmp[pos] & 0x0f; + pos += 1; + for( ; loop > 0; loop -- ) + { + lenline = tmp[pos+len]; + tmp[pos+len] = ' '; + len += lenline + 1; + } + if( len > 0 ) + tmp[pos+len] = 0; + else + tmp[pos+1] = 0; + + std::map<__u32, __u32>::iterator itProgid( m_program_ids.find( summary_id ) ); + if ( itProgid == m_program_ids.end() ) + { /* This part is to prevent to looping forever if some summaries are not received yet. + There is a timeout of 4 sec. after the last successfully read summary. */ + + if ( !m_program_ids.empty() && !checkTimeout() ) + return; // Continue reading of the current table. + } + else + { + startTimeout(4000); + std::string the_text = (char *) (data + pos + 1); + + // Find corresponding title, store title and summary in epgcache. + std::map<__u32, mhw_title_t>::iterator itTitle( m_titles.find( itProgid->second ) ); + if ( itTitle != m_titles.end() ) + { + storeTitle( itTitle, the_text, data ); + m_titles.erase( itTitle ); + } + m_program_ids.erase( itProgid ); + if ( !m_program_ids.empty() ) + return; // Continue reading of the current table. + } + } + } + if (isRunning & eEPGCache::MHW) + { + if ( m_MHWFilterMask2.pid == 0x231 && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) + { + // Channels table has been read, start reading the themes table. + startMHWReader2(0x231, 0xC8, 1); + return; + } + else if ( m_MHWFilterMask2.pid == 0x231 && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) + { + // Themes table has been read, start reading the titles table. + startMHWReader2(0x234, 0xe6); + return; + } + else + { + // Summaries have been read, titles that have summaries have been stored. + // Now store titles that do not have summaries. + for (std::map<__u32, mhw_title_t>::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) + storeTitle( itTitle, "", data ); + eDebug("[EPGC] mhw2 finished(%ld) %d summaries not found", + eDVBLocalTimeHandler::getInstance()->nowTime(), + m_program_ids.size()); + } + } +abort: + isRunning &= ~MHW; + m_MHWConn2=0; + if ( m_MHWReader2 ) + m_MHWReader2->stop(); + if (haveData) + finishEPG(); +} +#endif