diff --git a/data/keymap.xml b/data/keymap.xml index 67523cb1d8..ad4204c885 100644 --- a/data/keymap.xml +++ b/data/keymap.xml @@ -805,6 +805,8 @@ + + @@ -923,6 +925,7 @@ + diff --git a/data/setup.xml b/data/setup.xml index d743d3a29d..84251926f3 100755 --- a/data/setup.xml +++ b/data/setup.xml @@ -44,6 +44,7 @@ config.usage.infobar_timeout config.usage.show_second_infobar + config.usage.second_infobar_simple config.usage.fix_second_infobar config.usage.show_infobar_do_dimming config.usage.show_infobar_dimming_speed @@ -82,6 +83,7 @@ config.usage.pip_zero_button config.usage.historymode config.usage.boolean_graphic + config.usage.fast_skin_reload config.usage.alternative_number_mode diff --git a/data/startwizard.xml b/data/startwizard.xml index 638bb61431..8dddad8114 100644 --- a/data/startwizard.xml +++ b/data/startwizard.xml @@ -352,7 +352,7 @@ config.ParentalControl.setuppin.save() --> - + self.condition = config.misc.installwizard.hasnetwork.value and config.misc.installwizard.ipkgloaded.value @@ -364,18 +364,6 @@ self.selectKey("OK") - - -self.condition = config.misc.installwizard.hasnetwork.value and config.misc.installwizard.ipkgloaded.value - - - - -self.clearSelectedKeys() -self.selectKey("OK") - - - @@ -388,7 +376,7 @@ self.selectKey("YELLOW") - + @@ -397,6 +385,18 @@ self.selectKey("OK") + + +self.condition = config.misc.installwizard.hasnetwork.value and config.misc.installwizard.ipkgloaded.value + + + + +self.clearSelectedKeys() +self.selectKey("OK") + + + self.condition = self.isLastWizard diff --git a/lib/base/ebase.cpp b/lib/base/ebase.cpp index 5244687c2d..dafcc51596 100644 --- a/lib/base/ebase.cpp +++ b/lib/base/ebase.cpp @@ -277,7 +277,7 @@ int eMainloop::processOneEvent(long user_timeout, PyObject **res, ePyObject addi m_inActivate = 0; } if (pfd[i].revents & (POLLERR|POLLHUP|POLLNVAL)) - eLog(5, "[eMainloop::processOneEvent] unhandled POLLERR/HUP/NVAL for fd %d(%d)", pfd[i].fd, pfd[i].revents); + eTrace("[eMainloop::processOneEvent] unhandled POLLERR/HUP/NVAL for fd %d(%d)", pfd[i].fd, pfd[i].revents); } } for (; i < fdcount; ++i) diff --git a/lib/base/eerror.h b/lib/base/eerror.h index 2b806f9205..10ae1e8165 100644 --- a/lib/base/eerror.h +++ b/lib/base/eerror.h @@ -117,7 +117,7 @@ void DumpUnfreed(); extern int debugLvl; void CHECKFORMAT eDebugImpl(int flags, const char*, ...); -enum { lvlDebug=4, lvlInfo=3, lvlWarning=2, lvlError=1, lvlFatal=0 }; +enum { lvlTrace=5, lvlDebug=4, lvlInfo=3, lvlWarning=2, lvlError=1, lvlFatal=0 }; #define DEFAULT_DEBUG_LVL 4 @@ -125,7 +125,7 @@ enum { lvlDebug=4, lvlInfo=3, lvlWarning=2, lvlError=1, lvlFatal=0 }; # define MAX_DEBUG_LEVEL 0 #else # ifndef MAX_DEBUG_LEVEL -# define MAX_DEBUG_LEVEL 4 +# define MAX_DEBUG_LEVEL 5 # endif #endif @@ -149,6 +149,7 @@ enum { lvlDebug=4, lvlInfo=3, lvlWarning=2, lvlError=1, lvlFatal=0 }; #define eDebug(...) eDebugLow(lvlDebug, 0, __VA_ARGS__) #define eDebugNoNewLineStart(...) eDebugLow(lvlDebug, _DBGFLG_NONEWLINE, __VA_ARGS__) #define eDebugNoNewLine(...) eDebugLow(lvlDebug, _DBGFLG_NOTIME | _DBGFLG_NONEWLINE, __VA_ARGS__) +#define eTrace(...) eDebugLow(lvlTrace, 0, ##__VA_ARGS__) #define ASSERT(x) { if (!(x)) eFatal("%s:%d ASSERTION %s FAILED!", __FILE__, __LINE__, #x); } #endif // SWIG diff --git a/lib/base/estring.cpp b/lib/base/estring.cpp index 8424c87ab0..6724651716 100644 --- a/lib/base/estring.cpp +++ b/lib/base/estring.cpp @@ -511,13 +511,13 @@ std::string convertDVBUTF8(const unsigned char *data, int len, int table, int ts if (table != 11) table = data[i] + 4; ++i; - eLog(6, "[convertDVBUTF8] (1..11)text encoded in ISO-8859-%d", table); + eTrace("[convertDVBUTF8] (1..11)text encoded in ISO-8859-%d", table); break; case ISO8859_xx: { int n = data[++i] << 8; n |= (data[++i]); - eLog(6, "[convertDVBUTF8] (0x10)text encoded in ISO-8859-%d", n); + eTrace("[convertDVBUTF8] (0x10)text encoded in ISO-8859-%d", n); ++i; switch(n) { @@ -591,7 +591,7 @@ std::string convertDVBUTF8(const unsigned char *data, int len, int table, int ts bool useTwoCharMapping = !table || (tsidonid && encodingHandler.getTransponderUseTwoCharMapping(tsidonid)); if (useTwoCharMapping && table == 5) { // i hope this dont break other transponders which realy use ISO8859-5 and two char byte mapping... - eLog(6, "[convertDVBUTF8] Cyfra / Cyfrowy Polsat HACK... override given ISO8859-5 with ISO6937"); + eTrace("[convertDVBUTF8] Cyfra / Cyfrowy Polsat HACK... override given ISO8859-5 with ISO6937"); table = 0; } else if ( table == -1 ) @@ -690,14 +690,15 @@ std::string convertDVBUTF8(const unsigned char *data, int len, int table, int ts *pconvertedLen = convertedLen; if (convertedLen < len) - eLog(6, "[convertDVBUTF8] %d chars converted, and %d chars left..", convertedLen, len-convertedLen); - eLog(6, "[convertDVBUTF8] table=0x%02X twochar=%d output:%s\n", table, useTwoCharMapping, output.c_str()); + eTrace("[convertDVBUTF8] %d chars converted, and %d chars left..", convertedLen, len-convertedLen); + eTrace("[convertDVBUTF8] table=0x%02X twochar=%d output:%s\n", table, useTwoCharMapping, output.c_str()); - eLog(6, "[convertDVBUTF8] table=0x%02X tsid:onid=0x%X:0x%X data[0..14]=%s output:%s\n", + eTrace("[convertDVBUTF8] table=0x%02X tsid:onid=0x%X:0x%X data[0..14]=%s output:%s\n", table, (unsigned int)tsidonid >> 16, tsidonid & 0xFFFFU, string_to_hex(std::string((char*)data, len < 15 ? len : 15)).c_str(), output.c_str()); - + // replace EIT CR/LF with standard newline: + output = replace_all(replace_all(output, "\xC2\x8A", "\n"), "\xEE\x82\x8A", "\n"); return output; } diff --git a/lib/dvb/Makefile.inc b/lib/dvb/Makefile.inc index 2da1450c4f..96633c0346 100644 --- a/lib/dvb/Makefile.inc +++ b/lib/dvb/Makefile.inc @@ -11,6 +11,8 @@ dvb_libenigma_dvb_a_SOURCES = \ dvb/dvbtime.cpp \ dvb/eit.cpp \ dvb/epgcache.cpp \ + dvb/epgchanneldata.cpp \ + dvb/epgtransponderdatareader.cpp \ dvb/esection.cpp \ dvb/fastscan.cpp \ dvb/fbc.cpp \ @@ -48,6 +50,8 @@ dvbinclude_HEADERS = \ dvb/dvbtime.h \ dvb/eit.h \ dvb/epgcache.h \ + dvb/epgchanneldata.h \ + dvb/epgtransponderdatareader.h \ dvb/esection.h \ dvb/fastscan.h \ dvb/fbc.h \ diff --git a/lib/dvb/encoder.cpp b/lib/dvb/encoder.cpp index 1f9ebb8ab4..a8ee6054e8 100644 --- a/lib/dvb/encoder.cpp +++ b/lib/dvb/encoder.cpp @@ -196,16 +196,15 @@ int eEncoder::allocateEncoder(const std::string &serviceref, int &buffersize, snprintf(filename, sizeof(filename), "/proc/stb/encoder/%d/apply", encoder_index); CFile::writeInt(filename, 1); - if(encoder[encoder_index].navigation_instance->playService(serviceref) < 0) - { - eWarning("[eEncoder] navigation->playservice failed"); - return(-1); - } - - if(!source_file.empty() && ((encoder[encoder_index].file_fd = open(source_file.c_str(), O_RDONLY, 0)) < 0)) + if(source_file.empty()) + encoder[encoder_index].file_fd = -1; + else { - eWarning("[eEncoder] open source file failed"); - return(-1); + if((encoder[encoder_index].file_fd = open(source_file.c_str(), O_RDONLY, 0)) < 0) + { + eWarning("[eEncoder] open source file failed"); + return(-1); + } } snprintf(filename, sizeof(filename), "/dev/%s%d", bcm_encoder ? "bcm_enc" : "encoder", encoder_index); @@ -238,6 +237,9 @@ int eEncoder::allocateEncoder(const std::string &serviceref, int &buffersize, default: { eWarning("[eEncoder] only encoder 0 and encoder 1 implemented"); + close(encoder[encoder_index].encoder_fd); + encoder[encoder_index].encoder_fd = -1; + return(-1); break; } } @@ -248,6 +250,12 @@ int eEncoder::allocateEncoder(const std::string &serviceref, int &buffersize, encoder[encoder_index].state = EncoderContext::state_running; } + if(encoder[encoder_index].navigation_instance->playService(serviceref) < 0) + { + eWarning("[eEncoder] navigation->playservice failed"); + return(-1); + } + return(encoder[encoder_index].encoder_fd); } @@ -440,12 +448,30 @@ void eEncoder::navigation_event(int encoder_index, int event) if((vpid > 0) && (apid > 0) && (pmtpid > 0)) { - eDebug("[eEncoder] info complete: %d, %d, %d", vpid, apid, pmtpid); + eDebug("[eEncoder] info complete, vpid: %d (0x%x), apid: %d (0x%x), pmptpid: %d (0x%x)", vpid, vpid, apid, apid, pmtpid, pmtpid); pids.push_back(pmtpid); pids.push_back(vpid); pids.push_back(apid); + if(ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_PMTPID_MIPS, pmtpid) || + ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_VPID_MIPS, vpid) || + ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_APID_MIPS, apid)) + { + eDebug("[eEncoder] set ioctl(mips) failed"); + + if(ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_PMTPID_ARM, pmtpid) || + ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_VPID_ARM, vpid) || + ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_APID_ARM, apid)) + { + eWarning("[eEncoder] set ioctl(arm) failed too, giving up"); + freeEncoder(encoder[encoder_index].encoder_fd); + return; + } + } + + encoder[encoder_index].run(); + if(encoder[encoder_index].file_fd < 0) { service->tap(tservice); @@ -461,36 +487,20 @@ void eEncoder::navigation_event(int encoder_index, int event) } else { + service->stop(); + if(encoder[encoder_index].stream_thread != nullptr) { eWarning("[eEncoder] datapump already running"); return; } - encoder[encoder_index].stream_thread = new eDVBRecordStreamThread(188, -1, true); - + encoder[encoder_index].stream_thread = new eDVBRecordStreamThread(188, 188 * 256, true); encoder[encoder_index].stream_thread->setTargetFD(encoder[encoder_index].encoder_fd); encoder[encoder_index].stream_thread->start(encoder[encoder_index].file_fd); } - if(ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_PMTPID_MIPS, pmtpid) || - ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_VPID_MIPS, vpid) || - ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_APID_MIPS, apid)) - { - eDebug("[eEncoder] set ioctl(mips) failed"); - - if(ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_PMTPID_ARM, pmtpid) || - ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_VPID_ARM, vpid) || - ioctl(encoder[encoder_index].encoder_fd, IOCTL_BROADCOM_SET_APID_ARM, apid)) - { - eWarning("[eEncoder] set ioctl(arm) failed too, giving up"); - freeEncoder(encoder[encoder_index].encoder_fd); - return; - } - } - encoder[encoder_index].state = EncoderContext::state_running; - encoder[encoder_index].run(); } } } @@ -510,8 +520,12 @@ void eEncoder::EncoderContext::thread(void) { hasStarted(); + eDebug("[EncoderContext %x] start ioctl transcoding", (int)pthread_self()); + if(ioctl(encoder_fd, IOCTL_BROADCOM_START_TRANSCODING, 0)) eWarning("[eEncoder] thread encoder failed"); + + eDebug("[EncoderContext %x] finish ioctl transcoding", (int)pthread_self()); } eAutoInitPtr init_eEncoder(eAutoInitNumbers::service + 1, "Encoders"); diff --git a/lib/dvb/epgcache.cpp b/lib/dvb/epgcache.cpp index c0d57b382c..1578b99357 100644 --- a/lib/dvb/epgcache.cpp +++ b/lib/dvb/epgcache.cpp @@ -1,6 +1,4 @@ #include -#include -#include #undef EPG_DEBUG @@ -8,31 +6,21 @@ #include #endif -#include +#include #include -#include -#include -#include -#include -#include -#include // for usleep #include // for statfs -#include -#include #include #include -#include #include -#include +#include +#include +#include +#include #include #include /* Interval between "garbage collect" cycles */ #define CLEAN_INTERVAL 60000 // 1 min -/* Restart EPG data capture */ -#define UPDATE_INTERVAL 3600000 // 60 min -/* Time to wait after tuning in before EPG data capturing starts */ -#define ZAP_DELAY 2000 // 2 sec struct DescriptorPair { @@ -381,17 +369,16 @@ void eventData::cacheCorrupt(const char* context) eEPGCache* eEPGCache::instance; static pthread_mutex_t cache_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; -static pthread_mutex_t channel_map_lock = - PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; DEFINE_REF(eEPGCache) eEPGCache::eEPGCache() - :messages(this,1), cleanTimer(eTimer::create(this)), m_running(false), m_timeQueryRef(nullptr) + :messages(this,1), m_running(false), m_enabledEpgSources(0), cleanTimer(eTimer::create(this)), m_timeQueryRef(nullptr) { eDebug("[eEPGCache] Initialized EPGCache (wait for setCacheFile call now)"); - enabledSources = 0; + load_epg = true; /*eConfigManager::getConfigValue("config.usage.remote_fallback_import").find("epg") == std::string::npos;*/ + historySeconds = 0; CONNECT(messages.recv_msg, eEPGCache::gotMessage); @@ -403,41 +390,9 @@ eEPGCache::eEPGCache() int tmp_onid; while (onid_file >> std::hex >>tmp_onid) - onid_blacklist.insert(onid_blacklist.end(),1,tmp_onid); + onid_blacklist.insert(onid_blacklist.end(),1,tmp_onid); onid_file.close(); - std::ifstream pid_file ("/etc/enigma2/epgpids.custom"); - if (pid_file.is_open()) - { - eDebug("[eEPGCache] Custom pidfile found, parsing..."); - std::string line; - char optsidonid[12]; - int op, tsid, onid, eitpid; - while (!pid_file.eof()) - { - getline(pid_file, line); - if (line[0] == '#' || sscanf(line.c_str(), "%i %i %i %i", &op, &tsid, &onid, &eitpid) != 4) - continue; - if (op < 0) - op += 3600; - if (eitpid != 0) - { - snprintf(optsidonid, sizeof(optsidonid) - 1, "%x%04x%04x", op, tsid, onid); - customeitpids[std::string(optsidonid)] = eitpid; - eDebug("[eEPGCache] %s --> %#x", optsidonid, eitpid); - } - } - pid_file.close(); - eDebug("[eEPGCache] Done"); - } - - ePtr res_mgr; - eDVBResourceManager::getInstance(res_mgr); - if (!res_mgr) - eDebug("[eEPGCache] no resource manager !!!!!!!"); - else - res_mgr->connectChannelAdded(sigc::mem_fun(*this,&eEPGCache::DVBChannelAdded), m_chanAddedConn); - instance=this; } @@ -462,15 +417,7 @@ void eEPGCache::timeUpdated() eDebug("[eEPGCache] time updated.. start EPG Mainloop"); run(); m_running = true; - singleLock s(channel_map_lock); - for (ChannelMap::const_iterator it = m_knownChannels.begin(); - it != m_knownChannels.end(); ++it) - { - if (it->second->state == -1) { - it->second->state=0; - messages.send(Message(Message::startChannel, it->first)); - } - } + /*emit*/ epgCacheStarted(); } else messages.send(Message(Message::timeChanged)); } @@ -478,257 +425,6 @@ void eEPGCache::timeUpdated() eDebug("[eEPGCache] time updated.. but cache file not set yet.. dont start epg!!"); } -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 -#ifdef ENABLE_MHW_EPG - data->m_mhw2_channel_pid = 0x231; // defaults for astra 19.2 D+ - data->m_mhw2_title_pid = 0x234; // defaults for astra 19.2 D+ - data->m_mhw2_summary_pid = 0x236; // defaults for astra 19.2 D+ -#endif - singleLock s(channel_map_lock); - m_knownChannels.insert( std::pair(chan, data) ); - chan->connectStateChange(sigc::mem_fun(*this, &eEPGCache::DVBChannelStateChanged), data->m_stateChangedConn); - } -} - -void eEPGCache::DVBChannelRunning(iDVBChannel *chan) -{ - ChannelMap::const_iterator it = - m_knownChannels.find(chan); - if ( it == m_knownChannels.end() ) - eDebug("[eEPGCache] will start non existing channel %p !!!", chan); - else - { - 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_VIRGIN - res = demux->createSectionReader( this, data.m_VirginNowNextReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize virgin nownext reader!!"); - return; - } - - res = demux->createSectionReader( this, data.m_VirginScheduleReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize virgin schedule reader!!"); - return; - } -#endif -#ifdef ENABLE_NETMED - res = demux->createSectionReader( this, data.m_NetmedScheduleReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize netmed schedule reader!!"); - return; - } - - res = demux->createSectionReader( this, data.m_NetmedScheduleOtherReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize netmed schedule other reader!!"); - return; - } -#endif - res = demux->createSectionReader( this, data.m_ViasatReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize viasat 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 -#if ENABLE_FREESAT - res = demux->createSectionReader( this, data.m_FreeSatScheduleOtherReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize FreeSat reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_FreeSatScheduleOtherReader2 ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize FreeSat reader 2!!"); - return; - } -#endif -#ifdef ENABLE_ATSC - { - int system = iDVBFrontend::feSatellite; - ePtr parms; - chan->getCurrentFrontendParameters(parms); - if (parms) - { - parms->getSystem(system); - } - if (system == iDVBFrontend::feATSC) - { - res = demux->createSectionReader( this, data.m_ATSC_VCTReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize ATSC VCT reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_ATSC_MGTReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize ATSC MGT reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_ATSC_EITReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize ATSC EIT reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_ATSC_ETTReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize ATSC ETT reader!!"); - return; - } - } - } -#endif -#ifdef ENABLE_OPENTV - res = demux->createSectionReader( this, data.m_OPENTV_ChannelsReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize OpenTV channels reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_OPENTV_TitlesReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize OpenTV titles reader!!"); - return; - } - res = demux->createSectionReader( this, data.m_OPENTV_SummariesReader ); - if ( res ) - { - eDebug("[eEPGCache] couldnt initialize OpenTV summaries reader!!"); - return; - } -#endif - if (m_running) - { - data.state = 0; - messages.send(Message(Message::startChannel, chan)); - // -> gotMessage -> changedService - } - else - data.state=-1; - } - } - } -} - -void eEPGCache::DVBChannelStateChanged(iDVBChannel *chan) -{ - ChannelMap::iterator 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); - if (it->second->state >= 0) - messages.send(Message(Message::leaveChannel, chan)); - channel_data* cd = it->second; - pthread_mutex_lock(&cd->channel_active); - { - singleLock s(channel_map_lock); - m_knownChannels.erase(it); - } - pthread_mutex_unlock(&cd->channel_active); - delete cd; - // -> gotMessage -> abortEPG - return; - } - default: // ignore all other events - return; - } - if (it->second) - it->second->prevChannelState = state; - } - } -} - bool eEPGCache::FixOverlapping(EventCacheItem &servicemap, time_t TM, int duration, const timeMap::iterator &tm_it, const uniqueEPGKey &service) { bool ret = false; @@ -804,7 +500,7 @@ bool eEPGCache::FixOverlapping(EventCacheItem &servicemap, time_t TM, int durati return ret; } -void eEPGCache::sectionRead(const uint8_t *data, int source, channel_data *channel) +void eEPGCache::sectionRead(const uint8_t *data, int source, eEPGChannelData *channel) { const eit_t *eit = (const eit_t*) data; @@ -1071,10 +767,7 @@ void eEPGCache::clearCompleteEPGCache() #ifdef ENABLE_PRIVATE_EPG content_time_tables.clear(); #endif - channelLastUpdated.clear(); - singleLock m(channel_map_lock); - for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) - it->second->startEPG(); + eEPGTransponderDataReader::getInstance()->restartReader(); } void eEPGCache::flushEPG(const uniqueEPGKey & s, bool lock) // lock only affects complete flush @@ -1199,117 +892,9 @@ void eEPGCache::gotMessage( const Message &msg ) case Message::flush: flushEPG(msg.service); break; - case Message::startChannel: - { - singleLock s(channel_map_lock); - ChannelMap::const_iterator channel = - m_knownChannels.find(msg.channel); - if ( channel != m_knownChannels.end() ) - channel->second->startChannel(); - break; - } - case Message::leaveChannel: - { - singleLock s(channel_map_lock); - ChannelMap::const_iterator channel = - m_knownChannels.find(msg.channel); - if ( channel != m_knownChannels.end() ) - channel->second->abortEPG(); - break; - } case Message::quit: quit(0); break; -#ifdef ENABLE_PRIVATE_EPG - case Message::got_private_pid: - { - singleLock s(channel_map_lock); - for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) - { - 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 - ( (::time(0)-It->second) * 1000 ) ) : ZAP_DELAY ); - if (update < ZAP_DELAY) - update = ZAP_DELAY; - data->startPrivateTimer->start(update, 1); - if (update >= 60000) - eDebug("[eEPGCache] next private update in %i min", update/60000); - else if (update >= 1000) - eDebug("[eEPGCache] next private update in %i sec", update/1000); - break; - } - } - break; - } -#endif -#ifdef ENABLE_MHW_EPG - case Message::got_mhw2_channel_pid: - { - singleLock s(channel_map_lock); - for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) - { - 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_mhw2_channel_pid = msg.pid; - eDebug("[eEPGCache] got mhw2 channel pid %04x", msg.pid); - break; - } - } - break; - } - case Message::got_mhw2_title_pid: - { - singleLock s(channel_map_lock); - for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) - { - 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_mhw2_title_pid = msg.pid; - eDebug("[eEPGCache] got mhw2 title pid %04x", msg.pid); - break; - } - } - break; - } - case Message::got_mhw2_summary_pid: - { - singleLock s(channel_map_lock); - for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) - { - 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_mhw2_summary_pid = msg.pid; - eDebug("[eEPGCache] got mhw2 summary pid %04x", msg.pid); - break; - } - } - break; - } -#endif case Message::timeChanged: cleanLoop(); break; @@ -1326,7 +911,7 @@ void eEPGCache::thread() { eDebug("[eEPGCache] thread failed to modify scheduling priority (%m)"); } - load(); + if (load_epg) { load(); } cleanLoop(); runLoop(); save(); @@ -1467,6 +1052,8 @@ void eEPGCache::load() void eEPGCache::save() { + if (!load_epg) + return; const char* EPGDAT = m_filename.c_str(); if (eventData::isCacheCorrupt) return; @@ -1578,1336 +1165,94 @@ void eEPGCache::save() fclose(f); } -eEPGCache::channel_data::channel_data(eEPGCache *ml) - :cache(ml) - ,abortTimer(eTimer::create(ml)), zapTimer(eTimer::create(ml)), state(-2) - ,isRunning(0), haveData(0) -#ifdef ENABLE_PRIVATE_EPG - ,startPrivateTimer(eTimer::create(ml)) -#endif -#ifdef ENABLE_MHW_EPG - ,m_MHWTimeoutTimer(eTimer::create(ml)) -#endif -#ifdef ENABLE_OPENTV - ,m_OPENTV_Timer(eTimer::create(ml)) -#endif +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, const eventData *&result, int direction) +// if t == -1 we search the current event... { -#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 -#ifdef ENABLE_OPENTV - CONNECT(m_OPENTV_Timer->timeout, eEPGCache::channel_data::cleanupOPENTV); -#endif - pthread_mutex_init(&channel_active, 0); -} + uniqueEPGKey key(handleGroup(service)); -void eEPGCache::channel_data::finishEPG() -{ - if (!isRunning) // epg ready + // check if EPG for this service is ready... + eventCache::iterator It = eventDB.find( key ); + if ( It != eventDB.end() && !It->second.byEvent.empty() ) // entrys cached ? { - eDebug("[eEPGCache] stop caching events(%ld)", ::time(0)); - zapTimer->start(UPDATE_INTERVAL, 1); - eDebug("[eEPGCache] next update in %i min", UPDATE_INTERVAL / 60000); - for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) + if (t==-1) + t = ::time(0); + timeMap::iterator i = direction <= 0 ? It->second.byTime.lower_bound(t) : // find > or equal + It->second.byTime.upper_bound(t); // just > + if ( i != It->second.byTime.end() ) { - seenSections[i].clear(); - calcedSections[i].clear(); + if ( direction < 0 || (direction == 0 && i->first > t) ) + { + timeMap::iterator x = i; + --x; + if ( x != It->second.byTime.end() ) + { + time_t start_time = x->first; + if (direction >= 0) + { + if (t < start_time) + return -1; + if (t > (start_time+x->second->getDuration())) + return -1; + } + i = x; + } + else + return -1; + } + result = i->second; + return 0; } -#ifdef ENABLE_MHW_EPG - cleanupMHW(); -#endif -#ifdef ENABLE_FREESAT - cleanupFreeSat(); -#endif -#ifdef ENABLE_OPENTV - cleanupOPENTV(); -#endif - singleLock l(cache_lock); - cache->channelLastUpdated[channel->getChannelID()] = ::time(0); } + return -1; } -void eEPGCache::channel_data::startEPG() +RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, Event *& result, int direction) { - eDebug("[eEPGCache] start caching events(%ld)", ::time(0)); - state=0; - haveData=0; - for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) - { - seenSections[i].clear(); - calcedSections[i].clear(); - } -#ifdef ENABLE_MHW_EPG - cleanupMHW(); -#endif -#ifdef ENABLE_FREESAT - cleanupFreeSat(); -#endif -#ifdef ENABLE_OPENTV - huffman_dictionary_read = false; - cleanupOPENTV(); -#endif - eDVBSectionFilterMask mask; - memset(&mask, 0, sizeof(mask)); - -#ifdef ENABLE_MHW_EPG - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::MHW && m_MHWReader) - { - mask.pid = 0xD3; - mask.data[0] = 0x91; - mask.mask[0] = 0xFF; - m_MHWReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::readMHWData), m_MHWConn); - m_MHWReader->start(mask); - isRunning |= MHW; - memcpy(&m_MHWFilterMask, &mask, sizeof(eDVBSectionFilterMask)); - - mask.pid = m_mhw2_channel_pid; - mask.data[0] = 0xC8; - mask.mask[0] = 0xFF; - mask.data[1] = 0; - mask.mask[1] = 0xFF; - m_MHWReader2->connectRead(sigc::mem_fun(*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; - m_MHWTimeoutet=false; - } -#endif -#ifdef ENABLE_FREESAT - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::FREESAT_SCHEDULE_OTHER && m_FreeSatScheduleOtherReader) - { - mask.pid = 3842; - mask.flags = eDVBSectionFilterMask::rfCRC; - mask.data[0] = 0x60; - mask.mask[0] = 0xFE; - m_FreeSatScheduleOtherReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::readFreeSatScheduleOtherData), m_FreeSatScheduleOtherConn); - m_FreeSatScheduleOtherReader->start(mask); + 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; +} - /* - * faster pid, available on ITV HD transponder. - * We rely on the fact that we have either of the two, - * never both. (both readers share the same data callback - * and status maps) - */ - mask.pid = 3003; - m_FreeSatScheduleOtherReader2->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::readFreeSatScheduleOtherData), m_FreeSatScheduleOtherConn2); - m_FreeSatScheduleOtherReader2->start(mask); - isRunning |= FREESAT_SCHEDULE_OTHER; - } -#endif - mask.pid = 0x12; - mask.flags = eDVBSectionFilterMask::rfCRC; - - eDVBChannelID chid = channel->getChannelID(); - std::ostringstream epg_id; - epg_id << std::hex << std::setfill('0') << - std::setw(0) << ((chid.dvbnamespace.get() & 0xffff0000) >> 16) << - std::setw(4) << chid.transport_stream_id.get() << - std::setw(4) << chid.original_network_id.get(); - - std::map::iterator it = cache->customeitpids.find(epg_id.str()); - if (it != cache->customeitpids.end()) +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); + result = NULL; + if ( !ret && data ) { - mask.pid = it->second; - eDebug("[eEPGCache] Using non-standard pid %#x", mask.pid); + 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; +} - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NOWNEXT && m_NowNextReader) - { - mask.data[0] = 0x4E; - mask.mask[0] = 0xFE; - m_NowNextReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::NOWNEXT), m_NowNextConn); - m_NowNextReader->start(mask); - isRunning |= NOWNEXT; - } +RESULT eEPGCache::lookupEventId(const eServiceReference &service, int event_id, const eventData *&result ) +{ + uniqueEPGKey key(handleGroup(service)); - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::SCHEDULE && m_ScheduleReader) + eventCache::iterator It = eventDB.find(key); + if (It != eventDB.end()) { - mask.data[0] = 0x50; - mask.mask[0] = 0xF0; - m_ScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::SCHEDULE), m_ScheduleConn); - m_ScheduleReader->start(mask); - isRunning |= SCHEDULE; + eventMap::iterator i = It->second.byEvent.find(event_id); + if ( i != It->second.byEvent.end() ) + { + result = i->second; + return 0; + } + else + { + result = 0; + eDebug("[eEPGCache] event %04x not found in epgcache", event_id); + } } - - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::SCHEDULE_OTHER && m_ScheduleOtherReader) - { - mask.data[0] = 0x60; - mask.mask[0] = 0xF0; - m_ScheduleOtherReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::SCHEDULE_OTHER), m_ScheduleOtherConn); - m_ScheduleOtherReader->start(mask); - isRunning |= SCHEDULE_OTHER; - } - -#ifdef ENABLE_VIRGIN - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIRGIN_NOWNEXT && m_VirginNowNextReader) - { - mask.pid = 0x2bc; - mask.data[0] = 0x4E; - mask.mask[0] = 0xFE; - m_VirginNowNextReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::VIRGIN_NOWNEXT), m_VirginNowNextConn); - m_VirginNowNextReader->start(mask); - isRunning |= VIRGIN_NOWNEXT; - } - - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIRGIN_SCHEDULE && m_VirginScheduleReader) - { - mask.pid = 0x2bc; - mask.data[0] = 0x50; - mask.mask[0] = 0xFE; - m_VirginScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::VIRGIN_SCHEDULE), m_VirginScheduleConn); - m_VirginScheduleReader->start(mask); - isRunning |= VIRGIN_SCHEDULE; - } -#endif -#ifdef ENABLE_NETMED - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NETMED_SCHEDULE && m_NetmedScheduleReader) - { - mask.pid = 0x1388; - mask.data[0] = 0x50; - mask.mask[0] = 0xF0; - m_NetmedScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::NETMED_SCHEDULE), m_NetmedScheduleConn); - m_NetmedScheduleReader->start(mask); - isRunning |= NETMED_SCHEDULE; - } - - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NETMED_SCHEDULE_OTHER && m_NetmedScheduleOtherReader) - { - mask.pid = 0x1388; - mask.data[0] = 0x60; - mask.mask[0] = 0xF0; - m_NetmedScheduleOtherReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::NETMED_SCHEDULE_OTHER), m_NetmedScheduleOtherConn); - m_NetmedScheduleOtherReader->start(mask); - isRunning |= NETMED_SCHEDULE_OTHER; - } -#endif -#ifdef ENABLE_ATSC - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::ATSC_EIT && m_ATSC_MGTReader) - { - m_atsc_eit_index = 0; - m_ATSC_MGTReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_MGTsection), m_ATSC_MGTConn); - m_ATSC_VCTReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_VCTsection), m_ATSC_VCTConn); - m_ATSC_EITReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_EITsection), m_ATSC_EITConn); - m_ATSC_ETTReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_ETTsection), m_ATSC_ETTConn); - mask.pid = 0x1ffb; - mask.data[0] = 0xc7; - mask.mask[0] = 0xff; - m_ATSC_MGTReader->start(mask); - mask.pid = 0x1ffb; - mask.data[0] = 0xc8; - mask.mask[0] = 0xfe; - m_ATSC_VCTReader->start(mask); - isRunning |= ATSC_EIT; - } -#endif -#ifdef ENABLE_OPENTV - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::OPENTV && m_OPENTV_ChannelsReader) - { - char dictionary[256]; - memset(dictionary, '\0', 256); - - //load correct EPG dictionary data "otv_namespace_onid_tsid.dict" - sprintf (dictionary, "/usr/share/enigma2/otv_%08x_%04x_%04x.dict", - (chid.dvbnamespace.get() >> 16) << 16, // without subnet - chid.original_network_id.get(), - chid.transport_stream_id.get()); - - huffman_dictionary_read = huffman_read_dictionary(dictionary); - - if (huffman_dictionary_read) - { - m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; - m_OPENTV_ChannelsReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::OPENTV_ChannelsSection), m_OPENTV_ChannelsConn); - mask.pid = 0x11; - mask.data[0] = 0x4a; - mask.mask[0] = 0xff; - m_OPENTV_ChannelsReader->start(mask); - isRunning |= OPENTV; - } - else - eDebug("[eEPGCache] abort non avail OpenTV EIT reading"); - } -#endif - if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIASAT && m_ViasatReader) - { - mask.pid = 0x39; - - mask.data[0] = 0x40; - mask.mask[0] = 0x40; - m_ViasatReader->connectRead(bind(sigc::mem_fun(*this, &eEPGCache::channel_data::readData), (int)eEPGCache::VIASAT), m_ViasatConn); - m_ViasatReader->start(mask); - isRunning |= VIASAT; - } -#ifdef ENABLE_OPENTV - if ( isRunning & OPENTV ) - abortTimer->start(27000,true); - else -#endif - abortTimer->start(7000,true); -} - -#ifdef ENABLE_ATSC -void eEPGCache::channel_data::ATSC_checkCompletion() -{ - if (!m_ATSC_VCTConn && !m_ATSC_MGTConn && !m_ATSC_EITConn && !m_ATSC_ETTConn) - { - eDebug("[eEPGCache] ATSC EIT index %d completed", m_atsc_eit_index); - for (std::map::const_iterator it = m_ATSC_EIT_map.begin(); it != m_ATSC_EIT_map.end(); ++it) - { - std::vector sids; - std::vector chids; - int sourceid = (it->first >> 16) & 0xffff; - sids.push_back(m_ATSC_VCT_map[sourceid]); - chids.push_back(channel->getChannelID()); - cache->submitEventData(sids, chids, it->second.startTime, it->second.lengthInSeconds, it->second.title.c_str(), "", m_ATSC_ETT_map[it->first].c_str(), 0, eEPGCache::ATSC_EIT); - } - m_ATSC_EIT_map.clear(); - m_ATSC_ETT_map.clear(); - if (m_atsc_eit_index < 128) - { - eDVBSectionFilterMask mask = {}; - m_atsc_eit_index++; - m_ATSC_MGTReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_MGTsection), m_ATSC_MGTConn); - mask.pid = 0x1ffb; - mask.data[0] = 0xc7; - mask.mask[0] = 0xff; - m_ATSC_MGTReader->start(mask); - } - else - { - eDebug("[eEPGCache] ATSC EIT parsing completed"); - m_ATSC_VCT_map.clear(); - isRunning &= ~ATSC_EIT; - if (!isRunning) - { - finishEPG(); - } - } - } -} - -void eEPGCache::channel_data::ATSC_VCTsection(const uint8_t *d) -{ - VirtualChannelTableSection vct(d); - for (VirtualChannelListConstIterator channel = vct.getChannels()->begin(); channel != vct.getChannels()->end(); ++channel) - { - if (m_ATSC_VCT_map.find((*channel)->getSourceId()) == m_ATSC_VCT_map.end()) - { - m_ATSC_VCT_map[(*channel)->getSourceId()] = (*channel)->getServiceId(); - } - else - { - m_ATSC_VCTReader->stop(); - m_ATSC_VCTConn = NULL; - ATSC_checkCompletion(); - break; - } - } -} - -void eEPGCache::channel_data::ATSC_MGTsection(const uint8_t *d) -{ - MasterGuideTableSection mgt(d); - for (MasterGuideTableListConstIterator table = mgt.getTables()->begin(); table != mgt.getTables()->end(); ++table) - { - eDVBSectionFilterMask mask = {}; - if ((*table)->getTableType() == 0x0100 + m_atsc_eit_index) - { - /* EIT */ - mask.pid = (*table)->getPID(); - mask.data[0] = 0xcb; - mask.mask[0] = 0xff; - m_ATSC_EITReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_EITsection), m_ATSC_EITConn); - m_ATSC_EITReader->start(mask); - } - else if ((*table)->getTableType() == 0x0200 + m_atsc_eit_index) - { - /* ETT */ - mask.pid = (*table)->getPID(); - mask.data[0] = 0xcc; - mask.mask[0] = 0xff; - m_ATSC_ETTReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::ATSC_ETTsection), m_ATSC_ETTConn); - m_ATSC_ETTReader->start(mask); - } - } - m_ATSC_MGTReader->stop(); - m_ATSC_MGTConn = NULL; - if (!m_ATSC_EITConn) - { - /* no more EIT */ - m_ATSC_ETTReader->stop(); - m_ATSC_ETTConn = NULL; - m_atsc_eit_index = 128; - ATSC_checkCompletion(); - } -} - -void eEPGCache::channel_data::ATSC_EITsection(const uint8_t *d) -{ - ATSCEventInformationSection eit(d); - for (ATSCEventListConstIterator ev = eit.getEvents()->begin(); ev != eit.getEvents()->end(); ++ev) - { - uint32_t etm = ((eit.getTableIdExtension() & 0xffff) << 16) | (((*ev)->getEventId() & 0x3fff) << 2) | 0x2; - if (m_ATSC_EIT_map.find(etm) == m_ATSC_EIT_map.end()) - { - struct atsc_event event; - event.title = (*ev)->getTitle("---"); - event.eventId = (*ev)->getEventId(); - event.startTime = (*ev)->getStartTime() + (time_t)315964800; /* ATSC GPS system time epoch is 00:00 Jan 6th 1980 */ - event.lengthInSeconds = (*ev)->getLengthInSeconds(); - m_ATSC_EIT_map[etm] = event; - } - else - { - m_ATSC_EITReader->stop(); - m_ATSC_EITConn = NULL; - ATSC_checkCompletion(); - break; - } - } - haveData |= ATSC_EIT; -} - -void eEPGCache::channel_data::ATSC_ETTsection(const uint8_t *d) -{ - ExtendedTextTableSection ett(d); - if (m_ATSC_ETT_map.find(ett.getETMId()) == m_ATSC_ETT_map.end()) - { - m_ATSC_ETT_map[ett.getETMId()] = ett.getMessage("---"); - } - else - { - m_ATSC_ETTReader->stop(); - m_ATSC_ETTConn = NULL; - ATSC_checkCompletion(); - } -} - -void eEPGCache::channel_data::cleanupATSC() -{ - m_ATSC_VCTReader->stop(); - m_ATSC_MGTReader->stop(); - m_ATSC_EITReader->stop(); - m_ATSC_ETTReader->stop(); - m_ATSC_VCTConn = NULL; - m_ATSC_MGTConn = NULL; - m_ATSC_EITConn = NULL; - m_ATSC_ETTConn = NULL; - - m_ATSC_EIT_map.clear(); - m_ATSC_ETT_map.clear(); - m_ATSC_VCT_map.clear(); -} -#endif -#ifdef ENABLE_OPENTV -void eEPGCache::channel_data::OPENTV_checkCompletion(uint32_t data_crc) -{ - if (!m_OPENTV_crc32) - { - m_OPENTV_crc32 = data_crc; - } - else if (m_OPENTV_crc32 == data_crc) - { - m_OPENTV_crc32 = 0; - } - - eDVBSectionFilterMask mask; - memset(&mask, 0, sizeof(mask)); - - if ((m_OPENTV_ChannelsConn && (m_OPENTV_EIT_index > 0xff)) || (m_OPENTV_ChannelsConn && !m_OPENTV_crc32)) - { - eDebug("[eEPGCache] OpenTV channels, found=%d%s", (int)m_OPENTV_channels_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); - m_OPENTV_ChannelsReader->stop(); - m_OPENTV_ChannelsConn = NULL; - m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; - m_OPENTV_Timer->start(200000, true); - m_OPENTV_TitlesReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::OPENTV_TitlesSection), m_OPENTV_TitlesConn); - mask = {}; - mask.pid = m_OPENTV_pid = 0x30; - mask.data[0] = 0xa0; - mask.mask[0] = 0xfc; - mask.flags = eDVBSectionFilterMask::rfCRC; - m_OPENTV_TitlesReader->start(mask); - } - else if ((m_OPENTV_TitlesConn && (m_OPENTV_EIT_index > 0xfff)) || (m_OPENTV_TitlesConn && !m_OPENTV_crc32)) - { - m_OPENTV_TitlesReader->stop(); - m_OPENTV_TitlesConn = NULL; - m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; - m_OPENTV_pid += 0x10; - - if (m_OPENTV_pid < 0x48) - { - eDebug("[eEPGCache] OpenTV titles %d stored=%d%s", (int)m_OPENTV_EIT_map.size(), (int)m_OPENTV_descriptors_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); - m_OPENTV_SummariesReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::OPENTV_SummariesSection), m_OPENTV_SummariesConn); - mask = {}; - mask.pid = m_OPENTV_pid; - mask.data[0] = 0xa8; - mask.mask[0] = 0xfc; - mask.flags = eDVBSectionFilterMask::rfCRC; - m_OPENTV_SummariesReader->start(mask); - } - } - else if ((m_OPENTV_SummariesConn && (m_OPENTV_EIT_index > 0xfff)) || (m_OPENTV_SummariesConn && !m_OPENTV_crc32)) - { - m_OPENTV_SummariesReader->stop(); - m_OPENTV_SummariesConn = NULL; - m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; - m_OPENTV_pid -= 0x10; - - //cache remaining uncached events for which the provider only sends title with no summary data.. off air/overnight! - eDebug("[eEPGCache] OpenTV summaries, uncached=%d%s", (int)m_OPENTV_EIT_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); - - for (std::map::const_iterator it = m_OPENTV_EIT_map.begin(); it != m_OPENTV_EIT_map.end(); ++it) - { - int channelid = (it->first >> 16) & 0xffff; - - if (m_OPENTV_channels_map.find(channelid) != m_OPENTV_channels_map.end()) - { - std::vector sids; - std::vector chids; - eDVBChannelID chid = channel->getChannelID(); - chid.transport_stream_id = m_OPENTV_channels_map[channelid].transportStreamId; - chid.original_network_id = m_OPENTV_channels_map[channelid].originalNetworkId; - chids.push_back(chid); - sids.push_back(m_OPENTV_channels_map[channelid].serviceId); - cache->submitEventData(sids, chids, it->second.startTime, it->second.duration, m_OPENTV_descriptors_map[it->second.title_crc].c_str(), "", "", 0, eEPGCache::OPENTV); - } - } - m_OPENTV_descriptors_map.clear(); - m_OPENTV_EIT_map.clear(); - - if (++m_OPENTV_pid < 0x38) - { - m_OPENTV_TitlesReader->connectRead(sigc::mem_fun(*this, &eEPGCache::channel_data::OPENTV_TitlesSection), m_OPENTV_TitlesConn); - mask = {}; - mask.pid = m_OPENTV_pid; - mask.data[0] = 0xa0; - mask.mask[0] = 0xfc; - mask.flags = eDVBSectionFilterMask::rfCRC; - m_OPENTV_TitlesReader->start(mask); - } - else - eDebug("[eEPGCache] OpenTV finishing, uncached=%d", (int)m_OPENTV_EIT_map.size()); - } - else - m_OPENTV_EIT_index++; - - if (!m_OPENTV_ChannelsConn && !m_OPENTV_TitlesConn && !m_OPENTV_SummariesConn) - { - eDebug("[eEPGCache] OpenTV EIT parsing completed"); - isRunning &= ~OPENTV; - - if (!isRunning) - finishEPG(); - else - cleanupOPENTV(); - } -} - -void eEPGCache::channel_data::OPENTV_ChannelsSection(const uint8_t *d) -{ - OpenTvChannelSection otcs(d); - - for (OpenTvChannelListConstIterator channel = otcs.getChannels()->begin(); channel != otcs.getChannels()->end(); ++channel) - { - if (m_OPENTV_channels_map.find((*channel)->getChannelId()) == m_OPENTV_channels_map.end()) - { - struct opentv_channel otc; - otc.originalNetworkId = (*channel)->getOriginalNetworkId(); - otc.transportStreamId = (*channel)->getTransportStreamId(); - otc.serviceId = (*channel)->getServiceId(); - otc.serviceType = (*channel)->getServiceType(); - m_OPENTV_channels_map[(*channel)->getChannelId()] = otc; - } - } - OPENTV_checkCompletion(otcs.getCrc32()); -} - -void eEPGCache::channel_data::OPENTV_TitlesSection(const uint8_t *d) -{ - OpenTvTitleSection otts(d); - - for (OpenTvTitleListConstIterator title = otts.getTitles()->begin(); title != otts.getTitles()->end(); ++title) - { - uint32_t etm = ((otts.getTableIdExtension() & 0xffff) << 16) | ((*title)->getEventId() & 0xffff); - - if (m_OPENTV_EIT_map.find(etm) == m_OPENTV_EIT_map.end()) - { - struct opentv_event ote; - ote.eventId = (*title)->getEventId(); - ote.startTime = (*title)->getStartTime(); - ote.duration = (*title)->getDuration(); - ote.title_crc = (*title)->getCRC32(); - m_OPENTV_EIT_map[etm] = ote; - - if (m_OPENTV_descriptors_map.find(ote.title_crc) == m_OPENTV_descriptors_map.end()) - m_OPENTV_descriptors_map[ote.title_crc] = (*title)->getTitle(); - } - } - - OPENTV_checkCompletion(otts.getCrc32()); -} - -void eEPGCache::channel_data::OPENTV_SummariesSection(const uint8_t *d) -{ - OpenTvSummarySection otss(d); - - int channelid = otss.getTableIdExtension(); - - if (m_OPENTV_channels_map.find(channelid) != m_OPENTV_channels_map.end()) - { - for (OpenTvSummaryListConstIterator summary = otss.getSummaries()->begin(); summary != otss.getSummaries()->end(); ++summary) - { - uint32_t otce = ((channelid & 0xffff) << 16) | ((*summary)->getEventId() & 0xffff); - - if (m_OPENTV_EIT_map.find(otce) != m_OPENTV_EIT_map.end()) - { - struct opentv_event ote = m_OPENTV_EIT_map[otce]; - - //cache events with matching title and summary eventId on the fly! - if (m_OPENTV_descriptors_map.find(ote.title_crc) != m_OPENTV_descriptors_map.end()) - { - std::vector sids; - std::vector chids; - eDVBChannelID chid = channel->getChannelID(); - chid.transport_stream_id = m_OPENTV_channels_map[channelid].transportStreamId; - chid.original_network_id = m_OPENTV_channels_map[channelid].originalNetworkId; - chids.push_back(chid); - sids.push_back(m_OPENTV_channels_map[channelid].serviceId); - - // hack to fix split titles - std::string sTitle = m_OPENTV_descriptors_map[ote.title_crc]; - std::string sSummary = (*summary)->getSummary(); - - if (sTitle.length() > 3 && sSummary.length() > 3) - { - // check if the title is split - if (sTitle.substr(sTitle.length() - 3) == "..." && sSummary.substr(0, 3) == "...") - { - // find the end of the title in the sumarry - std::size_t found = sSummary.find_first_of(".:!?", 4); - - if (found < sSummary.length()) - { - std::string sTmpTitle; - std::string sTmpSummary; - - // strip off the ellipsis and any leading/trailing space - if (sTitle.substr(sTitle.length() - 4, 1) == " ") - { - sTmpTitle = sTitle.substr(0, sTitle.length() - 4); - } - else - { - sTmpTitle = sTitle.substr(0, sTitle.length() - 3); - } - - if (sSummary.substr(3, 1) == " ") - { - sTmpSummary = sSummary.substr(4); - } - else - { - sTmpSummary = sSummary.substr(3); - } - - // construct the new title and summary - found = sTmpSummary.find_first_of(".:!?"); - if (found < sTmpSummary.length()) - { - sTitle = sTmpTitle + " " + sTmpSummary.substr(0, found); - if (sTmpSummary.length() - found > 2) - { - sSummary = sTmpSummary.substr(found + 2); - } - else - { - sSummary = ""; - } - } - else - { - // shouldn't happen, but you never know... - sTitle + sTmpTitle; - sSummary = sTmpSummary; - } - } - } - } - cache->submitEventData(sids, chids, ote.startTime, ote.duration, sTitle.c_str(), "", sSummary.c_str(), 0, eEPGCache::OPENTV); - } - m_OPENTV_EIT_map.erase(otce); - } - } - } - haveData |= OPENTV; - - OPENTV_checkCompletion(otss.getCrc32()); -} - -void eEPGCache::channel_data::cleanupOPENTV() -{ - m_OPENTV_Timer->stop(); - if (m_OPENTV_ChannelsReader) - m_OPENTV_ChannelsReader->stop(); - if (m_OPENTV_TitlesReader) - m_OPENTV_TitlesReader->stop(); - if (m_OPENTV_SummariesReader) - m_OPENTV_SummariesReader->stop(); - m_OPENTV_ChannelsConn = NULL; - m_OPENTV_TitlesConn = NULL; - m_OPENTV_SummariesConn = NULL; - m_OPENTV_channels_map.clear(); - m_OPENTV_descriptors_map.clear(); - m_OPENTV_EIT_map.clear(); - - if (huffman_dictionary_read) - { - huffman_free_dictionary(); - huffman_dictionary_read = false; - } - - if (isRunning & OPENTV) - isRunning &= ~OPENTV; -} -#endif - -void eEPGCache::channel_data::abortNonAvail() -{ - if (!state) - { - if ( !(haveData&NOWNEXT) && (isRunning&NOWNEXT) ) - { - eDebug("[eEPGCache] abort non avail nownext reading"); - isRunning &= ~NOWNEXT; - m_NowNextReader->stop(); - m_NowNextConn=0; - } - if ( !(haveData&SCHEDULE) && (isRunning&SCHEDULE) ) - { - eDebug("[eEPGCache] abort non avail schedule reading"); - isRunning &= ~SCHEDULE; - m_ScheduleReader->stop(); - m_ScheduleConn=0; - } - if ( !(haveData&SCHEDULE_OTHER) && (isRunning&SCHEDULE_OTHER) ) - { - eDebug("[eEPGCache] abort non avail schedule other reading"); - isRunning &= ~SCHEDULE_OTHER; - m_ScheduleOtherReader->stop(); - m_ScheduleOtherConn=0; - } -#ifdef ENABLE_VIRGIN - if ( !(haveData&VIRGIN_NOWNEXT) && (isRunning&VIRGIN_NOWNEXT) ) - { - eDebug("[eEPGCache] abort non avail virgin nownext reading"); - isRunning &= ~VIRGIN_NOWNEXT; - m_VirginNowNextReader->stop(); - m_VirginNowNextConn=0; - } - if ( !(haveData&VIRGIN_SCHEDULE) && (isRunning&VIRGIN_SCHEDULE) ) - { - eDebug("[eEPGCache] abort non avail virgin schedule reading"); - isRunning &= ~VIRGIN_SCHEDULE; - m_VirginScheduleReader->stop(); - m_VirginScheduleConn=0; - } -#endif -#ifdef ENABLE_NETMED - if ( !(haveData&NETMED_SCHEDULE) && (isRunning&NETMED_SCHEDULE) ) - { - eDebug("[eEPGCache] abort non avail netmed schedule reading"); - isRunning &= ~NETMED_SCHEDULE; - m_NetmedScheduleReader->stop(); - m_NetmedScheduleConn=0; - } - if ( !(haveData&NETMED_SCHEDULE_OTHER) && (isRunning&NETMED_SCHEDULE_OTHER) ) - { - eDebug("[eEPGCache] abort non avail netmed schedule other reading"); - isRunning &= ~NETMED_SCHEDULE_OTHER; - m_NetmedScheduleOtherReader->stop(); - m_NetmedScheduleOtherConn=0; - } -#endif -#ifdef ENABLE_FREESAT - if ( !(haveData&FREESAT_SCHEDULE_OTHER) && (isRunning&FREESAT_SCHEDULE_OTHER) ) - { - eDebug("[eEPGCache] abort non avail FreeSat schedule_other reading"); - isRunning &= ~FREESAT_SCHEDULE_OTHER; - m_FreeSatScheduleOtherReader->stop(); - m_FreeSatScheduleOtherReader2->stop(); - m_FreeSatScheduleOtherConn=0; - m_FreeSatScheduleOtherConn2=0; - cleanupFreeSat(); - } -#endif - if ( !(haveData&VIASAT) && (isRunning&VIASAT) ) - { - eDebug("[eEPGCache] abort non avail viasat reading"); - isRunning &= ~VIASAT; - m_ViasatReader->stop(); - m_ViasatConn=0; - } -#ifdef ENABLE_MHW_EPG - if ( !(haveData&MHW) && (isRunning&MHW) ) - { - eDebug("[eEPGCache] abort non avail mhw reading"); - isRunning &= ~MHW; - m_MHWReader->stop(); - m_MHWConn=0; - m_MHWReader2->stop(); - m_MHWConn2=0; - } -#endif -#ifdef ENABLE_ATSC - if (!(haveData & ATSC_EIT) && (isRunning & ATSC_EIT)) - { - eDebug("[eEPGCache] abort non avail ATSC EIT reading"); - isRunning &= ~ATSC_EIT; - cleanupATSC(); - } -#endif -#ifdef ENABLE_OPENTV - if (!(haveData & OPENTV) && (isRunning & OPENTV)) - { - eDebug("[eEPGCache] abort non avail OpenTV EIT reading"); - isRunning &= ~OPENTV; - cleanupOPENTV(); - } -#endif - if ( isRunning & VIASAT ) - abortTimer->start(300000, true); - else if ( isRunning ) - abortTimer->start(90000, true); - else - { - ++state; - for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) - { - seenSections[i].clear(); - calcedSections[i].clear(); - } -#ifdef ENABLE_MHW_EPG - cleanupMHW(); -#endif -#ifdef ENABLE_FREESAT - cleanupFreeSat(); -#endif -#ifdef ENABLE_OPENTV - cleanupOPENTV(); -#endif - } - } - ++state; -} - -void eEPGCache::channel_data::startChannel() -{ - pthread_mutex_lock(&channel_active); - updateMap::iterator It = cache->channelLastUpdated.find( channel->getChannelID() ); - - int update = ( It != cache->channelLastUpdated.end() ? ( UPDATE_INTERVAL - ( (::time(0)-It->second) * 1000 ) ) : ZAP_DELAY ); - - if (update < ZAP_DELAY) - update = ZAP_DELAY; - - zapTimer->start(update, 1); - if (update >= 60000) - eDebug("[eEPGCache] next update in %i min", update/60000); - else if (update >= 1000) - eDebug("[eEPGCache] next update in %i sec", update/1000); -} - -void eEPGCache::channel_data::abortEPG() -{ - for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) - { - seenSections[i].clear(); - calcedSections[i].clear(); - } -#ifdef ENABLE_MHW_EPG - cleanupMHW(); -#endif -#ifdef ENABLE_FREESAT - cleanupFreeSat(); -#endif - abortTimer->stop(); - zapTimer->stop(); - if (isRunning) - { - eDebug("[eEPGCache] abort caching events !!"); - if (isRunning & SCHEDULE) - { - isRunning &= ~SCHEDULE; - m_ScheduleReader->stop(); - m_ScheduleConn=0; - } - if (isRunning & NOWNEXT) - { - isRunning &= ~NOWNEXT; - m_NowNextReader->stop(); - m_NowNextConn=0; - } - if (isRunning & SCHEDULE_OTHER) - { - isRunning &= ~SCHEDULE_OTHER; - m_ScheduleOtherReader->stop(); - m_ScheduleOtherConn=0; - } -#ifdef ENABLE_VIRGIN - if (isRunning & VIRGIN_NOWNEXT) - { - isRunning &= ~VIRGIN_NOWNEXT; - m_VirginNowNextReader->stop(); - m_VirginNowNextConn=0; - } - if (isRunning & VIRGIN_SCHEDULE) - { - isRunning &= ~VIRGIN_SCHEDULE; - m_VirginScheduleReader->stop(); - m_VirginScheduleConn=0; - } -#endif -#ifdef ENABLE_NETMED - if (isRunning & NETMED_SCHEDULE) - { - isRunning &= ~NETMED_SCHEDULE; - m_NetmedScheduleReader->stop(); - m_NetmedScheduleConn=0; - } - if (isRunning & NETMED_SCHEDULE_OTHER) - { - isRunning &= ~NETMED_SCHEDULE_OTHER; - m_NetmedScheduleOtherReader->stop(); - m_NetmedScheduleOtherConn=0; - } -#endif -#ifdef ENABLE_FREESAT - if (isRunning & FREESAT_SCHEDULE_OTHER) - { - isRunning &= ~FREESAT_SCHEDULE_OTHER; - m_FreeSatScheduleOtherReader->stop(); - m_FreeSatScheduleOtherReader2->stop(); - m_FreeSatScheduleOtherConn=0; - m_FreeSatScheduleOtherConn2=0; - } -#endif - if (isRunning & VIASAT) - { - isRunning &= ~VIASAT; - m_ViasatReader->stop(); - m_ViasatConn=0; - } -#ifdef ENABLE_MHW_EPG - if (isRunning & MHW) - { - isRunning &= ~MHW; - m_MHWReader->stop(); - m_MHWConn=0; - m_MHWReader2->stop(); - m_MHWConn2=0; - } -#endif -#ifdef ENABLE_ATSC - if (isRunning & ATSC_EIT) - { - isRunning &= ~ATSC_EIT; - cleanupATSC(); - } -#endif -#ifdef ENABLE_OPENTV - if (isRunning & OPENTV) - { - isRunning &= ~OPENTV; - cleanupOPENTV(); - } -#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::channel_data::readData( const uint8_t *data, int source) -{ - int map; - iDVBSectionReader *reader = NULL; - switch (source) - { - case NOWNEXT: - reader = m_NowNextReader; - map = 0; - break; - case SCHEDULE: - reader = m_ScheduleReader; - map = 1; - break; - case SCHEDULE_OTHER: - reader = m_ScheduleOtherReader; - map = 2; - break; - case VIASAT: - reader = m_ViasatReader; - map = 3; - break; -#ifdef ENABLE_NETMED - case NETMED_SCHEDULE: - reader = m_NetmedScheduleReader; - map = 1; - break; - case NETMED_SCHEDULE_OTHER: - reader = m_NetmedScheduleOtherReader; - map = 2; - break; -#endif -#ifdef ENABLE_VIRGIN - case VIRGIN_NOWNEXT: - reader = m_VirginNowNextReader; - map = 0; - break; - case VIRGIN_SCHEDULE: - reader = m_VirginScheduleReader; - map = 1; - break; -#endif - default: - eDebug("[eEPGCache] unknown source"); - return; - } - tidMap &seenSections = this->seenSections[map]; - tidMap &calcedSections = this->calcedSections[map]; - if ( (state == 1 && calcedSections == seenSections) || state > 1 ) - { - eDebugNoNewLineStart("[eEPGCache] "); - 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; - case VIASAT: - m_ViasatConn=0; - eDebugNoNewLine("viasat"); - break; -#ifdef ENABLE_NETMED - case NETMED_SCHEDULE: - m_NetmedScheduleConn=0; - eDebugNoNewLine("netmed schedule"); - break; - case NETMED_SCHEDULE_OTHER: - m_NetmedScheduleOtherConn=0; - eDebugNoNewLine("netmed schedule other"); - break; -#endif -#ifdef ENABLE_VIRGIN - case VIRGIN_NOWNEXT: - m_VirginNowNextConn=0; - eDebugNoNewLine("virgin nownext"); - break; - case VIRGIN_SCHEDULE: - m_VirginScheduleConn=0; - eDebugNoNewLine("virgin schedule"); - break; -#endif - default: eDebugNoNewLine("unknown");break; - } - eDebugNoNewLine(" finished(%ld)\n", ::time(0)); - if ( reader ) - reader->stop(); - isRunning &= ~source; - if (!isRunning) - finishEPG(); - } - else - { - eit_t *eit = (eit_t*) data; - uint32_t 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); - uint32_t tmpval = sectionNo & 0xFFFFFF00; - uint8_t incr = source == NOWNEXT ? 1 : 8; - for ( int i = 0; i <= eit->last_section_number; i+=incr ) - { - 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)); - } - cache->sectionRead(data, source, this); - } - } -} - -#if ENABLE_FREESAT - -freesatEITSubtableStatus::freesatEITSubtableStatus(u_char version, uint8_t maxSection) : version(version) -{ - initMap(maxSection); -} - -void freesatEITSubtableStatus::initMap(uint8_t maxSection) -{ - int i, maxSectionIdx = maxSection / 8; - for (i = 0; i < 32; i++) - { - sectionMap[i] = (i <= maxSectionIdx ? 0x0100 : 0x0000 ); - } -} - -bool freesatEITSubtableStatus::isSectionPresent(uint8_t sectionNo) -{ - uint8_t sectionIdx = sectionNo / 8; - uint8_t bitOffset = sectionNo % 8; - - return ((sectionMap[sectionIdx] & (1 << bitOffset)) != 0); -} - -bool freesatEITSubtableStatus::isCompleted() -{ - uint32_t i = 0; - uint8_t calc; - - while ( i < 32 ) - { - calc = sectionMap[i] >> 8; - if (! calc) return true; // Last segment passed - if (calc ^ ( sectionMap[i] & 0xFF ) ) // Segment not fully found - return false; - i++; - } - return true; // All segments ok -} - -void freesatEITSubtableStatus::seen(uint8_t sectionNo, uint8_t maxSegmentSection) -{ - uint8_t sectionIdx = sectionNo / 8; - uint8_t bitOffset = sectionNo % 8; - uint8_t maxBitOffset = maxSegmentSection % 8; - - sectionMap[sectionIdx] &= 0x00FF; // Clear calc map - sectionMap[sectionIdx] |= ((0x01FF << maxBitOffset) & 0xFF00); // Set calc map - sectionMap[sectionIdx] |= (1 << bitOffset); // Set seen map -} - -bool freesatEITSubtableStatus::isVersionChanged(u_char testVersion) -{ - return version != testVersion; -} - -void freesatEITSubtableStatus::updateVersion(u_char newVersion, uint8_t maxSection) -{ - version = newVersion; - initMap(maxSection); -} - -void eEPGCache::channel_data::cleanupFreeSat() -{ - m_FreeSatSubTableStatus.clear(); - m_FreesatTablesToComplete = 0; -} - -void eEPGCache::channel_data::readFreeSatScheduleOtherData( const uint8_t *data) -{ - eit_t *eit = (eit_t*) data; - uint32_t subtableNo = data[0] << 24; // Table ID - subtableNo |= data[3] << 16; // Service ID Hi - subtableNo |= data[4] << 8; // Service ID Lo - - // Check for sub-table version in map - std::map &freeSatSubTableStatus = this->m_FreeSatSubTableStatus; - std::map::iterator itmap = freeSatSubTableStatus.find(subtableNo); - - freesatEITSubtableStatus *fsstatus; - if ( itmap == freeSatSubTableStatus.end() ) - { - // New sub table. Store version. - //eDebug("[eEPGCache] New subtable (%x) version (%d) now/next (%d) tsid (%x/%x) onid (%x/%x)", subtableNo, eit->version_number, eit->current_next_indicator, eit->transport_stream_id_hi, eit->transport_stream_id_lo, eit->original_network_id_hi, eit->original_network_id_lo); - fsstatus = new freesatEITSubtableStatus(eit->version_number, eit->last_section_number); - m_FreesatTablesToComplete++; - freeSatSubTableStatus.insert(std::pair(subtableNo, *fsstatus)); - } - else - { - fsstatus = &itmap->second; - // Existing subtable. Check version. Should check current / next as well? Seems to always be current for Freesat - if ( fsstatus->isVersionChanged(eit->version_number) ) - { - eDebug("[eEPGCache] FS subtable (%x) version changed (%d) now/next (%d)", subtableNo, eit->version_number, eit->current_next_indicator); - m_FreesatTablesToComplete++; - fsstatus->updateVersion(eit->version_number, eit->last_section_number); - } - else - { - if ( fsstatus->isSectionPresent(eit->section_number) ) - { -// eDebug("[eEPGCache] DUP FS sub/sec/ver (%x/%d/%d)", subtableNo, eit->section_number, eit->version_number); - return; - } - } - } - -// eDebug("[eEPGCache] New FS sub/sec/ls/lss/ver (%x/%d/%d/%d/%d)", subtableNo, eit->section_number, eit->last_section_number, eit->segment_last_section_number, eit->version_number); - fsstatus->seen(eit->section_number, eit->segment_last_section_number); - if (fsstatus->isCompleted()) - { - m_FreesatTablesToComplete--; - } - cache->sectionRead(data, FREESAT_SCHEDULE_OTHER, this); -} -#endif - -/** @copydoc eEPGCache::lookupEventTime - */ -RESULT eEPGCache::lookupEventTime(const eServiceReference &service, time_t t, const eventData *&result, int direction) -{ - uniqueEPGKey key(handleGroup(service)); - - // check whether EPG for this service is ready... - eventCache::iterator It = eventDB.find( key ); - if ( It != eventDB.end() && !It->second.byEvent.empty() ) // entries cached ? - { - if ( t == -1 ) - t = ::time(0); - timeMap::iterator i = It->second.byTime.upper_bound(t); // find first > t - if ( direction > 0 ) - { - if ( i != It->second.byTime.end() ) { - result = i->second; - return 0; - } - else - return -1; - } - - // direction <= 0 - if ( i == It->second.byTime.begin() ) - return -1; - --i; - // time_t start_time = i->first; - time_t end_time = i->first + i->second->getDuration(); - if ( direction == 0 ) { - // start_time <= t from map and iterator properties - if ( t < end_time ) { - result = i->second; - return 0; - } - else - return -1; - } - - // direction < 0 - if ( t >= end_time ) { - result = i->second; - return 0; - } - if ( i != It->second.byTime.begin() ) { - --i; - result = i->second; - return 0; - } - } - return -1; -} - -/** - * @brief Look up an event in the EPG database by service reference and time. - * The service reference is specified in @p service. - * The lookup time is in @p t. - * The @p direction specifies whether to return the event matching @p t, its - * predecessor or successor. - * - * @param service as an eServiceReference. - * @param t the lookup time. If t == -1, look up the current time. - * @param result the matched event, if one is found. - * @param direction The event offset from the match. - * @p direction > 0 return the earliest event that starts after t. - * @p direction == 0 return the event that spans t. If t is spanned by a gap in the EPG, return None. - * @p direction < 0 return the event immediately before the event that spans t. * If t is spanned by a gap in the EPG, return the event immediately before the gap. - * @return 0 for successful match and valid data in @p result, - * -1 for unsuccessful. - * In a call from Python, a return of -1 corresponds to a return value of None. - */ -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; -} - -/** @copydoc eEPGCache::lookupEventTime - */ -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); - result = NULL; - 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 ) -{ - uniqueEPGKey key(handleGroup(service)); - - eventCache::iterator It = eventDB.find(key); - if (It != eventDB.end()) - { - eventMap::iterator i = It->second.byEvent.find(event_id); - if ( i != It->second.byEvent.end() ) - { - result = i->second; - return 0; - } - else - { - result = 0; - eDebug("[eEPGCache] event %04x not found in epgcache", event_id); - } - } - return -1; -} + return -1; +} RESULT eEPGCache::saveEventToFile(const char* filename, const eServiceReference &service, int eit_event_id, time_t begTime, time_t endTime) { @@ -3512,12 +1857,12 @@ void eEPGCache::submitEventData(const std::vector& service service->m_flags |= eDVBService::dxNoEIT; } } - submitEventData(sids, chids, start, duration, title, short_summary, long_description, event_type, EPG_IMPORT); + submitEventData(sids, chids, start, duration, title, short_summary, long_description, event_type, 0, EPG_IMPORT); } void eEPGCache::submitEventData(const std::vector& sids, const std::vector& chids, long start, long duration, const char* title, const char* short_summary, - const char* long_description, char event_type, int source) + const char* long_description, char event_type, int event_id, int source) { if (!title) return; @@ -3541,7 +1886,7 @@ void eEPGCache::submitEventData(const std::vector& sids, const std::vector< eit_event_t *evt_struct = (eit_event_t*) (data + EIT_SIZE); - uint16_t eventId = start & 0xFFFF; + uint16_t eventId = (event_id == 0) ? start & 0xFFFF : event_id; evt_struct->setEventId(eventId); //6 bytes start time, 3 bytes duration @@ -3660,12 +2005,12 @@ void eEPGCache::setEpgHistorySeconds(time_t seconds) void eEPGCache::setEpgSources(unsigned int mask) { - enabledSources = mask; + m_enabledEpgSources = mask; } unsigned int eEPGCache::getEpgSources() { - return enabledSources; + return m_enabledEpgSources; } static const char* getStringFromPython(ePyObject obj) @@ -3945,86 +2290,92 @@ PyObject *eEPGCache::search(ePyObject arg) eDebug("[eEPGCache] lookup events with '%s' in the description (%s)", str, casetype?"ignore case":"case sensitive"); break; } - singleLock s(cache_lock); - std::string text; - for (DescriptorMap::iterator it(eventData::descriptors.begin()); - it != eventData::descriptors.end(); ++it) + Py_BEGIN_ALLOW_THREADS; /* No Python code in this section, so other threads can run */ { - uint8_t *data = it->second.data; - int textlen = 0; - const char *textptr = NULL; - if ( data[0] == 0x4D && querytype > 0 && querytype < 5 ) // short event descriptor - { - textptr = (const char*)&data[6]; - textlen = data[5]; - if (data[6] < 0x20) - { - /* custom encoding */ - text = convertDVBUTF8((unsigned char*)textptr, textlen, 0x40, 0); - textptr = text.data(); - textlen = text.length(); - } - } - else if ( data[0] == 0x4E && querytype == 5 ) // extended event descriptor - { - textptr = (const char*)&data[8]; - textlen = data[7]; - if (data[8] < 0x20) - { - /* custom encoding */ - text = convertDVBUTF8((unsigned char*)textptr, textlen, 0x40, 0); - textptr = text.data(); - textlen = text.length(); - } - } - /* if we have a descriptor, the argument may not be bigger */ - if (textlen > 0 && strlen <= textlen ) + /* new block to release epgcache lock before Py_END_ALLOW_THREADS is called + otherwise there might be a deadlock with other python thread waiting for the epgcache lock */ + singleLock s(cache_lock); + std::string text; + for (DescriptorMap::iterator it(eventData::descriptors.begin()); + it != eventData::descriptors.end(); ++it) { - if (querytype == 1) - { - /* require exact text match */ - if (textlen != strlen) - continue; - } - else if (querytype == 3) + uint8_t *data = it->second.data; + int textlen = 0; + const char *textptr = NULL; + if ( data[0] == 0x4D && querytype > 0 && querytype < 5 ) // short event descriptor { - /* Do a "startswith" match by pretending the text isn't that long */ - textlen = strlen; + textptr = (const char*)&data[6]; + textlen = data[5]; + if (data[6] < 0x20) + { + /* custom encoding */ + text = convertDVBUTF8((unsigned char*)textptr, textlen, 0x40, 0); + textptr = text.data(); + textlen = text.length(); + } } - else if (querytype == 4) + else if ( data[0] == 0x4E && querytype == 5 ) // extended event descriptor { - /* Offset to adjust the pointer based on the text length difference */ - textptr = textptr + textlen - strlen; - textlen = strlen; + textptr = (const char*)&data[8]; + textlen = data[7]; + if (data[8] < 0x20) + { + /* custom encoding */ + text = convertDVBUTF8((unsigned char*)textptr, textlen, 0x40, 0); + textptr = text.data(); + textlen = text.length(); + } } - if (casetype) + /* if we have a descriptor, the argument may not be bigger */ + if (textlen > 0 && strlen <= textlen ) { - while (textlen >= strlen) + if (querytype == 1) + { + /* require exact text match */ + if (textlen != strlen) + continue; + } + else if (querytype == 3) + { + /* Do a "startswith" match by pretending the text isn't that long */ + textlen = strlen; + } + else if (querytype == 4) + { + /* Offset to adjust the pointer based on the text length difference */ + textptr = textptr + textlen - strlen; + textlen = strlen; + } + if (casetype) { - if (!strncasecmp(textptr, str, strlen)) + while (textlen >= strlen) { - descr.push_back(it->first); - break; + if (!strncasecmp(textptr, str, strlen)) + { + descr.push_back(it->first); + break; + } + textlen--; + textptr++; } - textlen--; - textptr++; } - } - else - { - while (textlen >= strlen) + else { - if (!memcmp(textptr, str, strlen)) + while (textlen >= strlen) { - descr.push_back(it->first); - break; + if (!memcmp(textptr, str, strlen)) + { + descr.push_back(it->first); + break; + } + textlen--; + textptr++; } - textlen--; - textptr++; } } } } + Py_END_ALLOW_THREADS; } else { @@ -4178,145 +2529,33 @@ PyObject *eEPGCache::search(ePyObject arg) fillTuple(tuple, argstring, argcount, service_reference, ev_data ? &ptr : 0, service_name, tmp, evit->second); PyList_Append(ret, tuple); Py_DECREF(tuple); - if (service_name) - Py_DECREF(service_name); - if (service_reference) - Py_DECREF(service_reference); - --maxcount; - } - } - } - } - 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_RETURN_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 0xC1: // user private - for (DescriptorConstIterator desc = (*es)->getDescriptors()->begin(); - desc != (*es)->getDescriptors()->end(); ++desc) - { - switch ((*desc)->getTag()) - { - case 0xC2: // user defined - if ((*desc)->getLength() == 8) - { - uint8_t buffer[10]; - (*desc)->writeToBuffer(buffer); - if (!memcmp((const char *)buffer+2, "EPGDATA", 7)) - { - eServiceReferenceDVB ref; - if (!pmthandler->getServiceReference(ref)) - { - int pid = (*es)->getPid(); - messages.send(Message(Message::got_mhw2_channel_pid, ref, pid)); - } - } - else if(!memcmp((const char *)buffer+2, "FICHAS", 6)) - { - eServiceReferenceDVB ref; - if (!pmthandler->getServiceReference(ref)) - { - int pid = (*es)->getPid(); - messages.send(Message(Message::got_mhw2_summary_pid, ref, pid)); - } - } - else if(!memcmp((const char *)buffer+2, "GENEROS", 7)) - { - eServiceReferenceDVB ref; - if (!pmthandler->getServiceReference(ref)) - { - int pid = (*es)->getPid(); - messages.send(Message(Message::got_mhw2_title_pid, ref, pid)); - } - } - } - break; - default: - break; - } - } - break; - 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: - { - Descriptor *descr = (Descriptor*)*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; + if (service_name) + Py_DECREF(service_name); + if (service_reference) + Py_DECREF(service_reference); + --maxcount; } } - 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; - } } } + if (first) + { + // now start at first service in epgcache database ( only in SIMILAR BROADCASTING SEARCH ) + first=false; + cit=eventDB.begin(); + } + else + ++cit; } } - else - eDebug("[eEPGCache] PMTready but no pmt!!"); + + if (!ret) + Py_RETURN_NONE; + + return ret; } +#ifdef ENABLE_PRIVATE_EPG struct date_time { uint8_t data[5]; @@ -4505,811 +2744,15 @@ void eEPGCache::privateSectionRead(const uniqueEPGKey ¤t_service, const ui 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 ); + eventData *d = new eventData( ev_struct, bptr, eEPGCache::PRIVATE ); evMap[event_id] = d; tmMap[stime] = d; ASSERT(bptr <= 4098); } } - -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("[eEPGCache] 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(sigc::mem_fun(*this, &eEPGCache::channel_data::readPrivateData), m_PrivateConn); - m_PrivateReader->start(mask); -} - -void eEPGCache::channel_data::readPrivateData( const uint8_t *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("[eEPGCache] 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] = ::time(0); - m_PrevVersion = (data[5] & 0x3E) >> 1; - startPrivateReader(); - } -} - -#endif // ENABLE_PRIVATE_EPG - -#ifdef ENABLE_MHW_EPG -void eEPGCache::channel_data::cleanupMHW() -{ - m_MHWTimeoutTimer->stop(); - m_channels.clear(); - m_themes.clear(); - m_titles.clear(); - m_program_ids.clear(); -} - -uint8_t *eEPGCache::channel_data::delimitName( uint8_t *in, uint8_t *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 -{ - char tz_saved[1024]; - // Remove offset in mhw time. - uint8_t 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 = ::time(0); - - char *old_tz = getenv( "TZ" ); - if (old_tz) - strcpy(tz_saved, old_tz); - putenv((char*)"TZ=CET-1CEST,M3.5.0/2,M10.5.0/3"); - tzset(); - - tm localnow; - localtime_r(&dt, &localnow); - - 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_r( &dt, &recdate ); // This will also take care of DST. - - if ( old_tz == NULL ) - unsetenv( "TZ" ); - else - setenv("TZ", tz_saved, 1); - 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::storeMHWTitle(std::map::iterator itTitle, std::string sumText, const uint8_t *data) -// data is borrowed from calling proc to save memory space. -{ - uint8_t 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; - - uint8_t *title = isMHW2 ? ((uint8_t*)(itTitle->second.title))-4 : (uint8_t*)itTitle->second.title; - std::string prog_title = (char *) delimitName( title, name, isMHW2 ? 35 : 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( itTitle->second.getMhw2Duration(), data+7 ); - } - else - { - timeMHW2DVB( itTitle->second.dh.day, itTitle->second.dh.hours, itTitle->second.ms.minutes, - (u_char *) event_data + 2 ); - timeMHW2DVB( itTitle->second.getDuration(), (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 - { - 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 ); -} - -void eEPGCache::channel_data::startMHWTimeout(int msec) -{ - m_MHWTimeoutTimer->start(msec,true); - m_MHWTimeoutet=false; -} - -void eEPGCache::channel_data::startMHWReader(uint16_t pid, uint8_t tid) -{ - m_MHWFilterMask.pid = pid; - m_MHWFilterMask.data[0] = tid; - m_MHWReader->start(m_MHWFilterMask); -// eDebug("[eEPGCache] start 0x%02x 0x%02x", pid, tid); -} - -void eEPGCache::channel_data::startMHWReader2(uint16_t pid, uint8_t 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("[eEPGCache] start 0x%03x 0x%02x 0x%02x", pid, tid, ext); - } - else - { - m_MHWFilterMask2.data[1] = 0; - m_MHWFilterMask2.mask[1] = 0; -// eDebug("[eEPGCache] start 0x%02x 0x%02x", pid, tid); - } - m_MHWReader2->start(m_MHWFilterMask2); -} - -void eEPGCache::channel_data::readMHWData(const uint8_t *data) -{ - if ( m_MHWReader2 ) - m_MHWReader2->stop(); - - if ( state > 1 || // aborted - // have si data.. so we dont read mhw data - (haveData & (SCHEDULE|SCHEDULE_OTHER|VIASAT)) ) - { - eDebug("[eEPGCache] 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); - - m_channels.resize(nbr_records); - for ( int i = 0; i < nbr_records; i++ ) - { - mhw_channel_name_t *channel = (mhw_channel_name_t*) &data[4 + i*record_size]; - m_channels[i]=*channel; - } - haveData |= MHW; - - eDebug("[eEPGCache] mhw %zu 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; - uint8_t next_idx = (uint8_t) *(data + 3 + idx_ptr); - uint8_t idx = 0; - uint8_t 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 = (uint8_t) *(data + 3 + idx_ptr); - sub_idx = 0; - } - else - sub_idx++; - - m_themes[idx+sub_idx] = *theme; - } - eDebug("[eEPGCache] mhw %zu themes found", m_themes.size()); - // Themes table has been read, start reading the titles table. - startMHWReader(0xD2, 0x90); - startMHWTimeout(4000); - return; - } - else if (m_MHWFilterMask.pid == 0xD2 && m_MHWFilterMask.data[0] == 0x90) - // Titles table - { - mhw_title_t *title = (mhw_title_t*) data; - uint8_t name[24]; - std::string prog_title = (char *) delimitName( title->title, name, 23 ); - - if ( title->channel_id == 0xFF || prog_title.substr(0,7) == "BIENTOT" ) // Separator or BIENTOT record - return; // Continue reading of the current table. - else - { - // Create unique key per title - uint32_t title_id = ((title->channel_id)<<16)|((title->dh.day)<<13)|((title->dh.hours)<<8)| - (title->ms.minutes); - uint32_t 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() ) - { - startMHWTimeout(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.insert(std::pair(program_id,title_id)); - return; // Continue reading of the current table. - } - else if (!checkMHWTimeout()) - 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("[eEPGCache] mhw %zu titles(%zu with summary) found", - m_titles.size(), - m_program_ids.size()); - startMHWTimeout(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 - uint32_t 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::multimap::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() && !checkMHWTimeout()) - return; // Continue reading of the current table. - } - else - { - std::string the_text = (char *) (data + 11 + summary->nb_replays * 7); - - size_t 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::iterator itTitle( m_titles.find( itProgid->second ) ); - if ( itTitle != m_titles.end() ) - { - startMHWTimeout(4000); - storeMHWTitle( 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("[eEPGCache] mhw finished(%ld) %zu summaries not found", - ::time(0), - 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::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) - storeMHWTitle( itTitle, "", data ); - isRunning &= ~MHW; - m_MHWConn=0; - if ( m_MHWReader ) - m_MHWReader->stop(); - if (haveData) - finishEPG(); -} - -void eEPGCache::channel_data::readMHWData2(const uint8_t *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 & (SCHEDULE|SCHEDULE_OTHER|VIASAT)) ) - { - eDebug("[eEPGCache] mhw2 aborted %d", state); - } - else if (m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) - // Channels table - { - int num_channels = data[120]; - m_channels.resize(num_channels); - if(dataLen > 120) - { - int ptr = 121 + 8 * 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 uint8_t *tmp = data+121; - for (int i=0; i < num_channels; ++i) - { - mhw_channel_name_t channel; - channel.network_id_hi = *(tmp++); - channel.network_id_lo = *(tmp++); - channel.transport_stream_id_hi = *(tmp++); - channel.transport_stream_id_lo = *(tmp++); - channel.channel_id_hi = *(tmp++); - channel.channel_id_lo = *(tmp++); - m_channels[i]=channel; -// eDebug("[eEPGCache] %d(%02x) %04x: %02x %02x", i, i, (channel.channel_id_hi << 8) | channel.channel_id_lo, *tmp, *(tmp+1)); - 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; -// eDebug("[eEPGCache] %d(%02x) %s", i, i, channel.name); - } - haveData |= MHW; - eDebug("[eEPGCache] mhw2 %zu channels found", m_channels.size()); - } - else if (m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) - { - // Themes table - eDebug("[eEPGCache] mhw2 themes nyi"); - } - else if (m_MHWFilterMask2.pid == m_mhw2_title_pid && m_MHWFilterMask2.data[0] == 0xe6) - // Titles table - { - int pos=18; - bool valid=false; - bool finish=false; - -// eDebug("[eEPGCache] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", -// data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], -// data[11], data[12], data[13], data[14], data[15], data[16], data[17] ); - - while( pos < dataLen && !valid) - { - pos += 18; - pos += (data[pos] & 0x3F) + 4; - if( pos == dataLen ) - valid = true; - } - - if (!valid) - { - if (dataLen > 18) - eDebug("[eEPGCache] mhw2 title table invalid!!"); - if (checkMHWTimeout()) - goto abort; - if (!m_MHWTimeoutTimer->isActive()) - startMHWTimeout(5000); - return; // continue reading - } - - // data seems consistent... - mhw_title_t title; - pos = 18; - while (pos < dataLen) - { -// eDebugNoNewLineStart("[eEPGCache] [%02x] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x [%02x %02x %02x %02x %02x %02x %02x] LL - DESCR - ", -// data[pos], data[pos+1], data[pos+2], data[pos+3], data[pos+4], data[pos+5], data[pos+6], data[pos+7], -// data[pos+8], data[pos+9], data[pos+10], data[pos+11], data[pos+12], data[pos+13], data[pos+14], data[pos+15], data[pos+16], data[pos+17]); - title.channel_id = data[pos]+1; - title.mhw2_mjd_hi = data[pos+11]; - title.mhw2_mjd_lo = data[pos+12]; - title.mhw2_hours = data[pos+13]; - title.mhw2_minutes = data[pos+14]; - title.mhw2_seconds = data[pos+15]; - int duration = ((data[pos+16] << 8)|data[pos+17]) >> 4; - title.mhw2_duration_hi = (duration&0xFF00) >> 8; - title.mhw2_duration_lo = duration&0xFF; - - // Create unique key per title - uint32_t title_id = (data[pos+7] << 24) | (data[pos+8] << 16) | (data[pos+9] << 8) | data[pos+10]; - - uint8_t slen = data[pos+18] & 0x3f; - uint8_t *dest = ((uint8_t*)title.title)-4; - memcpy(dest, &data[pos+19], slen>35 ? 35 : slen); - memset(dest+slen, 0, 35-slen); - pos += 19 + slen; -// eDebugNoNewLine("%02x [%02x %02x]: %s\n", data[pos], data[pos+1], data[pos+2], dest); - -// not used theme id (data[7] & 0x3f) + (data[pos] & 0x3f); - uint32_t summary_id = (data[pos+1] << 8) | data[pos+2]; - -// if (title.channel_id > m_channels.size()) -// eDebug("[eEPGCache] channel_id(%d %02x) to big!!", title.channel_id); - -// eDebug("[eEPGCache] pos %d prog_id %02x %02x chid %02x summary_id %04x dest %p len %d\n", -// pos, title.program_id_ml, title.program_id_lo, title.channel_id, summary_id, dest, slen); - -// eDebug("[eEPGCache] title_id %08x -> summary_id %04x\n", title_id, summary_id); - - pos += 3; - - std::map::iterator it = m_titles.find( title_id ); - if ( it == m_titles.end() ) - { - startMHWTimeout(5000); - m_titles[ title_id ] = title; - if (summary_id != 0xFFFF) - { - bool add=true; - std::multimap::iterator it(m_program_ids.lower_bound(summary_id)); - while (it != m_program_ids.end() && it->first == summary_id) - { - if (it->second == title_id) { - add=false; - break; - } - ++it; - } - if (add) - m_program_ids.insert(std::pair(summary_id,title_id)); - } - } - else - { - if ( !checkMHWTimeout() ) - continue; // Continue reading of the current table. - finish=true; - break; - } - } - if (finish) - { - eDebug("[eEPGCache] mhw2 %zu titles(%zu 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(m_mhw2_summary_pid, 0x96); - startMHWTimeout(15000); - return; - } - } - else - return; - } - else if (m_MHWFilterMask2.pid == m_mhw2_summary_pid && m_MHWFilterMask2.data[0] == 0x96) - // Summaries table - { - if (!checkMHWTimeout()) - { - int len, loop, pos, lenline; - bool valid; - valid = true; - if( dataLen > 15 ) - { - loop = data[14]; - pos = 15 + 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 - return; // continue reading - - if (valid) - { - // data seems consistent... - uint32_t summary_id = (data[3]<<8)|data[4]; -// eDebug ("[eEPGCache] summary id %04x\n", summary_id); -// eDebug("[eEPGCache] [%02x %02x] %02x %02x %02x %02x %02x %02x %02x %02x XX\n", data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13] ); - - // ugly workaround to convert const __u8* to char* - char *tmp=0; - memcpy(&tmp, &data, sizeof(void*)); - - len = 0; - loop = data[14]; - pos = 15 + 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::multimap::iterator itProgId( m_program_ids.lower_bound(summary_id) ); - if ( itProgId == m_program_ids.end() || itProgId->first != summary_id) - { /* 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() ) - return; // Continue reading of the current table. - } - else - { - startMHWTimeout(15000); - std::string the_text = (char *) (data + pos + 1); - -// eDebug ("[eEPGCache] summary id %04x : %s\n", summary_id, data+pos+1); - - while( itProgId != m_program_ids.end() && itProgId->first == summary_id ) - { -// eDebug("[eEPGCache] ."); - // Find corresponding title, store title and summary in epgcache. - std::map::iterator itTitle( m_titles.find( itProgId->second ) ); - if ( itTitle != m_titles.end() ) - { - storeMHWTitle( 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. - } - } - else - return; // continue reading - } - } - if (isRunning & eEPGCache::MHW) - { - if ( m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) - { - // Channels table has been read, start reading the themes table. - startMHWReader2(m_mhw2_channel_pid, 0xC8, 1); - return; - } - else if ( m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) - { - // Themes table has been read, start reading the titles table. - startMHWReader2(m_mhw2_title_pid, 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::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) - storeMHWTitle( itTitle, "", data ); - eDebug("[eEPGCache] mhw2 finished(%ld) %zu summaries not found", - ::time(0), - m_program_ids.size()); - } - } -abort: - isRunning &= ~MHW; - m_MHWConn2=0; - if ( m_MHWReader2 ) - m_MHWReader2->stop(); - if (haveData) - finishEPG(); -} #endif + typedef struct epgdb_title_s { uint16_t event_id; diff --git a/lib/dvb/epgcache.h b/lib/dvb/epgcache.h index 91dea73b29..6a25ad21e5 100644 --- a/lib/dvb/epgcache.h +++ b/lib/dvb/epgcache.h @@ -12,21 +12,9 @@ #ifndef SWIG #include -#include #include -#include - -#include -#ifdef ENABLE_MHW_EPG -#include -#endif -#ifdef ENABLE_OPENTV -#include -#include -#endif #include -#include #include #include #include @@ -36,7 +24,8 @@ struct eventData; class eServiceReferenceDVB; -class eDVBServicePMTHandler; +class eEPGChannelData; +class eEPGTransponderDataReader; struct uniqueEPGKey { @@ -89,8 +78,6 @@ typedef std::map eventMap; //timeMap is sorted by beginTime typedef std::map timeMap; -typedef std::map updateMap; - struct hash_uniqueEPGKey { inline size_t operator()( const uniqueEPGKey &x) const @@ -104,8 +91,6 @@ struct EventCacheItem { timeMap byTime; }; -typedef std::set tidMap; - typedef std::tr1::unordered_map eventCache; #ifdef ENABLE_PRIVATE_EPG typedef std::tr1::unordered_map > contentTimeMap; @@ -115,155 +100,10 @@ typedef std::tr1::unordered_map -class freesatEITSubtableStatus -{ -private: - u_char version; - uint16_t sectionMap[32]; - void initMap(uint8_t maxSection); - -public: - freesatEITSubtableStatus(u_char version, uint8_t maxSection); - bool isSectionPresent(uint8_t sectionNo); - void seen(uint8_t sectionNo, uint8_t maxSegmentSection); - bool isVersionChanged(u_char testVersion); - void updateVersion(u_char newVersion, uint8_t maxSection); - bool isCompleted(); -}; -#endif - class eEPGCache: public eMainloop, private eThread, public sigc::trackable { #ifndef SWIG DECLARE_REF(eEPGCache) - struct channel_data: public sigc::trackable - { - pthread_mutex_t channel_active; - channel_data(eEPGCache*); - eEPGCache *cache; - ePtr abortTimer, zapTimer; - int prevChannelState; - int state; - unsigned int isRunning, haveData; - ePtr channel; - ePtr m_stateChangedConn, m_NowNextConn, m_ScheduleConn, m_ScheduleOtherConn, m_ViasatConn; - ePtr m_NowNextReader, m_ScheduleReader, m_ScheduleOtherReader, m_ViasatReader; - tidMap seenSections[4], calcedSections[4]; -#ifdef ENABLE_VIRGIN - ePtr m_VirginNowNextConn, m_VirginScheduleConn; - ePtr m_VirginNowNextReader, m_VirginScheduleReader; -#endif -#ifdef ENABLE_NETMED - ePtr m_NetmedScheduleConn, m_NetmedScheduleOtherConn; - ePtr m_NetmedScheduleReader, m_NetmedScheduleOtherReader; -#endif -#ifdef ENABLE_FREESAT - ePtr m_FreeSatScheduleOtherConn, m_FreeSatScheduleOtherConn2; - ePtr m_FreeSatScheduleOtherReader, m_FreeSatScheduleOtherReader2; - std::map m_FreeSatSubTableStatus; - uint32_t m_FreesatTablesToComplete; - void readFreeSatScheduleOtherData(const uint8_t *data); - void cleanupFreeSat(); -#endif -#ifdef ENABLE_PRIVATE_EPG - ePtr startPrivateTimer; - int m_PrevVersion; - int m_PrivatePid; - uniqueEPGKey m_PrivateService; - ePtr m_PrivateConn; - ePtr m_PrivateReader; - std::set seenPrivateSections; - void readPrivateData(const uint8_t *data); - void startPrivateReader(); -#endif -#ifdef ENABLE_MHW_EPG - std::vector m_channels; - std::map m_themes; - std::map m_titles; - std::multimap m_program_ids; - ePtr m_MHWConn, m_MHWConn2; - ePtr m_MHWReader, m_MHWReader2; - eDVBSectionFilterMask m_MHWFilterMask, m_MHWFilterMask2; - ePtr m_MHWTimeoutTimer; - uint16_t m_mhw2_channel_pid, m_mhw2_title_pid, m_mhw2_summary_pid; - bool m_MHWTimeoutet; - void MHWTimeout() { m_MHWTimeoutet=true; } - void readMHWData(const uint8_t *data); - void readMHWData2(const uint8_t *data); - void startMHWReader(uint16_t pid, uint8_t tid); - void startMHWReader2(uint16_t pid, uint8_t tid, int ext=-1); - void startMHWTimeout(int msek); - bool checkMHWTimeout() { return m_MHWTimeoutet; } - void cleanupMHW(); - uint8_t *delimitName( uint8_t *in, uint8_t *out, int len_in ); - void timeMHW2DVB( u_char hours, u_char minutes, u_char *return_time); - void timeMHW2DVB( int minutes, u_char *return_time); - void timeMHW2DVB( u_char day, u_char hours, u_char minutes, u_char *return_time); - void storeMHWTitle(std::map::iterator itTitle, std::string sumText, const uint8_t *data); -#endif -#ifdef ENABLE_ATSC - int m_atsc_eit_index; - std::map m_ATSC_VCT_map; - std::map m_ATSC_ETT_map; - struct atsc_event - { - uint16_t eventId; - uint32_t startTime; - uint32_t lengthInSeconds; - std::string title; - }; - std::map m_ATSC_EIT_map; - ePtr m_ATSC_VCTReader, m_ATSC_MGTReader, m_ATSC_EITReader, m_ATSC_ETTReader; - ePtr m_ATSC_VCTConn, m_ATSC_MGTConn, m_ATSC_EITConn, m_ATSC_ETTConn; - void ATSC_checkCompletion(); - void ATSC_VCTsection(const uint8_t *d); - void ATSC_MGTsection(const uint8_t *d); - void ATSC_EITsection(const uint8_t *d); - void ATSC_ETTsection(const uint8_t *d); - void cleanupATSC(); -#endif -#ifdef ENABLE_OPENTV - typedef std::tr1::unordered_map OpenTvDescriptorMap; - int m_OPENTV_EIT_index; - uint16_t m_OPENTV_pid; - uint32_t m_OPENTV_crc32; - uint32_t opentv_title_crc32; - bool huffman_dictionary_read; - struct opentv_channel - { - uint16_t originalNetworkId; - uint16_t transportStreamId; - uint16_t serviceId; - uint8_t serviceType; - }; - struct opentv_event - { - uint16_t eventId; - uint32_t startTime; - uint32_t duration; - uint32_t title_crc; - }; - OpenTvDescriptorMap m_OPENTV_descriptors_map; - std::map m_OPENTV_channels_map; - std::map m_OPENTV_EIT_map; - ePtr m_OPENTV_Timer; - ePtr m_OPENTV_ChannelsReader, m_OPENTV_TitlesReader, m_OPENTV_SummariesReader; - ePtr m_OPENTV_ChannelsConn, m_OPENTV_TitlesConn, m_OPENTV_SummariesConn; - void OPENTV_checkCompletion(const uint32_t data_crc); - void OPENTV_ChannelsSection(const uint8_t *d); - void OPENTV_TitlesSection(const uint8_t *d); - void OPENTV_SummariesSection(const uint8_t *d); - void cleanupOPENTV(); -#endif - void readData(const uint8_t *data, int source); - void startChannel(); - void startEPG(); - void finishEPG(); - void abortEPG(); - void abortNonAvail(); - }; bool FixOverlapping(EventCacheItem &servicemap, time_t TM, int duration, const timeMap::iterator &tm_it, const uniqueEPGKey &service); public: struct Message @@ -271,58 +111,39 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable enum { flush, - startChannel, - leaveChannel, quit, - got_private_pid, - got_mhw2_channel_pid, - got_mhw2_title_pid, - got_mhw2_summary_pid, timeChanged }; int type; - iDVBChannel *channel; uniqueEPGKey service; union { int err; - time_t time; - bool avail; - int pid; }; Message() - :type(0), time(0) {} + :type(0) {} Message(int type) :type(type) {} - Message(int type, bool b) - :type(type), avail(b) {} - Message(int type, iDVBChannel *channel, int err=0) - :type(type), channel(channel), err(err) {} Message(int type, const eServiceReference& service, int err=0) :type(type), service(service), err(err) {} - Message(int type, time_t time) - :type(type), time(time) {} }; eFixedMessagePump messages; + private: - friend struct channel_data; friend struct eventData; + friend class eEPGChannelData; + friend class eEPGTransponderDataReader; static eEPGCache *instance; - typedef std::map ChannelMap; - - ePtr cleanTimer; - ChannelMap m_knownChannels; - ePtr m_chanAddedConn; - - unsigned int enabledSources; unsigned int historySeconds; std::vector onid_blacklist; - std::map customeitpids; eventCache eventDB; - updateMap channelLastUpdated; std::string m_filename; bool m_running; + unsigned int m_enabledEpgSources; + ePtr cleanTimer; + bool load_epg; + PSignal0 epgCacheStarted; #ifdef ENABLE_PRIVATE_EPG contentMaps content_time_tables; @@ -333,17 +154,13 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable #ifdef ENABLE_PRIVATE_EPG void privateSectionRead(const uniqueEPGKey &, const uint8_t *); #endif - void sectionRead(const uint8_t *data, int source, channel_data *channel); + void sectionRead(const uint8_t *data, int source, eEPGChannelData *channel); + void gotMessage(const Message &message); void cleanLoop(); - void submitEventData(const std::vector& sids, const std::vector& chids, long start, long duration, const char* title, const char* short_summary, const char* long_description, char event_type, int source); + void submitEventData(const std::vector& sids, const std::vector& chids, long start, long duration, const char* title, const char* short_summary, const char* long_description, char event_type, int event_id, int source); void clearCompleteEPGCache(); -// called from main thread - void DVBChannelAdded(eDVBChannel*); - void DVBChannelStateChanged(iDVBChannel*); - void DVBChannelRunning(iDVBChannel *); - eServiceReferenceDVB *m_timeQueryRef; time_t m_timeQueryBegin; int m_timeQueryMinutes; @@ -356,6 +173,7 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable static eEPGCache *getInstance() { return instance; } void crossepgImportEPGv21(std::string dbroot); + void save(); void load(); void timeUpdated(); @@ -364,12 +182,6 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable eEPGCache(); ~eEPGCache(); -#ifdef ENABLE_PRIVATE_EPG - void PMTready(eDVBServicePMTHandler *pmthandler); -#else - void PMTready(eDVBServicePMTHandler *pmthandler) {} -#endif - #endif // must be called once! void setCacheFile(const char *filename); @@ -384,6 +196,9 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable RESULT lookupEventTime(const eServiceReference &service, time_t, const eventData *&, int direction=0); public: + /* Only used by servicedvbrecord.cpp to write the EIT file */ + RESULT saveEventToFile(const char* filename, const eServiceReference &service, int eit_event_id, time_t begTime, time_t endTime); + // Events are parsed epg events.. it's safe to use them after cache unlock // after use the Event pointer must be released using "delete". RESULT lookupEventId(const eServiceReference &service, int event_id, Event* &); @@ -405,9 +220,6 @@ class eEPGCache: public eMainloop, private eThread, public sigc::trackable PyObject *lookupEvent(SWIG_PYOBJECT(ePyObject) list, SWIG_PYOBJECT(ePyObject) convertFunc=(PyObject*)0); PyObject *search(SWIG_PYOBJECT(ePyObject)); - /* Used by servicedvbrecord.cpp, timeshift, etc. to write the EIT file */ - RESULT saveEventToFile(const char* filename, const eServiceReference &service, int eit_event_id, time_t begTime, time_t endTime); - // eServiceEvent are parsed epg events.. it's safe to use them after cache unlock // for use from python ( members: m_start_time, m_duration, m_short_description, m_extended_description ) SWIG_VOID(RESULT) lookupEventId(const eServiceReference &service, int event_id, ePtr &SWIG_OUTPUT); diff --git a/lib/dvb/epgchanneldata.cpp b/lib/dvb/epgchanneldata.cpp new file mode 100644 index 0000000000..2e81d87c3b --- /dev/null +++ b/lib/dvb/epgchanneldata.cpp @@ -0,0 +1,2144 @@ +#include + +#include +#include + +#include +#include + + +eEPGChannelData::eEPGChannelData(eEPGTransponderDataReader *ml) + :epgReader(ml) + ,abortTimer(eTimer::create(ml)), zapTimer(eTimer::create(ml)), state(-2) + ,isRunning(0), haveData(0) +#ifdef ENABLE_PRIVATE_EPG + ,startPrivateTimer(eTimer::create(ml)) +#endif +#ifdef ENABLE_MHW_EPG + ,m_MHWTimeoutTimer(eTimer::create(ml)) +#endif +#ifdef ENABLE_OPENTV + ,m_OPENTV_Timer(eTimer::create(ml)) +#endif +{ +#ifdef ENABLE_MHW_EPG + CONNECT(m_MHWTimeoutTimer->timeout, eEPGChannelData::MHWTimeout); +#endif + CONNECT(zapTimer->timeout, eEPGChannelData::startEPG); + CONNECT(abortTimer->timeout, eEPGChannelData::abortNonAvail); +#ifdef ENABLE_PRIVATE_EPG + CONNECT(startPrivateTimer->timeout, eEPGChannelData::startPrivateReader); +#endif +#ifdef ENABLE_OPENTV + CONNECT(m_OPENTV_Timer->timeout, eEPGChannelData::cleanupOPENTV); +#endif + pthread_mutex_init(&channel_active, 0); +} + +void eEPGChannelData::startChannel() +{ + pthread_mutex_lock(&channel_active); + singleLock l(eEPGTransponderDataReader::last_channel_update_lock); + updateMap::iterator It = epgReader->m_channelLastUpdated.find( channel->getChannelID() ); + + int update = ( It != epgReader->m_channelLastUpdated.end() ? ( UPDATE_INTERVAL - ( (::time(0)-It->second) * 1000 ) ) : ZAP_DELAY ); + + if (update < ZAP_DELAY) + update = ZAP_DELAY; + + zapTimer->start(update, 1); + if (update >= 60000) + eDebug("[eEPGTransponderDataReader] next update in %i min", update/60000); + else if (update >= 1000) + eDebug("[eEPGTransponderDataReader] next update in %i sec", update/1000); +} + +void eEPGChannelData::startEPG() +{ + eDebug("[eEPGTransponderDataReader] start reading events(%ld)", ::time(0)); + state=0; + haveData=0; + for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } +#ifdef ENABLE_MHW_EPG + cleanupMHW(); +#endif +#ifdef ENABLE_FREESAT + cleanupFreeSat(); +#endif +#ifdef ENABLE_OPENTV + huffman_dictionary_read = false; + cleanupOPENTV(); +#endif + eDVBSectionFilterMask mask; + memset(&mask, 0, sizeof(mask)); + +#ifdef ENABLE_MHW_EPG + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::MHW && m_MHWReader) + { + mask.pid = 0xD3; + mask.data[0] = 0x91; + mask.mask[0] = 0xFF; + m_MHWReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::readMHWData), m_MHWConn); + m_MHWReader->start(mask); + isRunning |= eEPGCache::MHW; + memcpy(&m_MHWFilterMask, &mask, sizeof(eDVBSectionFilterMask)); + + mask.pid = m_mhw2_channel_pid; + mask.data[0] = 0xC8; + mask.mask[0] = 0xFF; + mask.data[1] = 0; + mask.mask[1] = 0xFF; + m_MHWReader2->connectRead(sigc::mem_fun(*this, &eEPGChannelData::readMHWData2), m_MHWConn2); + m_MHWReader2->start(mask); + isRunning |= eEPGCache::MHW; + memcpy(&m_MHWFilterMask2, &mask, sizeof(eDVBSectionFilterMask)); + mask.data[1] = 0; + mask.mask[1] = 0; + m_MHWTimeoutet=false; + } +#endif +#ifdef ENABLE_FREESAT + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::FREESAT_SCHEDULE_OTHER && m_FreeSatScheduleOtherReader) + { + mask.pid = 3842; + mask.flags = eDVBSectionFilterMask::rfCRC; + mask.data[0] = 0x60; + mask.mask[0] = 0xFE; + m_FreeSatScheduleOtherReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::readFreeSatScheduleOtherData), m_FreeSatScheduleOtherConn); + m_FreeSatScheduleOtherReader->start(mask); + + /* + * faster pid, available on ITV HD transponder. + * We rely on the fact that we have either of the two, + * never both. (both readers share the same data callback + * and status maps) + */ + mask.pid = 3003; + m_FreeSatScheduleOtherReader2->connectRead(sigc::mem_fun(*this, &eEPGChannelData::readFreeSatScheduleOtherData), m_FreeSatScheduleOtherConn2); + m_FreeSatScheduleOtherReader2->start(mask); + isRunning |= eEPGCache::FREESAT_SCHEDULE_OTHER; + } +#endif + mask.pid = 0x12; + mask.flags = eDVBSectionFilterMask::rfCRC; + + eDVBChannelID chid = channel->getChannelID(); + std::ostringstream epg_id; + epg_id << std::hex << std::setfill('0') << + std::setw(0) << ((chid.dvbnamespace.get() & 0xffff0000) >> 16) << + std::setw(4) << chid.transport_stream_id.get() << + std::setw(4) << chid.original_network_id.get(); + + std::map::iterator it = epgReader->customeitpids.find(epg_id.str()); + if (it != epgReader->customeitpids.end()) + { + mask.pid = it->second; + eDebug("[eEPGTransponderDataReader] Using non-standard pid %#x", mask.pid); + } + + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NOWNEXT && m_NowNextReader) + { + mask.data[0] = 0x4E; + mask.mask[0] = 0xFE; + m_NowNextReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::NOWNEXT), m_NowNextConn); + m_NowNextReader->start(mask); + isRunning |= eEPGCache::NOWNEXT; + } + + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::SCHEDULE && m_ScheduleReader) + { + mask.data[0] = 0x50; + mask.mask[0] = 0xF0; + m_ScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::SCHEDULE), m_ScheduleConn); + m_ScheduleReader->start(mask); + isRunning |= eEPGCache::SCHEDULE; + } + + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::SCHEDULE_OTHER && m_ScheduleOtherReader) + { + mask.data[0] = 0x60; + mask.mask[0] = 0xF0; + m_ScheduleOtherReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::SCHEDULE_OTHER), m_ScheduleOtherConn); + m_ScheduleOtherReader->start(mask); + isRunning |= eEPGCache::SCHEDULE_OTHER; + } + +#ifdef ENABLE_VIRGIN + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIRGIN_NOWNEXT && m_VirginNowNextReader) + { + mask.pid = 0x2bc; + mask.data[0] = 0x4E; + mask.mask[0] = 0xFE; + m_VirginNowNextReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::VIRGIN_NOWNEXT), m_VirginNowNextConn); + m_VirginNowNextReader->start(mask); + isRunning |= eEPGCache::VIRGIN_NOWNEXT; + } + + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIRGIN_SCHEDULE && m_VirginScheduleReader) + { + mask.pid = 0x2bc; + mask.data[0] = 0x50; + mask.mask[0] = 0xFE; + m_VirginScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::VIRGIN_SCHEDULE), m_VirginScheduleConn); + m_VirginScheduleReader->start(mask); + isRunning |= eEPGCache::VIRGIN_SCHEDULE; + } +#endif +#ifdef ENABLE_NETMED + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NETMED_SCHEDULE && m_NetmedScheduleReader) + { + mask.pid = 0x1388; + mask.data[0] = 0x50; + mask.mask[0] = 0xF0; + m_NetmedScheduleReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::NETMED_SCHEDULE), m_NetmedScheduleConn); + m_NetmedScheduleReader->start(mask); + isRunning |= eEPGCache::NETMED_SCHEDULE; + } + + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::NETMED_SCHEDULE_OTHER && m_NetmedScheduleOtherReader) + { + mask.pid = 0x1388; + mask.data[0] = 0x60; + mask.mask[0] = 0xF0; + m_NetmedScheduleOtherReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::NETMED_SCHEDULE_OTHER), m_NetmedScheduleOtherConn); + m_NetmedScheduleOtherReader->start(mask); + isRunning |= eEPGCache::NETMED_SCHEDULE_OTHER; + } +#endif +#ifdef ENABLE_ATSC + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::ATSC_EIT && m_ATSC_MGTReader) + { + m_atsc_eit_index = 0; + m_ATSC_MGTReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_MGTsection), m_ATSC_MGTConn); + m_ATSC_VCTReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_VCTsection), m_ATSC_VCTConn); + m_ATSC_EITReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_EITsection), m_ATSC_EITConn); + m_ATSC_ETTReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_ETTsection), m_ATSC_ETTConn); + mask.pid = 0x1ffb; + mask.data[0] = 0xc7; + mask.mask[0] = 0xff; + m_ATSC_MGTReader->start(mask); + mask.pid = 0x1ffb; + mask.data[0] = 0xc8; + mask.mask[0] = 0xfe; + m_ATSC_VCTReader->start(mask); + isRunning |= eEPGCache::ATSC_EIT; + } +#endif +#ifdef ENABLE_OPENTV + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::OPENTV && m_OPENTV_ChannelsReader) + { + char dictionary[256]; + memset(dictionary, '\0', 256); + + //load correct EPG dictionary data "otv_namespace_onid_tsid.dict" + sprintf (dictionary, "/usr/share/enigma2/otv_%08x_%04x_%04x.dict", + (chid.dvbnamespace.get() >> 16) << 16, // without subnet + chid.original_network_id.get(), + chid.transport_stream_id.get()); + + huffman_dictionary_read = huffman_read_dictionary(dictionary); + + if (huffman_dictionary_read) + { + m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; + m_OPENTV_ChannelsReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::OPENTV_ChannelsSection), m_OPENTV_ChannelsConn); + mask.pid = 0x11; + mask.data[0] = 0x4a; + mask.mask[0] = 0xff; + m_OPENTV_ChannelsReader->start(mask); + isRunning |= eEPGCache::OPENTV; + } + else + eDebug("[eEPGTransponderDataReader] abort non avail OpenTV EIT reading"); + } +#endif + if (eEPGCache::getInstance()->getEpgSources() & eEPGCache::VIASAT && m_ViasatReader) + { + mask.pid = 0x39; + + mask.data[0] = 0x40; + mask.mask[0] = 0x40; + m_ViasatReader->connectRead(bind(sigc::mem_fun(*this, &eEPGChannelData::readData), (int)eEPGCache::VIASAT), m_ViasatConn); + m_ViasatReader->start(mask); + isRunning |= eEPGCache::VIASAT; + } +#ifdef ENABLE_OPENTV + if ( isRunning & eEPGCache::OPENTV ) + abortTimer->start(27000,true); + else +#endif + abortTimer->start(7000,true); +} + +void eEPGChannelData::finishEPG() +{ + if (!isRunning) // epg ready + { + eDebug("[eEPGTransponderDataReader] stop caching events(%ld)", ::time(0)); + zapTimer->start(UPDATE_INTERVAL, 1); + eDebug("[eEPGTransponderDataReader] next update in %i min", UPDATE_INTERVAL / 60000); + for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } +#ifdef ENABLE_MHW_EPG + cleanupMHW(); +#endif +#ifdef ENABLE_FREESAT + cleanupFreeSat(); +#endif +#ifdef ENABLE_OPENTV + cleanupOPENTV(); +#endif + singleLock l(eEPGTransponderDataReader::last_channel_update_lock); + epgReader->m_channelLastUpdated[channel->getChannelID()] = ::time(0); + } +} + +void eEPGChannelData::abortEPG() +{ + for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } +#ifdef ENABLE_MHW_EPG + cleanupMHW(); +#endif +#ifdef ENABLE_FREESAT + cleanupFreeSat(); +#endif + abortTimer->stop(); + zapTimer->stop(); + if (isRunning) + { + eDebug("[eEPGTransponderDataReader] abort caching events !!"); + if (isRunning & eEPGCache::SCHEDULE) + { + isRunning &= ~eEPGCache::SCHEDULE; + m_ScheduleReader->stop(); + m_ScheduleConn=0; + } + if (isRunning & eEPGCache::NOWNEXT) + { + isRunning &= ~eEPGCache::NOWNEXT; + m_NowNextReader->stop(); + m_NowNextConn=0; + } + if (isRunning & eEPGCache::SCHEDULE_OTHER) + { + isRunning &= ~eEPGCache::SCHEDULE_OTHER; + m_ScheduleOtherReader->stop(); + m_ScheduleOtherConn=0; + } +#ifdef ENABLE_VIRGIN + if (isRunning & eEPGCache::VIRGIN_NOWNEXT) + { + isRunning &= ~eEPGCache::VIRGIN_NOWNEXT; + m_VirginNowNextReader->stop(); + m_VirginNowNextConn=0; + } + if (isRunning & eEPGCache::VIRGIN_SCHEDULE) + { + isRunning &= ~eEPGCache::VIRGIN_SCHEDULE; + m_VirginScheduleReader->stop(); + m_VirginScheduleConn=0; + } +#endif +#ifdef ENABLE_NETMED + if (isRunning & eEPGCache::NETMED_SCHEDULE) + { + isRunning &= ~eEPGCache::NETMED_SCHEDULE; + m_NetmedScheduleReader->stop(); + m_NetmedScheduleConn=0; + } + if (isRunning & eEPGCache::NETMED_SCHEDULE_OTHER) + { + isRunning &= ~eEPGCache::NETMED_SCHEDULE_OTHER; + m_NetmedScheduleOtherReader->stop(); + m_NetmedScheduleOtherConn=0; + } +#endif +#ifdef ENABLE_FREESAT + if (isRunning & eEPGCache::FREESAT_SCHEDULE_OTHER) + { + isRunning &= ~eEPGCache::FREESAT_SCHEDULE_OTHER; + m_FreeSatScheduleOtherReader->stop(); + m_FreeSatScheduleOtherReader2->stop(); + m_FreeSatScheduleOtherConn=0; + m_FreeSatScheduleOtherConn2=0; + } +#endif + if (isRunning & eEPGCache::VIASAT) + { + isRunning &= ~eEPGCache::VIASAT; + m_ViasatReader->stop(); + m_ViasatConn=0; + } +#ifdef ENABLE_MHW_EPG + if (isRunning & eEPGCache::MHW) + { + isRunning &= ~eEPGCache::MHW; + m_MHWReader->stop(); + m_MHWConn=0; + m_MHWReader2->stop(); + m_MHWConn2=0; + } +#endif +#ifdef ENABLE_ATSC + if (isRunning & eEPGCache::ATSC_EIT) + { + isRunning &= ~eEPGCache::ATSC_EIT; + cleanupATSC(); + } +#endif +#ifdef ENABLE_OPENTV + if (isRunning & eEPGCache::OPENTV) + { + isRunning &= ~eEPGCache::OPENTV; + cleanupOPENTV(); + } +#endif + } +#ifdef ENABLE_PRIVATE_EPG + if (m_PrivateReader) + m_PrivateReader->stop(); + if (m_PrivateConn) + m_PrivateConn=0; +#endif + pthread_mutex_unlock(&channel_active); +} + +void eEPGChannelData::readData( const uint8_t *data, int source) +{ + int map; + iDVBSectionReader *reader = NULL; + switch (source) + { + case eEPGCache::NOWNEXT: + reader = m_NowNextReader; + map = 0; + break; + case eEPGCache::SCHEDULE: + reader = m_ScheduleReader; + map = 1; + break; + case eEPGCache::SCHEDULE_OTHER: + reader = m_ScheduleOtherReader; + map = 2; + break; + case eEPGCache::VIASAT: + reader = m_ViasatReader; + map = 3; + break; +#ifdef ENABLE_NETMED + case eEPGCache::NETMED_SCHEDULE: + reader = m_NetmedScheduleReader; + map = 1; + break; + case eEPGCache::NETMED_SCHEDULE_OTHER: + reader = m_NetmedScheduleOtherReader; + map = 2; + break; +#endif +#ifdef ENABLE_VIRGIN + case eEPGCache::VIRGIN_NOWNEXT: + reader = m_VirginNowNextReader; + map = 0; + break; + case eEPGCache::VIRGIN_SCHEDULE: + reader = m_VirginScheduleReader; + map = 1; + break; +#endif + default: + eDebug("[eEPGTransponderDataReader] unknown source"); + return; + } + tidMap &seenSections = this->seenSections[map]; + tidMap &calcedSections = this->calcedSections[map]; + if ( (state == 1 && calcedSections == seenSections) || state > 1 ) + { + eDebugNoNewLineStart("[eEPGTransponderDataReader] "); + switch (source) + { + case eEPGCache::NOWNEXT: + m_NowNextConn=0; + eDebugNoNewLine("nownext"); + break; + case eEPGCache::SCHEDULE: + m_ScheduleConn=0; + eDebugNoNewLine("schedule"); + break; + case eEPGCache::SCHEDULE_OTHER: + m_ScheduleOtherConn=0; + eDebugNoNewLine("schedule other"); + break; + case eEPGCache::VIASAT: + m_ViasatConn=0; + eDebugNoNewLine("viasat"); + break; +#ifdef ENABLE_NETMED + case eEPGCache::NETMED_SCHEDULE: + m_NetmedScheduleConn=0; + eDebugNoNewLine("netmed schedule"); + break; + case eEPGCache::NETMED_SCHEDULE_OTHER: + m_NetmedScheduleOtherConn=0; + eDebugNoNewLine("netmed schedule other"); + break; +#endif +#ifdef ENABLE_VIRGIN + case eEPGCache::VIRGIN_NOWNEXT: + m_VirginNowNextConn=0; + eDebugNoNewLine("virgin nownext"); + break; + case eEPGCache::VIRGIN_SCHEDULE: + m_VirginScheduleConn=0; + eDebugNoNewLine("virgin schedule"); + break; +#endif + default: eDebugNoNewLine("unknown");break; + } + eDebugNoNewLine(" finished(%ld)\n", ::time(0)); + if ( reader ) + reader->stop(); + isRunning &= ~source; + if (!isRunning) + finishEPG(); + } + else + { + eit_t *eit = (eit_t*) data; + uint32_t 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); + uint32_t tmpval = sectionNo & 0xFFFFFF00; + uint8_t incr = source == eEPGCache::NOWNEXT ? 1 : 8; + for ( int i = 0; i <= eit->last_section_number; i+=incr ) + { + 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)); + } + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->sectionRead(data, source, this); + } + } +} + +void eEPGChannelData::abortNonAvail() +{ + if (!state) + { + if ( !(haveData & eEPGCache::NOWNEXT) && (isRunning & eEPGCache::NOWNEXT) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail nownext reading"); + isRunning &= ~eEPGCache::NOWNEXT; + m_NowNextReader->stop(); + m_NowNextConn=0; + } + if ( !(haveData & eEPGCache::SCHEDULE) && (isRunning & eEPGCache::SCHEDULE) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail schedule reading"); + isRunning &= ~eEPGCache::SCHEDULE; + m_ScheduleReader->stop(); + m_ScheduleConn=0; + } + if ( !(haveData & eEPGCache::SCHEDULE_OTHER) && (isRunning & eEPGCache::SCHEDULE_OTHER) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail schedule other reading"); + isRunning &= ~eEPGCache::SCHEDULE_OTHER; + m_ScheduleOtherReader->stop(); + m_ScheduleOtherConn=0; + } +#ifdef ENABLE_VIRGIN + if ( !(haveData & eEPGCache::VIRGIN_NOWNEXT) && (isRunning & eEPGCache::VIRGIN_NOWNEXT) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail virgin nownext reading"); + isRunning &= ~eEPGCache::VIRGIN_NOWNEXT; + m_VirginNowNextReader->stop(); + m_VirginNowNextConn=0; + } + if ( !(haveData & eEPGCache::VIRGIN_SCHEDULE) && (isRunning & eEPGCache::VIRGIN_SCHEDULE) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail virgin schedule reading"); + isRunning &= ~eEPGCache::VIRGIN_SCHEDULE; + m_VirginScheduleReader->stop(); + m_VirginScheduleConn=0; + } +#endif +#ifdef ENABLE_NETMED + if ( !(haveData & eEPGCache::NETMED_SCHEDULE) && (isRunning & eEPGCache::NETMED_SCHEDULE) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail netmed schedule reading"); + isRunning &= ~eEPGCache::NETMED_SCHEDULE; + m_NetmedScheduleReader->stop(); + m_NetmedScheduleConn=0; + } + if ( !(haveData & eEPGCache::NETMED_SCHEDULE_OTHER) && (isRunning & eEPGCache::NETMED_SCHEDULE_OTHER) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail netmed schedule other reading"); + isRunning &= ~eEPGCache::NETMED_SCHEDULE_OTHER; + m_NetmedScheduleOtherReader->stop(); + m_NetmedScheduleOtherConn=0; + } +#endif +#ifdef ENABLE_FREESAT + if ( !(haveData & eEPGCache::FREESAT_SCHEDULE_OTHER) && (isRunning & eEPGCache::FREESAT_SCHEDULE_OTHER) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail FreeSat schedule_other reading"); + isRunning &= ~eEPGCache::FREESAT_SCHEDULE_OTHER; + m_FreeSatScheduleOtherReader->stop(); + m_FreeSatScheduleOtherReader2->stop(); + m_FreeSatScheduleOtherConn=0; + m_FreeSatScheduleOtherConn2=0; + cleanupFreeSat(); + } +#endif + if ( !(haveData & eEPGCache::VIASAT) && (isRunning & eEPGCache::VIASAT) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail viasat reading"); + isRunning &= ~eEPGCache::VIASAT; + m_ViasatReader->stop(); + m_ViasatConn=0; + } +#ifdef ENABLE_MHW_EPG + if ( !(haveData & eEPGCache::MHW) && (isRunning & eEPGCache::MHW) ) + { + eDebug("[eEPGTransponderDataReader] abort non avail mhw reading"); + isRunning &= ~eEPGCache::MHW; + m_MHWReader->stop(); + m_MHWConn=0; + m_MHWReader2->stop(); + m_MHWConn2=0; + } +#endif +#ifdef ENABLE_ATSC + if (!(haveData & eEPGCache::ATSC_EIT) && (isRunning & eEPGCache::ATSC_EIT)) + { + eDebug("[eEPGTransponderDataReader] abort non avail ATSC EIT reading"); + isRunning &= ~eEPGCache::ATSC_EIT; + cleanupATSC(); + } +#endif +#ifdef ENABLE_OPENTV + if (!(haveData & eEPGCache::OPENTV) && (isRunning & eEPGCache::OPENTV)) + { + eDebug("[eEPGTransponderDataReader] abort non avail OpenTV EIT reading"); + isRunning &= ~eEPGCache::OPENTV; + cleanupOPENTV(); + } +#endif + if ( isRunning & eEPGCache::VIASAT ) + abortTimer->start(300000, true); + else if ( isRunning ) + abortTimer->start(90000, true); + else + { + ++state; + for (unsigned int i=0; i < sizeof(seenSections)/sizeof(tidMap); ++i) + { + seenSections[i].clear(); + calcedSections[i].clear(); + } +#ifdef ENABLE_MHW_EPG + cleanupMHW(); +#endif +#ifdef ENABLE_FREESAT + cleanupFreeSat(); +#endif +#ifdef ENABLE_OPENTV + cleanupOPENTV(); +#endif + } + } + ++state; +} + + +#ifdef ENABLE_PRIVATE_EPG +#include +#include +#include + +void eEPGTransponderDataReader::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 0xC1: // user private + for (DescriptorConstIterator desc = (*es)->getDescriptors()->begin(); + desc != (*es)->getDescriptors()->end(); ++desc) + { + switch ((*desc)->getTag()) + { + case 0xC2: // user defined + if ((*desc)->getLength() == 8) + { + uint8_t buffer[10]; + (*desc)->writeToBuffer(buffer); + if (!memcmp((const char *)buffer+2, "EPGDATA", 7)) + { + eServiceReferenceDVB ref; + if (!pmthandler->getServiceReference(ref)) + { + int pid = (*es)->getPid(); + m_messages.send(Message(Message::got_mhw2_channel_pid, ref, pid)); + } + } + else if(!memcmp((const char *)buffer+2, "FICHAS", 6)) + { + eServiceReferenceDVB ref; + if (!pmthandler->getServiceReference(ref)) + { + int pid = (*es)->getPid(); + m_messages.send(Message(Message::got_mhw2_summary_pid, ref, pid)); + } + } + else if(!memcmp((const char *)buffer+2, "GENEROS", 7)) + { + eServiceReferenceDVB ref; + if (!pmthandler->getServiceReference(ref)) + { + int pid = (*es)->getPid(); + m_messages.send(Message(Message::got_mhw2_title_pid, ref, pid)); + } + } + } + break; + default: + break; + } + } + break; + 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: + { + Descriptor *descr = (Descriptor*)*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(); + m_messages.send(Message(Message::got_private_pid, ref, pid)); + return; + } + } + } + } + } + else + eDebug("[eEPGTransponderDataReader] PMTready but no pmt!!"); +} + +void eEPGChannelData::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("[eEPGTransponderDataReader] 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(sigc::mem_fun(*this, &eEPGChannelData::readPrivateData), m_PrivateConn); + m_PrivateReader->start(mask); +} + +void eEPGChannelData::readPrivateData( const uint8_t *data) +{ + if ( seenPrivateSections.find(data[6]) == seenPrivateSections.end() ) + { + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->privateSectionRead(m_PrivateService, data); + seenPrivateSections.insert(data[6]); + } + if ( seenPrivateSections.size() == (unsigned int)(data[7] + 1) ) + { + eDebug("[eEPGTransponderDataReader] 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; + singleLock l(eEPGTransponderDataReader::last_channel_update_lock); + epgReader->m_channelLastUpdated[chid] = ::time(0); + m_PrevVersion = (data[5] & 0x3E) >> 1; + startPrivateReader(); + } +} + +#endif // ENABLE_PRIVATE_EPG + + +#ifdef ENABLE_MHW_EPG +void eEPGChannelData::cleanupMHW() +{ + m_MHWTimeoutTimer->stop(); + m_channels.clear(); + m_themes.clear(); + m_titles.clear(); + m_program_ids.clear(); +} + +uint8_t *eEPGChannelData::delimitName( uint8_t *in, uint8_t *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 eEPGChannelData::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 eEPGChannelData::timeMHW2DVB( int minutes, u_char *return_time) +{ + timeMHW2DVB( int(minutes/60), minutes%60, return_time ); +} + +void eEPGChannelData::timeMHW2DVB( u_char day, u_char hours, u_char minutes, u_char *return_time) +// For date plus time of day +{ + char tz_saved[1024]; + // Remove offset in mhw time. + uint8_t 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 = ::time(0); + + char *old_tz = getenv( "TZ" ); + if (old_tz) + strcpy(tz_saved, old_tz); + putenv((char*)"TZ=CET-1CEST,M3.5.0/2,M10.5.0/3"); + tzset(); + + tm localnow; + localtime_r(&dt, &localnow); + + 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_r( &dt, &recdate ); // This will also take care of DST. + + if ( old_tz == NULL ) + unsetenv( "TZ" ); + else + setenv("TZ", tz_saved, 1); + 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 eEPGChannelData::storeMHWTitle(std::map::iterator itTitle, std::string sumText, const uint8_t *data) +// data is borrowed from calling proc to save memory space. +{ + uint8_t name[34]; + + // For each title a separate EIT packet will be sent to eEPGTransponderDataReader::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; // eEPGTransponderDataReader::sectionRead() will dig this for the moment + packet->current_next_indicator = 0; + packet->section_number = 0; // eEPGTransponderDataReader::sectionRead() will dig this for the moment + packet->last_section_number = 0; // eEPGTransponderDataReader::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; // eEPGTransponderDataReader::sectionRead() will dig this for the moment + packet->segment_last_table_id = 0x50; + + uint8_t *title = isMHW2 ? ((uint8_t*)(itTitle->second.title))-4 : (uint8_t*)itTitle->second.title; + std::string prog_title = (char *) delimitName( title, name, isMHW2 ? 35 : 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( itTitle->second.getMhw2Duration(), data+7 ); + } + else + { + timeMHW2DVB( itTitle->second.dh.day, itTitle->second.dh.hours, itTitle->second.ms.minutes, + (u_char *) event_data + 2 ); + timeMHW2DVB( itTitle->second.getDuration(), (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 + { + 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 eEPGTransponderDataReader::sectionRead() + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->sectionRead( data, eEPGCache::MHW, this ); +} + +void eEPGChannelData::startMHWTimeout(int msec) +{ + m_MHWTimeoutTimer->start(msec,true); + m_MHWTimeoutet=false; +} + +void eEPGChannelData::startMHWReader(uint16_t pid, uint8_t tid) +{ + m_MHWFilterMask.pid = pid; + m_MHWFilterMask.data[0] = tid; + m_MHWReader->start(m_MHWFilterMask); +// eDebug("[eEPGTransponderDataReader] start 0x%02x 0x%02x", pid, tid); +} + +void eEPGChannelData::startMHWReader2(uint16_t pid, uint8_t 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("[eEPGTransponderDataReader] start 0x%03x 0x%02x 0x%02x", pid, tid, ext); + } + else + { + m_MHWFilterMask2.data[1] = 0; + m_MHWFilterMask2.mask[1] = 0; +// eDebug("[eEPGTransponderDataReader] start 0x%02x 0x%02x", pid, tid); + } + m_MHWReader2->start(m_MHWFilterMask2); +} + +void eEPGChannelData::readMHWData(const uint8_t *data) +{ + if ( m_MHWReader2 ) + m_MHWReader2->stop(); + + if ( state > 1 || // aborted + // have si data.. so we dont read mhw data + (haveData & (eEPGCache::SCHEDULE|eEPGCache::SCHEDULE_OTHER|eEPGCache::VIASAT)) ) + { + eDebug("[eEPGTransponderDataReader] 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); + + m_channels.resize(nbr_records); + for ( int i = 0; i < nbr_records; i++ ) + { + mhw_channel_name_t *channel = (mhw_channel_name_t*) &data[4 + i*record_size]; + m_channels[i]=*channel; + } + haveData |= eEPGCache::MHW; + + eDebug("[eEPGTransponderDataReader] mhw %zu 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; + uint8_t next_idx = (uint8_t) *(data + 3 + idx_ptr); + uint8_t idx = 0; + uint8_t 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 = (uint8_t) *(data + 3 + idx_ptr); + sub_idx = 0; + } + else + sub_idx++; + + m_themes[idx+sub_idx] = *theme; + } + eDebug("[eEPGTransponderDataReader] mhw %zu themes found", m_themes.size()); + // Themes table has been read, start reading the titles table. + startMHWReader(0xD2, 0x90); + startMHWTimeout(4000); + return; + } + else if (m_MHWFilterMask.pid == 0xD2 && m_MHWFilterMask.data[0] == 0x90) + // Titles table + { + mhw_title_t *title = (mhw_title_t*) data; + uint8_t name[24]; + std::string prog_title = (char *) delimitName( title->title, name, 23 ); + + if ( title->channel_id == 0xFF || prog_title.substr(0,7) == "BIENTOT" ) // Separator or BIENTOT record + return; // Continue reading of the current table. + else + { + // Create unique key per title + uint32_t title_id = ((title->channel_id)<<16)|((title->dh.day)<<13)|((title->dh.hours)<<8)| + (title->ms.minutes); + uint32_t 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() ) + { + startMHWTimeout(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.insert(std::pair(program_id,title_id)); + return; // Continue reading of the current table. + } + else if (!checkMHWTimeout()) + 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("[eEPGTransponderDataReader] mhw %zu titles(%zu with summary) found", + m_titles.size(), + m_program_ids.size()); + startMHWTimeout(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 + uint32_t 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::multimap::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() && !checkMHWTimeout()) + return; // Continue reading of the current table. + } + else + { + std::string the_text = (char *) (data + 11 + summary->nb_replays * 7); + + size_t 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::iterator itTitle( m_titles.find( itProgid->second ) ); + if ( itTitle != m_titles.end() ) + { + startMHWTimeout(4000); + storeMHWTitle( 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("[eEPGTransponderDataReader] mhw finished(%ld) %zu summaries not found", + ::time(0), + 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::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) + storeMHWTitle( itTitle, "", data ); + isRunning &= ~eEPGCache::MHW; + m_MHWConn=0; + if ( m_MHWReader ) + m_MHWReader->stop(); + if (haveData) + finishEPG(); +} + +void eEPGChannelData::readMHWData2(const uint8_t *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|eEPGCache::VIASAT)) ) + { + eDebug("[eEPGTransponderDataReader] mhw2 aborted %d", state); + } + else if (m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) + // Channels table + { + int num_channels = data[120]; + m_channels.resize(num_channels); + if(dataLen > 120) + { + int ptr = 121 + 8 * 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 uint8_t *tmp = data+121; + for (int i=0; i < num_channels; ++i) + { + mhw_channel_name_t channel; + channel.network_id_hi = *(tmp++); + channel.network_id_lo = *(tmp++); + channel.transport_stream_id_hi = *(tmp++); + channel.transport_stream_id_lo = *(tmp++); + channel.channel_id_hi = *(tmp++); + channel.channel_id_lo = *(tmp++); + m_channels[i]=channel; +// eDebug("[eEPGTransponderDataReader] %d(%02x) %04x: %02x %02x", i, i, (channel.channel_id_hi << 8) | channel.channel_id_lo, *tmp, *(tmp+1)); + 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; +// eDebug("[eEPGTransponderDataReader] %d(%02x) %s", i, i, channel.name); + } + haveData |= eEPGCache::MHW; + eDebug("[eEPGTransponderDataReader] mhw2 %zu channels found", m_channels.size()); + } + else if (m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) + { + // Themes table + eDebug("[eEPGTransponderDataReader] mhw2 themes nyi"); + } + else if (m_MHWFilterMask2.pid == m_mhw2_title_pid && m_MHWFilterMask2.data[0] == 0xe6) + // Titles table + { + int pos=18; + bool valid=false; + bool finish=false; + +// eDebug("[eEPGTransponderDataReader] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", +// data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], +// data[11], data[12], data[13], data[14], data[15], data[16], data[17] ); + + while( pos < dataLen && !valid) + { + pos += 18; + pos += (data[pos] & 0x3F) + 4; + if( pos == dataLen ) + valid = true; + } + + if (!valid) + { + if (dataLen > 18) + eDebug("[eEPGTransponderDataReader] mhw2 title table invalid!!"); + if (checkMHWTimeout()) + goto abort; + if (!m_MHWTimeoutTimer->isActive()) + startMHWTimeout(5000); + return; // continue reading + } + + // data seems consistent... + mhw_title_t title; + pos = 18; + while (pos < dataLen) + { +// eDebugNoNewLineStart("[eEPGTransponderDataReader] [%02x] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x [%02x %02x %02x %02x %02x %02x %02x] LL - DESCR - ", +// data[pos], data[pos+1], data[pos+2], data[pos+3], data[pos+4], data[pos+5], data[pos+6], data[pos+7], +// data[pos+8], data[pos+9], data[pos+10], data[pos+11], data[pos+12], data[pos+13], data[pos+14], data[pos+15], data[pos+16], data[pos+17]); + title.channel_id = data[pos]+1; + title.mhw2_mjd_hi = data[pos+11]; + title.mhw2_mjd_lo = data[pos+12]; + title.mhw2_hours = data[pos+13]; + title.mhw2_minutes = data[pos+14]; + title.mhw2_seconds = data[pos+15]; + int duration = ((data[pos+16] << 8)|data[pos+17]) >> 4; + title.mhw2_duration_hi = (duration&0xFF00) >> 8; + title.mhw2_duration_lo = duration&0xFF; + + // Create unique key per title + uint32_t title_id = (data[pos+7] << 24) | (data[pos+8] << 16) | (data[pos+9] << 8) | data[pos+10]; + + uint8_t slen = data[pos+18] & 0x3f; + uint8_t *dest = ((uint8_t*)title.title)-4; + memcpy(dest, &data[pos+19], slen>35 ? 35 : slen); + memset(dest+slen, 0, 35-slen); + pos += 19 + slen; +// eDebugNoNewLine("%02x [%02x %02x]: %s\n", data[pos], data[pos+1], data[pos+2], dest); + +// not used theme id (data[7] & 0x3f) + (data[pos] & 0x3f); + uint32_t summary_id = (data[pos+1] << 8) | data[pos+2]; + +// if (title.channel_id > m_channels.size()) +// eDebug("[eEPGTransponderDataReader] channel_id(%d %02x) to big!!", title.channel_id); + +// eDebug("[eEPGTransponderDataReader] pos %d prog_id %02x %02x chid %02x summary_id %04x dest %p len %d\n", +// pos, title.program_id_ml, title.program_id_lo, title.channel_id, summary_id, dest, slen); + +// eDebug("[eEPGTransponderDataReader] title_id %08x -> summary_id %04x\n", title_id, summary_id); + + pos += 3; + + std::map::iterator it = m_titles.find( title_id ); + if ( it == m_titles.end() ) + { + startMHWTimeout(5000); + m_titles[ title_id ] = title; + if (summary_id != 0xFFFF) + { + bool add=true; + std::multimap::iterator it(m_program_ids.lower_bound(summary_id)); + while (it != m_program_ids.end() && it->first == summary_id) + { + if (it->second == title_id) { + add=false; + break; + } + ++it; + } + if (add) + m_program_ids.insert(std::pair(summary_id,title_id)); + } + } + else + { + if ( !checkMHWTimeout() ) + continue; // Continue reading of the current table. + finish=true; + break; + } + } + if (finish) + { + eDebug("[eEPGTransponderDataReader] mhw2 %zu titles(%zu 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(m_mhw2_summary_pid, 0x96); + startMHWTimeout(15000); + return; + } + } + else + return; + } + else if (m_MHWFilterMask2.pid == m_mhw2_summary_pid && m_MHWFilterMask2.data[0] == 0x96) + // Summaries table + { + if (!checkMHWTimeout()) + { + int len, loop, pos, lenline; + bool valid; + valid = true; + if( dataLen > 15 ) + { + loop = data[14]; + pos = 15 + 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 + return; // continue reading + + if (valid) + { + // data seems consistent... + uint32_t summary_id = (data[3]<<8)|data[4]; +// eDebug ("[eEPGTransponderDataReader] summary id %04x\n", summary_id); +// eDebug("[eEPGTransponderDataReader] [%02x %02x] %02x %02x %02x %02x %02x %02x %02x %02x XX\n", data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13] ); + + // ugly workaround to convert const __u8* to char* + char *tmp=0; + memcpy(&tmp, &data, sizeof(void*)); + + len = 0; + loop = data[14]; + pos = 15 + 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::multimap::iterator itProgId( m_program_ids.lower_bound(summary_id) ); + if ( itProgId == m_program_ids.end() || itProgId->first != summary_id) + { /* 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() ) + return; // Continue reading of the current table. + } + else + { + startMHWTimeout(15000); + std::string the_text = (char *) (data + pos + 1); + +// eDebug ("[eEPGTransponderDataReader] summary id %04x : %s\n", summary_id, data+pos+1); + + while( itProgId != m_program_ids.end() && itProgId->first == summary_id ) + { +// eDebug("[eEPGTransponderDataReader] ."); + // Find corresponding title, store title and summary in epgcache. + std::map::iterator itTitle( m_titles.find( itProgId->second ) ); + if ( itTitle != m_titles.end() ) + { + storeMHWTitle( 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. + } + } + else + return; // continue reading + } + } + if (isRunning & eEPGCache::MHW) + { + if ( m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 0) + { + // Channels table has been read, start reading the themes table. + startMHWReader2(m_mhw2_channel_pid, 0xC8, 1); + return; + } + else if ( m_MHWFilterMask2.pid == m_mhw2_channel_pid && m_MHWFilterMask2.data[0] == 0xC8 && m_MHWFilterMask2.data[1] == 1) + { + // Themes table has been read, start reading the titles table. + startMHWReader2(m_mhw2_title_pid, 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::iterator itTitle(m_titles.begin()); itTitle != m_titles.end(); itTitle++) + storeMHWTitle( itTitle, "", data ); + eDebug("[eEPGTransponderDataReader] mhw2 finished(%ld) %zu summaries not found", + ::time(0), + m_program_ids.size()); + } + } +abort: + isRunning &= ~eEPGCache::MHW; + m_MHWConn2=0; + if ( m_MHWReader2 ) + m_MHWReader2->stop(); + if (haveData) + finishEPG(); +} +#endif // ENABLE_MHW_EPG + + +#if ENABLE_FREESAT + +freesatEITSubtableStatus::freesatEITSubtableStatus(u_char version, uint8_t maxSection) : version(version) +{ + initMap(maxSection); +} + +void freesatEITSubtableStatus::initMap(uint8_t maxSection) +{ + int i, maxSectionIdx = maxSection / 8; + for (i = 0; i < 32; i++) + { + sectionMap[i] = (i <= maxSectionIdx ? 0x0100 : 0x0000 ); + } +} + +bool freesatEITSubtableStatus::isSectionPresent(uint8_t sectionNo) +{ + uint8_t sectionIdx = sectionNo / 8; + uint8_t bitOffset = sectionNo % 8; + + return ((sectionMap[sectionIdx] & (1 << bitOffset)) != 0); +} + +bool freesatEITSubtableStatus::isCompleted() +{ + uint32_t i = 0; + uint8_t calc; + + while ( i < 32 ) + { + calc = sectionMap[i] >> 8; + if (! calc) return true; // Last segment passed + if (calc ^ ( sectionMap[i] & 0xFF ) ) // Segment not fully found + return false; + i++; + } + return true; // All segments ok +} + +void freesatEITSubtableStatus::seen(uint8_t sectionNo, uint8_t maxSegmentSection) +{ + uint8_t sectionIdx = sectionNo / 8; + uint8_t bitOffset = sectionNo % 8; + uint8_t maxBitOffset = maxSegmentSection % 8; + + sectionMap[sectionIdx] &= 0x00FF; // Clear calc map + sectionMap[sectionIdx] |= ((0x01FF << maxBitOffset) & 0xFF00); // Set calc map + sectionMap[sectionIdx] |= (1 << bitOffset); // Set seen map +} + +bool freesatEITSubtableStatus::isVersionChanged(u_char testVersion) +{ + return version != testVersion; +} + +void freesatEITSubtableStatus::updateVersion(u_char newVersion, uint8_t maxSection) +{ + version = newVersion; + initMap(maxSection); +} + +void eEPGChannelData::cleanupFreeSat() +{ + m_FreeSatSubTableStatus.clear(); + m_FreesatTablesToComplete = 0; +} + +void eEPGChannelData::readFreeSatScheduleOtherData( const uint8_t *data) +{ + eit_t *eit = (eit_t*) data; + uint32_t subtableNo = data[0] << 24; // Table ID + subtableNo |= data[3] << 16; // Service ID Hi + subtableNo |= data[4] << 8; // Service ID Lo + + // Check for sub-table version in map + std::map &freeSatSubTableStatus = this->m_FreeSatSubTableStatus; + std::map::iterator itmap = freeSatSubTableStatus.find(subtableNo); + + freesatEITSubtableStatus *fsstatus; + if ( itmap == freeSatSubTableStatus.end() ) + { + // New sub table. Store version. + //eDebug("[eEPGTransponderDataReader] New subtable (%x) version (%d) now/next (%d) tsid (%x/%x) onid (%x/%x)", subtableNo, eit->version_number, eit->current_next_indicator, eit->transport_stream_id_hi, eit->transport_stream_id_lo, eit->original_network_id_hi, eit->original_network_id_lo); + fsstatus = new freesatEITSubtableStatus(eit->version_number, eit->last_section_number); + m_FreesatTablesToComplete++; + freeSatSubTableStatus.insert(std::pair(subtableNo, *fsstatus)); + } + else + { + fsstatus = &itmap->second; + // Existing subtable. Check version. Should check current / next as well? Seems to always be current for Freesat + if ( fsstatus->isVersionChanged(eit->version_number) ) + { + eDebug("[eEPGTransponderDataReader] FS subtable (%x) version changed (%d) now/next (%d)", subtableNo, eit->version_number, eit->current_next_indicator); + m_FreesatTablesToComplete++; + fsstatus->updateVersion(eit->version_number, eit->last_section_number); + } + else + { + if ( fsstatus->isSectionPresent(eit->section_number) ) + { +// eDebug("[eEPGTransponderDataReader] DUP FS sub/sec/ver (%x/%d/%d)", subtableNo, eit->section_number, eit->version_number); + return; + } + } + } + +// eDebug("[eEPGTransponderDataReader] New FS sub/sec/ls/lss/ver (%x/%d/%d/%d/%d)", subtableNo, eit->section_number, eit->last_section_number, eit->segment_last_section_number, eit->version_number); + fsstatus->seen(eit->section_number, eit->segment_last_section_number); + if (fsstatus->isCompleted()) + { + m_FreesatTablesToComplete--; + } + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->sectionRead(data, eEPGCache::FREESAT_SCHEDULE_OTHER, this); +} +#endif // ENABLE_FREESAT + + +#ifdef ENABLE_ATSC +void eEPGChannelData::ATSC_checkCompletion() +{ + if (!m_ATSC_VCTConn && !m_ATSC_MGTConn && !m_ATSC_EITConn && !m_ATSC_ETTConn) + { + eDebug("[eEPGTransponderDataReader] ATSC EIT index %d completed", m_atsc_eit_index); + for (std::map::const_iterator it = m_ATSC_EIT_map.begin(); it != m_ATSC_EIT_map.end(); ++it) + { + std::vector sids; + std::vector chids; + int sourceid = (it->first >> 16) & 0xffff; + sids.push_back(m_ATSC_VCT_map[sourceid]); + chids.push_back(channel->getChannelID()); + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->submitEventData(sids, chids, it->second.startTime, it->second.lengthInSeconds, it->second.title.c_str(), "", m_ATSC_ETT_map[it->first].c_str(), 0, 0, eEPGCache::ATSC_EIT); + } + m_ATSC_EIT_map.clear(); + m_ATSC_ETT_map.clear(); + if (m_atsc_eit_index < 128) + { + eDVBSectionFilterMask mask = {}; + m_atsc_eit_index++; + m_ATSC_MGTReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_MGTsection), m_ATSC_MGTConn); + mask.pid = 0x1ffb; + mask.data[0] = 0xc7; + mask.mask[0] = 0xff; + m_ATSC_MGTReader->start(mask); + } + else + { + eDebug("[eEPGTransponderDataReader] ATSC EIT parsing completed"); + m_ATSC_VCT_map.clear(); + isRunning &= ~eEPGCache::ATSC_EIT; + if (!isRunning) + { + finishEPG(); + } + } + } +} + +void eEPGChannelData::ATSC_VCTsection(const uint8_t *d) +{ + VirtualChannelTableSection vct(d); + for (VirtualChannelListConstIterator channel = vct.getChannels()->begin(); channel != vct.getChannels()->end(); ++channel) + { + if (m_ATSC_VCT_map.find((*channel)->getSourceId()) == m_ATSC_VCT_map.end()) + { + m_ATSC_VCT_map[(*channel)->getSourceId()] = (*channel)->getServiceId(); + } + else + { + m_ATSC_VCTReader->stop(); + m_ATSC_VCTConn = NULL; + ATSC_checkCompletion(); + break; + } + } +} + +void eEPGChannelData::ATSC_MGTsection(const uint8_t *d) +{ + MasterGuideTableSection mgt(d); + for (MasterGuideTableListConstIterator table = mgt.getTables()->begin(); table != mgt.getTables()->end(); ++table) + { + eDVBSectionFilterMask mask = {}; + if ((*table)->getTableType() == 0x0100 + m_atsc_eit_index) + { + /* EIT */ + mask.pid = (*table)->getPID(); + mask.data[0] = 0xcb; + mask.mask[0] = 0xff; + m_ATSC_EITReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_EITsection), m_ATSC_EITConn); + m_ATSC_EITReader->start(mask); + } + else if ((*table)->getTableType() == 0x0200 + m_atsc_eit_index) + { + /* ETT */ + mask.pid = (*table)->getPID(); + mask.data[0] = 0xcc; + mask.mask[0] = 0xff; + m_ATSC_ETTReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::ATSC_ETTsection), m_ATSC_ETTConn); + m_ATSC_ETTReader->start(mask); + } + } + m_ATSC_MGTReader->stop(); + m_ATSC_MGTConn = NULL; + if (!m_ATSC_EITConn) + { + /* no more EIT */ + m_ATSC_ETTReader->stop(); + m_ATSC_ETTConn = NULL; + m_atsc_eit_index = 128; + ATSC_checkCompletion(); + } +} + +void eEPGChannelData::ATSC_EITsection(const uint8_t *d) +{ + ATSCEventInformationSection eit(d); + for (ATSCEventListConstIterator ev = eit.getEvents()->begin(); ev != eit.getEvents()->end(); ++ev) + { + uint32_t etm = ((eit.getTableIdExtension() & 0xffff) << 16) | (((*ev)->getEventId() & 0x3fff) << 2) | 0x2; + if (m_ATSC_EIT_map.find(etm) == m_ATSC_EIT_map.end()) + { + struct atsc_event event; + event.title = (*ev)->getTitle("---"); + event.eventId = (*ev)->getEventId(); + event.startTime = (*ev)->getStartTime() + (time_t)315964800; /* ATSC GPS system time epoch is 00:00 Jan 6th 1980 */ + event.lengthInSeconds = (*ev)->getLengthInSeconds(); + m_ATSC_EIT_map[etm] = event; + } + else + { + m_ATSC_EITReader->stop(); + m_ATSC_EITConn = NULL; + ATSC_checkCompletion(); + break; + } + } + haveData |= eEPGCache::ATSC_EIT; +} + +void eEPGChannelData::ATSC_ETTsection(const uint8_t *d) +{ + ExtendedTextTableSection ett(d); + if (m_ATSC_ETT_map.find(ett.getETMId()) == m_ATSC_ETT_map.end()) + { + m_ATSC_ETT_map[ett.getETMId()] = ett.getMessage("---"); + } + else + { + m_ATSC_ETTReader->stop(); + m_ATSC_ETTConn = NULL; + ATSC_checkCompletion(); + } +} + +void eEPGChannelData::cleanupATSC() +{ + m_ATSC_VCTReader->stop(); + m_ATSC_MGTReader->stop(); + m_ATSC_EITReader->stop(); + m_ATSC_ETTReader->stop(); + m_ATSC_VCTConn = NULL; + m_ATSC_MGTConn = NULL; + m_ATSC_EITConn = NULL; + m_ATSC_ETTConn = NULL; + + m_ATSC_EIT_map.clear(); + m_ATSC_ETT_map.clear(); + m_ATSC_VCT_map.clear(); +} +#endif // ENABLE_ATSC + + +#ifdef ENABLE_OPENTV +void eEPGChannelData::OPENTV_checkCompletion(uint32_t data_crc) +{ + if (!m_OPENTV_crc32) + { + m_OPENTV_crc32 = data_crc; + } + else if (m_OPENTV_crc32 == data_crc) + { + m_OPENTV_crc32 = 0; + } + + eDVBSectionFilterMask mask; + memset(&mask, 0, sizeof(mask)); + + if ((m_OPENTV_ChannelsConn && (m_OPENTV_EIT_index > 0xff)) || (m_OPENTV_ChannelsConn && !m_OPENTV_crc32)) + { + eDebug("[eEPGTransponderDataReader] OpenTV channels, found=%d%s", (int)m_OPENTV_channels_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); + m_OPENTV_ChannelsReader->stop(); + m_OPENTV_ChannelsConn = NULL; + m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; + m_OPENTV_Timer->start(200000, true); + m_OPENTV_TitlesReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::OPENTV_TitlesSection), m_OPENTV_TitlesConn); + mask = {}; + mask.pid = m_OPENTV_pid = 0x30; + mask.data[0] = 0xa0; + mask.mask[0] = 0xfc; + mask.flags = eDVBSectionFilterMask::rfCRC; + m_OPENTV_TitlesReader->start(mask); + } + else if ((m_OPENTV_TitlesConn && (m_OPENTV_EIT_index > 0xfff)) || (m_OPENTV_TitlesConn && !m_OPENTV_crc32)) + { + m_OPENTV_TitlesReader->stop(); + m_OPENTV_TitlesConn = NULL; + m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; + m_OPENTV_pid += 0x10; + + if (m_OPENTV_pid < 0x48) + { + eDebug("[eEPGTransponderDataReader] OpenTV titles %d stored=%d%s", (int)m_OPENTV_EIT_map.size(), (int)m_OPENTV_descriptors_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); + m_OPENTV_SummariesReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::OPENTV_SummariesSection), m_OPENTV_SummariesConn); + mask = {}; + mask.pid = m_OPENTV_pid; + mask.data[0] = 0xa8; + mask.mask[0] = 0xfc; + mask.flags = eDVBSectionFilterMask::rfCRC; + m_OPENTV_SummariesReader->start(mask); + } + } + else if ((m_OPENTV_SummariesConn && (m_OPENTV_EIT_index > 0xfff)) || (m_OPENTV_SummariesConn && !m_OPENTV_crc32)) + { + m_OPENTV_SummariesReader->stop(); + m_OPENTV_SummariesConn = NULL; + m_OPENTV_EIT_index = m_OPENTV_crc32 = 0; + m_OPENTV_pid -= 0x10; + + //cache remaining uncached events for which the provider only sends title with no summary data.. off air/overnight! + eDebug("[eEPGTransponderDataReader] OpenTV summaries, uncached=%d%s", (int)m_OPENTV_EIT_map.size(), m_OPENTV_crc32 ? ", crc32 incomplete" : ""); + + for (std::map::const_iterator it = m_OPENTV_EIT_map.begin(); it != m_OPENTV_EIT_map.end(); ++it) + { + int channelid = (it->first >> 16) & 0xffff; + + if (m_OPENTV_channels_map.find(channelid) != m_OPENTV_channels_map.end()) + { + std::vector sids; + std::vector chids; + eDVBChannelID chid = channel->getChannelID(); + chid.transport_stream_id = m_OPENTV_channels_map[channelid].transportStreamId; + chid.original_network_id = m_OPENTV_channels_map[channelid].originalNetworkId; + chids.push_back(chid); + sids.push_back(m_OPENTV_channels_map[channelid].serviceId); + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->submitEventData(sids, chids, it->second.startTime, it->second.duration, m_OPENTV_descriptors_map[it->second.title_crc].c_str(), "", "", 0, it->second.eventId, eEPGCache::OPENTV); + } + } + m_OPENTV_descriptors_map.clear(); + m_OPENTV_EIT_map.clear(); + + if (++m_OPENTV_pid < 0x38) + { + m_OPENTV_TitlesReader->connectRead(sigc::mem_fun(*this, &eEPGChannelData::OPENTV_TitlesSection), m_OPENTV_TitlesConn); + mask = {}; + mask.pid = m_OPENTV_pid; + mask.data[0] = 0xa0; + mask.mask[0] = 0xfc; + mask.flags = eDVBSectionFilterMask::rfCRC; + m_OPENTV_TitlesReader->start(mask); + } + else + eDebug("[eEPGTransponderDataReader] OpenTV finishing, uncached=%d", (int)m_OPENTV_EIT_map.size()); + } + else + m_OPENTV_EIT_index++; + + if (!m_OPENTV_ChannelsConn && !m_OPENTV_TitlesConn && !m_OPENTV_SummariesConn) + { + eDebug("[eEPGTransponderDataReader] OpenTV EIT parsing completed"); + isRunning &= ~eEPGCache::OPENTV; + + if (!isRunning) + finishEPG(); + else + cleanupOPENTV(); + } +} + +void eEPGChannelData::OPENTV_ChannelsSection(const uint8_t *d) +{ + OpenTvChannelSection otcs(d); + + for (OpenTvChannelListConstIterator channel = otcs.getChannels()->begin(); channel != otcs.getChannels()->end(); ++channel) + { + if (m_OPENTV_channels_map.find((*channel)->getChannelId()) == m_OPENTV_channels_map.end()) + { + struct opentv_channel otc; + otc.originalNetworkId = (*channel)->getOriginalNetworkId(); + otc.transportStreamId = (*channel)->getTransportStreamId(); + otc.serviceId = (*channel)->getServiceId(); + otc.serviceType = (*channel)->getServiceType(); + m_OPENTV_channels_map[(*channel)->getChannelId()] = otc; + } + } + OPENTV_checkCompletion(otcs.getCrc32()); +} + +void eEPGChannelData::OPENTV_TitlesSection(const uint8_t *d) +{ + OpenTvTitleSection otts(d); + + for (OpenTvTitleListConstIterator title = otts.getTitles()->begin(); title != otts.getTitles()->end(); ++title) + { + uint32_t etm = ((otts.getTableIdExtension() & 0xffff) << 16) | ((*title)->getEventId() & 0xffff); + + if (m_OPENTV_EIT_map.find(etm) == m_OPENTV_EIT_map.end()) + { + struct opentv_event ote; + ote.eventId = (*title)->getEventId(); + ote.startTime = (*title)->getStartTime(); + ote.duration = (*title)->getDuration(); + ote.title_crc = (*title)->getCRC32(); + m_OPENTV_EIT_map[etm] = ote; + + if (m_OPENTV_descriptors_map.find(ote.title_crc) == m_OPENTV_descriptors_map.end()) + m_OPENTV_descriptors_map[ote.title_crc] = (*title)->getTitle(); + } + } + + OPENTV_checkCompletion(otts.getCrc32()); +} + +void eEPGChannelData::OPENTV_SummariesSection(const uint8_t *d) +{ + OpenTvSummarySection otss(d); + + int channelid = otss.getTableIdExtension(); + + if (m_OPENTV_channels_map.find(channelid) != m_OPENTV_channels_map.end()) + { + for (OpenTvSummaryListConstIterator summary = otss.getSummaries()->begin(); summary != otss.getSummaries()->end(); ++summary) + { + uint32_t otce = ((channelid & 0xffff) << 16) | ((*summary)->getEventId() & 0xffff); + + if (m_OPENTV_EIT_map.find(otce) != m_OPENTV_EIT_map.end()) + { + struct opentv_event ote = m_OPENTV_EIT_map[otce]; + + //cache events with matching title and summary eventId on the fly! + if (m_OPENTV_descriptors_map.find(ote.title_crc) != m_OPENTV_descriptors_map.end()) + { + std::vector sids; + std::vector chids; + eDVBChannelID chid = channel->getChannelID(); + chid.transport_stream_id = m_OPENTV_channels_map[channelid].transportStreamId; + chid.original_network_id = m_OPENTV_channels_map[channelid].originalNetworkId; + chids.push_back(chid); + sids.push_back(m_OPENTV_channels_map[channelid].serviceId); + + // hack to fix split titles + std::string sTitle = m_OPENTV_descriptors_map[ote.title_crc]; + std::string sSummary = (*summary)->getSummary(); + + if (sTitle.length() > 3 && sSummary.length() > 3) + { + // check if the title is split + if (sTitle.substr(sTitle.length() - 3) == "..." && sSummary.substr(0, 3) == "...") + { + // find the end of the title in the sumarry + std::size_t found = sSummary.find_first_of(".:!?", 4); + + if (found < sSummary.length()) + { + std::string sTmpTitle; + std::string sTmpSummary; + + // strip off the ellipsis and any leading/trailing space + if (sTitle.substr(sTitle.length() - 4, 1) == " ") + { + sTmpTitle = sTitle.substr(0, sTitle.length() - 4); + } + else + { + sTmpTitle = sTitle.substr(0, sTitle.length() - 3); + } + + if (sSummary.substr(3, 1) == " ") + { + sTmpSummary = sSummary.substr(4); + } + else + { + sTmpSummary = sSummary.substr(3); + } + + // construct the new title and summary + found = sTmpSummary.find_first_of(".:!?"); + if (found < sTmpSummary.length()) + { + sTitle = sTmpTitle + " " + sTmpSummary.substr(0, found); + if (sTmpSummary.length() - found > 2) + { + sSummary = sTmpSummary.substr(found + 2); + } + else + { + sSummary = ""; + } + } + else + { + // shouldn't happen, but you never know... + sTitle + sTmpTitle; + sSummary = sTmpSummary; + } + } + } + } + if (eEPGCache::getInstance()) + eEPGCache::getInstance()->submitEventData(sids, chids, ote.startTime, ote.duration, sTitle.c_str(), "", sSummary.c_str(), 0, ote.eventId, eEPGCache::OPENTV); + } + m_OPENTV_EIT_map.erase(otce); + } + } + } + haveData |= eEPGCache::OPENTV; + + OPENTV_checkCompletion(otss.getCrc32()); +} + +void eEPGChannelData::cleanupOPENTV() +{ + m_OPENTV_Timer->stop(); + if (m_OPENTV_ChannelsReader) + m_OPENTV_ChannelsReader->stop(); + if (m_OPENTV_TitlesReader) + m_OPENTV_TitlesReader->stop(); + if (m_OPENTV_SummariesReader) + m_OPENTV_SummariesReader->stop(); + m_OPENTV_ChannelsConn = NULL; + m_OPENTV_TitlesConn = NULL; + m_OPENTV_SummariesConn = NULL; + m_OPENTV_channels_map.clear(); + m_OPENTV_descriptors_map.clear(); + m_OPENTV_EIT_map.clear(); + + if (huffman_dictionary_read) + { + huffman_free_dictionary(); + huffman_dictionary_read = false; + } + + if (isRunning & eEPGCache::OPENTV) + isRunning &= ~eEPGCache::OPENTV; +} +#endif // ENABLE_OPENTV diff --git a/lib/dvb/epgchanneldata.h b/lib/dvb/epgchanneldata.h new file mode 100644 index 0000000000..e7c2d3ba34 --- /dev/null +++ b/lib/dvb/epgchanneldata.h @@ -0,0 +1,151 @@ +#ifndef __epgchanneldata_h_ +#define __epgchanneldata_h_ + +#include +#include +#include +#include + +#ifdef ENABLE_MHW_EPG +#include +#endif +#ifdef ENABLE_FREESAT +#include +class freesatEITSubtableStatus; +#endif + +typedef std::set tidMap; + +class eEPGTransponderDataReader; +struct uniqueEPGKey; + +class eEPGChannelData: public sigc::trackable +{ + friend class eEPGTransponderDataReader; + friend class eEPGCache; + + pthread_mutex_t channel_active; + eEPGChannelData(eEPGTransponderDataReader*); + eEPGTransponderDataReader *epgReader; + ePtr abortTimer, zapTimer; + int prevChannelState; + int state; + unsigned int isRunning, haveData; + ePtr channel; + ePtr m_stateChangedConn, m_NowNextConn, m_ScheduleConn, m_ScheduleOtherConn, m_ViasatConn; + ePtr m_NowNextReader, m_ScheduleReader, m_ScheduleOtherReader, m_ViasatReader; + tidMap seenSections[4], calcedSections[4]; +#ifdef ENABLE_VIRGIN + ePtr m_VirginNowNextConn, m_VirginScheduleConn; + ePtr m_VirginNowNextReader, m_VirginScheduleReader; +#endif +#ifdef ENABLE_NETMED + ePtr m_NetmedScheduleConn, m_NetmedScheduleOtherConn; + ePtr m_NetmedScheduleReader, m_NetmedScheduleOtherReader; +#endif +#ifdef ENABLE_FREESAT + ePtr m_FreeSatScheduleOtherConn, m_FreeSatScheduleOtherConn2; + ePtr m_FreeSatScheduleOtherReader, m_FreeSatScheduleOtherReader2; + std::map m_FreeSatSubTableStatus; + uint32_t m_FreesatTablesToComplete; + void readFreeSatScheduleOtherData(const uint8_t *data); + void cleanupFreeSat(); +#endif +#ifdef ENABLE_PRIVATE_EPG + ePtr startPrivateTimer; + int m_PrevVersion; + int m_PrivatePid; + uniqueEPGKey m_PrivateService; + ePtr m_PrivateConn; + ePtr m_PrivateReader; + std::set seenPrivateSections; + void readPrivateData(const uint8_t *data); + void startPrivateReader(); +#endif +#ifdef ENABLE_MHW_EPG + std::vector m_channels; + std::map m_themes; + std::map m_titles; + std::multimap m_program_ids; + ePtr m_MHWConn, m_MHWConn2; + ePtr m_MHWReader, m_MHWReader2; + eDVBSectionFilterMask m_MHWFilterMask, m_MHWFilterMask2; + ePtr m_MHWTimeoutTimer; + uint16_t m_mhw2_channel_pid, m_mhw2_title_pid, m_mhw2_summary_pid; + bool m_MHWTimeoutet; + void MHWTimeout() { m_MHWTimeoutet=true; } + void readMHWData(const uint8_t *data); + void readMHWData2(const uint8_t *data); + void startMHWReader(uint16_t pid, uint8_t tid); + void startMHWReader2(uint16_t pid, uint8_t tid, int ext=-1); + void startMHWTimeout(int msek); + bool checkMHWTimeout() { return m_MHWTimeoutet; } + void cleanupMHW(); + uint8_t *delimitName( uint8_t *in, uint8_t *out, int len_in ); + void timeMHW2DVB( u_char hours, u_char minutes, u_char *return_time); + void timeMHW2DVB( int minutes, u_char *return_time); + void timeMHW2DVB( u_char day, u_char hours, u_char minutes, u_char *return_time); + void storeMHWTitle(std::map::iterator itTitle, std::string sumText, const uint8_t *data); +#endif +#ifdef ENABLE_ATSC + int m_atsc_eit_index; + std::map m_ATSC_VCT_map; + std::map m_ATSC_ETT_map; + struct atsc_event + { + uint16_t eventId; + uint32_t startTime; + uint32_t lengthInSeconds; + std::string title; + }; + std::map m_ATSC_EIT_map; + ePtr m_ATSC_VCTReader, m_ATSC_MGTReader, m_ATSC_EITReader, m_ATSC_ETTReader; + ePtr m_ATSC_VCTConn, m_ATSC_MGTConn, m_ATSC_EITConn, m_ATSC_ETTConn; + void ATSC_checkCompletion(); + void ATSC_VCTsection(const uint8_t *d); + void ATSC_MGTsection(const uint8_t *d); + void ATSC_EITsection(const uint8_t *d); + void ATSC_ETTsection(const uint8_t *d); + void cleanupATSC(); +#endif +#ifdef ENABLE_OPENTV + typedef std::tr1::unordered_map OpenTvDescriptorMap; + int m_OPENTV_EIT_index; + uint16_t m_OPENTV_pid; + uint32_t m_OPENTV_crc32; + uint32_t opentv_title_crc32; + bool huffman_dictionary_read; + struct opentv_channel + { + uint16_t originalNetworkId; + uint16_t transportStreamId; + uint16_t serviceId; + uint8_t serviceType; + }; + struct opentv_event + { + uint16_t eventId; + uint32_t startTime; + uint32_t duration; + uint32_t title_crc; + }; + OpenTvDescriptorMap m_OPENTV_descriptors_map; + std::map m_OPENTV_channels_map; + std::map m_OPENTV_EIT_map; + ePtr m_OPENTV_Timer; + ePtr m_OPENTV_ChannelsReader, m_OPENTV_TitlesReader, m_OPENTV_SummariesReader; + ePtr m_OPENTV_ChannelsConn, m_OPENTV_TitlesConn, m_OPENTV_SummariesConn; + void OPENTV_checkCompletion(const uint32_t data_crc); + void OPENTV_ChannelsSection(const uint8_t *d); + void OPENTV_TitlesSection(const uint8_t *d); + void OPENTV_SummariesSection(const uint8_t *d); + void cleanupOPENTV(); +#endif + void readData(const uint8_t *data, int source); + void startChannel(); + void startEPG(); + void finishEPG(); + void abortEPG(); + void abortNonAvail(); +}; +#endif diff --git a/lib/dvb/epgtransponderdatareader.cpp b/lib/dvb/epgtransponderdatareader.cpp new file mode 100644 index 0000000000..8c8162c40f --- /dev/null +++ b/lib/dvb/epgtransponderdatareader.cpp @@ -0,0 +1,471 @@ +#include + +#include + +#include +#include + + +eEPGTransponderDataReader* eEPGTransponderDataReader::instance; +pthread_mutex_t eEPGTransponderDataReader::known_channel_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +pthread_mutex_t eEPGTransponderDataReader::last_channel_update_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +DEFINE_REF(eEPGTransponderDataReader) + +eEPGTransponderDataReader::eEPGTransponderDataReader() + :m_messages(this,1), m_running(false) +{ + eDebug("[eEPGTransponderDataReader] Initialized"); + + CONNECT(m_messages.recv_msg, eEPGTransponderDataReader::gotMessage); + CONNECT(eEPGCache::getInstance()->epgCacheStarted, eEPGTransponderDataReader::startThread); + + ePtr res_mgr; + eDVBResourceManager::getInstance(res_mgr); + if (!res_mgr) + eDebug("[eEPGTransponderDataReader] no resource manager !!!!!!!"); + else + res_mgr->connectChannelAdded(sigc::mem_fun(*this,&eEPGTransponderDataReader::DVBChannelAdded), m_chanAddedConn); + + std::ifstream pid_file ("/etc/enigma2/epgpids.custom"); + if (pid_file.is_open()) + { + eDebug("[eEPGTransponderDataReader] Custom pidfile found, parsing..."); + std::string line; + char optsidonid[12]; + int op, tsid, onid, eitpid; + while (!pid_file.eof()) + { + getline(pid_file, line); + if (line[0] == '#' || sscanf(line.c_str(), "%i %i %i %i", &op, &tsid, &onid, &eitpid) != 4) + continue; + if (op < 0) + op += 3600; + if (eitpid != 0) + { + snprintf(optsidonid, sizeof(optsidonid) - 1, "%x%04x%04x", op, tsid, onid); + customeitpids[std::string(optsidonid)] = eitpid; + eDebug("[eEPGTransponderDataReader] %s --> %#x", optsidonid, eitpid); + } + } + pid_file.close(); + eDebug("[eEPGTransponderDataReader] Done"); + } + + instance=this; +} + +eEPGTransponderDataReader::~eEPGTransponderDataReader() +{ + m_running = false; + m_messages.send(Message::quit); + kill(); // waiting for thread shutdown + instance = nullptr; +} + +void eEPGTransponderDataReader::startThread() +{ + if (!m_running) + { + eDebug("[eEPGTransponderDataReader] start Mainloop"); + run(); + m_running = true; + singleLock s(known_channel_lock); + for (ChannelMap::const_iterator it = m_knownChannels.begin(); it != m_knownChannels.end(); ++it) + { + if (it->second->state == -1) + { + it->second->state=0; + m_messages.send(Message(Message::startChannel, it->first)); + } + } + } +} + +void eEPGTransponderDataReader::thread() +{ + hasStarted(); + if (nice(4) == -1) + { + eDebug("[eEPGTransponderDataReader] thread failed to modify scheduling priority (%m)"); + } + runLoop(); +} + +void eEPGTransponderDataReader::gotMessage( const Message &msg ) +{ + switch (msg.type) + { + case Message::quit: + quit(0); + break; + case Message::startChannel: + { + singleLock s(known_channel_lock); + ChannelMap::const_iterator channel = m_knownChannels.find(msg.channel); + if ( channel != m_knownChannels.end() ) + channel->second->startChannel(); + break; + } + case Message::leaveChannel: + { + singleLock s(known_channel_lock); + ChannelMap::const_iterator channel = m_knownChannels.find(msg.channel); + if ( channel != m_knownChannels.end() ) + channel->second->abortEPG(); + break; + } +#ifdef ENABLE_PRIVATE_EPG + case Message::got_private_pid: + { + singleLock s(known_channel_lock); + for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + { + eDVBChannel *channel = (eDVBChannel*) it->first; + eEPGChannelData *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; + singleLock l(last_channel_update_lock); + updateMap::iterator It = m_channelLastUpdated.find( chid ); + int update = ( It != m_channelLastUpdated.end() ? ( UPDATE_INTERVAL - ( (::time(0)-It->second) * 1000 ) ) : ZAP_DELAY ); + if (update < ZAP_DELAY) + update = ZAP_DELAY; + data->startPrivateTimer->start(update, 1); + if (update >= 60000) + eDebug("[eEPGTransponderDataReader] next private update in %i min", update/60000); + else if (update >= 1000) + eDebug("[eEPGTransponderDataReader] next private update in %i sec", update/1000); + break; + } + } + break; + } +#endif +#ifdef ENABLE_MHW_EPG + case Message::got_mhw2_channel_pid: + { + singleLock s(known_channel_lock); + for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + { + eDVBChannel *channel = (eDVBChannel*) it->first; + eEPGChannelData *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_mhw2_channel_pid = msg.pid; + eDebug("[eEPGTransponderDataReader] got mhw2 channel pid %04x", msg.pid); + break; + } + } + break; + } + case Message::got_mhw2_title_pid: + { + singleLock s(known_channel_lock); + for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + { + eDVBChannel *channel = (eDVBChannel*) it->first; + eEPGChannelData *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_mhw2_title_pid = msg.pid; + eDebug("[eEPGTransponderDataReader] got mhw2 title pid %04x", msg.pid); + break; + } + } + break; + } + case Message::got_mhw2_summary_pid: + { + singleLock s(known_channel_lock); + for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + { + eDVBChannel *channel = (eDVBChannel*) it->first; + eEPGChannelData *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_mhw2_summary_pid = msg.pid; + eDebug("[eEPGTransponderDataReader] got mhw2 summary pid %04x", msg.pid); + break; + } + } + break; + } +#endif + default: + eDebug("[eEPGTransponderDataReader] unhandled Message!!"); + break; + } +} + +void eEPGTransponderDataReader::restartReader() +{ + singleLock l(last_channel_update_lock); + m_channelLastUpdated.clear(); + singleLock k(known_channel_lock); + for (ChannelMap::const_iterator it(m_knownChannels.begin()); it != m_knownChannels.end(); ++it) + it->second->startEPG(); +} + +void eEPGTransponderDataReader::DVBChannelAdded(eDVBChannel *chan) +{ + if ( chan ) + { +// eDebug("[eEPGTransponderDataReader] add channel %p", chan); + eEPGChannelData *data = new eEPGChannelData(this); + data->channel = chan; + data->prevChannelState = -1; +#ifdef ENABLE_PRIVATE_EPG + data->m_PrivatePid = -1; +#endif +#ifdef ENABLE_MHW_EPG + data->m_mhw2_channel_pid = 0x231; // defaults for astra 19.2 D+ + data->m_mhw2_title_pid = 0x234; // defaults for astra 19.2 D+ + data->m_mhw2_summary_pid = 0x236; // defaults for astra 19.2 D+ +#endif + singleLock s(known_channel_lock); + m_knownChannels.insert( std::pair(chan, data) ); + chan->connectStateChange(sigc::mem_fun(*this, &eEPGTransponderDataReader::DVBChannelStateChanged), data->m_stateChangedConn); + } +} + +void eEPGTransponderDataReader::DVBChannelStateChanged(iDVBChannel *chan) +{ + ChannelMap::iterator 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("[eEPGTransponderDataReader] channel %p running", chan); + DVBChannelRunning(chan); + break; + } + case iDVBChannel::state_release: + { + eDebug("[eEPGTransponderDataReader] remove channel %p", chan); + if (it->second->state >= 0) + m_messages.send(Message(Message::leaveChannel, chan)); + eEPGChannelData* cd = it->second; + pthread_mutex_lock(&cd->channel_active); + { + singleLock s(known_channel_lock); + m_knownChannels.erase(it); + } + pthread_mutex_unlock(&cd->channel_active); + delete cd; + // -> gotMessage -> abortEPG + return; + } + default: // ignore all other events + return; + } + if (it->second) + it->second->prevChannelState = state; + } + } +} + +void eEPGTransponderDataReader::DVBChannelRunning(iDVBChannel *chan) +{ + ChannelMap::const_iterator it = m_knownChannels.find(chan); + if ( it == m_knownChannels.end() ) + eDebug("[eEPGTransponderDataReader] will start non existing channel %p !!!", chan); + else + { + eEPGChannelData &data = *it->second; + ePtr res_mgr; + if ( eDVBResourceManager::getInstance( res_mgr ) ) + eDebug("[eEPGTransponderDataReader] no res manager!!"); + else + { + ePtr demux; + if ( data.channel->getDemux(demux, 0) ) + { + eDebug("[eEPGTransponderDataReader] no demux!!"); + return; + } + else + { + RESULT res = demux->createSectionReader( this, data.m_NowNextReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize nownext reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_ScheduleReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize schedule reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_ScheduleOtherReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize schedule other reader!!"); + return; + } + +#ifdef ENABLE_VIRGIN + res = demux->createSectionReader( this, data.m_VirginNowNextReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize virgin nownext reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_VirginScheduleReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize virgin schedule reader!!"); + return; + } +#endif +#ifdef ENABLE_NETMED + res = demux->createSectionReader( this, data.m_NetmedScheduleReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize netmed schedule reader!!"); + return; + } + + res = demux->createSectionReader( this, data.m_NetmedScheduleOtherReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize netmed schedule other reader!!"); + return; + } +#endif + res = demux->createSectionReader( this, data.m_ViasatReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize viasat reader!!"); + return; + } +#ifdef ENABLE_PRIVATE_EPG + res = demux->createSectionReader( this, data.m_PrivateReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize private reader!!"); + return; + } +#endif +#ifdef ENABLE_MHW_EPG + res = demux->createSectionReader( this, data.m_MHWReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize mhw reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_MHWReader2 ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize mhw reader!!"); + return; + } +#endif +#if ENABLE_FREESAT + res = demux->createSectionReader( this, data.m_FreeSatScheduleOtherReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize FreeSat reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_FreeSatScheduleOtherReader2 ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize FreeSat reader 2!!"); + return; + } +#endif +#ifdef ENABLE_ATSC + { + int system = iDVBFrontend::feSatellite; + ePtr parms; + chan->getCurrentFrontendParameters(parms); + if (parms) + { + parms->getSystem(system); + } + if (system == iDVBFrontend::feATSC) + { + res = demux->createSectionReader( this, data.m_ATSC_VCTReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize ATSC VCT reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_ATSC_MGTReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize ATSC MGT reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_ATSC_EITReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize ATSC EIT reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_ATSC_ETTReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize ATSC ETT reader!!"); + return; + } + } + } +#endif +#ifdef ENABLE_OPENTV + res = demux->createSectionReader( this, data.m_OPENTV_ChannelsReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize OpenTV channels reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_OPENTV_TitlesReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize OpenTV titles reader!!"); + return; + } + res = demux->createSectionReader( this, data.m_OPENTV_SummariesReader ); + if ( res ) + { + eDebug("[eEPGTransponderDataReader] couldnt initialize OpenTV summaries reader!!"); + return; + } +#endif + if (m_running) + { + data.state = 0; + m_messages.send(Message(Message::startChannel, chan)); + // -> gotMessage -> changedService + } + else + data.state=-1; + } + } + } +} diff --git a/lib/dvb/epgtransponderdatareader.h b/lib/dvb/epgtransponderdatareader.h new file mode 100644 index 0000000000..3e269ca957 --- /dev/null +++ b/lib/dvb/epgtransponderdatareader.h @@ -0,0 +1,118 @@ +#ifndef __epgtransponderdatareader_h_ +#define __epgtransponderdatareader_h_ + +/* Restart EPG data capture */ +#define UPDATE_INTERVAL 3600000 // 60 min +/* Time to wait after tuning in before EPG data capturing starts */ +#define ZAP_DELAY 2000 // 2 sec + +#include + +#ifdef ENABLE_OPENTV +#include +#include +#endif +#include +#include +#include + +class eDVBServicePMTHandler; +class eEPGChannelData; + +typedef std::map updateMap; +typedef std::map ChannelMap; + +#ifdef ENABLE_FREESAT +#include +class freesatEITSubtableStatus +{ +private: + u_char version; + uint16_t sectionMap[32]; + void initMap(uint8_t maxSection); + +public: + freesatEITSubtableStatus(u_char version, uint8_t maxSection); + bool isSectionPresent(uint8_t sectionNo); + void seen(uint8_t sectionNo, uint8_t maxSegmentSection); + bool isVersionChanged(u_char testVersion); + void updateVersion(u_char newVersion, uint8_t maxSection); + bool isCompleted(); +}; +#endif + +class eEPGTransponderDataReader: public eMainloop, private eThread, public sigc::trackable +{ + DECLARE_REF(eEPGTransponderDataReader) +public: + eEPGTransponderDataReader(); + ~eEPGTransponderDataReader(); + + static pthread_mutex_t known_channel_lock; + static pthread_mutex_t last_channel_update_lock; + + struct Message + { + enum + { + quit, + startChannel, + leaveChannel, + got_private_pid, + got_mhw2_channel_pid, + got_mhw2_title_pid, + got_mhw2_summary_pid, + timeChanged + }; + int type; + iDVBChannel *channel; + uniqueEPGKey service; + union { + int err; + int pid; + }; + Message() + :type(0) {} + Message(int type) + :type(type) {} + Message(int type, iDVBChannel *channel, int err=0) + :type(type), channel(channel), err(err) {} + Message(int type, const eServiceReference& service, int err=0) + :type(type), service(service), err(err) {} + }; + eFixedMessagePump m_messages; + + static eEPGTransponderDataReader *getInstance() { return instance; } + + void restartReader(); + +#ifdef ENABLE_PRIVATE_EPG + void PMTready(eDVBServicePMTHandler *pmthandler); +#else + void PMTready(eDVBServicePMTHandler *pmthandler) {} +#endif + +private: + friend class eEPGChannelData; + + static eEPGTransponderDataReader *instance; + bool m_running; + + ChannelMap m_knownChannels; + ePtr m_chanAddedConn; + updateMap m_channelLastUpdated; + + std::map customeitpids; + + void thread(); // thread function + void startThread(); + + void gotMessage(const Message &message); + + // called from main thread + void DVBChannelAdded(eDVBChannel*); + void DVBChannelStateChanged(iDVBChannel*); + void DVBChannelRunning(iDVBChannel *); +}; + +#endif diff --git a/lib/dvb/filepush.h b/lib/dvb/filepush.h index 5efd2b92e1..2e94d427a4 100644 --- a/lib/dvb/filepush.h +++ b/lib/dvb/filepush.h @@ -43,6 +43,7 @@ class eFilePushThread: public eThread, public sigc::trackable, public iObject private: iFilePushScatterGather *m_sg; int m_stop; + bool m_stopped; int m_fd_dest; int m_send_pvr_commit; int m_stream_mode; diff --git a/lib/dvb/frontend.cpp b/lib/dvb/frontend.cpp index 528256f308..de3cb643e9 100644 --- a/lib/dvb/frontend.cpp +++ b/lib/dvb/frontend.cpp @@ -558,7 +558,7 @@ int eDVBFrontend::PreferredFrontendIndex = -1; eDVBFrontend::eDVBFrontend(const char *devicenodename, int fe, int &ok, bool simulate, eDVBFrontend *simulate_fe) :m_simulate(simulate), m_enabled(false), m_fbc(false), m_simulate_fe(simulate_fe), m_type(-1), m_dvbid(fe), m_slotid(fe) - ,m_fd(-1), m_dvbversion(0), m_rotor_mode(false), m_need_rotor_workaround(false), m_multitype(false) + ,m_fd(-1), m_dvbversion(0), m_rotor_mode(false), m_need_rotor_workaround(false), m_multitype(false), m_voltage5_terrestrial(-1) ,m_state(stateClosed), m_timeout(0), m_tuneTimer(0) { m_filename = devicenodename; @@ -734,6 +734,7 @@ int eDVBFrontend::openFrontend() setTone(iDVBFrontend::toneOff); setVoltage(iDVBFrontend::voltageOff); + m_voltage5_terrestrial = -1; return 0; } @@ -813,6 +814,7 @@ int eDVBFrontend::closeFrontend(bool force, bool no_delayed) m_sn=0; m_state = stateClosed; + m_voltage5_terrestrial = -1; return 0; } @@ -2614,6 +2616,11 @@ RESULT eDVBFrontend::tune(const iDVBFrontendParameters &where, bool blindscan) if (res) goto tune_error; + if (m_voltage5_terrestrial == 1) + { + m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_VOLTAGE, iDVBFrontend::voltageOff) ); + m_voltage5_terrestrial = -1; + } m_sec_sequence.push_back( eSecCommand(eSecCommand::START_TUNE_TIMEOUT, timeout) ); m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_FRONTEND, 1) ); break; @@ -2631,13 +2638,19 @@ RESULT eDVBFrontend::tune(const iDVBFrontendParameters &where, bool blindscan) if (res) goto tune_error; - char configStr[255]; - snprintf(configStr, 255, "config.Nims.%d.terrestrial_5V", m_slotid); + if (m_voltage5_terrestrial == -1) + { + char configStr[255]; + snprintf(configStr, 255, "config.Nims.%d.terrestrial_5V", m_slotid); + if (eConfigManager::getConfigBoolValue(configStr)) + { + m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_VOLTAGE, iDVBFrontend::voltage5_terrestrial) ); + m_voltage5_terrestrial = 1; + } + else + m_voltage5_terrestrial = 0; + } m_sec_sequence.push_back( eSecCommand(eSecCommand::START_TUNE_TIMEOUT, timeout) ); - if (eConfigManager::getConfigBoolValue(configStr)) - m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_VOLTAGE, iDVBFrontend::voltage13) ); - else - m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_VOLTAGE, iDVBFrontend::voltageOff) ); m_sec_sequence.push_back( eSecCommand(eSecCommand::SET_FRONTEND, 1) ); break; } @@ -2698,17 +2711,24 @@ RESULT eDVBFrontend::connectStateChange(const sigc::slot1 &s RESULT eDVBFrontend::setVoltage(int voltage) { bool increased=false; + int active_antenna_power = -1; fe_sec_voltage_t vlt; m_data[CUR_VOLTAGE]=voltage; + switch (voltage) { case voltageOff: m_data[CSW]=m_data[UCSW]=m_data[TONEBURST]=-1; // reset diseqc vlt = SEC_VOLTAGE_OFF; + active_antenna_power = 0; break; case voltage13_5: increased = true; [[fallthrough]]; + case voltage5_terrestrial: + vlt = SEC_VOLTAGE_13; + active_antenna_power = 1; + break; case voltage13: vlt = SEC_VOLTAGE_13; break; @@ -2721,8 +2741,22 @@ RESULT eDVBFrontend::setVoltage(int voltage) default: return -ENODEV; } + if (m_simulate) return 0; + + if (active_antenna_power != -1 && (m_type == feTerrestrial || m_type == feCable)) + { + char filename[256]; + snprintf(filename, sizeof(filename), "/proc/stb/frontend/%d/active_antenna_power", m_slotid); + CFile proc_name(filename, "w"); + if(proc_name && fprintf(proc_name, "%s", active_antenna_power == 1 ? "on" : "off") > 0) + { + eDebug("[eDVBFrontend%d] set Voltage via proc %s", m_dvbid, active_antenna_power == 1 ? "on" : "off"); + return 1; + } + } + eDebug("[eDVBFrontend%d] setVoltage FE_ENABLE_HIGH_LNB_VOLTAGE %d FE_SET_VOLTAGE %d", m_dvbid, increased, vlt); ::ioctl(m_fd, FE_ENABLE_HIGH_LNB_VOLTAGE, increased); return ::ioctl(m_fd, FE_SET_VOLTAGE, vlt); diff --git a/lib/dvb/frontend.h b/lib/dvb/frontend.h index 41ce0485d7..f20555ddab 100644 --- a/lib/dvb/frontend.h +++ b/lib/dvb/frontend.h @@ -100,6 +100,7 @@ class eDVBFrontend: public iDVBFrontend, public sigc::trackable char m_description[128]; dvb_frontend_info fe_info; int satfrequency; + int m_voltage5_terrestrial; // -1 undefined, 0 off, 1 on eDVBFrontendParameters oparm; int m_state; diff --git a/lib/dvb/idvb.h b/lib/dvb/idvb.h index 1aefd33cb9..aa1f212b2b 100644 --- a/lib/dvb/idvb.h +++ b/lib/dvb/idvb.h @@ -462,7 +462,7 @@ class iDVBFrontend_ENUMS enum { feSatellite, feCable, feTerrestrial, feATSC }; enum { stateIdle, stateTuning, stateFailed, stateLock, stateLostLock, stateClosed }; enum { toneOff, toneOn }; - enum { voltageOff, voltage13, voltage18, voltage13_5, voltage18_5 }; + enum { voltageOff, voltage13, voltage18, voltage13_5, voltage18_5, voltage5_terrestrial }; }; class iDVBFrontendStatus: public iDVBFrontend_ENUMS, public iObject diff --git a/lib/dvb/pmt.cpp b/lib/dvb/pmt.cpp index 9529d2e667..c3b24869d0 100644 --- a/lib/dvb/pmt.cpp +++ b/lib/dvb/pmt.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -162,7 +162,7 @@ void eDVBServicePMTHandler::PMTready(int error) case streamserver: case scrambled_streamserver: case streamclient: - eEPGCache::getInstance()->PMTready(this); + eEPGTransponderDataReader::getInstance()->PMTready(this); break; default: /* do not start epg caching for other types of services */ diff --git a/lib/python/Components/ClientMode.py b/lib/python/Components/ClientMode.py index ef5424783e..8a0d1480d9 100644 --- a/lib/python/Components/ClientMode.py +++ b/lib/python/Components/ClientMode.py @@ -9,7 +9,7 @@ def clientModeChanged(configElement): SystemInfo["ClientModeEnabled"] = configElement.value == True SystemInfo["ClientModeDisabled"] = configElement.value != True - config.clientmode.enabled.addNotifier(clientModeChanged, immediate_feedback=True, initial_call=True) + config.clientmode.enabled.addNotifier(clientModeChanged) config.clientmode.serverAddressType = ConfigSelection(default="ip", choices=[("ip", _("IP")), ("domain", _("Domain"))]) config.clientmode.serverIP = ConfigIP(default=[0,0,0,0], auto_jump=True) config.clientmode.serverDomain = ConfigText(default="", fixed_size=False) diff --git a/lib/python/Components/Converter/FrontendInfo.py b/lib/python/Components/Converter/FrontendInfo.py index 776af80022..78f96a21fb 100644 --- a/lib/python/Components/Converter/FrontendInfo.py +++ b/lib/python/Components/Converter/FrontendInfo.py @@ -67,12 +67,12 @@ def getText(self): elif self.type == self.STRING: string = "" for n in nimmanager.nim_slots: - if n.type: + if n.enabled: if n.slot == self.source.slot_number: color = Hex2strColor(colors[0]) elif self.source.tuner_mask & 1 << n.slot: color = Hex2strColor(colors[1]) - elif len(nimmanager.nim_slots) <= self.space_for_tuners or self.show_all_non_link_tuners and not (n.isFBCLink() or n.internally_connectable): + elif len(nimmanager.nim_slots) <= self.space_for_tuners or n.isFBCRoot() or self.show_all_non_link_tuners and not(n.isFBCLink() or n.internally_connectable): color = Hex2strColor(colors[2]) else: continue @@ -83,7 +83,7 @@ def getText(self): if self.type == self.USE_TUNERS_STRING: string = "" for n in nimmanager.nim_slots: - if n.type: + if n.enabled: if n.slot == self.source.slot_number: color = Hex2strColor(colors[0]) elif self.source.tuner_mask & 1 << n.slot: @@ -100,17 +100,20 @@ def getText(self): @cached def getBool(self): - assert self.type in (self.LOCK, self.BER), "the boolean output of FrontendInfo can only be used for lock or BER info" + assert self.type in (self.LOCK, self.BER, self.SNR, self.SNRdB, self.AGC), "the boolean output of FrontendInfo can only be used for lock, BER, SNR, SNRdB or AGC info" if self.type == self.LOCK: lock = self.source.lock if lock is None: lock = False return lock - else: - ber = self.source.ber - if ber is None: - ber = 0 - return ber > 0 + elif self.type == self.BER: + return self.source.ber is not None + elif self.type == self.SNR: + return self.source.snr is not None + elif self.type == self.SNRdB: + return self.source.snr_db is not None + elif self.type == self.AGC: + return self.source.agc is not None text = property(getText) diff --git a/lib/python/Components/Converter/PliExtraInfo.py b/lib/python/Components/Converter/PliExtraInfo.py index 209a84e7ec..62aad3569a 100755 --- a/lib/python/Components/Converter/PliExtraInfo.py +++ b/lib/python/Components/Converter/PliExtraInfo.py @@ -481,8 +481,8 @@ def getText(self): feraw = self.feraw if not feraw: feraw = info.getInfoObject(iServiceInformation.sTransponderData) - if not feraw: - return "" +# if not feraw: # commented out for further testing. This "return" is blocking information display when no tuner is active, e.g. streaming. +# return "" fedata = ConvertToHumanReadable(feraw) else: fedata = self.fedata diff --git a/lib/python/Components/Converter/RotorPosition.py b/lib/python/Components/Converter/RotorPosition.py index a89f7a0f42..f01f611eea 100644 --- a/lib/python/Components/Converter/RotorPosition.py +++ b/lib/python/Components/Converter/RotorPosition.py @@ -34,8 +34,14 @@ def getText(self): return orbpos(config.misc.lastrotorposition.value) return "" + @cached + def getBool(self): + return bool(self.getText()) + text = property(getText) + boolean = property(getBool) + def isMotorizedTuner(self): try: for x in nimmanager.nim_slots: diff --git a/lib/python/Components/Lcd.py b/lib/python/Components/Lcd.py index 29192a33d8..4ae12441b4 100644 --- a/lib/python/Components/Lcd.py +++ b/lib/python/Components/Lcd.py @@ -365,7 +365,6 @@ def setLEDblinkingtime(configElement): config.lcd.ledbrightness = ConfigSlider(default = 3, increment = 1, limits = (0,15)) config.lcd.ledbrightness.addNotifier(setLEDnormalstate) config.lcd.ledbrightness.apply = lambda : setLEDnormalstate(config.lcd.ledbrightness) - config.lcd.ledbrightness.callNotifiersOnSaveAndCancel = True if detected: config.lcd.scroll_speed = ConfigSelection(default = "300", choices = [ @@ -432,8 +431,6 @@ def setLCDminitvfps(configElement): config.lcd.bright = ConfigSlider(default=5, limits=(0, 10)) config.lcd.bright.addNotifier(setLCDbright) config.lcd.bright.apply = lambda : setLCDbright(config.lcd.bright) - config.lcd.bright.callNotifiersOnSaveAndCancel = True - config.lcd.dimbright = ConfigSlider(default=standby_default, limits=(0, 10)) config.lcd.dimbright.addNotifier(setLCDdimbright); config.lcd.dimbright.apply = lambda : setLCDdimbright(config.lcd.dimbright) diff --git a/lib/python/Components/MovieList.py b/lib/python/Components/MovieList.py index dc5bc85a83..9130321bdb 100644 --- a/lib/python/Components/MovieList.py +++ b/lib/python/Components/MovieList.py @@ -5,17 +5,16 @@ from GUIComponent import GUIComponent from Tools.FuzzyDate import FuzzyTime -from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest, MultiContentEntryPixmapAlphaBlend, MultiContentEntryProgress +from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaBlend, MultiContentEntryProgress from Components.config import config from Components.Renderer.Picon import getPiconName from Screens.LocationBox import defaultInhibitDirs -from Tools.LoadPixmap import LoadPixmap from Tools.Directories import SCOPE_ACTIVE_SKIN, resolveFilename -from Tools.Trashcan import getTrashFolder +from Tools.Trashcan import getTrashFolder, isTrashFolder import NavigationInstance from skin import parseColor, parseFont, parseScale -from enigma import eListboxPythonMultiContent, eListbox, gFont, iServiceInformation, eSize, loadPNG, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_VALIGN_CENTER, BT_SCALE, BT_KEEP_ASPECT_RATIO, BT_HALIGN_CENTER, BT_VALIGN_CENTER, eServiceReference, eServiceCenter, eTimer +from enigma import eListboxPythonMultiContent, eListbox, gFont, iServiceInformation, eSize, loadPNG, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_VALIGN_CENTER, BT_SCALE, BT_KEEP_ASPECT_RATIO, BT_HALIGN_CENTER, BT_ALIGN_CENTER, BT_VALIGN_CENTER, eServiceReference, eServiceCenter, eTimer AUDIO_EXTENSIONS = frozenset((".dts", ".mp3", ".wav", ".wave", ".wv", ".oga", ".ogg", ".flac", ".m4a", ".mp2", ".m2a", ".wma", ".ac3", ".mka", ".aac", ".ape", ".alac", ".amr", ".au", ".mid")) DVD_EXTENSIONS = frozenset((".iso", ".img", ".nrg")) @@ -23,6 +22,19 @@ MOVIE_EXTENSIONS = frozenset((".mpg", ".vob", ".m4v", ".mkv", ".avi", ".divx", ".dat", ".flv", ".mp4", ".mov", ".wmv", ".asf", ".3gp", ".3g2", ".mpeg", ".mpe", ".rm", ".rmvb", ".ogm", ".ogv", ".m2ts", ".mts", ".webm", ".pva", ".wtv", ".ts")) KNOWN_EXTENSIONS = MOVIE_EXTENSIONS.union(IMAGE_EXTENSIONS, DVD_EXTENSIONS, AUDIO_EXTENSIONS) +# Gets the name of a movielist item for display in the UI honouring the hide extensions setting +def getItemDisplayName(itemRef, info, removeExtension=None): + name = info.getName(itemRef) + if itemRef.flags & eServiceReference.isDirectory: + name = os.path.basename(name.rstrip("/")) + else: + removeExtension = config.movielist.hide_extensions.value if removeExtension is None else removeExtension + if removeExtension: + fileName, fileExtension = os.path.splitext(name) + if fileExtension in KNOWN_EXTENSIONS: + name = fileName + return name + cutsParser = struct.Struct('>QI') # big-endian, 64-bit PTS and 32-bit type class MovieListData: @@ -126,8 +138,6 @@ def resetMoviePlayState(cutsFileName, ref=None): f.close() except: pass - #import sys - #print "Exception in resetMoviePlayState: %s: %s" % sys.exc_info()[:2] class MovieList(GUIComponent): SORT_ALPHANUMERIC = 1 @@ -186,6 +196,7 @@ def __init__(self, root, sort_type=None, descr_state=None): self.reloadDelayTimer = None self.l = eListboxPythonMultiContent() self.tags = set() + self.markList = [] self.root = None self._playInBackground = None self._playInForeground = None @@ -197,13 +208,14 @@ def __init__(self, root, sort_type=None, descr_state=None): self.onSelectionChanged = [ ] self.iconPart = [] for part in range(5): - self.iconPart.append(LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_%d_4.png" % part))) - self.iconMovieRec = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_new.png")) - self.iconMoviePlay = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/movie_play.png")) - self.iconMoviePlayRec = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/movie_play_rec.png")) - self.iconUnwatched = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_unwatched.png")) - self.iconFolder = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/folder.png")) - self.iconTrash = LoadPixmap(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/trashcan.png")) + self.iconPart.append(loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_%d_4.png" % part))) + self.iconMovieRec = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_new.png")) + self.iconMoviePlay = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/movie_play.png")) + self.iconMoviePlayRec = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/movie_play_rec.png")) + self.iconUnwatched = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/part_unwatched.png")) + self.iconFolder = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/folder.png")) + self.iconMarked = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/mark_on.png")) + self.iconTrash = loadPNG(resolveFilename(SCOPE_ACTIVE_SKIN, "icons/trashcan.png")) self.runningTimers = {} self.updateRecordings() self.updatePlayPosCache() @@ -375,11 +387,13 @@ def buildMovieListEntry(self, serviceref, info, begin, data): p = os.path.split(p[0]) txt = p[1] if txt == ".Trash": - res.append(MultiContentEntryPixmapAlphaBlend(pos=((col0iconSize-self.iconTrash.size().width())/2,(self.itemHeight-self.iconFolder.size().height())/2), size=(iconSize,self.iconTrash.size().height()), png=self.iconTrash)) + res.append(MultiContentEntryPixmapAlphaBlend(pos=(0, 0), size=(col0iconSize, self.itemHeight), png=self.iconTrash, flags=BT_ALIGN_CENTER)) res.append(MultiContentEntryText(pos=(col0iconSize + space, 0), size=(width-145, self.itemHeight), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = _("Deleted items"))) res.append(MultiContentEntryText(pos=(width-145-r, 0), size=(145, self.itemHeight), font=1, flags=RT_HALIGN_RIGHT|RT_VALIGN_CENTER, text=_("Trash can"))) return res - res.append(MultiContentEntryPixmapAlphaBlend(pos=((col0iconSize-self.iconFolder.size().width())/2,(self.itemHeight-self.iconFolder.size().height())/2), size=(iconSize,iconSize), png=self.iconFolder)) + res.append(MultiContentEntryPixmapAlphaBlend(pos=(0, 0), size=(col0iconSize, self.itemHeight), png=self.iconFolder, flags=BT_ALIGN_CENTER)) + if self.getCurrent() in self.markList: + res.append(MultiContentEntryPixmapAlphaBlend(pos=(0, 0), size=(col0iconSize, self.itemHeight), png=self.iconMarked)) res.append(MultiContentEntryText(pos=(col0iconSize + space, 0), size=(width-145, self.itemHeight), font=0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = txt)) res.append(MultiContentEntryText(pos=(width-145-r, 0), size=(145, self.itemHeight), font=1, flags=RT_HALIGN_RIGHT|RT_VALIGN_CENTER, text=_("Directory"))) return res @@ -392,11 +406,13 @@ def buildMovieListEntry(self, serviceref, info, begin, data): else: data.len = 0 #dont recalc movielist to speedup loading the list self.list[cur_idx] = (x[0], x[1], x[2], data) #update entry in list... so next time we don't need to recalc - data.txt = info.getName(serviceref) - if config.movielist.hide_extensions.value: - fileName, fileExtension = os.path.splitext(data.txt) - if fileExtension in KNOWN_EXTENSIONS: - data.txt = fileName + data.picon = None + if showPicons: + refs = info.getInfoString(x[0], iServiceInformation.sServiceref) + picon = getPiconName(refs) + if picon != "": + data.picon = loadPNG(picon) + data.txt = getItemDisplayName(serviceref, info) data.icon = None data.part = None if os.path.split(pathName)[1] in self.runningTimers: @@ -446,21 +462,21 @@ def addProgress(): res.append(MultiContentEntryPixmapAlphaBlend(pos=(colX,self.pbarShift), size=(iconSize, self.pbarHeight), png=data.icon)) return iconSize - serviceref = info.getInfoString(serviceref, iServiceInformation.sServiceref) - displayPicon = None if piconWidth > 0: # Picon - picon = getPiconName(serviceref) - if picon != "": - displayPicon = loadPNG(picon) - if displayPicon is not None: + if data and data.picon is not None: res.append(MultiContentEntryPixmapAlphaBlend( pos = (colX, 0), size = (piconWidth, ih), - png = displayPicon, + png = data.picon, backcolor = None, backcolor_sel = None, flags = BT_SCALE | BT_KEEP_ASPECT_RATIO | BT_HALIGN_CENTER | BT_VALIGN_CENTER)) - colX += piconWidth + space + colX += piconWidth else: - colX += addProgress() + space + colX += addProgress() + + # The selection mark floats over the top of the first column + if self.getCurrent() in self.markList: + res.append(MultiContentEntryPixmapAlphaBlend(pos=(0, 0), size=(colX, self.itemHeight), png=self.iconMarked)) + colX += space # Recording name res.append(MultiContentEntryText(pos=(colX, 0), size=(width-iconSize-space-durationWidth-dateWidth-r-colX, ih), font = 0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER, text = data.txt)) @@ -486,6 +502,7 @@ def addProgress(): begin_string = strftime("%s, %s" % (config.usage.date.daylong.value, config.usage.time.short.value), localtime(begin)) res.append(MultiContentEntryText(pos=(width-dateWidth-r, 0), size=(dateWidth, ih), font=1, flags=RT_HALIGN_RIGHT|RT_VALIGN_CENTER, text=begin_string)) + return res def moveToFirstMovie(self): @@ -548,14 +565,26 @@ def reload(self, root = None, filter_tags = None): else: self.load(self.root, filter_tags) self.l.setBuildFunc(self.buildMovieListEntry) # don't move that to __init__ as this will create memory leak when calling MovieList from WebIf + self.refreshDisplay() + + def refreshDisplay(self): self.l.setList(self.list) - def removeService(self, service): - index = self.findService(service) - if index is not None: - del self.list[index] + def removeServices(self, services): + refresh = False + for service in services: + refresh = self.removeService(service) or refresh + if refresh: self.l.setList(self.list) + def removeService(self, service): + for index, item in enumerate(self.list): + if item[0] == service: + self.removeMark(item[0]) + del self.list[index] + return True + return False + def findService(self, service): if service is None: return None @@ -577,6 +606,7 @@ def load(self, root, filter_tags): # this lists our root service, then building a # nice list del self.list[:] + del self.markList[:] serviceHandler = eServiceCenter.getInstance() numberOfDirs = 0 @@ -599,6 +629,7 @@ def load(self, root, filter_tags): ref = eServiceReference.fromDirectory(parent) self.list.append((ref, None, 0, -1)) numberOfDirs += 1 + firstDir = numberOfDirs if config.usage.movielist_trashcan.value: here = os.path.realpath(rootPath) @@ -703,15 +734,16 @@ def load(self, root, filter_tags): elif self.current_sort == MovieList.SORT_ALPHANUMERIC: self.list = sorted(self.list[:numberOfDirs], key=self.buildAlphaNumericSortKey) + sorted(self.list[numberOfDirs:], key=self.buildAlphaNumericSortKey) elif self.current_sort == MovieList.SORT_ALPHANUMERIC_REVERSE: - self.list = sorted(self.list[:numberOfDirs], key=self.buildAlphaNumericSortKey, reverse = True) + sorted(self.list[numberOfDirs:], key=self.buildAlphaNumericSortKey, reverse = True) + self.list = (self.list[:firstDir] + sorted(self.list[firstDir:numberOfDirs], key=self.buildAlphaNumericSortKey, reverse = True) + + sorted(self.list[numberOfDirs:], key=self.buildAlphaNumericSortKey, reverse = True)) elif self.current_sort == MovieList.SORT_ALPHANUMERIC_FLAT: self.list.sort(key=self.buildAlphaNumericFlatSortKey) elif self.current_sort == MovieList.SORT_ALPHANUMERIC_FLAT_REVERSE: - self.list.sort(key=self.buildAlphaNumericFlatSortKey, reverse = True) + self.list = self.list[:firstDir] + sorted(self.list[firstDir:], key=self.buildAlphaNumericFlatSortKey, reverse = True) elif self.current_sort == MovieList.SORT_RECORDED: self.list = sorted(self.list[:numberOfDirs], key=self.buildBeginTimeSortKey) + sorted(self.list[numberOfDirs:], key=self.buildBeginTimeSortKey) elif self.current_sort == MovieList.SORT_RECORDED_REVERSE: - self.list = sorted(self.list[:numberOfDirs], key=self.buildBeginTimeSortKey, reverse = True) + sorted(self.list[numberOfDirs:], key=self.buildBeginTimeSortKey, reverse = True) + self.list = self.list[:firstDir] + sorted(self.list[firstDir:numberOfDirs], key=self.buildBeginTimeSortKey, reverse = True) + sorted(self.list[numberOfDirs:], key=self.buildBeginTimeSortKey, reverse = True) elif self.current_sort == MovieList.SHUFFLE: dirlist = self.list[:numberOfDirs] shufflelist = self.list[numberOfDirs:] @@ -720,7 +752,7 @@ def load(self, root, filter_tags): elif self.current_sort == MovieList.SORT_ALPHA_DATE_OLDEST_FIRST: self.list = sorted(self.list[:numberOfDirs], key=self.buildAlphaDateSortKey) + sorted(self.list[numberOfDirs:], key=self.buildAlphaDateSortKey) elif self.current_sort == MovieList.SORT_ALPHAREV_DATE_NEWEST_FIRST: - self.list = sorted(self.list[:numberOfDirs], key=self.buildAlphaDateSortKey, reverse = True) + sorted(self.list[numberOfDirs:], key=self.buildAlphaDateSortKey, reverse = True) + self.list = self.list[:firstDir] + sorted(self.list[firstDir:numberOfDirs], key=self.buildAlphaDateSortKey, reverse = True) + sorted(self.list[numberOfDirs:], key=self.buildAlphaDateSortKey, reverse = True) elif self.current_sort == MovieList.SORT_LONGEST: self.list = sorted(self.list[:numberOfDirs], key=self.buildAlphaNumericSortKey) + sorted(self.list[numberOfDirs:], key=self.buildLengthSortKey, reverse = True) elif self.current_sort == MovieList.SORT_SHORTEST: @@ -798,7 +830,7 @@ def buildLengthSortKey(self, x): # x = ref,info,begin,... ref = x[0] name = x[1] and x[1].getName(ref) - len = x[1] and x[1].getLength(ref) + len = x[1] and (x[1].getLength(ref) // 60) # we only display minutes, so sort by minutes if ref.flags & eServiceReference.mustDescent: return 0, len or 0, name and name.lower() or "", -x[2] return 1, len or 0, name and name.lower() or "", -x[2] @@ -923,6 +955,45 @@ def _moveToChrStr(self): if self._lbl: self._lbl.visible = False + def clearMarks(self): + if self.markList: + self.markList = [] + self.refreshDisplay() + + def removeMark(self, itemRef): + if self.markList: + try: + self.markList.remove(itemRef) + except: + pass + + def toggleMark(self): + item = self.l.getCurrentSelection() + if not item: + return + itemRef, info = item[:2] + # don't allow marks on the parent directory or trash can items + if item and item[0] and item[1] and not isTrashFolder(item[0].getPath()): + if item[0] in self.markList: + self.markList.remove(item[0]) + else: + self.markList.append(item[0]) + self.invalidateCurrentItem() + + def getMarked(self, excludeDirs=False): + marked = [] + for service in self.markList[:]: + idx = self.findService(service) + if idx is not None: + if not excludeDirs or not(service.flags & eServiceReference.isDirectory): + marked.append(self.list[idx]) + else: + self.markList.remove(service) + return marked + + def countMarked(self): + return len(self.markList) + def getShortName(name, serviceref): if serviceref.flags & eServiceReference.mustDescent: #Directory pathName = serviceref.getPath() diff --git a/lib/python/Components/NimManager.py b/lib/python/Components/NimManager.py index d0f8b3916b..48f63d4493 100644 --- a/lib/python/Components/NimManager.py +++ b/lib/python/Components/NimManager.py @@ -688,6 +688,12 @@ def getFriendlyFullDescriptionCompressed(self): return "%s-%s: %s" % (self.slot_name, self.getSlotID(self.slot + 1), self.getFullDescription()) return self.getFriendlyFullDescription() + def isFBCLinkEnabled(self): + return self.isFBCLink() and (config.Nims[(self.slot >> 3 << 3)].configMode.value != "nothing" or self.getType() != "DVB-C" and config.Nims[(self.slot >> 3 << 3) + 1].configMode.value != "nothing") + + def isEnabled(self): + return self.config_mode != "nothing" or self.isFBCLinkEnabled() or self.internally_connectable is not None and config.Nims[self.internally_connectable].configMode.value != "nothing" + slot_id = property(getSlotID) slot_name = property(getSlotName) friendly_full_description = property(getFriendlyFullDescription) @@ -696,6 +702,7 @@ def getFriendlyFullDescriptionCompressed(self): config_mode = property(lambda self: config.Nims[self.slot].configMode.value) config = property(lambda self: config.Nims[self.slot]) empty = property(lambda self: self.getType() is None) + enabled = property(isEnabled) class NimManager: def getConfiguredSats(self): @@ -992,7 +999,7 @@ def canEqualTo(self, slotid): nimList = self.getNimListOfType(type, slotid) for nim in nimList[:]: mode = self.getNimConfig(nim) - if self.nim_slots[nim].isFBCLink() or mode.configMode.value == "loopthrough" or mode.configMode.value == "satposdepends": + if self.nim_slots[nim].isFBCLink() or mode.configMode.value in ("loopthrough", "satposdepends", "equal"): nimList.remove(nim) return nimList diff --git a/lib/python/Components/Renderer/LcdPicon.py b/lib/python/Components/Renderer/LcdPicon.py index 8fbbaac6af..b40e820c70 100644 --- a/lib/python/Components/Renderer/LcdPicon.py +++ b/lib/python/Components/Renderer/LcdPicon.py @@ -1,11 +1,8 @@ import os, re, unicodedata from Renderer import Renderer from enigma import ePixmap, ePicLoad -from Tools.Alternatives import GetWithAlternative from Tools.Directories import pathExists, SCOPE_ACTIVE_SKIN, resolveFilename -from Components.Harddisk import harddiskmanager from boxbranding import getDisplayType -from ServiceReference import ServiceReference from Components.config import config from Picon import PiconLocator diff --git a/lib/python/Components/Renderer/Picon.py b/lib/python/Components/Renderer/Picon.py index fe7e580be0..e6d9ea9cd5 100644 --- a/lib/python/Components/Renderer/Picon.py +++ b/lib/python/Components/Renderer/Picon.py @@ -1,10 +1,9 @@ import os, re, unicodedata from Renderer import Renderer -from enigma import ePixmap, ePicLoad +from enigma import ePixmap, eServiceReference from Tools.Alternatives import GetWithAlternative from Tools.Directories import pathExists, SCOPE_ACTIVE_SKIN, resolveFilename from Components.Harddisk import harddiskmanager -from ServiceReference import ServiceReference class PiconLocator: def __init__(self, piconDirectories = ['picon']): @@ -89,7 +88,7 @@ def getPiconName(self, serviceName): fields[9] = '0' pngname = self.findPicon('_'.join(fields)) if not pngname: # picon by channel name - name = ServiceReference(serviceName).getServiceName() + name = eServiceReference(serviceName).getServiceName() name = unicodedata.normalize('NFKD', unicode(name, 'utf_8', errors='ignore')).encode('ASCII', 'ignore') name = re.sub('[^a-z0-9]', '', name.replace('&', 'and').replace('+', 'plus').replace('*', 'star').lower()) if len(name) > 0: diff --git a/lib/python/Components/SystemInfo.py b/lib/python/Components/SystemInfo.py index 819bbd2a8f..8e079fa3dc 100644 --- a/lib/python/Components/SystemInfo.py +++ b/lib/python/Components/SystemInfo.py @@ -1,4 +1,4 @@ -from boxbranding import getBoxType, getBrandOEM, getDisplayType, getHaveAVJACK, getHaveHDMIinFHD, getHaveHDMIinHD, getHaveRCA, getHaveSCART, getHaveSCARTYUV, getHaveYUV, getMachineBrand, getMachineBuild, getMachineMtdRoot, getMachineName +from boxbranding import getBoxType, getBrandOEM, getDisplayType, getHaveAVJACK, getHaveHDMIinFHD, getHaveHDMIinHD, getHaveRCA, getHaveSCART, getHaveSCARTYUV, getHaveYUV, getImageType, getMachineBrand, getMachineBuild, getMachineMtdRoot, getMachineName from enigma import Misc_Options, eDVBCIInterfaces, eDVBResourceManager from Components.About import getChipSetString @@ -26,6 +26,7 @@ def countFrontpanelLEDs(): SystemInfo["MachineBrand"] = getMachineBrand() SystemInfo["MachineName"] = getMachineName() +SystemInfo["DeveloperImage"] = getImageType().lower() != "release" SystemInfo["CommonInterface"] = eDVBCIInterfaces.getInstance().getNumOfSlots() SystemInfo["CommonInterfaceCIDelay"] = fileCheck("/proc/stb/tsmux/rmx_delay") for cislot in range(0, SystemInfo["CommonInterface"]): diff --git a/lib/python/Components/Timezones.py b/lib/python/Components/Timezones.py index 0a074935c7..7786823e4f 100755 --- a/lib/python/Components/Timezones.py +++ b/lib/python/Components/Timezones.py @@ -83,9 +83,8 @@ def timezoneAreaChoices(configElement): def timezoneNotifier(configElement): timezones.activateTimezone(configElement.value, config.timezone.area.value) - config.timezone.area.addNotifier(timezoneAreaChoices, initial_call=False, immediate_feedback=True) - config.timezone.val.addNotifier(timezoneNotifier, initial_call=True, immediate_feedback=True) - config.timezone.val.callNotifiersOnSaveAndCancel = True + config.timezone.area.addNotifier(timezoneAreaChoices, initial_call=False) + config.timezone.val.addNotifier(timezoneNotifier) class Timezones: @@ -94,11 +93,6 @@ def __init__(self): self.loadTimezones() self.readTimezones() self.callbacks = [] - # This is a work around to maintain support of AutoTimers - # until AutoTimers are updated to use the Timezones - # callbacks. Once AutoTimers are updated *all* AutoTimer - # code should be removed from the Timezones.py code! - self.autotimerInit() # Scan the zoneinfo directory tree and all load all time zones found. # @@ -294,41 +288,5 @@ def removeCallback(self, callback): if callback in self.callbacks: self.callbacks.remove(callback) - def autotimerInit(self): # This code should be moved into the AutoTimer plugin! - try: - # Create attributes autotimer & autopoller for backwards compatibility. - # Their use is deprecated. - from enigma import eTimer - from Plugins.Extensions.AutoTimer.plugin import autotimer, autopoller - self.autotimerPoller = autopoller - self.autotimerTimer = autotimer - try: - self.autotimerPollDelay = config.plugins.autotimer.delay.value - except AttributeError: - self.autotimerPollDelay = 3 - self.timer = eTimer() - self.timer.callback.append(self.autotimeQuery) - self.addCallback(self.autotimerCallback) - except ImportError: - self.autotimerPoller = None - self.autotimerTimer = None - - def autotimerCallback(self): - if config.plugins.autotimer.autopoll.value: - self.timer.stop() - print("[Timezones] Trying to stop main AutoTimer poller.") - if self.autotimerPoller is not None: - self.autotimerPoller.stop() - print("[Timezones] AutoTimer poller will be run in %d minutes." % self.autotimerPollDelay) - self.timer.startLongTimer(self.autotimerPollDelay * 60) - - def autotimeQuery(self): - print("[Timezones] AutoTimer poll is running.") - if self.autotimerTimer is not None: - print("[Timezones] AutoTimer is parsing the EPG.") - self.autotimerTimer.parseEPG(autoPoll=True) - if self.autotimerPoller is not None: - self.autotimerPoller.start() - timezones = Timezones() diff --git a/lib/python/Components/UsageConfig.py b/lib/python/Components/UsageConfig.py index 7f4ba0561d..f108cce8ed 100755 --- a/lib/python/Components/UsageConfig.py +++ b/lib/python/Components/UsageConfig.py @@ -2,7 +2,7 @@ import os import skin from time import time -from boxbranding import getBrandOEM, getBoxType, getDisplayType +from boxbranding import getBrandOEM, getDisplayType from enigma import eDVBDB, eEPGCache, setTunerTypePriorityOrder, setPreferredTuner, setSpinnerOnOff, setEnableTtCachingOnOff, eEnv, Misc_Options, eBackgroundFileEraser, eServiceEvent, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP @@ -91,7 +91,15 @@ def showsecondinfobarChanged(configElement): SystemInfo["InfoBarEpg"] = True else: SystemInfo["InfoBarEpg"] = False - config.usage.show_second_infobar.addNotifier(showsecondinfobarChanged, immediate_feedback = True) + config.usage.show_second_infobar.addNotifier(showsecondinfobarChanged) + + try: + SystemInfo["SecondInfoBarSimple"] = skin.parameters.get("SecondInfoBarSimple", 0) > 0 + except Exception as err: + print("[UsageConfig] Error loading 'SecondInfoBarSimple' skin parameter! (%s)" % err) + SystemInfo["SecondInfoBarSimple"] = False + config.usage.second_infobar_simple = ConfigBoolean(descriptions={False: _("Standard"), True: _("Simple")}, graphic=False) + config.usage.infobar_frontend_source = ConfigSelection(default = "tuner", choices = [("settings", _("Settings")), ("tuner", _("Tuner"))]) config.usage.show_picon_bkgrn = ConfigSelection(default = "transparent", choices = [("none", _("Disabled")), ("transparent", _("Transparent")), ("blue", _("Blue")), ("red", _("Red")), ("black", _("Black")), ("white", _("White")), ("lightgrey", _("Light Grey")), ("grey", _("Grey"))]) @@ -718,6 +726,9 @@ def setTimeDisplayStyles(configElement): config.usage.time.display.value = config.usage.time.display.default config.usage.boolean_graphic = ConfigSelection(default="no", choices={"no": _("no"), "yes": _("yes"), "only_bool": _("yes, but not in multi selections")}) + config.usage.fast_skin_reload = ConfigYesNo(default = False) + if not SystemInfo["DeveloperImage"]: + config.usage.fast_skin_reload.value = False if SystemInfo["hasXcoreVFD"]: def set12to8characterVFD(configElement): @@ -772,8 +783,8 @@ def EpgCacheLoadSchedChanged(configElement): def EpgCacheSaveSchedChanged(configElement): import EpgLoadSave EpgLoadSave.EpgCacheSaveCheck() - config.epg.cacheloadsched.addNotifier(EpgCacheLoadSchedChanged, immediate_feedback = False) - config.epg.cachesavesched.addNotifier(EpgCacheSaveSchedChanged, immediate_feedback = False) + config.epg.cacheloadsched.addNotifier(EpgCacheLoadSchedChanged, immediate_feedback=False) + config.epg.cachesavesched.addNotifier(EpgCacheSaveSchedChanged, immediate_feedback=False) config.epg.cacheloadtimer = ConfigSelectionNumber(default = 24, stepwidth = 1, min = 1, max = 24, wraparound = True) config.epg.cachesavetimer = ConfigSelectionNumber(default = 24, stepwidth = 1, min = 1, max = 24, wraparound = True) @@ -795,8 +806,8 @@ def EpgCacheChanged(configElement): if not config.misc.epgcache_filename.value.startswith("/etc/enigma2/"): if os.path.exists('/etc/enigma2/' + config.misc.epgcachefilename.value.replace('.dat','') + '.dat'): os.remove('/etc/enigma2/' + config.misc.epgcachefilename.value.replace('.dat','') + '.dat') - config.misc.epgcachepath.addNotifier(EpgCacheChanged, immediate_feedback = False) - config.misc.epgcachefilename.addNotifier(EpgCacheChanged, immediate_feedback = False) + config.misc.epgcachepath.addNotifier(EpgCacheChanged, immediate_feedback=False) + config.misc.epgcachefilename.addNotifier(EpgCacheChanged, immediate_feedback=False) config.misc.epgratingcountry = ConfigSelection(default="", choices=[("", _("Auto Detect")), ("ETSI", _("Generic")), ("AUS", _("Australia"))]) config.misc.epggenrecountry = ConfigSelection(default="", choices=[("", _("Auto Detect")), ("ETSI", _("Generic")), ("AUS", _("Australia"))]) @@ -895,7 +906,7 @@ def set12VOutput(configElement): def updatedebug_path(configElement): if not os.path.exists(config.crash.debug_path.value): os.mkdir(config.crash.debug_path.value,0755) - config.crash.debug_path.addNotifier(updatedebug_path, immediate_feedback = False) + config.crash.debug_path.addNotifier(updatedebug_path, immediate_feedback=False) config.usage.timerlist_showpicons = ConfigYesNo(default = True) config.usage.timerlist_finished_timer_position = ConfigSelection(default = "end", choices = [("beginning", _("at beginning")), ("end", _("at end")), ("hide", _("hide"))]) @@ -905,14 +916,14 @@ def updateEnterForward(configElement): configElement.value = [2] updateChoices(config.seek.enter_forward, configElement.value) - config.seek.speeds_forward.addNotifier(updateEnterForward, immediate_feedback = False) + config.seek.speeds_forward.addNotifier(updateEnterForward, immediate_feedback=False) def updateEnterBackward(configElement): if not configElement.value: configElement.value = [2] updateChoices(config.seek.enter_backward, configElement.value) - config.seek.speeds_backward.addNotifier(updateEnterBackward, immediate_feedback = False) + config.seek.speeds_backward.addNotifier(updateEnterBackward, immediate_feedback=False) def updateEraseSpeed(el): eBackgroundFileEraser.getInstance().setEraseSpeed(int(el.value)) @@ -923,12 +934,12 @@ def updateEraseFlags(el): ("20", _("20 MB/s")), ("50", _("50 MB/s")), ("100", _("100 MB/s"))]) - config.misc.erase_speed.addNotifier(updateEraseSpeed, immediate_feedback = False) + config.misc.erase_speed.addNotifier(updateEraseSpeed, immediate_feedback=False) config.misc.erase_flags = ConfigSelection(default="1", choices = [ ("0", _("Disable")), ("1", _("Internal hdd only")), ("3", _("Everywhere"))]) - config.misc.erase_flags.addNotifier(updateEraseFlags, immediate_feedback = False) + config.misc.erase_flags.addNotifier(updateEraseFlags, immediate_feedback=False) config.misc.zapkey_delay = ConfigSelectionNumber(default = 5, stepwidth = 1, min = 0, max = 20, wraparound = True) config.misc.numzap_picon = ConfigYesNo(default = True) @@ -939,7 +950,7 @@ def setZapmode(el): file.close() config.misc.zapmode = ConfigSelection(default = "mute", choices = [ ("mute", _("Black screen")), ("hold", _("Hold screen")), ("mutetilllock", _("Black screen till locked")), ("holdtilllock", _("Hold till locked"))]) - config.misc.zapmode.addNotifier(setZapmode, immediate_feedback = False) + config.misc.zapmode.addNotifier(setZapmode, immediate_feedback=False) config.usage.historymode = ConfigSelection(default = "1", choices = [("0", _("Just zap")), ("1", _("Show menu"))]) config.subtitles = ConfigSubsection() diff --git a/lib/python/Components/config.py b/lib/python/Components/config.py index 805d0c4f98..e96f505365 100644 --- a/lib/python/Components/config.py +++ b/lib/python/Components/config.py @@ -83,7 +83,7 @@ def __init__(self): self.__notifiers = None self.__notifiers_final = None self.enabled = True - self.callNotifiersOnSaveAndCancel = False + self.callNotifiersOnSaveAndCancel = False # this is here for compatibility only. Do not use. def getNotifiers(self): if self.__notifiers is None: @@ -125,7 +125,8 @@ def load(self): if sv is None: self.value = self.default else: - self.last_value = self.value = self.fromstring(sv) + self.value = self.fromstring(sv) + self.last_value = self.tostring(self.value) def tostring(self, value): return str(value) @@ -139,13 +140,13 @@ def save(self): self.saved_value = None else: self.saved_value = self.tostring(self.value) - if self.callNotifiersOnSaveAndCancel: - self.changedFinal() # call none immediate_feedback notifiers, immediate_feedback Notifiers are called as they are chanaged, so do not need to be called here. + + if self.last_value != self.tostring(self.value): + self.last_value = self.tostring(self.value) + self.changedFinal() def cancel(self): self.load() - if self.callNotifiersOnSaveAndCancel: - self.changedFinal() # call none immediate_feedback notifiers, immediate_feedback Notifiers are called as they are chanaged, so do not need to be called here. def isChanged(self): # NOTE - self.saved_value should already be stringified! @@ -180,11 +181,9 @@ def addNotifier(self, notifier, initial_call=True, immediate_feedback=True, extr # "immediate_feedback=True" notifiers are called on every single change of the config item, # e.g. if going left/right through a ConfigSelection it will trigger on every step. # - # "immediate_feedback=False" notifiers are only called from ConfigList screens, and only called - # "onDeselect", i.e. moving to another list item, or closing the ConfigList screen. + # "immediate_feedback=False" notifiers are called on ConfigElement.save() only. # - # Use of the "self.callNotifiersOnSaveAndCancel" flag is dubious as it affects all notifiers of the current - # config element (i.e. self). It is impossible to confine this to individual notifiers. This need looking at. + # Use of the "self.callNotifiersOnSaveAndCancel" flag serves no purpose in the current code. # assert callable(notifier), "notifiers must be callable" if extra_args is not None: @@ -234,9 +233,7 @@ def onSelect(self, session): pass def onDeselect(self, session): - if not self.last_value == self.value: - self.changedFinal() - self.last_value = self.value + pass def hideHelp(self, session): pass @@ -414,7 +411,8 @@ def __init__(self, choices, default=None, graphic=True): default = self.choices.default() self._descr = None - self.default = self._value = self.last_value = default + self.default = self._value = default + self.last_value = self.tostring(default) self.graphic = graphic def setChoices(self, choices, default=None): @@ -452,7 +450,8 @@ def load(self): if sv is None: self.value = self.default else: - self.last_value = self.value = self.choices[self.choices.index(sv)] + self.value = self.choices[self.choices.index(sv)] + self.last_value = self.tostring(self.value) def setCurrentText(self, text): i = self.choices.index(self.value) @@ -528,7 +527,8 @@ def unsafeAssign(self, value): class ConfigBoolean(ConfigElement): def __init__(self, default=False, descriptions={False: _("False"), True: _("True")}, graphic=True): ConfigElement.__init__(self) - self.value = self.last_value = self.default = default + self.value = self.default = default + self.last_value = self.tostring(self.value) self.descriptions = descriptions self.graphic = graphic @@ -561,11 +561,6 @@ def getMulti(self, selected): return ('pixmap', switchPixmap["menu_on" if self.value else "menu_off"]) return ("text", self.descriptions[self.value]) - def onDeselect(self, session): - if self.last_value != self.value: - self.changedFinal() - self.last_value = self.value - # For HTML Interface - Is this still used? def getHTML(self, id): # DEBUG: Is this still used? @@ -599,7 +594,8 @@ def __init__(self, default, formatstring, increment=86400): ConfigElement.__init__(self) self.increment = increment self.formatstring = formatstring - self.value = self.last_value = self.default = int(default) + self.value = self.default = int(default) + self.last_value = self.tostring(self.value) def handleKey(self, key): if key == KEYA_LEFT: @@ -638,8 +634,9 @@ def __init__(self, seperator, limits, default, censor_char=""): self.limits = limits self.censor_char = censor_char - self.last_value = self.default = default + self.default = default self.value = copy_copy(default) + self.last_value = self.tostring(self.value) self.endNotifier = None def validate(self): @@ -781,11 +778,6 @@ def fromstring(self, value): ret = [int(x) for x in value.split(self.seperator)] return ret + [int(x[0]) for x in self.limits[len(ret):]] - def onDeselect(self, session): - if self.last_value != self._value: - self.changedFinal() - self.last_value = copy_copy(self._value) - ip_limits = [(0, 255), (0, 255), (0, 255), (0, 255)] class ConfigIP(ConfigSequence): @@ -887,7 +879,8 @@ def __init__(self, default="", visible_width=False): self.offset = 0 self.overwrite = 17 self.help_window = None - self.value = self.last_value = self.default = default + self.value = self.default = default + self.last_value = self.tostring(self.value) self.useableChars = '0123456789ABCDEF' def validateMarker(self): @@ -999,9 +992,6 @@ def onDeselect(self, session): if self.help_window: session.deleteDialog(self.help_window) self.help_window = None - if not self.last_value == self.value: - self.changedFinal() - self.last_value = self.value def getHTML(self, id): return '
\n' @@ -1193,7 +1183,8 @@ def __init__(self, default="", fixed_size=True, visible_width=False): self.offset = 0 self.overwrite = fixed_size self.help_window = None - self.value = self.last_value = self.default = default + self.value = self.default = default + self.last_value = self.tostring(self.value) def validateMarker(self): textlen = len(self.text) @@ -1369,9 +1360,6 @@ def onDeselect(self, session): if self.help_window: session.deleteDialog(self.help_window) self.help_window = None - if not self.last_value == self.value: - self.changedFinal() - self.last_value = self.value def hideHelp(self, session): if session is not None and self.help_window is not None: @@ -1506,9 +1494,6 @@ def onSelect(self, session): def onDeselect(self, session): self.marked_pos = 0 self.offset = 0 - if not self.last_value == self.value: - self.changedFinal() - self.last_value = self.value class ConfigSearchText(ConfigText): def __init__(self, default="", fixed_size=False, visible_width=False): @@ -1546,7 +1531,8 @@ def onSelect(self, session): class ConfigSlider(ConfigElement): def __init__(self, default=0, increment=1, limits=(0, 100)): ConfigElement.__init__(self) - self.value = self.last_value = self.default = default + self.value = self.default = default + self.last_value = self.tostring(self.value) self.min = limits[0] self.max = limits[1] self.increment = increment @@ -1609,8 +1595,9 @@ def __init__(self, choices, default=None): if default is None: default = [] default.sort() - self.last_value = self.default = default + self.default = default self.value = default[:] + self.last_value = self.tostring(self.value) self.pos = 0 def handleKey(self, key): @@ -1645,7 +1632,7 @@ def load(self): if not isinstance(self.value, list): self.value = list(self.value) self.value.sort() - self.last_value = self.value[:] + self.last_value = self.tostring(self.value[:]) def fromstring(self, val): return eval(val) @@ -1679,9 +1666,7 @@ def getMulti(self, selected): def onDeselect(self, session): # self.pos = 0 # Enable this to reset the position marker to the first element. - if self.last_value != self.value: - self.changedFinal() - self.last_value = self.value[:] + pass description = property(lambda self: descriptionList(self.choices.choices, choicesList.LIST_TYPE_LIST)) @@ -1736,7 +1721,6 @@ def load(self): x[1] = self.getMountpoint(x[0]) x[2] = True self.locations = locations - self.last_value = self.locations[:] def save(self): locations = self.locations @@ -1835,14 +1819,6 @@ def getMulti(self, selected): def onDeselect(self, session): self.pos = -1 - if self.last_value != self.locations: - # No "notifiers final" have ever been available in ConfigLocations class. - # This "if" clause is here for consistency only, or possible future use. - # Currently its sole function is to keep self.last_value up to date, should - # this variable ever be required by external code. - # Adding "self.changedFinal()" here would enable "notifiers final" in - # this class if this were ever necessary. - self.last_value = self.locations[:] # nothing. class ConfigNothing(ConfigSelection): diff --git a/lib/python/Plugins/Extensions/MediaPlayer/plugin.py b/lib/python/Plugins/Extensions/MediaPlayer/plugin.py index 5ada1d13c2..e49dab85aa 100644 --- a/lib/python/Plugins/Extensions/MediaPlayer/plugin.py +++ b/lib/python/Plugins/Extensions/MediaPlayer/plugin.py @@ -26,6 +26,7 @@ from Components.Harddisk import harddiskmanager from Components.config import config from Components.SystemInfo import SystemInfo +from Components.Sources.StaticText import StaticText from Tools.Directories import fileExists, pathExists, resolveFilename, SCOPE_CONFIG, SCOPE_PLAYLIST, SCOPE_CURRENT_SKIN from Tools.BoundFunction import boundFunction from settings import MediaPlayerSettings @@ -150,6 +151,7 @@ def __init__(self, session, args = None): self["genre"] = Label("") self["coverArt"] = MediaPixmap() self["repeat"] = MultiPixmap() + self["key_menu"] = StaticText(_("MENU")) self.seek_target = None diff --git a/lib/python/Plugins/Extensions/PicturePlayer/ui.py b/lib/python/Plugins/Extensions/PicturePlayer/ui.py index 0b2fb274f1..d488a9f5dd 100644 --- a/lib/python/Plugins/Extensions/PicturePlayer/ui.py +++ b/lib/python/Plugins/Extensions/PicturePlayer/ui.py @@ -61,6 +61,7 @@ def __init__(self, session): self["key_red"] = StaticText(_("Close")) self["key_green"] = StaticText(_("Thumbnails")) self["key_yellow"] = StaticText("") + self["key_menu"] = StaticText(_("MENU")) self["label"] = StaticText("") self["thn"] = Pixmap() diff --git a/lib/python/Plugins/SystemPlugins/OpentvZapper/opentv_zapper.py b/lib/python/Plugins/SystemPlugins/OpentvZapper/opentv_zapper.py index d3a7bf27eb..2a0efc8c51 100644 --- a/lib/python/Plugins/SystemPlugins/OpentvZapper/opentv_zapper.py +++ b/lib/python/Plugins/SystemPlugins/OpentvZapper/opentv_zapper.py @@ -21,7 +21,7 @@ debug_name = "opentv_zapper" lamedb_path = "/etc/enigma2" -download_interval = 6 * 60 * 60 # 6 hours +download_interval = config.plugins.opentvzapper.update_interval.value * 60 * 60 # 6 hours download_duration = 180 # stay tuned for 3 minutes start_first_download = 5 * 60 # 5 minutes after booting wait_time_on_fail = 15 * 60 # 15 minutes @@ -49,8 +49,7 @@ def play(self, service): return True def stop(self): - if self.previousService is not None: - self.navcore.playService(self.previousService) + self.navcore.playService(self.previousService) class RecordAdapter: diff --git a/lib/python/Plugins/SystemPlugins/OpentvZapper/plugin.py b/lib/python/Plugins/SystemPlugins/OpentvZapper/plugin.py index 9b3992e855..881adc7755 100644 --- a/lib/python/Plugins/SystemPlugins/OpentvZapper/plugin.py +++ b/lib/python/Plugins/SystemPlugins/OpentvZapper/plugin.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from Components.config import config, ConfigSelection, ConfigSubsection, ConfigYesNo +from Components.config import config, ConfigSelection, ConfigSubsection, ConfigYesNo, ConfigSelectionNumber from Components.NimManager import nimmanager from Plugins.Plugin import PluginDescriptor @@ -12,6 +12,7 @@ config.plugins.opentvzapper = ConfigSubsection() config.plugins.opentvzapper.enabled = ConfigYesNo(default = False) config.plugins.opentvzapper.providers = ConfigSelection(default="Astra 28.2", choices=list(providers.keys())) +config.plugins.opentvzapper.update_interval = ConfigSelectionNumber(min = 3, max = 24, stepwidth = 3, default = 6, wraparound = True) config.plugins.opentvzapper.extensions = ConfigYesNo(default = True) config.plugins.opentvzapper.notifications = ConfigYesNo(default = False) diff --git a/lib/python/Plugins/SystemPlugins/OpentvZapper/setup.xml b/lib/python/Plugins/SystemPlugins/OpentvZapper/setup.xml index 92a6ca7d3c..3abf9d6724 100644 --- a/lib/python/Plugins/SystemPlugins/OpentvZapper/setup.xml +++ b/lib/python/Plugins/SystemPlugins/OpentvZapper/setup.xml @@ -3,6 +3,7 @@ config.plugins.opentvzapper.enabled config.plugins.opentvzapper.providers + config.plugins.opentvzapper.update_interval config.plugins.opentvzapper.extensions config.plugins.opentvzapper.notifications diff --git a/lib/python/Plugins/SystemPlugins/PositionerSetup/plugin.py b/lib/python/Plugins/SystemPlugins/PositionerSetup/plugin.py index fb22ca2956..3ada1a196a 100644 --- a/lib/python/Plugins/SystemPlugins/PositionerSetup/plugin.py +++ b/lib/python/Plugins/SystemPlugins/PositionerSetup/plugin.py @@ -194,6 +194,9 @@ def __init__(self, session, feid): self.blue = Button("") self["key_blue"] = self.blue + self["key_menu"] = StaticText(_("MENU")) + self["key_info"] = StaticText(_("INFO")) + self.list = [] self["list"] = ConfigList(self.list) @@ -238,7 +241,7 @@ def __init__(self, session, feid): "green": self.greenKey, "yellow": self.yellowKey, "blue": self.blueKey, - "log": self.showLog, + "log": self.showLog, # KEY_INFO "mainMenu": self.furtherOptions, "1": self.keyNumberGlobal, "2": self.keyNumberGlobal, diff --git a/lib/python/Plugins/SystemPlugins/SoftwareManager/plugin.py b/lib/python/Plugins/SystemPlugins/SoftwareManager/plugin.py index 9a93e83551..65f7fe3c67 100644 --- a/lib/python/Plugins/SystemPlugins/SoftwareManager/plugin.py +++ b/lib/python/Plugins/SystemPlugins/SoftwareManager/plugin.py @@ -169,6 +169,7 @@ def __init__(self, session, args = 0): self["menu"] = List(self.list) self["key_red"] = StaticText(_("Close")) + self["key_menu"] = StaticText(_("MENU")) self["status"] = StaticText(self.menutext) self["shortcuts"] = NumberActionMap(["ShortcutActions", "WizardActions", "InfobarEPGActions", "MenuActions", "NumberActions"], diff --git a/lib/python/Screens/ChannelSelection.py b/lib/python/Screens/ChannelSelection.py index 87cff21223..4a8ddba913 100644 --- a/lib/python/Screens/ChannelSelection.py +++ b/lib/python/Screens/ChannelSelection.py @@ -12,6 +12,7 @@ from Components.MenuList import MenuList from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase from Components.Sources.List import List +from Components.Sources.StaticText import StaticText from Components.SystemInfo import SystemInfo from Components.Renderer.Picon import getPiconName from Screens.TimerEntry import addTimerFromEventSilent @@ -1332,6 +1333,9 @@ def __init__(self, session): self["key_green"] = Button(_("Satellites")) self["key_yellow"] = Button(_("Providers")) self["key_blue"] = Button(_("Favourites")) + + self["key_menu"] = StaticText(_("MENU")) + self["key_info"] = StaticText(_("INFO")) self["list"] = ServiceList(self) self.servicelist = self["list"] diff --git a/lib/python/Screens/ChoiceBox.py b/lib/python/Screens/ChoiceBox.py index 9fd37244da..ba449de7bb 100644 --- a/lib/python/Screens/ChoiceBox.py +++ b/lib/python/Screens/ChoiceBox.py @@ -212,6 +212,9 @@ def goEntry(self, entry): entry[2](arg) elif entry and len(entry) > 2 and isinstance(entry[1], str) and entry[1] == "CALLFUNC": entry[2](None) + elif entry and len(entry) > 1 and callable(entry[1]): + entry[1](*entry[2:]) + self.close() else: self.close(entry) diff --git a/lib/python/Screens/Ci.py b/lib/python/Screens/Ci.py index 7a807fbce3..28e29c079e 100644 --- a/lib/python/Screens/Ci.py +++ b/lib/python/Screens/Ci.py @@ -62,6 +62,7 @@ def __init__(self, session, slotid, action, handler=eDVBCI_UI.getInstance(), wai self["title"] = Label("") self["subtitle"] = Label("") self["bottom"] = Label("") + self["key_menu"] = StaticText(_("MENU")) self["entries"] = ConfigList([ ]) self["actions"] = NumberActionMap(["SetupActions", "MenuActions"], diff --git a/lib/python/Screens/EpgSelectionBase.py b/lib/python/Screens/EpgSelectionBase.py index b746ecb51b..fa2bafb48d 100644 --- a/lib/python/Screens/EpgSelectionBase.py +++ b/lib/python/Screens/EpgSelectionBase.py @@ -9,6 +9,7 @@ from Components.Label import Label from Components.Sources.Event import Event from Components.Sources.ServiceEvent import ServiceEvent +from Components.Sources.StaticText import StaticText from Components.config import ConfigClock, ConfigDateTime, config from Screens.ChoiceBox import PopupChoiceBox from Screens.EventView import EventViewEPGSelect @@ -106,6 +107,9 @@ def __init__(self, session, epgConfig, startBouquet=None, startRef=None, bouquet self["key_green"] = Button(_("Add Timer")) self["key_yellow"] = Button(_("EPG Search")) self["key_blue"] = Button(_("Add AutoTimer")) + + self["key_menu"] = StaticText(_("MENU")) + self["key_info"] = StaticText(_("INFO")) helpDescription = _("EPG Commands") diff --git a/lib/python/Screens/EventView.py b/lib/python/Screens/EventView.py index e9c96054d5..c1417a18e7 100644 --- a/lib/python/Screens/EventView.py +++ b/lib/python/Screens/EventView.py @@ -58,6 +58,8 @@ def __init__(self, event, ref, callback=None, similarEPGCB=None): self["datetime"] = Label() self["channel"] = Label() self["duration"] = Label() + if [p for p in plugins.getPlugins(PluginDescriptor.WHERE_EVENTINFO) if 'servicelist' not in p.__call__.func_code.co_varnames]: + self["key_menu"] = StaticText(_("MENU")) if similarEPGCB is not None: self["key_red"] = Button("") self.SimilarBroadcastTimer = eTimer() diff --git a/lib/python/Screens/FactoryReset.py b/lib/python/Screens/FactoryReset.py index d49250017d..ad162f3900 100644 --- a/lib/python/Screens/FactoryReset.py +++ b/lib/python/Screens/FactoryReset.py @@ -7,6 +7,7 @@ from Components.config import ConfigYesNo, config from Components.Sources.StaticText import StaticText +from Screens.MessageBox import MessageBox from Screens.ParentalControlSetup import ProtectedScreen from Screens.Setup import Setup from Tools.Directories import SCOPE_CONFIG, SCOPE_SKIN, resolveFilename @@ -106,6 +107,12 @@ def analyseEnigma2(self): self.others.append(file) def keySave(self): + restartBox = self.session.openWithCallback(self.keySaveCallback, MessageBox, _("This will permanently delete the current configuration. It would be a good idea to make a backup before taking this drastic action. Are you certain you want to continue with a factory reset?"), default=False) + restartBox.setTitle(_("Factory Reset: Clearing data")) + + def keySaveCallback(self, answer): + if not answer: + return configDir = resolveFilename(SCOPE_CONFIG) if self.resetFull.value: print("[FactoryReset] Performing a full factory reset.") diff --git a/lib/python/Screens/InfoBarGenerics.py b/lib/python/Screens/InfoBarGenerics.py index eb2d20c988..aafa1c6b3c 100644 --- a/lib/python/Screens/InfoBarGenerics.py +++ b/lib/python/Screens/InfoBarGenerics.py @@ -42,7 +42,7 @@ from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive from Screens.TimeDateInput import TimeDateInput from Screens.TimerEdit import TimerEditList -from Screens.TimerEntry import TimerEntry as addTimerFromEvent +from Screens.TimerEntry import TimerEntry, addTimerFromEvent from Screens.UnhandledKey import UnhandledKey from ServiceReference import ServiceReference, isPlayableForCur @@ -335,6 +335,8 @@ class SecondInfoBar(Screen, HelpableScreen): def __init__(self, session): Screen.__init__(self, session) + if config.usage.second_infobar_simple.value: + self.skinName = ["SecondInfoBarSimple", "SecondInfoBar"] HelpableScreen.__init__(self) self["epg_description"] = ScrollLabel() self["channel"] = Label() @@ -1501,7 +1503,6 @@ def getDefaultEPGtype(self): if not hasattr(config.usage, "defaultEPGType"): # first run config.usage.defaultEPGType = ConfigSelection(default=default, choices=choices) config.usage.defaultEPGType.addNotifier(self.defaultEPGtypeNotifier, initial_call=False, immediate_feedback=False) - config.usage.defaultEPGType.callNotifiersOnSaveAndCancel = True for plugin in pluginlist: if plugin[0] == self.plugintexts.get(config.usage.defaultEPGType.value, config.usage.defaultEPGType.value): return plugin[1] @@ -1514,7 +1515,6 @@ def getDefaultINFOtype(self): if not hasattr(config.usage, "defaultINFOType"): # first run config.usage.defaultINFOType = ConfigSelection(default=default, choices=choices) config.usage.defaultINFOType.addNotifier(self.defaultINFOtypeNotifier, initial_call=False, immediate_feedback=False) - config.usage.defaultINFOType.callNotifiersOnSaveAndCancel = True for plugin in pluginlist: if plugin[0] == self.plugintexts.get(config.usage.defaultINFOType.value, config.usage.defaultINFOType.value): return plugin[1] diff --git a/lib/python/Screens/InstallWizard.py b/lib/python/Screens/InstallWizard.py index eb7a3f87d2..12653b390d 100644 --- a/lib/python/Screens/InstallWizard.py +++ b/lib/python/Screens/InstallWizard.py @@ -112,7 +112,7 @@ def run(self): # self.session.open(PluginDownloadBrowser, 0, True, "PluginDownloadBrowserWizard") elif self.index == self.INSTALL_SKINS and self.enabled.value: from SkinSelector import SkinSelector - self.session.open(SkinSelector, "SkinSelectorWizard") + self.session.open(SkinSelector, skin_name="SkinSelectorWizard", reboot=False) return class InstallWizardIpkgUpdater(Screen): diff --git a/lib/python/Screens/LocationBox.py b/lib/python/Screens/LocationBox.py index 710a93282b..669ea0d005 100644 --- a/lib/python/Screens/LocationBox.py +++ b/lib/python/Screens/LocationBox.py @@ -23,6 +23,7 @@ from Components.Label import Label from Components.Pixmap import Pixmap from Components.Button import Button +from Components.Sources.StaticText import StaticText from Components.FileList import FileList from Components.MenuList import MenuList @@ -79,6 +80,7 @@ def __init__(self, session, text="", filename="", currDir=None, bookmarks=None, self["key_yellow"] = Button(_("Rename")) self["key_blue"] = Button(_("Remove bookmark")) self["key_red"] = Button(_("Cancel")) + self["key_menu"] = StaticText(_("MENU")) # Background for Buttons self["green"] = Pixmap() diff --git a/lib/python/Screens/MessageBox.py b/lib/python/Screens/MessageBox.py index cf87c8dc22..07f33b7b99 100644 --- a/lib/python/Screens/MessageBox.py +++ b/lib/python/Screens/MessageBox.py @@ -77,13 +77,11 @@ def __init__(self, session, text, type=TYPE_YESNO, timeout=0, close_on_any_key=F self["icon"].show() self.skinName = ["MessageBox"] if simple: - self.skinName = ["MessageBoxSimple"] + self.skinName = ["MessageBoxSimple"] + self.skinName if wizard: self["rc"] = MultiPixmap() self["rc"].setPixmapNum(config.misc.rcused.value) self.skinName = ["MessageBoxWizard"] - if not skin_name: - skin_name = [] if isinstance(skin_name, str): self.skinName = [skin_name] + self.skinName if not list: diff --git a/lib/python/Screens/MovieSelection.py b/lib/python/Screens/MovieSelection.py index 8111d92b49..5265091bc4 100644 --- a/lib/python/Screens/MovieSelection.py +++ b/lib/python/Screens/MovieSelection.py @@ -3,7 +3,7 @@ from Components.ActionMap import HelpableActionMap, ActionMap, HelpableNumberActionMap from Components.ChoiceList import ChoiceList, ChoiceEntryComponent from Components.MenuList import MenuList -from Components.MovieList import MovieList, resetMoviePlayState, AUDIO_EXTENSIONS, DVD_EXTENSIONS, IMAGE_EXTENSIONS, moviePlayState +from Components.MovieList import MovieList, getItemDisplayName, resetMoviePlayState, AUDIO_EXTENSIONS, DVD_EXTENSIONS, IMAGE_EXTENSIONS, moviePlayState from Components.DiskInfo import DiskInfo from Tools.Trashcan import TrashInfo from Components.Pixmap import Pixmap, MultiPixmap @@ -114,7 +114,7 @@ def isTrashFolder(ref): return os.path.realpath(ref.getPath()).endswith('.Trash') or os.path.realpath(ref.getPath()).endswith('.Trash/') def isInTrashFolder(ref): - if not config.usage.movielist_trashcan.value or not ref.flags & eServiceReference.mustDescent: + if not config.usage.movielist_trashcan.value: return False path = os.path.realpath(ref.getPath()) return path.startswith(Tools.Trashcan.getTrashFolder(path)) @@ -134,13 +134,19 @@ def isFolder(item): return (item[0].flags & eServiceReference.mustDescent) != 0 def canMove(item): + if not item: + return False + if not item[0] or not item[1] or isTrashFolder(item[0]): + return False + return True + +def canDelete(item): if not item: return False if not item[0] or not item[1]: return False return True -canDelete = canMove canCopy = canMove canRename = canMove @@ -200,14 +206,77 @@ def copyServiceFiles(serviceref, dest, name=None): # rethrow exception raise -# Appends possible destinations to the bookmarks object. Appends tuples -# in the form (description, path) to it. -def buildMovieLocationList(bookmarks): +# Changes the title contained in a media file's .meta if it exists, otherwise, renames +# the media file and it's associated data files. Also renames a directory. +def renameServiceFiles(serviceref, newName): + oldPath = serviceref.getPath().rstrip("/") + oldDir, oldBaseName = os.path.split(oldPath) + oldName, oldExt = os.path.splitext(oldPath) + newPath = os.path.join(oldDir, newName + oldExt) + # rename will overwrite existing files, check first + if os.path.exists(newPath): + return False + # rename the directory/media file. If successful we'll rename any associated files + print("[MovieSelection] Rename %s to %s" % (oldPath, newPath)) + os.rename(oldPath, newPath) + if os.path.isdir(newPath): + serviceref.setPath(newPath) + return serviceref + # Now rename any data files associated with the media file. If there are any same + # named orphaned file types, this will either either overwrite them using rename, or remove them + cleanupList = [".eit", oldExt+".cuts"] + for ext in cleanupList[:]: + oldPath = os.path.join(oldDir, oldName + ext) + if os.path.exists(oldPath): + newPath = os.path.join(oldDir, newName + ext) + os.rename(oldPath, newPath) + cleanupList.remove(ext) + try: + for item in cleanupList + [oldExt+".ap", oldExt+".meta", oldExt+".sc"]: + newPath = os.path.join(oldDir, newName + ext) + if os.path.exists(newPath): + os.remove(newPath) + except Exception as ex: + # cleanup failures aren't so terrible; just log and carry on + print("[MovieSelection] Error removing orphaned data files. %s" % ex) + return None + +# Appends possible destinations to the bookmarks dictionary +def buildMovieLocationList(includeOther=False, path=None, includeSubdirs=False, includeParentDir=False): inlist = [] + bookmarks = [] + if includeOther: + bookmarks.append(("(" + _("Other") + "...)", None)) + if path: + base = os.path.split(path)[0] + if includeParentDir and base != config.movielist.root.value: + d = os.path.split(base)[0] + if os.path.isdir(d) and d != config.movielist.root.value and (d not in inlist): + bookmarks.append((d, d)) + inlist.append(d) + if includeSubdirs: + try: + base = os.path.split(path)[0] + for fn in os.listdir(base): + if not fn.startswith('.'): # Skip hidden things + d = os.path.join(base, fn) + if os.path.isdir(d) and (d not in inlist): + bookmarks.append((fn, d)) + inlist.append(d) + except Exception as e: + print("[MovieSelection] %s" % e) + # Last favourites + for d in last_selected_dest: + if d not in inlist: + bookmarks.append((d,d)) + inlist.append(d) + # Other favourites for d in config.movielist.videodirs.value: d = os.path.normpath(d) - bookmarks.append((d,d)) - inlist.append(d) + if d not in inlist: + bookmarks.append((d,d)) + inlist.append(d) + # Mount points for p in Components.Harddisk.harddiskmanager.getMountedPartitions(): d = os.path.normpath(p.mountpoint) if d in inlist: @@ -218,10 +287,21 @@ def buildMovieLocationList(bookmarks): pass # When already listed as some "friendly" name else: bookmarks.append((p.tabbedDescription(), d)) - inlist.append(d) - for d in last_selected_dest: - if d not in inlist: - bookmarks.append((d,d)) + inlist.append(d) + return bookmarks + +def countFiles(directory): + directories = files = 0 + for filename in os.listdir(directory): + if filename not in (".", "..", ".e2settings.pkl"): + filepath = os.path.join(directory, filename) + if os.path.isdir(filepath): + directories += 1 + else: + filenameOnly, extension = os.path.splitext(filename) + if extension not in (".eit", ".ap", ".cuts", ".meta", ".sc"): + files += 1 + return directories, files class MovieBrowserConfiguration(Setup): def __init__(self, session, args = 0): @@ -286,17 +366,12 @@ def selectionChanged(self): class MovieContextMenu(Screen, ProtectedScreen): # Contract: On OK returns a callable object (e.g. delete) - def __init__(self, session, csel, service): + def __init__(self, session, csel, currentSelection): Screen.__init__(self, session) self.skinName = "Setup" self.setup_title = _("Movie List Setup") Screen.setTitle(self, _(self.setup_title)) - # No ConfigText fields in MovieBrowserConfiguration so these are not currently used. - #self["HelpWindow"] = Pixmap() - #self["HelpWindow"].hide() - #self["VKeyIcon"] = Boolean(False) - self['footnote'] = Label("") self["description"] = StaticText() @@ -309,19 +384,19 @@ def __init__(self, session, csel, service): "red": self.cancelClick, "ok": self.okbuttonClick, "cancel": self.cancelClick, - "green": self.do_showDeviceMounts, - "yellow": self.do_showNetworkMounts, - "blue": self.do_selectSortby, - "menu": self.do_configure, - "1": self.do_addbookmark, - "2": self.do_createdir, - "3": self.do_delete, - "4": self.do_move, - "5": self.do_copy, - "6": self.do_rename, - "7": self.do_reset, - "8": self.do_decode, - "9": self.do_unhideParentalServices + "green": boundFunction(self.close, csel.showDeviceMounts), + "yellow": boundFunction(self.close, csel.showNetworkMounts), + "blue": boundFunction(self.close, csel.selectSortby), + "menu": boundFunction(self.close, csel.configure), + "1": boundFunction(self.close, csel.do_addbookmark), + "2": boundFunction(self.close, csel.do_createdir), + "3": boundFunction(self.close, csel.do_delete), + "4": boundFunction(self.close, csel.do_move), + "5": boundFunction(self.close, csel.do_copy), + "6": boundFunction(self.close, csel.do_rename), + "7": boundFunction(self.close, csel.do_reset), + "8": boundFunction(self.close, csel.do_decode), + "9": boundFunction(self.close, csel.unhideParentalServices) }) self["key_red"] = StaticText(_("Cancel")) @@ -340,29 +415,31 @@ def append_to_menu(menu, args, key=""): append_to_menu(menu, (_("Add bookmark"), csel.do_addbookmark), key="1") append_to_menu(menu, (_("Create directory"), csel.do_createdir), key="2") - if service: - if (service.flags & eServiceReference.mustDescent) and isTrashFolder(service): - append_to_menu(menu, (_("Permanently remove all deleted items"), csel.purgeAll), key="3") - else: + if currentSelection: + service = currentSelection[0] + if isTrashFolder(service): + append_to_menu(menu, (_("Empty trash can"), csel.purgeAll), key="3") + elif csel.can_delete(currentSelection): append_to_menu(menu, (_("Delete"), csel.do_delete), key="3") + if csel.can_move(currentSelection): append_to_menu(menu, (_("Move"), csel.do_move), key="4") append_to_menu(menu, (_("Copy"), csel.do_copy), key="5") + if csel.can_rename(currentSelection): append_to_menu(menu, (_("Rename"), csel.do_rename), key="6") - if not (service.flags & eServiceReference.mustDescent): - if self.isResetable(): - append_to_menu(menu, (_("Reset playback position"), csel.do_reset), key="7") - if service.getPath().endswith('.ts'): - append_to_menu(menu, (_("Start offline decode"), csel.do_decode), key="8") - elif BlurayPlayer is None and csel.isBlurayFolderAndFile(service): - append_to_menu(menu, (_("Auto play blu-ray file"), csel.playBlurayFile)) - if config.ParentalControl.hideBlacklist.value and config.ParentalControl.storeservicepin.value != "never": - from Components.ParentalControl import parentalControl - if not parentalControl.sessionPinCached: - append_to_menu(menu, (_("Unhide parental control services"), csel.unhideParentalServices), key="9") - # Plugins expect a valid selection, so only include them if we selected a non-dir - if not(service.flags & eServiceReference.mustDescent): - for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST): - append_to_menu( menu, (p.description, boundFunction(p, session, service)), key="bullet") + if csel.can_reset(currentSelection): + append_to_menu(menu, (_("Reset playback position"), csel.do_reset), key="7") + if csel.can_decode(currentSelection): + append_to_menu(menu, (_("Start offline decode"), csel.do_decode), key="8") + if (service.flags & eServiceReference.mustDescent) and BlurayPlayer is None and csel.isBlurayFolderAndFile(service): + append_to_menu(menu, (_("Auto play blu-ray file"), csel.playBlurayFile)) + if config.ParentalControl.hideBlacklist.value and config.ParentalControl.storeservicepin.value != "never": + from Components.ParentalControl import parentalControl + if not parentalControl.sessionPinCached: + append_to_menu(menu, (_("Unhide parental control services"), csel.unhideParentalServices), key="9") + # Plugins expect a valid selection, so only include them if we selected a non-dir + if not(service.flags & eServiceReference.mustDescent): + for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST): + append_to_menu( menu, (p.description, boundFunction(p, session, service)), key="bullet") self["config"] = ChoiceList(menu) @@ -370,10 +447,6 @@ def append_to_menu(menu, args, key=""): def isProtected(self): return self.csel.protectContextMenu and config.ParentalControl.setuppinactive.value and config.ParentalControl.config_sections.context_menus.value - def isResetable(self): - item = self.csel.getCurrentSelection() - return not(item[1] and moviePlayState(item[0].getPath() + ".cuts", item[0], item[1].getLength(item[0])) is None) - def pinEntered(self, answer): if answer: self.csel.protectContextMenu = False @@ -385,32 +458,6 @@ def createSummary(self): def okbuttonClick(self): self.close(self["config"].getCurrent()[0][1]) - def do_rename(self): - self.close(self.csel.do_rename()) - def do_copy(self): - self.close(self.csel.do_copy()) - def do_move(self): - self.close(self.csel.do_move()) - def do_createdir(self): - self.close(self.csel.do_createdir()) - def do_delete(self): - self.close(self.csel.do_delete()) - def do_unhideParentalServices(self): - self.close(self.csel.unhideParentalServices()) - def do_configure(self): - self.close(self.csel.configure()) - def do_showDeviceMounts(self): - self.close(self.csel.showDeviceMounts()) - def do_showNetworkMounts(self): - self.close(self.csel.showNetworkMounts()) - def do_addbookmark(self): - self.close(self.csel.do_addbookmark()) - def do_selectSortby(self): - self.close(self.csel.selectSortby()) - def do_decode(self): - self.close(self.csel.do_decode()) - def do_reset(self): - self.close(self.csel.do_reset()) def cancelClick(self): self.close(None) @@ -462,6 +509,12 @@ def selectionChanged(self): if item[0].flags & eServiceReference.mustDescent: if len(name) > 12: name = os.path.split(os.path.normpath(name))[1] + if name == ".Trash": + name = _("Trash") + else: + path, dir = os.path.split(os.path.normpath(name)) + if dir == ".Trash": + name = os.path.join(path, _("Trash") + "/") name = "> " + name self["name"].text = name else: @@ -549,6 +602,9 @@ def __init__(self, session, selectedmovie = None, timeshiftEnabled = False): self["key_yellow"] = Button("") self["key_blue"] = Button("") self._updateButtonTexts() + + self["key_menu"] = StaticText(_("MENU")) + self["key_info"] = StaticText(_("INFO")) self["movie_off"] = MultiPixmap() self["movie_off"].hide() @@ -571,7 +627,6 @@ def __init__(self, session, selectedmovie = None, timeshiftEnabled = False): self["NumberActions"] = HelpableNumberActionMap(self, ["NumberActions", "InputAsciiActions"], { "gotAsciiCode": self.keyAsciiCode, - "0": (self.keyNumberGlobal, numberActionHelp), "1": (self.keyNumberGlobal, numberActionHelp), "2": (self.keyNumberGlobal, numberActionHelp), "3": (self.keyNumberGlobal, numberActionHelp), @@ -595,6 +650,8 @@ def __init__(self, session, selectedmovie = None, timeshiftEnabled = False): { "contextMenu": (self.doContext, _("Menu")), "showEventInfo": (self.showEventInformation, _("Show event details")), + "toggleMark": (self.toggleMark, _("Toggle a selection mark")), + "clearMarks": (self.clearMarks, _("Remove all selection marks")) }, description=_("Settings, information and more functions")) self["ColorActions"] = HelpableActionMap(self, "ColorActions", @@ -709,30 +766,32 @@ def initUserDefinedActions(self): 'sortdefault': _("Sort by default"), 'preview': _("Preview"), 'movieoff': _("On end of movie"), - 'movieoff_menu': _("On end of movie (as menu)") + 'movieoff_menu': _("On end of movie (as menu)"), + 'mark': _("Toggle mark"), + 'clearmarks': _("Clear marks") } for p in plugins.getPlugins(PluginDescriptor.WHERE_MOVIELIST): - userDefinedActions['@' + p.name] = p.description - locations = [] - buildMovieLocationList(locations) + userDefinedActions['@' + p.name] = p.description.capitalize() prefix = _("Goto") + ": " - for d,p in locations: + for d,p in buildMovieLocationList(): if p and p.startswith('/'): userDefinedActions[p] = prefix + d - config.movielist.btn_red = ConfigSelection(default='delete', choices=userDefinedActions) - config.movielist.btn_green = ConfigSelection(default='move', choices=userDefinedActions) - config.movielist.btn_yellow = ConfigSelection(default='bookmarks', choices=userDefinedActions) - config.movielist.btn_blue = ConfigSelection(default='sortby', choices=userDefinedActions) - config.movielist.btn_redlong = ConfigSelection(default='rename', choices=userDefinedActions) - config.movielist.btn_greenlong = ConfigSelection(default='copy', choices=userDefinedActions) - config.movielist.btn_yellowlong = ConfigSelection(default='tags', choices=userDefinedActions) - config.movielist.btn_bluelong = ConfigSelection(default='sortdefault', choices=userDefinedActions) - config.movielist.btn_radio = ConfigSelection(default='tags', choices=userDefinedActions) - config.movielist.btn_tv = ConfigSelection(default='gohome', choices=userDefinedActions) - config.movielist.btn_text = ConfigSelection(default='movieoff', choices=userDefinedActions) - config.movielist.btn_F1 = ConfigSelection(default='movieoff_menu', choices=userDefinedActions) - config.movielist.btn_F2 = ConfigSelection(default='preview', choices=userDefinedActions) - config.movielist.btn_F3 = ConfigSelection(default='/media', choices=userDefinedActions) + choices = [(k, v) for k, v in userDefinedActions.items()] + choices.sort(key=lambda t: t[1]) + config.movielist.btn_red = ConfigSelection(default='delete', choices=choices) + config.movielist.btn_green = ConfigSelection(default='move', choices=choices) + config.movielist.btn_yellow = ConfigSelection(default='bookmarks', choices=choices) + config.movielist.btn_blue = ConfigSelection(default='sortby', choices=choices) + config.movielist.btn_redlong = ConfigSelection(default='rename', choices=choices) + config.movielist.btn_greenlong = ConfigSelection(default='copy', choices=choices) + config.movielist.btn_yellowlong = ConfigSelection(default='tags', choices=choices) + config.movielist.btn_bluelong = ConfigSelection(default='sortdefault', choices=choices) + config.movielist.btn_radio = ConfigSelection(default='tags', choices=choices) + config.movielist.btn_tv = ConfigSelection(default='gohome', choices=choices) + config.movielist.btn_text = ConfigSelection(default='movieoff', choices=choices) + config.movielist.btn_F1 = ConfigSelection(default='movieoff_menu', choices=choices) + config.movielist.btn_F2 = ConfigSelection(default='preview', choices=choices) + config.movielist.btn_F3 = ConfigSelection(default='/media', choices=choices) userDefinedButtons ={ 'red': config.movielist.btn_red, 'green': config.movielist.btn_green, @@ -772,45 +831,21 @@ def _callButton(self, name): a() def btn_red(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if not InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_red.value) + self._callButton(config.movielist.btn_red.value) def btn_green(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if not InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_green.value) + self._callButton(config.movielist.btn_green.value) def btn_yellow(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if not InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_yellow.value) + self._callButton(config.movielist.btn_yellow.value) def btn_blue(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if not InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_blue.value) + self._callButton(config.movielist.btn_blue.value) def btn_redlong(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_redlong.value) + self._callButton(config.movielist.btn_redlong.value) def btn_greenlong(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_greenlong.value) + self._callButton(config.movielist.btn_greenlong.value) def btn_yellowlong(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_yellowlong.value) + self._callButton(config.movielist.btn_yellowlong.value) def btn_bluelong(self): - from InfoBar import InfoBar - InfoBarInstance = InfoBar.instance - if InfoBarInstance.LongButtonPressed: - self._callButton(config.movielist.btn_bluelong.value) + self._callButton(config.movielist.btn_bluelong.value) def btn_radio(self): self._callButton(config.movielist.btn_radio.value) def btn_tv(self): @@ -951,14 +986,14 @@ def unPauseService(self): self.reloadList() def can_move(self, item): - if not item: - return False + if self.list.countMarked() > 0: + return True return canMove(item) def can_delete(self, item): - if not item: - return False - return canDelete(item) or isTrashFolder(item[0]) + if self.list.countMarked() > 0: + return True + return canDelete(item) def can_default(self, item): # returns whether item is a regular file @@ -1045,6 +1080,13 @@ def getCurrentSelection(self): # Returns None or (serviceref, info, begin, len) return self["list"].l.getCurrentSelection() + def getMarkedOrCurrentSelection(self): + items = self.list.getMarked() + if not items: + item = self.getCurrentSelection() + items = [item] if item else [] + return items + def playAsBLURAY(self, path): try: from Plugins.Extensions.BlurayPlayer import BlurayUi @@ -1223,7 +1265,69 @@ def playbackStop(self): self.LivePlayTimer.start(100) self.filePlayingTimer.start(100) - def itemSelected(self, answer = True): + def toggleMark(self): + self.list.toggleMark() + if self.list.getCurrentIndex() < len(self.list)-1: + self.list.moveDown() + self.hideActionFeedback() + + def clearMarks(self): + self.list.clearMarks() + self.hideActionFeedback() + + def itemSelected(self): + markedFiles = self.list.getMarked(excludeDirs=True) + markedFilesCount = len(markedFiles) + currentSelection = self.getCurrentSelection() + + # Rules: + # - if a directory (include parent .. and trash can) is selected, marks are completely ignored and the directory is opened + # - if a recording is selected, it's played using the single selection code + # - if there are marked recordings and another unmarked recording is selected, ask what to do + if (currentSelection[0].flags and eServiceReference.isDirectory) or markedFilesCount == 0 or (markedFilesCount == 1 and markedFiles[0] == currentSelection): + self.__playCurrentItem() + return + + if currentSelection in markedFiles: + self.__addItemsToPlaylist(markedFiles) + else: + title = ngettext("You have a marked recording", "You have marked recordings", markedFilesCount) + choices = [ + (ngettext("Play the marked recording", "Play %d marked recordings" % markedFilesCount, markedFilesCount), self.__addItemsToPlaylist, markedFiles), + (_("Play the selected recording"), self.__playCurrentItem)] + self.session.open(ChoiceBox, title=title, list=choices) + + def __addItemsToPlaylist(self, markedItems): + global playlist + items = playlist + del items[:] + audio = config.movielist.play_audio_internal.value + for item in markedItems: + itemRef = item[0] + path = itemRef.getPath() + if not itemRef.flags & eServiceReference.mustDescent: + ext = os.path.splitext(path)[1].lower() + if ext in IMAGE_EXTENSIONS: + continue + else: + items.append(itemRef) + if audio and ext not in AUDIO_EXTENSIONS: + audio = False + if items: + Screens.InfoBar.InfoBar.instance.checkTimeshiftRunning(boundFunction(self.__addItemsToPlaylistTimeshiftCallback, audio, items)) + + def __addItemsToPlaylistTimeshiftCallback(self, audio, items, answer): + if answer: + global playingList + playingList = True + if audio: + self.list.moveTo(items[0]) + self.preview() + else: + self.saveconfig() + self.close(items[0]) + + def __playCurrentItem(self): current = self.getCurrent() if current is not None: path = current.getPath() @@ -1246,7 +1350,7 @@ def itemSelected(self, answer = True): # Stop preview, come back later self.session.nav.stopService() self.list.playInBackground = None - self.callLater(self.itemSelected) + self.callLater(self.__playCurrentItem) return if ext in IMAGE_EXTENSIONS: try: @@ -1294,9 +1398,9 @@ def movieSelected(self): self.close(current) def doContext(self): - current = self.getCurrent() - if current is not None: - self.session.openWithCallback(self.doneContext, MovieContextMenu, self, current) + currentSelection = self.getCurrentSelection() + if currentSelection is not None: + self.session.openWithCallback(self.doneContext, MovieContextMenu, self, currentSelection) def doneContext(self, action): if action is not None: @@ -1552,7 +1656,7 @@ def reloadWithDelay(self): if self.reload_sel is None: self.reload_sel = self.getCurrent() if config.usage.movielist_trashcan.value and os.access(config.movielist.last_videodir.value, os.W_OK): - trash = Tools.Trashcan.createTrashFolder(config.movielist.last_videodir.value) + Tools.Trashcan.createTrashFolder(config.movielist.last_videodir.value) self.loadLocalSettings() self["list"].reload(self.current_ref, self.selected_tags) self.updateTags() @@ -1690,33 +1794,28 @@ def showTagWarning(self): mbox.setTitle(self.getTitle()) def selectMovieLocation(self, title, callback): - bookmarks = [("("+_("Other")+"...)", None)] - buildMovieLocationList(bookmarks) - self.onMovieSelected = callback - self.movieSelectTitle = title - self.session.openWithCallback(self.gotMovieLocation, ChoiceBox, title=title, list = bookmarks) + bookmarks = buildMovieLocationList(includeOther=True) + self.session.openWithCallback(lambda choice: self.gotMovieLocation(title, callback, choice), ChoiceBox, title=title, list=bookmarks) - def gotMovieLocation(self, choice): + def gotMovieLocation(self, title, callback, choice): if not choice: # cancelled - self.onMovieSelected(None) - del self.onMovieSelected + callback(None) return if isinstance(choice, tuple): if choice[1] is None: # Display full browser, which returns string self.session.openWithCallback( - self.gotMovieLocation, + lambda choice: self.gotMovieLocation(title, callback, choice), MovieLocationBox, - self.movieSelectTitle, + title, config.movielist.last_videodir.value ) return choice = choice[1] choice = os.path.normpath(choice) self.rememberMovieLocation(choice) - self.onMovieSelected(choice) - del self.onMovieSelected + callback(choice) def rememberMovieLocation(self, where): if where in last_selected_dest: @@ -1755,19 +1854,35 @@ def isBlurayFolderAndFile(self, service): return True return False + def can_mark(self): + return True + + def do_mark(self): + self.toggleMark() + + def can_clearmarks(self): + return True + + def do_clearmarks(self): + self.clearMarks() + def can_bookmarks(self, item): return True def do_bookmarks(self): self.selectMovieLocation(title=_("Please select the movie path..."), callback=self.gotFilename) def can_addbookmark(self, item): - return True + self.list.countMarked() == 0 + def exist_bookmark(self): path = config.movielist.last_videodir.value if path in config.movielist.videodirs.value: return True return False + def do_addbookmark(self): + if self.list.countMarked(): + return path = config.movielist.last_videodir.value if path in config.movielist.videodirs.value: if len(path) > 40: @@ -1788,15 +1903,15 @@ def removeBookmark(self, yes): def can_createdir(self, item): return True + def do_createdir(self): dirname = "" - item = self.getCurrentSelection() - if item is not None and item[0] and item[1] and not isFolder(item): - info = item[1] - dirname = info.getName(item[0]) - full_name = os.path.split(item[0].getPath())[1] - if full_name == dirname: # split extensions for files without metafile - dirname, self.extension = os.path.splitext(dirname) + # use most recently marked item or the selection as a template + # for the new directory name + items = self.getMarkedOrCurrentSelection() + item = items[-1] if len(items) > 0 else None + if item is not None and item[0] and item[1]: + dirname = getItemDisplayName(item[0], item[1], removeExtension=True) self.session.openWithCallback(self.createDirCallback, VirtualKeyBoard, title = _("Please enter the name of the new directory"), text = dirname) @@ -1823,29 +1938,95 @@ def createDirCallback(self, name): mbox=self.session.open(MessageBox, msg, type = MessageBox.TYPE_ERROR, timeout = 5) mbox.setTitle(self.getTitle()) + def can_rename(self, item): + return self.can_move(item) + def do_rename(self): - item = self.getCurrentSelection() - if not canRename(item): + renameList = [] + itemCount = 0 + msg = None + for item in self.getMarkedOrCurrentSelection(): + itemRef = item[0] + if not canRename(item): + continue + if isFolder(item): + if itemCount > 0: + msg = _("Directories cannot be bulk renamed") + break + else: + path = itemRef.getPath().rstrip('/') + meta = path + '.meta' + if not os.path.isfile(meta): + name, extension = os.path.splitext(path) + if itemCount > 0: + msg = _("%s files cannot be bulk renamed") % extension[1:] + break + itemCount += 1 + renameList.append(item) + + if msg: + mbox = self.session.open(MessageBox, msg, type=MessageBox.TYPE_ERROR, timeout=5) + mbox.setTitle(self.getTitle()) return - self.extension = "" - if isFolder(item): - p = os.path.split(item[0].getPath()) - if not p[1]: - # if path ends in '/', p is blank. - p = os.path.split(p[0]) - name = p[1] - else: - info = item[1] - name = info.getName(item[0]) - full_name = os.path.split(item[0].getPath())[1] - if full_name == name: # split extensions for files without metafile - name, self.extension = os.path.splitext(name) + if not renameList: + return + + # use the most recently marked item as the rename suggestion + primaryItem = renameList[-1] + itemRef, info = primaryItem[:2] + name = getItemDisplayName(itemRef, info, removeExtension=True) + + self.session.openWithCallback(lambda newname: self.renameCallback(renameList, newname), VirtualKeyBoard, + title=_("Rename"), text=name) + + def renameCallback(self, renameList, newname): + if not newname: + return + + newname = newname.strip() + failedList = [] + for item in renameList: + itemRef = item[0] + path = itemRef.getPath().rstrip('/') + meta = path + '.meta' + try: + if isFolder(item) or not os.path.isfile(meta): + newItemRef = renameServiceFiles(itemRef, newname) + if newItemRef: + self.list.removeMark(itemRef) + index = self.list.findService(newItemRef) + self.list.invalidateItem(index) + else: + failedList.append(_("'%s' already exists") % os.path.basename(path)) + else: + metafile = open(meta, "r+") + sid = metafile.readline() + oldtitle = metafile.readline() + rest = metafile.read() + metafile.seek(0) + metafile.write("%s%s\n%s" % (sid, newname, rest)) + metafile.truncate() + metafile.close() + self.list.removeMark(itemRef) + if item[3]: + item[3].txt = newname + else: + index = self.list.findService(itemRef) + self.list.invalidateItem(index) + except Exception as ex: + print("[MovieSelection] Unexpected error renaming '%s':%s" % (path, ex)) + failedList.append(_("Error renaming '%s'") % os.path.basename(path) + '\n' + str(ex)) + if len(failedList) > 0: + msg = "\n".join(failedList) + mbox = self.session.open(MessageBox, msg, type=MessageBox.TYPE_ERROR, timeout=5) + mbox.setTitle(self.getTitle()) - self.session.openWithCallback(self.renameCallback, VirtualKeyBoard, - title = _("Rename"), - text = name) + def can_decode(self, item): + return self.list.countMarked() == 0 and item[0].getPath().endswith('.ts') def do_decode(self): + if self.list.countMarked() > 0: + return item = self.getCurrentSelection() info = item[1] filepath = item[0].getPath() @@ -1860,144 +2041,104 @@ def do_decode(self): recording.setAutoincreaseEnd() self.session.nav.RecordTimer.record(recording, ignoreTSC = True) - def renameCallback(self, name): - if not name: - return - name = "".join((name.strip(), self.extension)) - item = self.getCurrentSelection() - if item and item[0]: - try: - path = item[0].getPath().rstrip('/') - meta = path + '.meta' - if os.path.isfile(meta): - metafile = open(meta, "r+") - sid = metafile.readline() - oldtitle = metafile.readline() - rest = metafile.read() - metafile.seek(0) - metafile.write("%s%s\n%s" %(sid, name, rest)) - metafile.truncate() - metafile.close() - index = self.list.getCurrentIndex() - info = self.list.list[index] - if hasattr(info[3], 'txt'): - info[3].txt = name - else: - self.list.invalidateCurrentItem() - return - pathname,filename = os.path.split(path) - newpath = os.path.join(pathname, name) - msg = None - print "[MovieSelection] rename", path, "to", newpath - os.rename(path, newpath) - self.reloadList(sel = eServiceReference.fromDirectory(newpath)) - except OSError, e: - print "[MovieSelection] Error %s:" % e.errno, e - if e.errno == 17: - msg = _("The path %s already exists.") % name - else: - msg = _("Error") + '\n' + str(e) - except Exception, e: - import traceback - print "[MovieSelection] Unexpected error:", e - traceback.print_exc() - msg = _("Error") + '\n' + str(e) - if msg: - mbox=self.session.open(MessageBox, msg, type = MessageBox.TYPE_ERROR, timeout = 5) - mbox.setTitle(self.getTitle()) + def can_reset(self, item): + for item in self.getMarkedOrCurrentSelection(): + if isSimpleFile(item): + return True + return False def do_reset(self): - current = self.getCurrent() - if current: - resetMoviePlayState(current.getPath() + ".cuts", current) - self["list"].invalidateCurrentItem() # trigger repaint + for item in self.getMarkedOrCurrentSelection(): + itemRef = item[0] + path = itemRef.getPath() + if os.path.isfile(path): + resetMoviePlayState(itemRef.getPath() + ".cuts", itemRef) + index = self.list.findService(itemRef) + self.list.removeMark(itemRef) + self.list.invalidateItem(index) def do_move(self): - item = self.getCurrentSelection() - if canMove(item): - current = item[0] - info = item[1] - if info is None: - # Special case - return - name = info and info.getName(current) or _("this recording") - path = os.path.normpath(current.getPath()) - # show a more limited list of destinations, no point - # in showing mountpoints. - title = _("Select destination for:") + " " + name - bookmarks = [("("+_("Other")+"...)", None)] - inlist = [] - # Subdirs - try: - base = os.path.split(path)[0] - for fn in os.listdir(base): - if not fn.startswith('.'): # Skip hidden things - d = os.path.join(base, fn) - if os.path.isdir(d) and (d not in inlist): - bookmarks.append((fn,d)) - inlist.append(d) - except Exception, e : - print "[MovieSelection]", e - # Last favourites - for d in last_selected_dest: - if d not in inlist: - bookmarks.append((d,d)) - # Other favourites - for d in config.movielist.videodirs.value: - d = os.path.normpath(d) - bookmarks.append((d,d)) - inlist.append(d) - for p in Components.Harddisk.harddiskmanager.getMountedPartitions(): - d = os.path.normpath(p.mountpoint) - if d not in inlist: - bookmarks.append((p.description, d)) - inlist.append(d) - self.onMovieSelected = self.gotMoveMovieDest - self.movieSelectTitle = title - self.session.openWithCallback(self.gotMovieLocation, ChoiceBox, title=title, list=bookmarks) - - def gotMoveMovieDest(self, choice): + moveList = [item for item in self.getMarkedOrCurrentSelection() if canMove(item)] + if not moveList: + return + + itemRef, info = moveList[0][:2] + path = os.path.normpath(itemRef.getPath()) + moveCount = len(moveList) + if moveCount == 1: + name = getItemDisplayName(itemRef, info) + else: + name = _("%d items") % moveCount + # show a more limited list of destinations, no point in showing mountpoints. + title = _("Select destination for:") + " " + name + bookmarks = buildMovieLocationList(includeOther=True, path=path, includeSubdirs=True, includeParentDir=True) + callback = lambda choice: self.gotMoveMovieDest(moveList, choice) + self.session.openWithCallback(lambda choice: self.gotMovieLocation(title, callback, choice), ChoiceBox, title=title, list=bookmarks) + + def gotMoveMovieDest(self, moveList, choice): if not choice: return dest = os.path.normpath(choice) - try: - item = self.getCurrentSelection() - current = item[0] - if item[1] is None: - name = None + movedList = [] + failedList = [] + name = "" + for item in moveList: + itemRef, info = item[:2] + name = getItemDisplayName(itemRef, info) + try: + moveServiceFiles(itemRef, dest, name) + movedList.append(itemRef) + except Exception as ex: + failedList.append((name, ex)) + + if movedList: + self["list"].removeServices(movedList) + movedCount = len(movedList) + self.showActionFeedback(_("Moved '%s'") % name if movedCount == 1 else _("Moved %d items") % movedCount) + if failedList: + failedCount = len(failedList) + if failedCount == 1: + msg = _("Couldn't move '%s'.\n%s") % (failedList[0], failedList[1]) else: - name = item[1].getName(current) - moveServiceFiles(current, dest, name) - self["list"].removeService(current) - except Exception, e: - mbox=self.session.open(MessageBox, str(e), MessageBox.TYPE_ERROR) + msg = _("Couldn't move %d items.\n%s") % (failedCount, failedList[0][1]) + mbox = self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR) mbox.setTitle(self.getTitle()) def do_copy(self): - item = self.getCurrentSelection() - if canCopy(item): - current = item[0] - info = item[1] - if info is None: - # Special case - return - name = info and info.getName(current) or _("this recording") - self.selectMovieLocation(title=_("Select copy destination for:") + " " + name, callback=self.gotCopyMovieDest) + copyList = [item for item in self.getMarkedOrCurrentSelection() if canCopy(item)] + if not copyList: + return + copyCount = len(copyList) + if copyCount == 1: + item = copyList[0] + itemRef, info = item[:2] + name = getItemDisplayName(itemRef, info) + else: + name = _("%d items") % copyCount + + self.selectMovieLocation(title=_("Select copy destination for:") + " " + name, callback=lambda choice: self.gotCopyMovieDest(copyList, choice)) - def gotCopyMovieDest(self, choice): + def gotCopyMovieDest(self, copyList, choice): if not choice: return dest = os.path.normpath(choice) - try: - item = self.getCurrentSelection() - current = item[0] - if item[1] is None: - name = None + failedList = [] + for item in copyList: + itemRef, info = item[:2] + name = getItemDisplayName(itemRef, info) + try: + copyServiceFiles(itemRef, dest, name) + self.list.removeMark(itemRef) + except Exception as ex: + failedList.append((name, ex)) + + if failedList: + failedCount = len(failedList) + if failedCount == 1: + msg = _("Couldn't copy '%s'.\n%s") % (failedList[0][0], failedList[0][1]) else: - name = item[1].getName(current) - copyServiceFiles(current, dest, name) - except Exception, e: - mbox=self.session.open(MessageBox, str(e), MessageBox.TYPE_ERROR) + msg = _("Couldn't copy %d items.\n%s") % (failedCount, failedList[0][1]) + mbox = self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR) mbox.setTitle(self.getTitle()) def stopTimer(self, timer): @@ -2010,185 +2151,189 @@ def stopTimer(self, timer): timer.afterEvent = RecordTimer.AFTEREVENT.NONE NavigationInstance.instance.RecordTimer.removeEntry(timer) - def onTimerChoice(self, choice): - if isinstance(choice, tuple) and choice[1]: - choice, timer = choice[1] - if not choice: - # cancel - return - if "s" in choice: - self.stopTimer(timer) - if "d" in choice: - self.delete(True) - def do_delete(self): - self.delete() + delList = [] + recList = [] + dirCount = fileCount = subItemCount = 0 + inTrash = None + markedItems = self.getMarkedOrCurrentSelection() + # Check for the items that can only be single selected: + # - Trash can't be marked but can be deleted as a shortcut for deleting all trash + # - Parent directory (..) cannot be deleted + if len(markedItems) == 1: + item = markedItems[0] + itemRef, info = item[:2] + if isTrashFolder(itemRef): + self.purgeAll() + return + elif not info: # parent directory + return + for item in markedItems: + itemRef = item[0] + if inTrash is None: + inTrash = isInTrashFolder(itemRef) + itemPath = os.path.realpath(itemRef.getPath()) + if not os.path.exists(itemPath): + continue + delList.append(item) + if isFolder(item): + dirs, files = countFiles(itemPath) + dirCount += 1 + subItemCount += dirs + files + else: + fileCount += 1 + # check if this item is currently recording + rec_filename = itemPath[:-3] if itemPath.endswith(".ts") else itemPath + for timer in NavigationInstance.instance.RecordTimer.timer_list: + if timer.isRunning() and not timer.justplay and rec_filename == timer.Filename: + recList.append((item, timer)) - def delete(self, *args): - item = self.getCurrentSelection() - if not item or args and (not args[0]): - # cancelled by user (passing any arg means it's a dialog return) - return - current = item[0] - info = item[1] - cur_path = os.path.realpath(current.getPath()) - if not os.path.exists(cur_path): - # file does not exist. + delInfo = (dirCount, fileCount, subItemCount, inTrash) + if len(recList) == 0: + self.__showDeleteConfirmation(delList, delInfo) + else: + # Some of the recordings are in progress, show timer confirmation + recCount = len(recList) + if recCount == 1: + item = recList[0][0] + itemRef, info = item[:2] + name = item and info.getName(itemRef) or "" + else: + name = "" + title = "Recording in progress: %s" % name if recCount == 1 else "Recordings in progress: %d" % recCount + choices = [ + (_("Cancel"), None), + (ngettext("Stop this recording", "Stop these recordings", recCount), "s"), + (ngettext("Stop this recording and delete it", "Stop these recordings and delete them", recCount), "d")] + self.session.openWithCallback(lambda choice: self.__onTimerChoice(delList, recList, delInfo, choice), ChoiceBox, title=title, list=choices) + + def __onTimerChoice(self, delList, recList, delInfo, choice): + if choice is None or choice[1] is None: return - st = os.stat(cur_path) - name = info and info.getName(current) or _("this recording") - are_you_sure = "" - pathtest = info and info.getName(current) - if not pathtest: + for rec in recList: + self.stopTimer(rec[1]) + # only continue if the delete option was selected, otherwise unmark everything + if choice[1] == "d": + self.__showDeleteConfirmation(delList, delInfo) + else: + self.clearMarks() + + def __showDeleteConfirmation(self, delList, delInfo): + dirCount, fileCount, subItemCount, inTrash = delInfo + callback = lambda confirmed: self.__permanentDeleteListConfirmed(delList, confirmed) + itemCount = dirCount + fileCount + singleName = None + if itemCount == 1: + itemRef, info = delList[0][:2] + singleName = getItemDisplayName(itemRef, info) + if inTrash: + if itemCount == 1: + are_you_sure = _("Do you really want to permanently delete '%s' from the trash can?") % singleName + else: + are_you_sure = _("Do you really want to permanently delete these %d items from the trash can?") % itemCount + elif config.usage.movielist_trashcan.value: + if itemCount == 1: + are_you_sure = _("Do you really want to move '%s' to the trash can?") % singleName + else: + are_you_sure = _("Do you really want to move these %d items to the trash can?") % itemCount + callback = lambda confirmed: self.__deleteListConfirmed(delList, confirmed) + else: + if itemCount == 1: + are_you_sure = _("Do you really want to permanently delete '%s'?") % singleName + else: + are_you_sure = _("Do you really want to permanently delete these %d items?") % itemCount + if dirCount > 0 and subItemCount > 0: + # deleting one or more non empty directories, so it's a good idea to get confirmation + if itemCount == 1: + are_you_sure += _("\nIt contains other items.") + else: + are_you_sure += ngettext("\nOne is a directory that isn't empty.", "\nThere are directories that aren't empty.", dirCount) + elif not inTrash and config.usage.movielist_trashcan.value: + # currently we don't ask for confirmation when moving just files into the trash can + self.__deleteListConfirmed(delList, True) return - if item and isTrashFolder(item[0]): - # Red button to empty trashcan... - self.purgeAll() + mbox = self.session.openWithCallback(callback, MessageBox, are_you_sure) + mbox.setTitle(self.getTitle()) + + def __deleteListConfirmed(self, delList, confirmed): + if not confirmed or not delList: return - if current.flags & eServiceReference.mustDescent: - files = 0 - subdirs = 0 - if '.Trash' not in cur_path and config.usage.movielist_trashcan.value: - if isFolder(item): - are_you_sure = _("Do you really want to move to the trash can ?") - subdirs += 1 - else: - args = True - if args: - trash = Tools.Trashcan.createTrashFolder(cur_path) - if trash: - try: - moveServiceFiles(current, trash, name, allowCopy=True) - self["list"].removeService(current) - self.showActionFeedback(_("Deleted") + " " + name) - return - except: - msg = _("Cannot move to the trash can") + "\n" - are_you_sure = _("Do you really want to delete %s ?") % name - else: - msg = _("Cannot move to the trash can") + "\n" - are_you_sure = _("Do you really want to delete %s ?") % name - for fn in os.listdir(cur_path): - if (fn != '.') and (fn != '..'): - ffn = os.path.join(cur_path, fn) - if os.path.isdir(ffn): - subdirs += 1 - else: - tempfn, tempfext = os.path.splitext(fn) - if tempfext not in ('.eit', '.ap', '.cuts', '.meta', '.sc'): - files += 1 - if files or subdirs: - folder_filename = os.path.split(os.path.split(name)[0])[1] - mbox=self.session.openWithCallback(self.delete, MessageBox, _("'%s' contains %d file(s) and %d sub-directories.\n") % (folder_filename,files,subdirs-1) + are_you_sure) - mbox.setTitle(self.getTitle()) - return - else: - if '.Trash' in cur_path: - are_you_sure = _("Do you really want to permanently remove from the trash can ?") - else: - are_you_sure = _("Do you really want to delete ?") - if args: - try: - msg = '' - Tools.CopyFiles.deleteFiles(cur_path, name) - self["list"].removeService(current) - self.showActionFeedback(_("Deleted") + " " + name) - return - except Exception, e: - print "[MovieSelection] Weird error moving to trash", e - msg = _("Cannot delete file") + "\n" + str(e) + "\n" - return - for fn in os.listdir(cur_path): - if (fn != '.') and (fn != '..'): - ffn = os.path.join(cur_path, fn) - if os.path.isdir(ffn): - subdirs += 1 - else: - tempfn, tempfext = os.path.splitext(fn) - if tempfext not in ('.eit', '.ap', '.cuts', '.meta', '.sc'): - files += 1 - if files or subdirs: - folder_filename = os.path.split(os.path.split(name)[0])[1] - mbox=self.session.openWithCallback(self.delete, MessageBox, _("'%s' contains %d file(s) and %d sub-directories.\n") % (folder_filename,files,subdirs) + are_you_sure) - mbox.setTitle(self.getTitle()) - return - else: - try: - os.rmdir(cur_path) - except Exception, e: - print "[MovieSelection] Failed delete", e - self.session.open(MessageBox, _("Delete failed!") + "\n" + str(e), MessageBox.TYPE_ERROR) - else: - self["list"].removeService(current) - self.showActionFeedback(_("Deleted") + " " + name) + + path = os.path.realpath(delList[0][0].getPath()) + trash = Tools.Trashcan.createTrashFolder(path) + name = "" + if trash: + deletedList = [] + failedList = [] + for delItem in delList: + itemRef, info = delItem[:2] + name = getItemDisplayName(itemRef, info) + path = os.path.realpath(itemRef.getPath()) + if not os.path.exists(path): + continue + try: + moveServiceFiles(itemRef, trash) + from Screens.InfoBarGenerics import delResumePoint + delResumePoint(itemRef) + deletedList.append(itemRef) + except Exception as ex: + print("[MovieSelection] Couldn't move to trash '%s'. %s" % (path, ex)) + failedList.append(delItem) + + if deletedList: + self["list"].removeServices(deletedList) + deletedCount = len(deletedList) + self.showActionFeedback(_("Deleted '%s'") % name if deletedCount == 1 else _("Deleted %d items") % deletedCount) else: - if not args: - rec_filename = os.path.split(current.getPath())[1] - if rec_filename.endswith(".ts"): rec_filename = rec_filename[:-3] - for timer in NavigationInstance.instance.RecordTimer.timer_list: - if timer.isRunning() and not timer.justplay and rec_filename in timer.Filename: - choices = [ - (_("Cancel"), None), - (_("Stop recording"), ("s", timer)), - (_("Stop recording and delete"), ("sd", timer))] - self.session.openWithCallback(self.onTimerChoice, ChoiceBox, title=_("Recording in progress") + ":\n%s" % name, list=choices) - return - if time.time() - st.st_mtime < 5: - if not args: - are_you_sure = _("Do you really want to delete ?") - mbox=self.session.openWithCallback(self.delete, MessageBox, _("File appears to be busy.\n") + are_you_sure) - mbox.setTitle(self.getTitle()) - return - if '.Trash' not in cur_path and config.usage.movielist_trashcan.value: - trash = Tools.Trashcan.createTrashFolder(cur_path) - if trash: - try: - moveServiceFiles(current, trash, name, allowCopy=True) - self["list"].removeService(current) - # Files were moved to .Trash, ok. - from Screens.InfoBarGenerics import delResumePoint - delResumePoint(current) - self.showActionFeedback(_("Deleted") + " " + name) - return - except: - msg = _("Cannot move to the trash can") + "\n" - are_you_sure = _("Do you really want to delete %s ?") % name - else: - msg = _("Cannot move to the trash can") + "\n" - are_you_sure = _("Do you really want to delete %s ?") % name + failedList = delList + + # some things didn't move to the trash can. Ask whether we should try doing a permanent delete instead + if failedList: + failedCount = len(failedList) + if failedCount == 1: + msg = _("Couldn't move '%s' to the trash can. Do you want to delete it instead?") % getItemDisplayName(*failedList[0][:2]) else: - if '.Trash' in cur_path: - are_you_sure = _("Do you really want to permamently remove '%s' from the trash can ?") % name - else: - are_you_sure = _("Do you really want to delete %s ?") % name - msg = '' - mbox=self.session.openWithCallback(self.deleteConfirmed, MessageBox, msg + are_you_sure) + msg= _("Couldn't move %d items to the trash can. Do you want to delete them instead?") % failedCount + mbox = self.session.openWithCallback(lambda confirmed: self.__permanentDeleteListConfirmed(failedList, confirmed), MessageBox, msg) mbox.setTitle(self.getTitle()) - def deleteConfirmed(self, confirmed): + def __permanentDeleteListConfirmed(self, delList, confirmed): if not confirmed: return - item = self.getCurrentSelection() - if item is None: - return # huh? - current = item[0] - info = item[1] - name = info and info.getName(current) or _("this recording") - serviceHandler = eServiceCenter.getInstance() - offline = serviceHandler.offlineOperations(current) - try: - if offline is None: - from enigma import eBackgroundFileEraser - eBackgroundFileEraser.getInstance().erase(os.path.realpath(current.getPath())) - else: - if offline.deleteFromDisk(0): - raise Exception, "Offline delete failed" - self["list"].removeService(current) - from Screens.InfoBarGenerics import delResumePoint - delResumePoint(current) - self.showActionFeedback(_("Deleted") + " " + name) - except Exception, ex: - mbox=self.session.open(MessageBox, _("Delete failed!") + "\n" + name + "\n" + str(ex), MessageBox.TYPE_ERROR) + + deletedList = [] + failedList = [] + name = "" + for delItem in delList: + itemRef, info = delItem[:2] + name = getItemDisplayName(itemRef, info) + serviceHandler = eServiceCenter.getInstance() + offline = serviceHandler.offlineOperations(itemRef) + path = os.path.realpath(itemRef.getPath()) + try: + if offline is None: + from enigma import eBackgroundFileEraser + eBackgroundFileEraser.getInstance().erase(path) + else: + if offline.deleteFromDisk(0): + raise Exception("Offline delete failed") + from Screens.InfoBarGenerics import delResumePoint + delResumePoint(itemRef) + deletedList.append(itemRef) + except Exception as ex: + print("[MovieSelection] Couldn't delete '%s'. %s" % (path, ex)) + failedList.append((name, ex)) + + if deletedList: + self["list"].removeServices(deletedList) + deletedCount = len(deletedList) + self.showActionFeedback(_("Deleted '%s'") % name if deletedCount == 1 else _("Deleted %d items") % deletedCount) + + # some things didn't delete. Ask whether we should try doing a permanent delete instead + if failedList: + failedCount = len(failedList) + msg = _("Couldn't delete '%s'.") % failedList[0] if failedCount == 1 else _("Couldn't delete %d items.") % failedCount + mbox = self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR) mbox.setTitle(self.getTitle()) def purgeAll(self): @@ -2228,10 +2373,14 @@ def showActionFeedback(self, text): self.diskinfo.setText(text) def hideActionFeedback(self): - self.diskinfo.update() - current = self.getCurrent() - if current is not None: - self.trashinfo.update(current.getPath()) + markedCount = self.list.countMarked() + if markedCount > 0: + self.diskinfo.setText(ngettext(_("%d marked item"), _("%d marked items"), markedCount) % markedCount) + else: + self.diskinfo.update() + current = self.getCurrent() + if current is not None: + self.trashinfo.update(current.getPath()) def can_gohome(self, item): return True diff --git a/lib/python/Screens/MultiBootSelector.py b/lib/python/Screens/MultiBootSelector.py index cb0a30e574..cee34f0ec9 100644 --- a/lib/python/Screens/MultiBootSelector.py +++ b/lib/python/Screens/MultiBootSelector.py @@ -105,7 +105,7 @@ def reboot(self): if self.imagedict[self.slotx]["imagename"] == _("Deleted image"): self.session.open(MessageBox, _("Cannot reboot to deleted image"), MessageBox.TYPE_ERROR, timeout=3) self.getImagelist() - if self.currentSelected[0][1] != "Queued": + elif self.currentSelected[0][1] != "Queued": slot = self.currentSelected[0][1][0] boxmode = self.currentSelected[0][1][1] print "[MultiBootSelector] reboot2 reboot slot = %s, " % slot diff --git a/lib/python/Screens/NetworkSetup.py b/lib/python/Screens/NetworkSetup.py index f4665eee01..054545d0c0 100644 --- a/lib/python/Screens/NetworkSetup.py +++ b/lib/python/Screens/NetworkSetup.py @@ -56,17 +56,25 @@ def StartStopCallback(self, result = None, retval = None, extra_args = None): time.sleep(3) self.updateService() - def removeComplete(self,result = None, retval = None, extra_args = None): + def removeComplete(self, result = None, retval = None, extra_args = None): if self.reboot_at_end: - self.session.open(TryQuitMainloop, 2) - self.message.close() - self.close() + restartbox = self.session.openWithCallback(self.operationComplete, MessageBox, + _('Your %s %s needs to be restarted to complete the removal of %s\nDo you want to reboot now ?') % (getMachineBrand(), getMachineName(), self.getTitle()), MessageBox.TYPE_YESNO) + restartbox.setTitle(_("Reboot required")) + else: + self.operationComplete() - def installComplete(self,result = None, retval = None, extra_args = None): + def installComplete(self, result = None, retval = None, extra_args = None): if self.reboot_at_end: - self.session.open(TryQuitMainloop, 2) + restartbox = self.session.openWithCallback(self.operationComplete, MessageBox, + _('Your %s %s needs to be restarted to complete the installation of %s\nDo you want to reboot now ?') % (getMachineBrand(), getMachineName(), self.getTitle()), MessageBox.TYPE_YESNO) + restartbox.setTitle(_("Reboot required")) else: self.updateService() + + def operationComplete(self, reboot=False): + if reboot: + self.session.open(TryQuitMainloop, 2) self.message.close() self.close() @@ -87,10 +95,7 @@ def checkNetworkState(self, str, retval, extra_args): if (getImageType() != 'release' and feedsstatuscheck.getFeedsBool() != 'unknown') or (getImageType() == 'release' and feedsstatuscheck.getFeedsBool() not in ('stable', 'unstable')): self.session.openWithCallback(self.InstallPackageFailed, MessageBox, feedsstatuscheck.getFeedsErrorMessage(), type=MessageBox.TYPE_INFO, timeout=10, close_on_any_key=True) else: - if self.reboot_at_end: - mtext = _('Your %s %s will be restarted after the installation of the service\nAre you ready to install "%s" ?') % (getMachineBrand(), getMachineName(), self.service_name) - else: - mtext = _('Are you ready to install "%s" ?') % self.service_name + mtext = _('Are you ready to install %s ?') % self.getTitle() self.session.openWithCallback(self.InstallPackage, MessageBox, mtext, MessageBox.TYPE_YESNO) else: self.updateService() @@ -100,11 +105,7 @@ def UninstallCheck(self): def RemovedataAvail(self, str, retval, extra_args): if str: - if self.reboot_at_end: - restartbox = self.session.openWithCallback(self.RemovePackage,MessageBox,_('Your %s %s will be restarted after the removal of the service\nDo you want to remove the service now ?') % (getMachineBrand(), getMachineName()), MessageBox.TYPE_YESNO) - restartbox.setTitle(_('Are you ready to remove "%s" ?') % self.service_name) - else: - self.session.openWithCallback(self.RemovePackage, MessageBox, _('Ready to remove "%s" ?') % self.service_name, MessageBox.TYPE_YESNO) + self.session.openWithCallback(self.RemovePackage, MessageBox, _('Are you ready to remove %s ?') % self.getTitle(), MessageBox.TYPE_YESNO) else: self.updateService() @@ -2256,6 +2257,7 @@ def __init__(self, session): self['key_green'] = Label(_("Start")) self['key_yellow'] = Label(_("Autostart")) self['key_blue'] = Label(_("Show Log")) + self["key_menu"] = StaticText(_("MENU")) self['actions'] = ActionMap(['WizardActions', 'ColorActions', 'SetupActions'], { 'ok': self.setupinadyn, @@ -2535,7 +2537,7 @@ def __init__(self, session): self['labport'] = Label() self['telnetport'] = Label(_("Telnet Port") + ":") self['labtelnetport'] = Label() - self['sharedir'] = Label(_("Share Folder's") + ":") + self['sharedir'] = Label(_("Share Folders") + ":") self['labsharedir'] = Label() self['web'] = Label(_("Web Interface") + ":") self['webactive'] = Pixmap() @@ -2554,6 +2556,7 @@ def __init__(self, session): self['key_green'] = Label(_("Start")) self['key_yellow'] = Label(_("Autostart")) self['key_blue'] = Label(_("Show Log")) + self["key_menu"] = StaticText(_("MENU")) self['actions'] = ActionMap(['WizardActions', 'ColorActions', 'SetupActions'], { 'ok': self.setupushare, @@ -2963,7 +2966,7 @@ def __init__(self, session): self['labport'] = Label() self['serialno'] = Label(_("Serial No") + ":") self['labserialno'] = Label() - self['sharedir'] = Label(_("Share Folder's") + ":") + self['sharedir'] = Label(_("Share Folders") + ":") self['labsharedir'] = Label() self['inotify'] = Label(_("Inotify Monitoring") + ":") self['inotifyactive'] = Pixmap() @@ -2979,6 +2982,7 @@ def __init__(self, session): self['key_green'] = Label(_("Start")) self['key_yellow'] = Label(_("Autostart")) self['key_blue'] = Label(_("Show Log")) + self["key_menu"] = StaticText(_("MENU")) self['actions'] = ActionMap(['WizardActions', 'ColorActions', 'SetupActions'], { 'ok': self.setupminidlna, diff --git a/lib/python/Screens/ParentalControlSetup.py b/lib/python/Screens/ParentalControlSetup.py index a1d53a803c..6af139e23a 100644 --- a/lib/python/Screens/ParentalControlSetup.py +++ b/lib/python/Screens/ParentalControlSetup.py @@ -47,6 +47,7 @@ def __init__(self, session): "cancel": self.keyCancel, "save": self.keySave, "menu": self.closeRecursive, + "ok": self.keyOK, }, -2) self["key_red"] = StaticText(_("Cancel")) self["key_green"] = StaticText(_("Save")) diff --git a/lib/python/Screens/Satconfig.py b/lib/python/Screens/Satconfig.py index d2b4c08c20..f4fb478106 100644 --- a/lib/python/Screens/Satconfig.py +++ b/lib/python/Screens/Satconfig.py @@ -759,6 +759,8 @@ def __init__(self, session): self["key_green"] = StaticText(_("Select")) self["key_yellow"] = StaticText(_("Client mode")) + self["key_info"] = StaticText(_("INFO")) + self["actions"] = ActionMap(["SetupActions", "ColorActions", "MenuActions", "ChannelSelectEPGActions"], { "ok": self.okbuttonClick, @@ -827,6 +829,8 @@ def showNim(self, nim): def updateList(self, index=None): self.list = [ ] for x in nimmanager.nim_slots: + if x.isFBCLink() and not x.isFBCLinkEnabled(): + continue slotid = x.slot nimConfig = nimmanager.getNimConfig(x.slot) text = "" diff --git a/lib/python/Screens/Screen.py b/lib/python/Screens/Screen.py index cb4d613eed..1e194e1e99 100644 --- a/lib/python/Screens/Screen.py +++ b/lib/python/Screens/Screen.py @@ -151,8 +151,8 @@ def getScreenPath(self): def setTitle(self, title, showPath=True): try: # This protects against calls to setTitle() before being fully initialised like self.session is accessed *before* being defined. - if self.session and len(self.session.dialog_stack) > 1: - self.screenPath = " > ".join(ds[0].getTitle() for ds in self.session.dialog_stack[1:]) + if self.session and len(self.session.dialog_stack) > 2: + self.screenPath = " > ".join(ds[0].getTitle() for ds in self.session.dialog_stack[2:]) else: self.screenPath = "" if self.instance: @@ -161,7 +161,7 @@ def setTitle(self, title, showPath=True): except AttributeError: pass self.screenTitle = title - if showPath and config.usage.showScreenPath.value == "large" and title: + if showPath and config.usage.showScreenPath.value == "large": screenPath = "" screenTitle = "%s > %s" % (self.screenPath, title) if self.screenPath else title elif showPath and config.usage.showScreenPath.value == "small": diff --git a/lib/python/Screens/SkinSelector.py b/lib/python/Screens/SkinSelector.py index 0898e0f936..b23659109d 100644 --- a/lib/python/Screens/SkinSelector.py +++ b/lib/python/Screens/SkinSelector.py @@ -20,11 +20,14 @@ class SkinSelector(Screen, HelpableScreen): - def __init__(self, session, screenTitle=_("GUI Skin")): - Screen.__init__(self, session, mandatoryWidgets=["skins", "preview", "description"]) + def __init__(self, session, screenTitle=_("GUI Skin"), skin_name=None, reboot=True): + Screen.__init__(self, session, mandatoryWidgets=["skins", "preview"]) HelpableScreen.__init__(self) self.setTitle(screenTitle) + self.reboot = reboot self.skinName = ["SkinSelector","__SkinSelector__"] + if isinstance(skin_name, str): + self.skinName = [skin_name] + self.skinName self.rootDir = resolveFilename(SCOPE_SKIN) self.config = config.skin.primary_skin from skin import currentPrimarySkin # value types are imported by value at import time @@ -92,14 +95,17 @@ def refreshList(self): skinSize = None resolution = None if skinFile == "skin.xml": - with open(skinPath, "r") as fd: - mm = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ) - skinWidth = re.search(r" %s' % (path, trash)) if trash and os.access(os.path.split(trash)[0], os.W_OK): if not os.path.isdir(trash): try: diff --git a/lib/service/servicedvb.cpp b/lib/service/servicedvb.cpp index a95e88aaec..f59f806fd7 100644 --- a/lib/service/servicedvb.cpp +++ b/lib/service/servicedvb.cpp @@ -1006,7 +1006,7 @@ RESULT eServiceFactoryDVB::lookupService(ePtr &service, const eServ int err; if ((err = eDVBDB::getInstance()->getService((eServiceReferenceDVB&)ref, service)) != 0) { - eLog(6, "[eServiceFactoryDVB] lookupService getService failed!"); + eTrace("[eServiceFactoryDVB] lookupService getService failed!"); return err; } } diff --git a/main/enigma.cpp b/main/enigma.cpp index ef09e3c7c9..0fa72db5bf 100644 --- a/main/enigma.cpp +++ b/main/enigma.cpp @@ -98,6 +98,7 @@ void keyEvent(const eRCKey &key) #include #include #include +#include /* Defined in eerror.cpp */ void setDebugTime(int level); @@ -111,6 +112,7 @@ class eMain: public eApplication, public sigc::trackable ePtr m_mgr; ePtr m_locale_time_handler; ePtr m_epgcache; + ePtr m_epgtransponderdatareader; public: eMain() @@ -123,6 +125,7 @@ class eMain: public eApplication, public sigc::trackable m_mgr = new eDVBResourceManager(); m_locale_time_handler = new eDVBLocalTimeHandler(); m_epgcache = new eEPGCache(); + m_epgtransponderdatareader = new eEPGTransponderDataReader(); m_mgr->setChannelList(m_dvbdb); } diff --git a/mytest.py b/mytest.py index 755ae39875..817ed177a6 100644 --- a/mytest.py +++ b/mytest.py @@ -112,7 +112,6 @@ def NTPserverChanged(configelement): Console = Console() Console.ePopen('/usr/bin/ntpdate-sync') config.misc.NTPserver.addNotifier(NTPserverChanged, immediate_feedback = False) -config.misc.NTPserver.callNotifiersOnSaveAndCancel = True profile("Twisted") try: diff --git a/skin.py b/skin.py index fee7677c7c..0e7a86f801 100644 --- a/skin.py +++ b/skin.py @@ -100,8 +100,8 @@ def InitSkins(booting=True): break print("[Skin] Error: Adding %s GUI skin '%s' has failed!" % (name, config.skin.primary_skin.value)) processed.append(skin) - # Add an optional skin related user skin "user_skin_.xml". If there is - # not a skin related user skin then try to add am optional generic user skin. + # Add an optional skin related user skin "skin_user_.xml". If there is + # not a skin related user skin then try to add an optional generic user skin. loadedUser = False if isfile(resolveFilename(SCOPE_SKIN, config.skin.primary_skin.value)): name = USER_SKIN_TEMPLATE % dirname(config.skin.primary_skin.value)