def checkDuplicates(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2, force=False): if name1 and name2: sequenceMatcher = SequenceMatcher(" ".__eq__, name1, name2) else: return False ratio = sequenceMatcher.ratio() ratio_value = force and 0.8 or timer.ratioThresholdDuplicate doDebug("[AutoTimer] names ratio %f - %s - %d - %s - %d" % (ratio, name1, len(name1), name2, len(name2))) if name1 in name2 or (0.8 < ratio): # this is probably a match foundShort = True if (force or timer.searchForDuplicateDescription > 0) and shortdesc1 and shortdesc2: sequenceMatcher.set_seqs(shortdesc1, shortdesc2) ratio = sequenceMatcher.ratio() doDebug("[AutoTimer] shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundShort = shortdesc1 in shortdesc2 or ((ratio_value < ratio) or (ratio_value == 1.0 and ratio_value == ratio)) if foundShort: doLog("[AutoTimer] shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) elif not force and timer.descShortExtEmpty and not shortdesc1 and not shortdesc2 and name1 != name2: foundShort = False foundExt = True # NOTE: only check extended if short description already is a match because otherwise # it won't evaluate to True anyway if foundShort and (force or timer.searchForDuplicateDescription > 1) and extdesc1 and extdesc2: sequenceMatcher.set_seqs(extdesc1, extdesc2) ratio = sequenceMatcher.ratio() doDebug("[AutoTimer] extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) foundExt = (ratio_value < ratio) or (ratio_value == 1.0 and ratio_value == ratio) if foundExt: doLog("[AutoTimer] extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) elif not force and timer.descShortExtEmpty and not extdesc1 and not extdesc2 and name1 != name2: foundExt = False return foundShort and foundExt return False
def addToFilterList(self, session, services, *args, **kwargs): if services: serviceHandler = eServiceCenter.getInstance() add_counter=0 try: for service in services: info = serviceHandler.info(service) name = info and info.getName(service) or "" if info: begin = info.getInfo(service, iServiceInformation.sTimeCreate) else: doLog("No recordinfo available") continue ret = self.addToFilterfile(name, begin) if ret: add_counter +=1 session.open( MessageBox, _("finished add to filterList with %s event(s):\n\n %s event(s) added \n %s event(s) skipped") % (len(services), add_counter,len(services)-add_counter), type = MessageBox.TYPE_INFO, timeout = config.plugins.autotimer.popup_timeout.value ) except Exception as e: doLog("Error in addToFilterList", e) print ("======== Error in addToFilterList ", e)
def populateTimerdict(self, epgcache, recordHandler, timerdict, simulateOnly=False): remove = [] for timer in chain(recordHandler.timer_list, recordHandler.processed_timers): if timer and timer.service_ref: if timer.eit is not None: event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit) if event: timer.extdesc = event.getExtendedDescription() else: remove.append(timer) else: remove.append(timer) continue if not hasattr(timer, 'extdesc'): timer.extdesc = '' timerdict[str(timer.service_ref)].append(timer) if config.plugins.autotimer.check_eit_and_remove.value: for timer in remove: if hasattr(timer, "isAutoTimer") or (config.plugins.autotimer.add_autotimer_to_tags.value and TAG in timer.tags): try: # Because of the duplicate check, we only want to remove future timer if timer in recordHandler.timer_list: if not timer.isRunning(): global NavigationInstance doLog("Remove timer because of eit check " + timer.name) self.addToSearchLogfile(timer,"-", simulateOnly) NavigationInstance.instance.RecordTimer.removeEntry(timer) except: pass del remove
def populateTimerdict(self, epgcache, recordHandler, timerdict): remove = [] for timer in chain(recordHandler.timer_list, recordHandler.processed_timers): if timer and timer.service_ref: if timer.eit is not None: event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit) if event: timer.extdesc = event.getExtendedDescription() else: remove.append(timer) else: remove.append(timer) continue if not hasattr(timer, 'extdesc'): timer.extdesc = '' timerdict[str(timer.service_ref)].append(timer) if config.plugins.autotimer.check_eit_and_remove.value: for timer in remove: if "autotimer" in timer.flags: try: # Because of the duplicate check, we only want to remove future timer if timer in recordHandler.timer_list: if not timer.isRunning(): recordHandler.removeEntry(timer) doLog("[AutoTimer] Remove timer because of eit check %s." % (timer.name)) except: pass del remove
def blockingCallFromMainThread(f, *a, **kw): """ Modified version of twisted.internet.threads.blockingCallFromThread which waits 30s for results and otherwise assumes the system to be shut down. This is an ugly workaround for a twisted-internal deadlock. Please keep the look intact in case someone comes up with a way to reliably detect from the outside if twisted is currently shutting down. """ queue = Queue.Queue() def _callFromThread(): result = defer.maybeDeferred(f, *a, **kw) result.addBoth(queue.put) reactor.callFromThread(_callFromThread) result = None while True: try: result = queue.get(True, config.plugins.autotimer.timeout.value * 60) except Queue.Empty as qe: if True: #not reactor.running: # reactor.running is only False AFTER shutdown, we are during. doLog("[AutoTimer] Reactor no longer active, aborting.") else: break if isinstance(result, failure.Failure): print("[AutoTimer]", result.getTraceback()) doLog(result.getTraceback()) result.raiseException() return result
def addToFilterfile(self, name, begin, simulateOnlyValue=False): #== add to filterList path_filter_txt = "/etc/enigma2/autotimer_filter.txt" if os_path.exists(path_filter_txt): search_txt = '"' + name + '"' if search_txt in open(path_filter_txt).read(): print( "Skipping an event because found event in autotimer_filter" ) doLog( "Skipping an event because found event in autotimer_filter" ) return False if simulateOnlyValue: return True #write eventname totextfile filter_txt = str(strftime('%d.%m.%Y, %H:%M', localtime(begin))) + ' - "' + name + '"\n' file_filter_txt = open(path_filter_txt, "a") file_filter_txt.write(filter_txt) file_filter_txt.close() doLog("added a new event to autotimer_filter") return True
def parseEPG(self, simulateOnly=False, uniqueId=None, callback=None): from plugin import AUTOTIMER_VERSION doLog("AutoTimer Version: " + AUTOTIMER_VERSION) if NavigationInstance.instance is None: doLog("Navigation is not available, can't parse EPG") return (0, 0, 0, [], [], []) new = 0 modified = 0 timers = [] conflicting = [] similars = [] skipped = [] if currentThread().getName() == 'MainThread': doBlockingCallFromMainThread = lambda f, *a, **kw: f(*a, **kw) else: doBlockingCallFromMainThread = blockingCallFromMainThread # NOTE: the config option specifies "the next X days" which means today (== 1) + X delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1) evtLimit = mktime((date.today() + delta).timetuple()) checkEvtLimit = delta.days > 1 del delta # Read AutoTimer configuration self.readXml() # Get E2 instances epgcache = eEPGCache.getInstance() serviceHandler = eServiceCenter.getInstance() recordHandler = NavigationInstance.instance.RecordTimer # Save Timer in a dict to speed things up a little # We include processed timers as we might search for duplicate descriptions # NOTE: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match timerdict = defaultdict(list) doBlockingCallFromMainThread(self.populateTimerdict, epgcache, recordHandler, timerdict) # Create dict of all movies in all folders used by an autotimer to compare with recordings # The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings moviedict = defaultdict(list) # Iterate Timer for timer in self.getEnabledTimerList(): if uniqueId == None or timer.id == uniqueId: tup = doBlockingCallFromMainThread(self.parseTimer, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=simulateOnly) if callback: callback(timers, conflicting, similars, skipped) del timers[:] del conflicting[:] del similars[:] del skipped[:] else: new += tup[0] modified += tup[1] return (len(timers), new, modified, timers, conflicting, similars)
def readXml(self, **kwargs): if "xml_string" in kwargs: # reset time self.configMtime = -1 # Parse Config configuration = cet_fromstring(kwargs["xml_string"]) else: # Abort if no config found if not os_path.exists(XML_CONFIG): doLog("No configuration file present") return # Parse if mtime differs from whats saved mtime = os_path.getmtime(XML_CONFIG) if mtime == self.configMtime: doLog("No changes in configuration, won't parse") return # Save current mtime self.configMtime = mtime # Parse Config configuration = cet_parse(XML_CONFIG).getroot() # Empty out timers and reset Ids del self.timers[:] self.defaultTimer.clear(-1, True) parseConfig(configuration, self.timers, configuration.get("version"), 0, self.defaultTimer) self.uniqueTimerId = len(self.timers)
def checkDuplicates(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2, force=False): if name1 and name2: sequenceMatcher = SequenceMatcher(" ".__eq__, name1, name2) else: return False title_ratio = int(config.plugins.autotimer.title_match_ratio.value) / float(100) shortdesc_ratio = int(config.plugins.autotimer.shortdesc_match_ratio.value) / float(100) extdesc_ratio = int(config.plugins.autotimer.extdesc_match_ratio.value) / float(100) ratio = sequenceMatcher.ratio() doDebug("names ratio %f - %s - %d - %s - %d" % (ratio, name1, len(name1), name2, len(name2))) if name1 in name2 or (title_ratio < ratio): # this is probably a match foundShort = True if (force or timer.searchForDuplicateDescription > 0) and shortdesc1 and shortdesc2: sequenceMatcher.set_seqs(shortdesc1, shortdesc2) ratio = sequenceMatcher.ratio() doDebug("shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundShort = shortdesc1 in shortdesc2 or (shortdesc_ratio < ratio) if foundShort: doLog("shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundExt = True # NOTE: only check extended if short description already is a match because otherwise # it won't evaluate to True anyway if foundShort and (force or timer.searchForDuplicateDescription > 1) and extdesc1 and extdesc2: sequenceMatcher.set_seqs(extdesc1, extdesc2) ratio = sequenceMatcher.ratio() doDebug("extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) foundExt = (extdesc_ratio < ratio) if foundExt: doLog("extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) return foundShort and foundExt
def blockingCallFromMainThread(f, *a, **kw): """ Modified version of twisted.internet.threads.blockingCallFromThread which waits 30s for results and otherwise assumes the system to be shut down. This is an ugly workaround for a twisted-internal deadlock. Please keep the look intact in case someone comes up with a way to reliably detect from the outside if twisted is currently shutting down. """ queue = Queue.Queue() def _callFromThread(): result = defer.maybeDeferred(f, *a, **kw) result.addBoth(queue.put) reactor.callFromThread(_callFromThread) result = None while True: try: result = queue.get(True, config.plugins.autotimer.timeout.value*60) except Queue.Empty as qe: if True: #not reactor.running: # reactor.running is only False AFTER shutdown, we are during. doLog("[AutoTimer] Reactor no longer active, aborting.") else: break if isinstance(result, failure.Failure): result.raiseException() return result
def addToFilterList(self, session, services, *args, **kwargs): if services: serviceHandler = eServiceCenter.getInstance() add_counter = 0 try: for service in services: info = serviceHandler.info(service) name = info and info.getName(service) or "" if info: begin = info.getInfo(service, iServiceInformation.sTimeCreate) else: doLog("No recordinfo available") continue ret = self.addToFilterfile(name, begin) if ret: add_counter += 1 session.open( MessageBox, _("finished add to filterList with %s event(s):\n\n %s event(s) added \n %s event(s) skipped" ) % (len(services), add_counter, len(services) - add_counter), type=MessageBox.TYPE_INFO, timeout=config.plugins.autotimer.popup_timeout.value) except Exception as e: doLog("Error in addToFilterList", e) print("======== Error in addToFilterList ", e)
def addDirectoryToMovieDict(self, moviedict, dest, serviceHandler): movielist = serviceHandler.list( eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest)) if movielist is None: doLog("listing of movies in " + dest + " failed") else: append = moviedict[dest].append while 1: movieref = movielist.getNext() if not movieref.valid(): break if movieref.flags & eServiceReference.mustDescent: continue info = serviceHandler.info(movieref) if info is None: continue event = info.getEvent(movieref) if event is None: continue append({ "name": info.getName(movieref), "shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription), "extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string? })
def parseConfig(configuration, list, version = None, uniqueTimerId = 0, defaultTimer = None): try: intVersion = int(version) except ValueError: doLog('[AutoTimer] Config version "%s" is not a valid integer, assuming old version' % version) intVersion = -1 if intVersion < 5: parseConfigOld(configuration, list, uniqueTimerId) return if defaultTimer is not None: # Read in defaults for a new timer for defaults in configuration.findall("defaults"): parseEntry(defaults, defaultTimer, True) for timer in configuration.findall("timer"): uniqueTimerId += 1 baseTimer = preferredAutoTimerComponent( uniqueTimerId, '', '', True ) if parseEntry(timer, baseTimer): list.append(baseTimer)
def readXml(self): # Abort if no config found if not os_path.exists(XML_CONFIG): doLog("No configuration file present") return # Parse if mtime differs from whats saved mtime = os_path.getmtime(XML_CONFIG) if mtime == self.configMtime: doLog("No changes in configuration, won't parse") return # Save current mtime self.configMtime = mtime # Parse Config configuration = cet_parse(XML_CONFIG).getroot() # Empty out timers and reset Ids del self.timers[:] self.defaultTimer.clear(-1, True) parseConfig( configuration, self.timers, configuration.get("version"), 0, self.defaultTimer ) self.uniqueTimerId = len(self.timers)
def parseConfig(configuration, list, version=None, uniqueTimerId=0, defaultTimer=None): try: intVersion = int(version) except ValueError: doLog( '[AutoTimer] Config version "%s" is not a valid integer, assuming old version' % version) intVersion = -1 if intVersion < 5: parseConfigOld(configuration, list, uniqueTimerId) return if defaultTimer is not None: # Read in defaults for a new timer for defaults in configuration.findall("defaults"): parseEntry(defaults, defaultTimer, True) for timer in configuration.findall("timer"): uniqueTimerId += 1 baseTimer = preferredAutoTimerComponent(uniqueTimerId, '', '', True) if parseEntry(timer, baseTimer): list.append(baseTimer)
def checkDuplicates(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2, force=False): if name1 and name2: sequenceMatcher = SequenceMatcher(" ".__eq__, name1, name2) else: return False ratio = sequenceMatcher.ratio() doDebug("names ratio %f - %s - %d - %s - %d" % (ratio, name1, len(name1), name2, len(name2))) if name1 in name2 or (0.8 < ratio): # this is probably a match foundShort = True if (force or timer.searchForDuplicateDescription > 0) and shortdesc1 and shortdesc2: sequenceMatcher.set_seqs(shortdesc1, shortdesc2) ratio = sequenceMatcher.ratio() doDebug("shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundShort = shortdesc1 in shortdesc2 or (0.8 < ratio) if foundShort: doLog("shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundExt = True # NOTE: only check extended if short description already is a match because otherwise # it won't evaluate to True anyway if foundShort and (force or timer.searchForDuplicateDescription > 1) and extdesc1 and extdesc2: sequenceMatcher.set_seqs(extdesc1, extdesc2) ratio = sequenceMatcher.ratio() doDebug("extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) foundExt = (0.8 < ratio) if foundExt: doLog("extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) return foundShort and foundExt
def populateTimerdict(self, epgcache, recordHandler, timerdict, simulateOnly=False): remove = [] check_eit_and_remove = config.plugins.autotimer.check_eit_and_remove.value for timer in chain(recordHandler.timer_list, recordHandler.processed_timers): if timer and timer.service_ref: if timer.eit is not None: event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit) if event: timer.extdesc = event.getExtendedDescription() elif check_eit_and_remove and "autotimer" in timer.flags and not timer.isRunning(): remove.append(timer) continue else: remove.append(timer) continue if not hasattr(timer, 'extdesc'): timer.extdesc = '' timerdict[str(timer.service_ref)].append(timer) if check_eit_and_remove: for timer in remove: if "autotimer" in timer.flags: try: # Because of the duplicate check, we only want to remove future timer if timer in recordHandler.timer_list: if not timer.isRunning(): recordHandler.removeEntry(timer) doLog("[AutoTimer] Remove timer because of eit check %s." % (timer.name)) self.addToSearchLogfile(timer, "-", simulateOnly) except: pass del remove
def maybeRemoveWhitespaces(self): # XXX: Hack alert if self["list"].current[1] == "removeTrailingWhitespaces": doLog("[AutoTimer] Next step would be to remove trailing whitespaces, removing them and redirecting to 'conf2'") self.timer.match = self.timer.match.rstrip() self.match.value = self.match.value.rstrip() self.currStep = self.getStepWithID("conf2") self.trailingWhitespacesMatch = False
def housekeepingExtensionsmenu(el): if el.value: plugins.addPlugin(extDescriptor) else: try: plugins.removePlugin(extDescriptor) except ValueError as ve: doLog("[AutoTimer] housekeepingExtensionsmenu got confused, tried to remove non-existant plugin entry... ignoring.")
def maybeRemoveWhitespaces(self): # XXX: Hack alert if self["list"].current[1] == "removeTrailingWhitespaces": doLog("Next step would be to remove trailing whitespaces, removing them and redirecting to 'conf2'") self.timer.match = self.timer.match.rstrip() self.match.value = self.match.value.rstrip() self.currStep = self.getStepWithID("conf2") self.trailingWhitespacesMatch = False
def add_to_filterList(session, service, services=None, *args, **kwargs): try: if services: if not isinstance(services, list): services = [services] else: services = [service] autotimer.addToFilterList(session, services) except Exception as e: print ("[AutoTimer] Unable to add Recordtitle to FilterList:", e) doLog("[AutoTimer] Unable to add Recordtitle to FilterList:", e)
def reloadTimerList(self, recordHandler): doLog("[AutoTimer] Start reload timers list after search") # checking and deleting duplicate timers disabled_at = removed_at = 0 check_timer_list = recordHandler.timer_list[:] for timer in check_timer_list: check_timer_list.remove(timer) timersanitycheck = TimerSanityCheck(check_timer_list, timer) if not timersanitycheck.check(): simulTimerList = timersanitycheck.getSimulTimerList() if simulTimerList and timer in simulTimerList and "autotimer" in timer.flags and not timer.isRunning(): timer.disabled = True recordHandler.timeChanged(timer) disabled_at += 1 conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in simulTimerList]) doLog("[AutoTimer-reload] Timer %s disabled because of conflicts with %s." % (timer.name, conflictString)) elif timersanitycheck.doubleCheck() and "autotimer" in timer.flags and not timer.isRunning(): try: recordHandler.removeEntry(timer) removed_at += 1 doLog("[AutoTimer-reload] Remove double timer %s." % (timer.name)) except: doLog("[AutoTimer-reload] Error for remove double timer %s." % (timer.name)) if config.plugins.autotimer.remove_double_and_conflicts_timers.value == "yes_notify": if Standby.inStandby is None and (disabled_at or removed_at): AddPopup(_("Reload timers list.\n%d autotimer(s) disabled because conflict.\n%d double autotimer(s) removed.\n") % (disabled_at, removed_at), MessageBox.TYPE_INFO, config.plugins.autotimer.popup_timeout.value, CONFLICTINGDOUBLEID)
def reloadTimerList(self, recordHandler): doLog("[AutoTimer] Start reload timers list after search") # checking and deleting duplicate timers disabled_at = removed_at = 0 check_timer_list = recordHandler.timer_list[:] for timer in check_timer_list: check_timer_list.remove(timer) timersanitycheck = TimerSanityCheck(check_timer_list, timer) if not timersanitycheck.check(): simulTimerList = timersanitycheck.getSimulTimerList() if simulTimerList and timer in simulTimerList and "autotimer" in timer.flags and not timer.isRunning(): timer.disabled = True recordHandler.timeChanged(timer) disabled_at += 1 conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in simulTimerList]) doLog("[AutoTimer-reload] Timer %s disabled because of conflicts with %s." % (timer.name, conflictString)) elif timersanitycheck.doubleCheck() and "autotimer" in timer.flags and not timer.isRunning(): try: recordHandler.removeEntry(timer) removed_at += 1 doLog("[AutoTimer-reload] Remove double timer %s."% (timer.name)) except: doLog("[AutoTimer-reload] Error for remove double timer %s."% (timer.name)) if config.plugins.autotimer.remove_double_and_conflicts_timers.value == "yes_notify": if Standby.inStandby is None and (disabled_at or removed_at): AddPopup(_("Reload timers list.\n%d autotimer(s) disabled because conflict.\n%d double autotimer(s) removed.\n") % (disabled_at, removed_at), MessageBox.TYPE_INFO, config.plugins.autotimer.popup_timeout.value, CONFLICTINGDOUBLEID)
def run(self): sem = self.__semaphore queue = self.__queue pump = self.__pump timer = self.__timer self.running = True while 1: sem.acquire() # NOTE: we have to check this here and not using the while to prevent the parser to be started on shutdown if not self.running: break if config.plugins.autotimer.skip_during_records.value: try: import NavigationInstance if NavigationInstance.instance.RecordTimer.isRecording(): doLog("[AutoTimer]: Skip check during running records") reactor.callFromThread( timer.startLongTimer, config.plugins.autotimer.interval.value * 3600) continue except: pass if config.plugins.autotimer.skip_during_epgrefresh.value: try: from Plugins.Extensions.EPGRefresh.EPGRefresh import epgrefresh if epgrefresh.isrunning: doLog("[AutoTimer]: Skip check during EPGRefresh") reactor.callFromThread( timer.startLongTimer, config.plugins.autotimer.interval.value * 3600) continue except: pass from plugin import autotimer # Ignore any program errors try: queue.append(autotimer.parseEPG()) pump.send(0) except Exception: # Dump error to stdout import traceback, sys traceback.print_exc(file=sys.stdout) #Keep that eTimer in the mainThread reactor.callFromThread( timer.startLongTimer, config.plugins.autotimer.interval.value * 3600)
def readXml(self): # Abort if no config found if not os.path.exists(XML_CONFIG): doLog("[AutoTimer] No configuration file present") return # Parse if mtime differs from whats saved mtime = os.path.getmtime(XML_CONFIG) if mtime == self.configMtime: doLog("[AutoTimer] No changes in configuration, won't parse") return # Save current mtime self.configMtime = mtime # Parse Config try: configuration = cet_parse(XML_CONFIG).getroot() except: try: if os_path.exists(XML_CONFIG + "_old"): os_rename(XML_CONFIG + "_old", XML_CONFIG + "_old(1)") os_rename(XML_CONFIG, XML_CONFIG + "_old") doLog("[AutoTimer] autotimer.xml is corrupt rename file to /etc/enigma2/autotimer.xml_old") except: pass if Standby.inStandby is None: AddPopup(_("The autotimer file (/etc/enigma2/autotimer.xml) is corrupt and could not be loaded.") + "\n" +_("A new and empty config was created. A backup of the config can be found here (/etc/enigma2/autotimer.xml_old)."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "AutoTimerLoadFailed") self.timers = [] self.defaultTimer = preferredAutoTimerComponent( 0, # Id "", # Name "", # Match True # Enabled ) try: self.writeXml() configuration = cet_parse(XML_CONFIG).getroot() except: doLog("[AutoTimer] fatal error, the autotimer.xml cannot create") return # Empty out timers and reset Ids del self.timers[:] self.defaultTimer.clear(-1, True) parseConfig( configuration, self.timers, configuration.get("version"), 0, self.defaultTimer ) self.uniqueTimerId = len(self.timers)
def readXmlTimer(self, xml_string): # Parse xml string try: configuration = cet_fromstring(xml_string) except: doLog("[AutoTimer] fatal error, the xml_string not read") return parseConfig( configuration, self.timers, configuration.get("version"), self.uniqueTimerId, self.defaultTimer ) self.uniqueTimerId += 1 # reset time self.configMtime = -1
def addToFilterfile(self, name, begin, simulateOnlyValue=False, sp_title="xxxxxxxxxxxxxxxx"): path_filter_txt = "/etc/enigma2/autotimer_filter.txt" if os.path.exists(path_filter_txt): search_txt = '"' + name + '"' search_txt_sp = '"' + sp_title + '"' if (search_txt or search_txt_sp) in open(path_filter_txt).read(): print ("[AutoTimer] Skipping an event because found event in autotimer_filter") doLog("[AutoTimer] Skipping an event because found event in autotimer_filter") return False if simulateOnlyValue: return True #write eventname totextfile filter_txt = str(strftime('%d.%m.%Y, %H:%M', localtime(begin))) + ' - "' + name + '"\n' file_filter_txt = open(path_filter_txt, "a") file_filter_txt.write(filter_txt) file_filter_txt.close() doLog("[AutoTimer] added a new event to autotimer_filter") return True
def run(self): sem = self.__semaphore queue = self.__queue pump = self.__pump timer = self.__timer self.running = True while 1: sem.acquire() # NOTE: we have to check this here and not using the while to prevent the parser to be started on shutdown if not self.running: break if config.plugins.autotimer.skip_during_records.value: try: import NavigationInstance if NavigationInstance.instance.RecordTimer.isRecording(): doLog("[AutoTimer]: Skip check during running records") reactor.callFromThread(timer.startLongTimer, config.plugins.autotimer.interval.value*3600) continue except: pass if config.plugins.autotimer.skip_during_epgrefresh.value: try: from Plugins.Extensions.EPGRefresh.EPGRefresh import epgrefresh if epgrefresh.isrunning: doLog("[AutoTimer]: Skip check during EPGRefresh") reactor.callFromThread(timer.startLongTimer, config.plugins.autotimer.interval.value*3600) continue except: pass from plugin import autotimer # Ignore any program errors try: queue.append(autotimer.parseEPG()) pump.send(0) except Exception: # Dump error to stdout import traceback, sys traceback.print_exc(file=sys.stdout) #Keep that eTimer in the mainThread reactor.callFromThread(timer.startLongTimer, config.plugins.autotimer.interval.value*3600)
def modifyTimer(self, timer, name, shortdesc, begin, end, serviceref, eit=None, base_timer=None): if base_timer: old_timer = timer timer.justplay = base_timer.justplay timer.conflict_detection = base_timer.conflict_detection timer.always_zap = base_timer.always_zap timer.name = name timer.description = shortdesc timer.begin = int(begin) timer.end = int(end) timer.service_ref = ServiceReference(serviceref) if eit: timer.eit = eit if base_timer: timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, timer) if not timersanitycheck.check(): return False elif timersanitycheck.doubleCheck(): return False else: doLog("[AutoTimer] conflict not found for modify timer %s." % timer.name) return True
def addDirectoryToMovieDict(self, moviedict, dest, serviceHandler): movielist = serviceHandler.list(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest)) if movielist is None: doLog("[AutoTimer] listing of movies in " + dest + " failed") else: append = moviedict[dest].append while 1: movieref = movielist.getNext() if not movieref.valid(): break if movieref.flags & eServiceReference.mustDescent: continue info = serviceHandler.info(movieref) if info is None: continue event = info.getEvent(movieref) if event is None: continue append({ "name": info.getName(movieref), "shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription), "extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string? })
def checkSimilarity(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2, force=False, isMovie=False): if name1 and name2: sequenceMatcher = SequenceMatcher(" ".__eq__, name1, name2) else: return False retValue = False ratio = sequenceMatcher.ratio() ratio_value = force and 0.8 or timer.ratioThresholdDuplicate doDebug("[AutoTimer] names ratio %f - %s - %d - %s - %d" % (ratio, name1, len(name1), name2, len(name2))) if name1 in name2 or (0.8 < ratio): # this is probably a match if not force and timer.descShortExtEmpty and (((isMovie and shortdesc1 and not shortdesc2) or (not isMovie and not shortdesc1 and not shortdesc2 and name1 != name2)) or ((isMovie and extdesc1 and not extdesc2) or (not isMovie and not extdesc1 and not extdesc2 and name1 != name2))): doDebug("[AutoTimer] Configuration caused this sortdesc/extdesc match to be ignored!") return False if force or timer.searchForDuplicateDescription > 0: if shortdesc1 and shortdesc2: sequenceMatcher.set_seqs(shortdesc1, shortdesc2) ratio = sequenceMatcher.ratio() doDebug("[AutoTimer] shortdesc ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) foundShort = shortdesc1 in shortdesc2 or ((ratio_value < ratio) or (ratio_value == 1.0 and ratio_value == ratio)) doDebug("[AutoTimer] Final result for found shortdesc: %s" % foundShort) if foundShort: doLog("[AutoTimer] shortdesc match: ratio %f - %s - %d - %s - %d" % (ratio, shortdesc1, len(shortdesc1), shortdesc2, len(shortdesc2))) if force or timer.searchForDuplicateDescription > 1: if extdesc1 and extdesc2: sequenceMatcher.set_seqs(extdesc1, extdesc2) ratio = sequenceMatcher.ratio() doDebug("[AutoTimer] extdesc ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) retValue = (ratio_value < ratio) or (ratio_value == 1.0 and ratio_value == ratio) doDebug("[AutoTimer] Final result for found extdesc: %s" % retValue) if retValue: doLog("[AutoTimer] extdesc match: ratio %f - %s - %d - %s - %d" % (ratio, extdesc1, len(extdesc1), extdesc2, len(extdesc2))) else: retValue = True else: retValue = True return retValue
def modifyTimer(self, timer, name, shortdesc, begin, end, serviceref, eit=None, base_timer=None): if base_timer: timer.justplay = base_timer.justplay timer.conflict_detection = base_timer.conflict_detection timer.always_zap = base_timer.always_zap timer.name = name timer.description = shortdesc timer.begin = int(begin) timer.end = int(end) timer.service_ref = ServiceReference(serviceref) if eit: timer.eit = eit if base_timer: check_timer_list = NavigationInstance.instance.RecordTimer.timer_list[:] if timer in check_timer_list: check_timer_list.remove(timer) timersanitycheck = TimerSanityCheck(check_timer_list, timer) if not timersanitycheck.check(): return False elif timersanitycheck.doubleCheck(): return False else: doLog("[AutoTimer] conflict not found for modify timer %s." % timer.name) return True
def readXml(self): # Abort if no config found if not os.path.exists(XML_CONFIG): doLog("[AutoTimer] No configuration file present") return # Parse if mtime differs from whats saved mtime = os.path.getmtime(XML_CONFIG) if mtime == self.configMtime: doLog("[AutoTimer] No changes in configuration, won't parse") return # Save current mtime self.configMtime = mtime # Parse Config try: configuration = cet_parse(XML_CONFIG).getroot() except: try: os.rename(XML_CONFIG, XML_CONFIG + "_old") doLog("[AutoTimer] autotimer.xml is corrupt rename file to /etc/enigma2/autotimer.xml_old") except: pass if Standby.inStandby is None: AddPopup(_("The autotimer file (/etc/enigma2/autotimer.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "AutoTimerLoadFailed") return # Empty out timers and reset Ids del self.timers[:] self.defaultTimer.clear(-1, True) parseConfig( configuration, self.timers, configuration.get("version"), 0, self.defaultTimer ) self.uniqueTimerId = len(self.timers)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Workaround to allow search for umlauts if we know the encoding #match = timer.match match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": epgmatches = [] mask = (eServiceReference.isMarker | eServiceReference.isDirectory) casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() # Service filter defined # Search only using the specified services test = [(service, 0, -1, -1) for service in timer.services] for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory and not s.flags & eServiceReference.isInvisible: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 31) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet'%(service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 2500, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() doLog("[AutoTimer] Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc and timer.descShortEqualExt: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldEntry = None oldExists = False allow_modify = True newAT = None # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") newEntry = rtimer oldEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8) or timeSimilarityPercent(rtimer, evtBegin, evtEnd, timer) > 80: oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") newEntry = rtimer oldEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) if allow_modify: if self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit, base_timer=timer): msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 else: msg = "[AutoTimer] AutoTimer modification not allowed for timer %s because conflicts or double timer." % (newEntry.name) doLog(msg) if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] conflict for modification timer %s detected return to old timer" % (newEntry.name)) continue else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) continue else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newAT = True msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if 'AutoTimer' not in tags: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists and newAT is None: if self.isResolvedConflict(newEntry): recordHandler.timeChanged(newEntry) else: if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] rechecking - conflict for timer %s detected return to old timer" % (newEntry.name)) continue elif newAT: newAT = newEntry conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # add new timer in AT timer list atDoubleTimer = False refstr = ':'.join(newEntry.service_ref.ref.toString().split(':')[:11]) for at in addNewTimers: needed_ref = ':'.join(at.service_ref.ref.toString().split(':')[:11]) == refstr if needed_ref and at.eit == newEntry.eit and (newEntry.begin < at.begin <= newEntry.end or at.begin <= newEntry.begin <= at.end): atDoubleTimer = True break if atDoubleTimer: doLog("[AutoTimer] ignore double new auto timer %s." % newEntry.name) continue else: addNewTimers.append(newEntry) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() else: doLog("[AutoTimer] ignore double timer %s." % newEntry.name) # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): recordHandler.timeChanged(newEntry) else: # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) elif newAT != newEntry and newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): if not self.isResolvedConflict(newEntry): newEntry.disabled = True recordHandler.timeChanged(newEntry) doLog("[AutoTimer] Unknown conflict, disable this timer %s." % newEntry.name) return (new, modified)
from AutoTimer import AutoTimer autotimer = AutoTimer() autopoller = None AUTOTIMER_VERSION = "4.6.6" try: from Plugins.SystemPlugins.MPHelp import registerHelp, XMLHelpReader from Tools.Directories import resolveFilename, SCOPE_PLUGINS reader = XMLHelpReader(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/mphelp.xml"), translate=_) autotimerHelp = registerHelp(*reader) except Exception as e: doLog("[AutoTimer] Unable to initialize MPHelp:", e, "- Help not available!") autotimerHelp = None def isOriginalWebifInstalled(): try: from Tools.Directories import fileExists except: return False pluginpath = eEnv.resolve( '${libdir}/enigma2/python/Plugins/Extensions/WebInterface/plugin.py') if fileExists(pluginpath) or fileExists(pluginpath + "o") or fileExists(pluginpath + "c"): return True return False
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], timer.match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, (serviceref, eit, name, begin, duration, shortdesc, extdesc) in enumerate(epgmatches): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("possible epgmatch %s" % (name)) doLog("Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("Could not create Event!") skipped.append( (name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n - 1) serviceref = i.toString() doLog("Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog( "Skipping an event because it starts in less than 60 seconds" ) skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog( "Skipping an event because of maximum days in future is reached" ) skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("Skipping an event because of check services") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("Skipping an event because of duration check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("Skipping an event because of timestamp check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("Skipping an event because of timeframe check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("Skipping an event because of filter check") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset if ServiceRecordingSettings: begin -= ServiceRecordingSettings.instance.getMarginBefore( eserviceref) end += ServiceRecordingSettings.instance.getMarginAfter( eserviceref) else: begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc")): doLog( "We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("Skipping an event because movie already exists") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] rend = rtimer.end - timer.offset[1] else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration / 10) * 8): oldExists = True doLog("We found a timer based on time guessing") newEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog( "We found a timer (similar service) with same description, skipping event" ) break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog( "Skipping an event because a timer on same service exists" ) skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable(itervalues(timerdict)): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog( "We found a timer (any service) with same description, skipping event" ) break if oldExists: doLog( "Skipping an event because a timer on any service exists" ) skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("Not adding new timer because counter is depleted.") skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # if set option for check/save timer in filterlist and only if not found an existing timer isnewFilterEntry = False if (config.plugins.autotimer.series_save_filter.value or timer.series_save_filter) and oldExists == False: # only if use series_labeling and if sp_getSeasonEpisode was succesful if timer.series_labeling and sp_getSeasonEpisode is not None: if sp and type(sp) in (tuple, list) and len(sp) == 4: ret = self.addToFilterfile(str(sp[0]), begin, simulateOnly) if ret: if simulateOnly: doLog( "only simulate - new Timer would be saved in autotimer_filter" ) else: doLog("new Timer saved in autotimer_filter") isnewFilterEntry = True else: skipped.append( (name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog( "Won't modify existing timer because either no modification allowed or repeated timer" ) continue if hasattr(newEntry, "isAutoTimer"): msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % ( timer.name) doLog(msg) newEntry.log(501, msg) elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % ( timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog( "Won't modify existing timer because it's no timer set by us" ) continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % ( timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) if newEntry.begin != begin or newEntry.end != end or newEntry.name != name: modified += 1 #self.addToSearchLogfile(newEntry,"#", simulateOnly) if allow_modify: modified_for_searchlog = True if newEntry.begin != begin or newEntry.end != end or newEntry.name != name else False self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) if modified_for_searchlog: self.addToSearchLogfile(newEntry, "#", simulateOnly) msg = "[AutoTimer] AutoTimer modified timer: %s ." % ( newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % ( newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % ( timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if TAG not in tags: tags.append(TAG) if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace( " ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % ( conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join([ "%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts ]) doLog("conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i + idx + 1) % lepgm] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True): # Check if the similar is already known if eitS not in similardict: doLog("Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append( (servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 if isnewFilterEntry: self.addToSearchLogfile(newEntry, "++", simulateOnly) else: self.addToSearchLogfile(newEntry, "+", simulateOnly) newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append( (name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append( (name, begin, end, serviceref, timer.name)) self.addToSearchLogfile(newEntry, "x", simulateOnly) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % ( conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
from AutoTimer import AutoTimer autotimer = AutoTimer() autopoller = None AUTOTIMER_VERSION = "4.1.2" # pragma mark - Help try: from Plugins.SystemPlugins.MPHelp import registerHelp, XMLHelpReader from Tools.Directories import resolveFilename, SCOPE_PLUGINS reader = XMLHelpReader(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/mphelp.xml")) autotimerHelp = registerHelp(*reader) except Exception as e: doLog("[AutoTimer] Unable to initialize MPHelp:", e, "- Help not available!") autotimerHelp = None # pragma mark - def isOriginalWebifInstalled(): try: from Tools.Directories import fileExists except: return False pluginpath = eEnv.resolve("${libdir}/enigma2/python/Plugins/Extensions/WebInterface/plugin.py") if fileExists(pluginpath) or fileExists(pluginpath + "o") or fileExists(pluginpath + "c"): return True return False
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], timer.match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("possible epgmatch %s" % (name)) doLog("Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) serviceref = i.toString() doLog("Serviceref2 %s" % (str(serviceref))) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) allow_modify = True else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset if ServiceRecordingSettings: begin -= ServiceRecordingSettings.instance.getMarginBefore(eserviceref) end += ServiceRecordingSettings.instance.getMarginAfter(eserviceref) else: begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True doLog("We found a timer based on time guessing") newEntry = rtimer break if timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("We found a timer (any service) with same description, skipping event") break if oldExists: doLog("Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("Won't modify existing timer because either no modification allowed or repeated timer") continue if hasattr(newEntry, "isAutoTimer"): msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 if allow_modify: self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set) # It is only temporarily, after a restart it will be lost, # because it won't be stored in the timer xml file newEntry.isAutoTimer = True # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if TAG not in tags: tags.append(TAG) if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # enable multiple timer if services or bouquets specified (eg. recording the same event on sd service and hd service) enable_multiple_timer = ((timer.services and 's' in config.plugins.autotimer.enable_multiple_timer.value or False) or (timer.bouquets and 'b' in config.plugins.autotimer.enable_multiple_timer.value or False)) # Workaround to allow search for umlauts if we know the encoding match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "favoritedesc": epgmatches = [] casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() test = [] if timer.services or timer.bouquets: if timer.services: test = [(service, 0, -1, -1) for service in timer.services] if timer.bouquets: for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if services: while True: service = services.getNext() if not service.valid(): break playable = not (service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or (service.flags & eServiceReference.isNumberedMarker) if playable: test.append((service.toString(), 0, -1, -1)) else: # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory and not s.flags & eServiceReference.isInvisible: info = serviceHandler.info(s) if info: bouquetlist.append(s) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 31) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet' % (service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append(bouquetroot) if bouquetlist: for bouquet in bouquetlist: if not bouquet.valid(): break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if services: while True: service = services.getNext() if not service.valid(): break playable = not (service.flags & (eServiceReference.isMarker | eServiceReference.isDirectory)) or (service.flags & eServiceReference.isNumberedMarker) if playable: test.append((service.toString(), 0, -1, -1)) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) or match in (extdesc if casesensitive else extdesc.lower()) or match in (name if casesensitive else name.lower()): epgmatches.append((serviceref, eit, name, begin, duration, shortdesc, extdesc)) else: # Search EPG, default to empty list epgmatches = epgcache.search(('RITBDSE', int(config.plugins.autotimer.max_search_events_match.value), typeMap[timer.searchType], match, caseMap[timer.searchCase])) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) dayofweek_exclude = timer.exclude[3] if dayofweek_exclude: dayofweek_exclude_values = dayofweek_exclude[:] if "weekend" in dayofweek_exclude_values: dayofweek_exclude_values.extend(("5", "6")) if "weekday" in dayofweek_exclude_values: dayofweek_exclude_values.extend(("0", "1", "2", "3", "4")) dayofweek_include = timer.include[3] if dayofweek_include: dayofweek_include_values = dayofweek_include[:] if "weekend" in dayofweek_include_values: dayofweek_include_values.extend(("5", "6")) if "weekday" in dayofweek_include_values: dayofweek_include_values.extend(("0", "1", "2", "3", "4")) # Loop over all EPG matches for idx, (serviceref, eit, name, begin, duration, shortdesc, extdesc) in enumerate(epgmatches): startLog() # timer destination dir dest = timer.destination or config.usage.default_path.value evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % serviceref) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Try to determine real service (we always choose the last one) #n = evt.getNumOfLinkageServices() #if n > 0: # i = evt.getLinkageService(eserviceref, n-1) # serviceref = i.toString() # doLog("[AutoTimer] Serviceref2 %s" % serviceref) # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc and timer.descShortEqualExt and extdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue current_dayofweek = tdow = timestamp.tm_wday if (timer.timespan[0] != None) and timer.timespan[2]: begin_offset = 60 * timestamp.tm_hour + timestamp.tm_min timer_offset = 60 * timer.timespan[0][0] + timer.timespan[0][1] if begin_offset < timer_offset: tdow = (tdow - 1) % 7 dayofweek = str(tdow) # Update dayofweek when programmes that cross midnight and have a dayofweek filter if str(current_dayofweek) == dayofweek and (dayofweek_exclude or dayofweek_include): end_timestamp = localtime(end) end_dayofweek = str(end_timestamp.tm_wday) if dayofweek != end_dayofweek: if dayofweek_exclude: if dayofweek in dayofweek_exclude_values: if not end_dayofweek in dayofweek_exclude_values: doLog("[AutoTimer] [AutoTimer] Update dayofweek by reason of exclude dayofweek filter") dayofweek = end_dayofweek if dayofweek_include and dayofweek != end_dayofweek: if not dayofweek in dayofweek_include_values: if end_dayofweek in dayofweek_include_values: doLog("[AutoTimer] [AutoTimer] Update dayofweek by reason of include dayofweek filter") dayofweek = end_dayofweek # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldEntry = None oldExists = False allow_modify = True newAT = None # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest, True) if sp and type(sp) in (tuple, list) and len(sp) > 3: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name, shortdesc, dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest, True) if sp and type(sp) in (tuple, list) and len(sp) > 3: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name, shortdesc, dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) offsetBegin = timer.offset[0] offsetEnd = timer.offset[1] else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 offsetBegin = config.recording.margin_before.value * 60 offsetEnd = config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin evtEnd = evtBegin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkSimilarity(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc"), isMovie=True): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): try: # protect against vps plugin not being present vps_changed = hasVps and (rtimer.vpsplugin_enabled != timer.vps_enabled or rtimer.vpsplugin_overwrite != timer.vps_overwrite) except: vps_changed = False time_changed = (evtBegin - offsetBegin != rtimer.begin) or (evtEnd + offsetEnd != rtimer.end) desc_changed = (timer.avoidDuplicateDescription >= 1 and shortdesc and rtimer.description and shortdesc != rtimer.description) or (timer.avoidDuplicateDescription >= 2 and extdesc and rtimer.extdesc and extdesc != rtimer.extdesc) if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") if time_changed or desc_changed or vps_changed: newEntry = rtimer oldEntry = [rtimer.name, rtimer.description, rtimer.extdesc, rtimer.begin, rtimer.end, rtimer.service_ref, rtimer.eit, rtimer.disabled] break elif config.plugins.autotimer.try_guessing.value: if timeSimilarityPercent(rtimer, evtBegin, evtEnd, timer) > 80: oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") if time_changed or desc_changed or vps_changed: newEntry = rtimer oldEntry = [rtimer.name, rtimer.description, rtimer.extdesc, rtimer.begin, rtimer.end, rtimer.service_ref, rtimer.eit, rtimer.disabled] break if oldExists is None and timer.avoidDuplicateDescription >= 1 and not rtimer.disabled: # searchForDuplicateDescription is 1 - check short description / searchForDuplicateDescription is 2 - check extended description if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles for rtimer in chain.from_iterable(itervalues(timerdict)): if not rtimer.disabled: if self.checkDoubleTimers(timer, name, rtimer.name, begin, rtimer.begin, end, rtimer.end, serviceref, rtimer.service_ref.ref.toString(), enable_multiple_timer): oldExists = True print("[AutoTimer] We found a timer with same start time, skipping event") break if timer.avoidDuplicateDescription >= 2: if self.checkSimilarity(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # if set option for check/save timer in filterlist and only if not found an existing timer isnewFilterEntry = False if (config.plugins.autotimer.series_save_filter.value or timer.series_save_filter) and not oldExists: if timer.series_labeling and sp_getSeasonEpisode is not None: if sp and type(sp) in (tuple, list) and len(sp) == 4: ret = self.addToFilterfile(str(sp[0]), begin, simulateOnly) if sp and type(sp) in (tuple, list) and len(sp) > 3: filter_title = str(sp[0]) if len(sp) > 4: filter_title = "{series:s} - S{season:02d}E{rawepisode:s} - {title:s}".format(**sp[4]) ret = self.addToFilterfile(filter_title, begin, simulateOnly, str(sp[0])) if ret: if simulateOnly: doLog("[AutoTimer SeriesPlugin] only simulate - new Timer would be saved in autotimer_filter") else: doLog("[AutoTimer SeriesPlugin] new Timer saved in autotimer_filter") isnewFilterEntry = True else: skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) changed = newEntry.begin != begin or newEntry.end != end or newEntry.name != name if allow_modify: if oldExists and newEntry.service_ref.ref.toString() == serviceref and newEntry.eit == eit and newEntry.name == name and newEntry.begin < begin and newEntry.end < end and (0 < begin - newEntry.end <= 600): begin = newEntry.begin doLog("[AutoTimer] This same eit and different times end - update only end") if self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit, base_timer=timer): msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) if changed: self.addToSearchLogfile(newEntry, "#", simulateOnly) modified += 1 else: msg = "[AutoTimer] AutoTimer modification not allowed for timer %s because conflicts or double timer." % (newEntry.name) doLog(msg) if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] conflict for modification timer %s detected return to old timer" % (newEntry.name)) continue else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) continue else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) newAT = True msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) msg = "[AutoTimer] Timer start on: %s" % ctime(begin) doLog(msg) newEntry.log(509, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Mark this entry as timer name newEntry.flags.add(stringToXML(timer.name)) # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: if 'AutoTimer' not in tags: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") if tagname not in tags: tags.append(tagname) newEntry.tags = tags if oldExists and newAT is None: if self.isResolvedConflict(newEntry): recordHandler.timeChanged(newEntry) else: if oldEntry: self.setOldTimer(newEntry, oldEntry) doLog("[AutoTimer] rechecking - conflict for timer %s detected return to old timer" % (newEntry.name)) continue elif newAT: newAT = newEntry conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # add new timer in AT timer list atDoubleTimer = False refstr = ':'.join(newEntry.service_ref.ref.toString().split(':')[:11]) for at in addNewTimers: needed_ref = ':'.join(at.service_ref.ref.toString().split(':')[:11]) == refstr if needed_ref and at.eit == newEntry.eit and (newEntry.begin < at.begin <= newEntry.end or at.begin <= newEntry.begin <= at.end): atDoubleTimer = True break if atDoubleTimer: doLog("[AutoTimer] ignore double new auto timer %s." % newEntry.name) continue else: addNewTimers.append(newEntry) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts and not timer.hasOffset() and not config.recording.margin_before.value and not config.recording.margin_after.value and len(conflicts) > 1: change_end = change_begin = False conflict_begin = conflicts[1].begin conflict_end = conflicts[1].end if conflict_begin == newEntry.end: newEntry.end -= 30 change_end = True elif newEntry.begin == conflict_end: newEntry.begin += 30 change_begin = True if change_end or change_begin: conflicts = recordHandler.record(newEntry) if conflicts: if change_end: newEntry.end += 30 elif change_begin: newEntry.begin -= 30 else: doLog("[AutoTimer] The conflict is resolved by offset time begin/end (30 sec) for %s." % newEntry.name) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[(i + idx + 1) % lepgm] if self.checkSimilarity(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): new += 1 if isnewFilterEntry: self.addToSearchLogfile(newEntry, "++", simulateOnly) else: self.addToSearchLogfile(newEntry, "+", simulateOnly) newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() else: doLog("[AutoTimer] ignore double timer %s." % newEntry.name) # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True if newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): recordHandler.timeChanged(newEntry) else: # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) elif newAT != newEntry and newEntry in (recordHandler.timer_list[:] + recordHandler.processed_timers[:]): if not self.isResolvedConflict(newEntry): newEntry.disabled = True recordHandler.timeChanged(newEntry) doLog("[AutoTimer] Unknown conflict, disable this timer %s." % newEntry.name) return (new, modified)
def parseEntry(element, baseTimer, defaults=False): if not defaults: # Read out match baseTimer.match = element.get("match", "").encode("UTF-8") if not baseTimer.match: doLog( '[AutoTimer] Erroneous config is missing attribute "match", skipping entry' ) return False # Read out name baseTimer.name = element.get("name", "").encode("UTF-8") if not baseTimer.name: doLog( '[AutoTimer] Timer is missing attribute "name", defaulting to match' ) baseTimer.name = baseTimer.match # Read out enabled enabled = element.get("enabled", "yes") if enabled == "no": baseTimer.enabled = False elif enabled == "yes": baseTimer.enabled = True else: doLog( '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled, ', disabling') baseTimer.enabled = False # Read timeframe before = element.get("before") after = element.get("after") if before and after: baseTimer.timeframe = (int(after), int(before)) # VPS-Plugin settings vps_enabled = element.get("vps_enabled", "no") vps_overwrite = element.get("vps_overwrite", "no") baseTimer.vps_enabled = True if vps_enabled == "yes" else False baseTimer.vps_overwrite = True if vps_overwrite == "yes" else False del vps_enabled, vps_overwrite # SeriesPlugin settings series_labeling = element.get("series_labeling", "no") series_save_filter = element.get("series_save_filter", "no") baseTimer.series_labeling = True if series_labeling == "yes" else False baseTimer.series_save_filter = True if series_save_filter == "yes" else False del series_labeling, series_save_filter # Read conflict detection conflict_detection = element.get("conflict_detection", "yes") baseTimer.conflict_detection = True if conflict_detection == "yes" else False del conflict_detection # Read always_zap baseTimer.always_zap = int(element.get("always_zap", 0)) # Read zap_wakeup baseTimer.zap_wakeup = element.get("zap_wakeup", "always") # Read out encoding (won't change if no value is set) baseTimer.encoding = element.get("encoding") # Read out search type/case baseTimer.searchType = element.get("searchType", baseTimer.searchType) baseTimer.searchCase = element.get("searchCase", baseTimer.searchCase) # Read out if we should change to alternative services baseTimer.overrideAlternatives = int( element.get("overrideAlternatives", baseTimer.overrideAlternatives)) # Read out timespan start = element.get("from") end = element.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] baseTimer.timespan = (start, end) # Read out max length maxduration = element.get("maxduration") if maxduration: baseTimer.maxduration = int(maxduration) * 60 # Read out recording path default = baseTimer.destination or "" baseTimer.destination = element.get("location", default).encode("UTF-8") or None # Read out offset offset = element.get("offset") if offset: offset = offset.split(",") if len(offset) == 1: before = after = int(offset[0] or 0) * 60 else: before = int(offset[0] or 0) * 60 after = int(offset[1] or 0) * 60 baseTimer.offset = (before, after) # Read out counter baseTimer.matchCount = int(element.get("counter", 0)) baseTimer.matchFormatString = element.get("counterFormat", "") if not defaults: baseTimer.matchLeft = int(element.get("left", baseTimer.matchCount)) baseTimer.matchLimit = element.get("lastActivation", "") baseTimer.lastBegin = int(element.get("lastBegin", 0)) # Read out justplay baseTimer.justplay = int(element.get("justplay", 0)) baseTimer.setEndtime = int(element.get("setEndtime", 1)) # Read out avoidDuplicateDescription baseTimer.avoidDuplicateDescription = int( element.get("avoidDuplicateDescription", 0)) baseTimer.searchForDuplicateDescription = int( element.get("searchForDuplicateDescription", 2)) # Read out description behavior descShortEqualExt = element.get("descShortEqualExt", "no") baseTimer.descShortEqualExt = True if descShortEqualExt == "yes" else False del descShortEqualExt descShortExtEmpty = element.get("descShortExtEmpty", "no") baseTimer.descShortExtEmpty = True if descShortExtEmpty == "yes" else False del descShortExtEmpty # Read out ratioThresholdDuplicate baseTimer.ratioThresholdDuplicate = float( element.get("ratioThresholdDuplicate", 0.8)) # Read out allowed services l = element.findall("serviceref") if l: servicelist = [] for service in l: value = service.text if value: myref = eServiceReference(str(value)) if not (myref.flags & eServiceReference.isGroup): # strip all after last : pos = value.rfind(':') if pos != -1: if value[pos - 1] == ':': pos -= 1 value = value[:pos + 1] servicelist.append(value) baseTimer.services = servicelist # Read out allowed bouquets l = element.findall("bouquet") if l: bouquets = [] for bouquet in l: value = bouquet.text if value: bouquets.append(value) baseTimer.bouquets = bouquets # Read out afterevent l = element.findall("afterevent") if l: idx = { "none": AFTEREVENT.NONE, "deepstandby": AFTEREVENT.DEEPSTANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "standby": AFTEREVENT.STANDBY, "auto": AFTEREVENT.AUTO } afterevents = [] for afterevent in l: value = afterevent.text if value in idx: value = idx[value] else: doLog( '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent, ', ignoring definition') continue start = afterevent.get("from") end = afterevent.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] afterevents.append((value, (start, end))) else: afterevents.append((value, None)) baseTimer.afterevent = afterevents # Read out exclude l = element.findall("exclude") idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3} if l: excludes = ([], [], [], []) for exclude in l: where = exclude.get("where") value = exclude.text if not (value and where): continue if where in idx: excludes[idx[where]].append(value.encode("UTF-8")) baseTimer.exclude = excludes # Read out includes (use same idx) l = element.findall("include") if l: includes = ([], [], [], []) for include in l: where = include.get("where") value = include.text if not (value and where): continue if where in idx: includes[idx[where]].append(value.encode("UTF-8")) baseTimer.include = includes # Read out recording tags l = element.findall("tag") if l: tags = [] for tag in l: value = tag.text if not value: continue tags.append(value.encode("UTF-8")) baseTimer.tags = tags return True
def parseConfigOld(configuration, list, uniqueTimerId = 0): doLog("[AutoTimer] Trying to parse old config") # Iterate Timers for timer in configuration.findall("timer"): # Increment uniqueTimerId uniqueTimerId += 1 # Get name (V2+) name = timer.get("name") if name: name = name.encode("UTF-8") # Get name (= match) (V1) else: # Read out name name = getValue(timer.findall("name"), "").encode("UTF-8") if not name: doLog('[AutoTimer] Erroneous config is missing attribute "name", skipping entry') continue # Read out match (V3+) match = timer.get("match") if match: # Read out match match = match.encode("UTF-8") if not match: doLog('[AutoTimer] Erroneous config contains empty attribute "match", skipping entry') continue # V2- else: # Setting match to name match = name # See if Timer is ensabled (V2+) enabled = timer.get("enabled") if enabled: if enabled == "no": enabled = False elif enabled == "yes": enabled = True else: doLog('[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', skipping entry') enabled = False # V1 else: elements = timer.findall("enabled") if elements: if getValue(elements, "yes") == "no": enabled = False else: enabled = True else: enabled = True # Read out timespan (V4+; Falling back on missing definition should be OK) start = timer.get("from") end = timer.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] timetuple = (start, end) # V3- else: elements = timer.findall("timespan") Len = len(elements) if Len: # Read out last definition start = elements[Len-1].get("from") end = elements[Len-1].get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] timetuple = (start, end) else: doLog('[AutoTimer] Erroneous config contains invalid definition of "timespan", ignoring definition') timetuple = None else: timetuple = None # Read out allowed services (V*) elements = timer.findall("serviceref") if elements: servicelist = [] for service in elements: value = service.text if value: myref = eServiceReference(str(value)) if not (myref.flags & eServiceReference.isGroup): # strip all after last : pos = value.rfind(':') if pos != -1: if value[pos-1] == ':': pos -= 1 value = value[:pos+1] servicelist.append(value) else: servicelist = None # Read out allowed bouquets (V* though officially supported since V4) bouquets = [] for bouquet in timer.findall("bouquet"): value = bouquet.text if value: bouquets.append(value) # Read out offset (V4+) offset = timer.get("offset") if offset: offset = offset.split(",") if len(offset) == 1: before = after = int(offset[0] or 0) * 60 else: before = int(offset[0] or 0) * 60 after = int(offset[1] or 0) * 60 offset = (before, after) # V3- else: elements = timer.findall("offset") Len = len(elements) if Len: value = elements[Len-1].get("both") if value == '': before = int(elements[Len-1].get("before", 0)) * 60 after = int(elements[Len-1].get("after", 0)) * 60 else: before = after = int(value) * 60 offset = (before, after) else: offset = None # Read out counter counter = int(timer.get("counter", '0')) counterLeft = int(timer.get("left", counter)) counterLimit = timer.get("lastActivation") counterFormat = timer.get("counterFormat", "") lastBegin = int(timer.get("lastBegin", 0)) # Read out justplay justplay = int(timer.get("justplay", '0')) setEndtime = int(timer.get("setEndtime", '1')) # Read out avoidDuplicateDescription avoidDuplicateDescription = int(timer.get("avoidDuplicateDescription", 0)) searchForDuplicateDescription = int(timer.get("searchForDuplicateDescription", 3)) - 1 if searchForDuplicateDescription < 0 or searchForDuplicateDescription > 2: searchForDuplicateDescription = 2 # Read out afterevent (compatible to V* though behaviour for V3- is different as V4+ allows multiple afterevents while the last definication was chosen before) idx = { "none": AFTEREVENT.NONE, "deepstandby": AFTEREVENT.DEEPSTANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "standby": AFTEREVENT.STANDBY, "auto": AFTEREVENT.AUTO } afterevent = [] for element in timer.findall("afterevent"): value = element.text if value in idx: value = idx[value] else: doLog('[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition') continue start = element.get("from") end = element.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] afterevent.append((value, (start, end))) else: afterevent.append((value, None)) # Read out exclude (V*) idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3} excludes = ([], [], [], []) for exclude in timer.findall("exclude"): where = exclude.get("where") value = exclude.text if not (value and where): continue if where in idx: excludes[idx[where]].append(value.encode("UTF-8")) # Read out includes (use same idx) (V4+ feature, should not harm V3-) includes = ([], [], [], []) for include in timer.findall("include"): where = include.get("where") value = include.text if not (value and where): continue if where in idx: includes[idx[where]].append(value.encode("UTF-8")) # Read out max length (V4+) maxlen = timer.get("maxduration") if maxlen: maxlen = int(maxlen)*60 # V3- else: elements = timer.findall("maxduration") if elements: maxlen = getValue(elements, None) if maxlen is not None: maxlen = int(maxlen)*60 else: maxlen = None # Read out recording path destination = timer.get("destination", "").encode("UTF-8") or None # Read out recording tags tags = [] for tag in timer.findall("tag"): value = tag.text if not value: continue tags.append(value.encode("UTF-8")) # Finally append timer list.append(preferredAutoTimerComponent( uniqueTimerId, name, match, enabled, timespan = timetuple, services = servicelist, offset = offset, afterevent = afterevent, exclude = excludes, include = includes, maxduration = maxlen, destination = destination, matchCount = counter, matchLeft = counterLeft, matchLimit = counterLimit, matchFormatString = counterFormat, lastBegin = lastBegin, justplay = justplay, setEndtime = setEndtime, avoidDuplicateDescription = avoidDuplicateDescription, searchForDuplicateDescription = searchForDuplicateDescription, bouquets = bouquets, tags = tags ))
def timezoneChanged(self): if config.plugins.autotimer.autopoll.value and autopoller is not None: autopoller.pause() autopoller.start(initial=False) doLog("[AutoTimer] Timezone change detected.")
def sessionstart(reason, **kwargs): if reason == 0 and "session" in kwargs: try: AutoTimerChannelContextMenuInit() except: pass try: AutoTimerEPGSelectionInit() except: pass if isOriginalWebifInstalled(): try: from Plugins.Extensions.WebInterface.WebChilds.Toplevel import addExternalChild from Plugins.Extensions.WebInterface.WebChilds.Screenpage import ScreenPage from twisted.web import static from twisted.python import util from WebChilds.UploadResource import UploadResource from AutoTimerResource import AutoTimerDoParseResource, \ AutoTimerListAutoTimerResource, AutoTimerAddOrEditAutoTimerResource, \ AutoTimerRemoveAutoTimerResource, AutoTimerChangeSettingsResource, \ AutoTimerSettingsResource, AutoTimerSimulateResource, AutoTimerTestResource, API_VERSION except ImportError as ie: pass else: if hasattr(static.File, 'render_GET'): class File(static.File): def render_POST(self, request): return self.render_GET(request) else: File = static.File # webapi root = AutoTimerListAutoTimerResource() root.putChild('parse', AutoTimerDoParseResource()) root.putChild('remove', AutoTimerRemoveAutoTimerResource()) root.putChild('edit', AutoTimerAddOrEditAutoTimerResource()) root.putChild('get', AutoTimerSettingsResource()) root.putChild('set', AutoTimerChangeSettingsResource()) root.putChild('simulate', AutoTimerSimulateResource()) root.putChild('test', AutoTimerTestResource()) addExternalChild( ("autotimer", root , "AutoTimer-Plugin", API_VERSION, False) ) # webgui session = kwargs["session"] root = File(util.sibpath(__file__, "web-data")) root.putChild("web", ScreenPage(session, util.sibpath(__file__, "web"), True) ) root.putChild('tmp', File('/tmp')) root.putChild("uploadfile", UploadResource(session)) addExternalChild( ("autotimereditor", root, "AutoTimer", "1", True) ) doLog("[AutoTimer] Use WebInterface") else: if isOpenWebifInstalled(): try: from Plugins.Extensions.WebInterface.WebChilds.Toplevel import addExternalChild from AutoTimerResource import AutoTimerDoParseResource, \ AutoTimerListAutoTimerResource, AutoTimerAddOrEditAutoTimerResource, \ AutoTimerRemoveAutoTimerResource, AutoTimerChangeSettingsResource, \ AutoTimerSettingsResource, AutoTimerSimulateResource, AutoTimerTestResource, API_VERSION except ImportError as ie: pass else: root = AutoTimerListAutoTimerResource() root.putChild('parse', AutoTimerDoParseResource()) root.putChild('remove', AutoTimerRemoveAutoTimerResource()) root.putChild('edit', AutoTimerAddOrEditAutoTimerResource()) root.putChild('get', AutoTimerSettingsResource()) root.putChild('set', AutoTimerChangeSettingsResource()) root.putChild('simulate', AutoTimerSimulateResource()) root.putChild('test', AutoTimerTestResource()) addExternalChild(("autotimer", root , "AutoTimer-Plugin", API_VERSION)) doLog("[AutoTimer] Use OpenWebif")
def parseEntry(element, baseTimer, defaults = False): if not defaults: # Read out match baseTimer.match = element.get("match", "").encode("UTF-8") if not baseTimer.match: doLog('[AutoTimer] Erroneous config is missing attribute "match", skipping entry') return False # Read out name baseTimer.name = element.get("name", "").encode("UTF-8") if not baseTimer.name: doLog('[AutoTimer] Timer is missing attribute "name", defaulting to match') baseTimer.name = baseTimer.match # Read out enabled enabled = element.get("enabled", "yes") if enabled == "no": baseTimer.enabled = False elif enabled == "yes": baseTimer.enabled = True else: doLog('[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', disabling') baseTimer.enabled = False # Read timeframe before = element.get("before") after = element.get("after") if before and after: baseTimer.timeframe = (int(after), int(before)) # VPS-Plugin settings vps_enabled = element.get("vps_enabled", "no") vps_overwrite = element.get("vps_overwrite", "no") baseTimer.vps_enabled = True if vps_enabled == "yes" else False baseTimer.vps_overwrite = True if vps_overwrite == "yes" else False del vps_enabled, vps_overwrite # SeriesPlugin settings series_labeling = element.get("series_labeling", "no") baseTimer.series_labeling = True if series_labeling == "yes" else False del series_labeling # Read conflict detection conflict_detection = element.get("conflict_detection", "yes") baseTimer.conflict_detection = True if conflict_detection == "yes" else False del conflict_detection # Read always_zap baseTimer.always_zap = int(element.get("always_zap", 0)) # Read zap_wakeup baseTimer.zap_wakeup = element.get("zap_wakeup", "always") # Read out encoding (won't change if no value is set) baseTimer.encoding = element.get("encoding") # Read out search type/case baseTimer.searchType = element.get("searchType", baseTimer.searchType) baseTimer.searchCase = element.get("searchCase", baseTimer.searchCase) # Read out if we should change to alternative services baseTimer.overrideAlternatives = int(element.get("overrideAlternatives", baseTimer.overrideAlternatives)) # Read out timespan start = element.get("from") end = element.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] baseTimer.timespan = (start, end) # Read out max length maxduration = element.get("maxduration") if maxduration: baseTimer.maxduration = int(maxduration)*60 # Read out recording path default = baseTimer.destination or "" baseTimer.destination = element.get("location", default).encode("UTF-8") or None # Read out offset offset = element.get("offset") if offset: offset = offset.split(",") if len(offset) == 1: before = after = int(offset[0] or 0) * 60 else: before = int(offset[0] or 0) * 60 after = int(offset[1] or 0) * 60 baseTimer.offset = (before, after) # Read out counter baseTimer.matchCount = int(element.get("counter", 0)) baseTimer.matchFormatString = element.get("counterFormat", "") if not defaults: baseTimer.matchLeft = int(element.get("left", baseTimer.matchCount)) baseTimer.matchLimit = element.get("lastActivation", "") baseTimer.lastBegin = int(element.get("lastBegin", 0)) # Read out justplay baseTimer.justplay = int(element.get("justplay", 0)) baseTimer.setEndtime = int(element.get("setEndtime", 1)) # Read out avoidDuplicateDescription baseTimer.avoidDuplicateDescription = int(element.get("avoidDuplicateDescription", 0)) baseTimer.searchForDuplicateDescription = int(element.get("searchForDuplicateDescription", 2)) # Read out description behavior descShortEqualExt = element.get("descShortEqualExt", "no") baseTimer.descShortEqualExt = True if descShortEqualExt == "yes" else False del descShortEqualExt descShortExtEmpty = element.get("descShortExtEmpty", "no") baseTimer.descShortExtEmpty = True if descShortExtEmpty == "yes" else False del descShortExtEmpty # Read out ratioThresholdDuplicate baseTimer.ratioThresholdDuplicate = float(element.get("ratioThresholdDuplicate", 0.8)) # Read out allowed services l = element.findall("serviceref") if l: servicelist = [] for service in l: value = service.text if value: myref = eServiceReference(str(value)) if not (myref.flags & eServiceReference.isGroup): # strip all after last : pos = value.rfind(':') if pos != -1: if value[pos-1] == ':': pos -= 1 value = value[:pos+1] servicelist.append(value) baseTimer.services = servicelist # Read out allowed bouquets l = element.findall("bouquet") if l: bouquets = [] for bouquet in l: value = bouquet.text if value: bouquets.append(value) baseTimer.bouquets = bouquets # Read out afterevent l = element.findall("afterevent") if l: idx = { "none": AFTEREVENT.NONE, "deepstandby": AFTEREVENT.DEEPSTANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "standby": AFTEREVENT.STANDBY, "auto": AFTEREVENT.AUTO } afterevents = [] for afterevent in l: value = afterevent.text if value in idx: value = idx[value] else: doLog('[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition') continue start = afterevent.get("from") end = afterevent.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] afterevents.append((value, (start, end))) else: afterevents.append((value, None)) baseTimer.afterevent = afterevents # Read out exclude l = element.findall("exclude") idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3} if l: excludes = ([], [], [], []) for exclude in l: where = exclude.get("where") value = exclude.text if not (value and where): continue if where in idx: excludes[idx[where]].append(value.encode("UTF-8")) baseTimer.exclude = excludes # Read out includes (use same idx) l = element.findall("include") if l: includes = ([], [], [], []) for include in l: where = include.get("where") value = include.text if not (value and where): continue if where in idx: includes[idx[where]].append(value.encode("UTF-8")) baseTimer.include = includes # Read out recording tags l = element.findall("tag") if l: tags = [] for tag in l: value = tag.text if not value: continue tags.append(value.encode("UTF-8")) baseTimer.tags = tags return True
def parseEPG(self, simulateOnly=False, uniqueId=None, callback=None): from plugin import AUTOTIMER_VERSION doLog("AutoTimer Version: " + AUTOTIMER_VERSION) if NavigationInstance.instance is None: doLog("[AutoTimer] Navigation is not available, can't parse EPG") return (0, 0, 0, [], [], []) new = 0 modified = 0 timers = [] conflicting = [] similars = [] skipped = [] # Init new added timers list global addNewTimers addNewTimers = [] if config.plugins.autotimer.searchlog_write.value and not simulateOnly: self.prepareSearchLogfile() if currentThread().getName() == 'MainThread': doBlockingCallFromMainThread = lambda f, *a, **kw: f(*a, **kw) else: doBlockingCallFromMainThread = blockingCallFromMainThread # NOTE: the config option specifies "the next X days" which means today (== 1) + X delta = timedelta(days=config.plugins.autotimer.maxdaysinfuture.value + 1) evtLimit = mktime((date.today() + delta).timetuple()) checkEvtLimit = delta.days > 1 del delta # Read AutoTimer configuration self.readXml() # Get E2 instances epgcache = eEPGCache.getInstance() serviceHandler = eServiceCenter.getInstance() recordHandler = NavigationInstance.instance.RecordTimer # Save Timer in a dict to speed things up a little # We include processed timers as we might search for duplicate descriptions # NOTE: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match timerdict = defaultdict(list) doBlockingCallFromMainThread(self.populateTimerdict, epgcache, recordHandler, timerdict, simulateOnly=simulateOnly) # Create dict of all movies in all folders used by an autotimer to compare with recordings # The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings moviedict = defaultdict(list) # Iterate Timer for timer in self.getEnabledTimerList(): if uniqueId is None or timer.id == uniqueId: doLog("[AutoTimer] Start search for %s" % (timer.match.replace('\xc2\x86', '').replace('\xc2\x87', ''))) tup = doBlockingCallFromMainThread(self.parseTimer, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=simulateOnly) if callback: callback(timers, conflicting, similars, skipped) del timers[:] del conflicting[:] del similars[:] del skipped[:] else: new += tup[0] modified += tup[1] if not simulateOnly: if sp_showResult is not None: blockingCallFromMainThread(sp_showResult) if config.plugins.autotimer.remove_double_and_conflicts_timers.value != "no": self.reloadTimerList(recordHandler) return (len(timers), new, modified, timers, conflicting, similars)
def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False): new = 0 modified = 0 # Workaround to allow search for umlauts if we know the encoding #match = timer.match match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '') if timer.encoding != 'UTF-8': try: match = match.decode('UTF-8').encode(timer.encoding) except UnicodeDecodeError: pass if timer.searchType == "description": epgmatches = [] mask = (eServiceReference.isMarker | eServiceReference.isDirectory) casesensitive = timer.searchCase == "sensitive" if not casesensitive: match = match.lower() # Service filter defined # Search only using the specified services test = [(service, 0, -1, -1) for service in timer.services] for bouquet in timer.bouquets: services = serviceHandler.list(eServiceReference(bouquet)) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if not test: # No service filter defined # Search within all services - could be very slow # Get all bouquets bouquetlist = [] if config.usage.multibouquet.value: refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' bouquetroot = eServiceReference(refstr) bouquets = serviceHandler.list(bouquetroot) if bouquets: while True: s = bouquets.getNext() if not s.valid(): break if s.flags & eServiceReference.isDirectory: info = serviceHandler.info(s) if info: bouquetlist.append((info.getName(s), s)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if not bouquet.valid(): #check end of list break if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) else: service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 134) || (type == 195)' refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet'%(service_types_tv) bouquetroot = eServiceReference(refstr) info = serviceHandler.info(bouquetroot) if info: bouquetlist.append((info.getName(bouquetroot), bouquetroot)) mask = (eServiceReference.isMarker | eServiceReference.isDirectory) for name, bouquet in bouquetlist: if bouquet.flags & eServiceReference.isDirectory: services = serviceHandler.list(bouquet) if not services is None: while True: service = services.getNext() if not service.valid(): #check end of list break if not (service.flags & mask): test.append( (service.toString(), 0, -1, -1 ) ) if test: # Get all events # eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] ) test.insert(0, 'RITBDSE') allevents = epgcache.lookupEvent(test) or [] # Filter events for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents: if match in (shortdesc if casesensitive else shortdesc.lower()) \ or match in (extdesc if casesensitive else extdesc.lower()): epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) ) else: # Search EPG, default to empty list epgmatches = epgcache.search( ('RITBDSE', 2000, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or [] # Sort list of tuples by begin time 'B' epgmatches.sort(key=itemgetter(3)) # Contains the the marked similar eits and the conflicting strings similardict = defaultdict(list) # Loop over all EPG matches for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ): startLog() # timer destination dir dest = timer.destination evtBegin = begin evtEnd = end = begin + duration doLog("[AutoTimer] possible epgmatch %s" % (name)) doLog("[AutoTimer] Serviceref %s" % (str(serviceref))) eserviceref = eServiceReference(serviceref) evt = epgcache.lookupEventId(eserviceref, eit) if not evt: doLog("[AutoTimer] Could not create Event!") skipped.append((name, begin, end, str(serviceref), timer.name, getLog())) continue # Try to determine real service (we always choose the last one) n = evt.getNumOfLinkageServices() if n > 0: i = evt.getLinkageService(eserviceref, n-1) doLog("[AutoTimer] Serviceref2 %s" % (str(serviceref))) serviceref = i.toString() evtBegin = begin evtEnd = end = begin + duration # If event starts in less than 60 seconds skip it if begin < time() + 60: doLog("[AutoTimer] Skipping an event because it starts in less than 60 seconds") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Set short description to equal extended description if it is empty. if not shortdesc: shortdesc = extdesc # Convert begin time timestamp = localtime(begin) # Update timer timer.update(begin, timestamp) # Check if eit is in similar matches list # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive similarTimer = False if eit in similardict: similarTimer = True dayofweek = None # NOTE: ignore day on similar timer else: # If maximum days in future is set then check time if checkEvtLimit: if begin > evtLimit: doLog("[AutoTimer] Skipping an event because of maximum days in future is reached") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue dayofweek = str(timestamp.tm_wday) # Check timer conditions # NOTE: similar matches do not care about the day/time they are on, so ignore them if timer.checkServices(serviceref): doLog("[AutoTimer] Skipping an event because of check services") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkDuration(duration): doLog("[AutoTimer] Skipping an event because of duration check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if not similarTimer: if timer.checkTimespan(timestamp): doLog("[AutoTimer] Skipping an event because of timestamp check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkTimeframe(begin): doLog("[AutoTimer] Skipping an event because of timeframe check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Initialize newEntry = None oldExists = False allow_modify = True # Eventually change service to alternative if timer.overrideAlternatives: serviceref = timer.getAlternative(serviceref) if timer.series_labeling and sp_getSeasonEpisode is not None: allow_modify = False #doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True #doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) else: # Nothing found doLog(str(sp)) # If AutoTimer name not equal match, do a second lookup with the name if timer.name.lower() != timer.match.lower(): #doLog("[AutoTimer SeriesPlugin] Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest)) sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest) if sp and type(sp) in (tuple, list) and len(sp) == 4: name = sp[0] or name shortdesc = sp[1] or shortdesc dest = sp[2] or dest doLog(str(sp[3])) allow_modify = True #doLog("[AutoTimer SeriesPlugin] Returned name, desc, path %s %s %s" % (name,shortdesc,dest)) else: doLog(str(sp)) if timer.checkFilter(name, shortdesc, extdesc, dayofweek): doLog("[AutoTimer] Skipping an event because of filter check") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.hasOffset(): # Apply custom Offset begin, end = timer.applyOffset(begin, end) else: # Apply E2 Offset begin -= config.recording.margin_before.value * 60 end += config.recording.margin_after.value * 60 # Overwrite endtime if requested if timer.justplay and not timer.setEndtime: end = begin # Check for existing recordings in directory if timer.avoidDuplicateDescription == 3: # Reset movie Exists movieExists = False if dest and dest not in moviedict: self.addDirectoryToMovieDict(moviedict, dest, serviceHandler) for movieinfo in moviedict.get(dest, ()): if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ): doLog("[AutoTimer] We found a matching recorded movie, skipping event:", name) movieExists = True break if movieExists: doLog("[AutoTimer] Skipping an event because movie already exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Check for double Timers # We first check eit and if user wants us to guess event based on time # we try this as backup. The allowed diff should be configurable though. for rtimer in timerdict.get(serviceref, ()): if rtimer.eit == eit: oldExists = True doLog("[AutoTimer] We found a timer based on eit") newEntry = rtimer break elif config.plugins.autotimer.try_guessing.value: if timer.hasOffset(): # Remove custom Offset rbegin = rtimer.begin + timer.offset[0] * 60 rend = rtimer.end - timer.offset[1] * 60 else: # Remove E2 Offset rbegin = rtimer.begin + config.recording.margin_before.value * 60 rend = rtimer.end - config.recording.margin_after.value * 60 # As alternative we could also do a epg lookup #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit) #rbegin = revent.getBeginTime() or 0 #rduration = revent.getDuration() or 0 #rend = rbegin + rduration or 0 if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8): oldExists = True doLog("[AutoTimer] We found a timer based on time guessing") newEntry = rtimer break elif timer.avoidDuplicateDescription >= 1 \ and not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): # if searchForDuplicateDescription > 1 then check short description oldExists = True doLog("[AutoTimer] We found a timer (similar service) with same description, skipping event") break # We found no timer we want to edit if newEntry is None: # But there is a match if oldExists: doLog("[AutoTimer] Skipping an event because a timer on same service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # We want to search for possible doubles if timer.avoidDuplicateDescription >= 2: for rtimer in chain.from_iterable( itervalues(timerdict) ): if not rtimer.disabled: if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ): oldExists = True doLog("[AutoTimer] We found a timer (any service) with same description, skipping event") break if oldExists: doLog("[AutoTimer] Skipping an event because a timer on any service exists") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue if timer.checkCounter(timestamp): doLog("[AutoTimer] Not adding new timer because counter is depleted.") skipped.append((name, begin, end, serviceref, timer.name, getLog())) continue # Append to timerlist and abort if simulating timers.append((name, begin, end, serviceref, timer.name, getLog())) if simulateOnly: continue if newEntry is not None: # Abort if we don't want to modify timers or timer is repeated if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated: doLog("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer") continue if "autotimer" in newEntry.flags: msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name) doLog(msg) newEntry.log(501, msg) else: if config.plugins.autotimer.refresh.value != "all": doLog("[AutoTimer] Won't modify existing timer because it's no timer set by us") continue msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name) doLog(msg) newEntry.log(501, msg) modified += 1 if allow_modify: self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit) msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name) doLog(msg) newEntry.log(501, msg) else: msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name) doLog(msg) else: newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit) msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name) doLog(msg) newEntry.log(500, msg) # Mark this entry as AutoTimer newEntry.flags.add("autotimer") # Apply afterEvent if timer.hasAfterEvent(): afterEvent = timer.getAfterEventTimespan(localtime(end)) if afterEvent is None: afterEvent = timer.getAfterEvent() if afterEvent is not None: newEntry.afterEvent = afterEvent newEntry.dirname = dest newEntry.calculateFilename() newEntry.justplay = timer.justplay newEntry.vpsplugin_enabled = timer.vps_enabled newEntry.vpsplugin_overwrite = timer.vps_overwrite newEntry.conflict_detection = timer.conflict_detection newEntry.always_zap = timer.always_zap newEntry.zap_wakeup = timer.zap_wakeup tags = timer.tags[:] if config.plugins.autotimer.add_autotimer_to_tags.value: tags.append('AutoTimer') if config.plugins.autotimer.add_name_to_tags.value: tagname = timer.name.strip() if tagname: tagname = tagname[0].upper() + tagname[1:].replace(" ", "_") tags.append(tagname) newEntry.tags = tags if oldExists: # XXX: this won't perform a sanity check, but do we actually want to do so? recordHandler.timeChanged(newEntry) else: conflictString = "" if similarTimer: conflictString = similardict[eit].conflictString msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(504, msg) # Try to add timer conflicts = recordHandler.record(newEntry) if conflicts: # Maybe use newEntry.log conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts]) doLog("[AutoTimer] conflict with %s detected" % (conflictString)) if config.plugins.autotimer.addsimilar_on_conflict.value: # We start our search right after our actual index # Attention we have to use a copy of the list, because we have to append the previous older matches lepgm = len(epgmatches) for i in xrange(lepgm): servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ] if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ): # Check if the similar is already known if eitS not in similardict: doLog("[AutoTimer] Found similar Timer: " + name) # Store the actual and similar eit and conflictString, so it can be handled later newEntry.conflictString = conflictString similardict[eit] = newEntry similardict[eitS] = newEntry similarTimer = True if beginS <= evtBegin: # Event is before our actual epgmatch so we have to append it to the epgmatches list epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS)) # If we need a second similar it will be found the next time else: similarTimer = False newEntry = similardict[eitS] break if conflicts is None: timer.decrementCounter() new += 1 newEntry.extdesc = extdesc timerdict[serviceref].append(newEntry) #if renameTimer is not None and timer.series_labeling: # renameTimer(newEntry, name, evtBegin, evtEnd) # Similar timers are in new timers list and additionally in similar timers list if similarTimer: similars.append((name, begin, end, serviceref, timer.name)) similardict.clear() # Don't care about similar timers elif not similarTimer: conflicting.append((name, begin, end, serviceref, timer.name)) if config.plugins.autotimer.disabled_on_conflict.value: msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString) doLog(msg) newEntry.log(503, msg) newEntry.disabled = True # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway conflicts = recordHandler.record(newEntry) return (new, modified)
def parseConfigOld(configuration, list, uniqueTimerId=0): doLog("[AutoTimer] Trying to parse old config") # Iterate Timers for timer in configuration.findall("timer"): # Increment uniqueTimerId uniqueTimerId += 1 # Get name (V2+) name = timer.get("name") if name: name = name.encode("UTF-8") # Get name (= match) (V1) else: # Read out name name = getValue(timer.findall("name"), "").encode("UTF-8") if not name: doLog( '[AutoTimer] Erroneous config is missing attribute "name", skipping entry' ) continue # Read out match (V3+) match = timer.get("match") if match: # Read out match match = match.encode("UTF-8") if not match: doLog( '[AutoTimer] Erroneous config contains empty attribute "match", skipping entry' ) continue # V2- else: # Setting match to name match = name # See if Timer is ensabled (V2+) enabled = timer.get("enabled") if enabled: if enabled == "no": enabled = False elif enabled == "yes": enabled = True else: doLog( '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled, ', skipping entry') enabled = False # V1 else: elements = timer.findall("enabled") if elements: if getValue(elements, "yes") == "no": enabled = False else: enabled = True else: enabled = True # Read out timespan (V4+; Falling back on missing definition should be OK) start = timer.get("from") end = timer.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] timetuple = (start, end) # V3- else: elements = timer.findall("timespan") Len = len(elements) if Len: # Read out last definition start = elements[Len - 1].get("from") end = elements[Len - 1].get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] timetuple = (start, end) else: doLog( '[AutoTimer] Erroneous config contains invalid definition of "timespan", ignoring definition' ) timetuple = None else: timetuple = None # Read out allowed services (V*) elements = timer.findall("serviceref") if elements: servicelist = [] for service in elements: value = service.text if value: myref = eServiceReference(str(value)) if not (myref.flags & eServiceReference.isGroup): # strip all after last : pos = value.rfind(':') if pos != -1: if value[pos - 1] == ':': pos -= 1 value = value[:pos + 1] servicelist.append(value) else: servicelist = None # Read out allowed bouquets (V* though officially supported since V4) bouquets = [] for bouquet in timer.findall("bouquet"): value = bouquet.text if value: bouquets.append(value) # Read out offset (V4+) offset = timer.get("offset") if offset: offset = offset.split(",") if len(offset) == 1: before = after = int(offset[0] or 0) * 60 else: before = int(offset[0] or 0) * 60 after = int(offset[1] or 0) * 60 offset = (before, after) # V3- else: elements = timer.findall("offset") Len = len(elements) if Len: value = elements[Len - 1].get("both") if value == '': before = int(elements[Len - 1].get("before", 0)) * 60 after = int(elements[Len - 1].get("after", 0)) * 60 else: before = after = int(value) * 60 offset = (before, after) else: offset = None # Read out counter counter = int(timer.get("counter", '0')) counterLeft = int(timer.get("left", counter)) counterLimit = timer.get("lastActivation") counterFormat = timer.get("counterFormat", "") lastBegin = int(timer.get("lastBegin", 0)) # Read out justplay justplay = int(timer.get("justplay", '0')) setEndtime = int(timer.get("setEndtime", '1')) # Read out avoidDuplicateDescription avoidDuplicateDescription = int( timer.get("avoidDuplicateDescription", 0)) searchForDuplicateDescription = int( timer.get("searchForDuplicateDescription", 3)) - 1 if searchForDuplicateDescription < 0 or searchForDuplicateDescription > 2: searchForDuplicateDescription = 2 # Read out afterevent (compatible to V* though behaviour for V3- is different as V4+ allows multiple afterevents while the last definication was chosen before) idx = { "none": AFTEREVENT.NONE, "deepstandby": AFTEREVENT.DEEPSTANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "standby": AFTEREVENT.STANDBY, "auto": AFTEREVENT.AUTO } afterevent = [] for element in timer.findall("afterevent"): value = element.text if value in idx: value = idx[value] else: doLog( '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent, ', ignoring definition') continue start = element.get("from") end = element.get("to") if start and end: start = [int(x) for x in start.split(':')] end = [int(x) for x in end.split(':')] afterevent.append((value, (start, end))) else: afterevent.append((value, None)) # Read out exclude (V*) idx = { "title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3 } excludes = ([], [], [], []) for exclude in timer.findall("exclude"): where = exclude.get("where") value = exclude.text if not (value and where): continue if where in idx: excludes[idx[where]].append(value.encode("UTF-8")) # Read out includes (use same idx) (V4+ feature, should not harm V3-) includes = ([], [], [], []) for include in timer.findall("include"): where = include.get("where") value = include.text if not (value and where): continue if where in idx: includes[idx[where]].append(value.encode("UTF-8")) # Read out max length (V4+) maxlen = timer.get("maxduration") if maxlen: maxlen = int(maxlen) * 60 # V3- else: elements = timer.findall("maxduration") if elements: maxlen = getValue(elements, None) if maxlen is not None: maxlen = int(maxlen) * 60 else: maxlen = None # Read out recording path destination = timer.get("destination", "").encode("UTF-8") or None # Read out recording tags tags = [] for tag in timer.findall("tag"): value = tag.text if not value: continue tags.append(value.encode("UTF-8")) # Finally append timer list.append( preferredAutoTimerComponent( uniqueTimerId, name, match, enabled, timespan=timetuple, services=servicelist, offset=offset, afterevent=afterevent, exclude=excludes, include=includes, maxduration=maxlen, destination=destination, matchCount=counter, matchLeft=counterLeft, matchLimit=counterLimit, matchFormatString=counterFormat, lastBegin=lastBegin, justplay=justplay, setEndtime=setEndtime, avoidDuplicateDescription=avoidDuplicateDescription, searchForDuplicateDescription=searchForDuplicateDescription, bouquets=bouquets, tags=tags))
def sessionstart(reason, **kwargs): if reason == 0 and "session" in kwargs: # try: # AutoTimerGraphMultiEPGInit() # except: # pass try: AutoTimerChannelContextMenuInit() except: pass try: AutoTimerEPGSelectionInit() except: pass if isOriginalWebifInstalled(): try: from Plugins.Extensions.WebInterface.WebChilds.Toplevel import addExternalChild from Plugins.Extensions.WebInterface.WebChilds.Screenpage import ScreenPage from twisted.web import static from twisted.python import util from WebChilds.UploadResource import UploadResource from AutoTimerResource import ( AutoTimerDoParseResource, AutoTimerListAutoTimerResource, AutoTimerAddOrEditAutoTimerResource, AutoTimerRemoveAutoTimerResource, AutoTimerChangeSettingsResource, AutoTimerSettingsResource, AutoTimerSimulateResource, AutoTimerTestResource, API_VERSION, ) except ImportError as ie: pass else: if hasattr(static.File, "render_GET"): class File(static.File): def render_POST(self, request): return self.render_GET(request) else: File = static.File # webapi root = AutoTimerListAutoTimerResource() root.putChild("parse", AutoTimerDoParseResource()) root.putChild("remove", AutoTimerRemoveAutoTimerResource()) root.putChild("edit", AutoTimerAddOrEditAutoTimerResource()) root.putChild("get", AutoTimerSettingsResource()) root.putChild("set", AutoTimerChangeSettingsResource()) root.putChild("simulate", AutoTimerSimulateResource()) root.putChild("test", AutoTimerTestResource()) addExternalChild(("autotimer", root, "AutoTimer-Plugin", API_VERSION, False)) # webgui session = kwargs["session"] root = File(util.sibpath(__file__, "web-data")) root.putChild("web", ScreenPage(session, util.sibpath(__file__, "web"), True)) root.putChild("tmp", File("/tmp")) root.putChild("uploadfile", UploadResource(session)) addExternalChild(("autotimereditor", root, "AutoTimer", "1", True)) doLog("[AutoTimer] Use WebInterface") else: if isOpenWebifInstalled(): try: from Plugins.Extensions.WebInterface.WebChilds.Toplevel import addExternalChild from AutoTimerResource import ( AutoTimerDoParseResource, AutoTimerListAutoTimerResource, AutoTimerAddOrEditAutoTimerResource, AutoTimerRemoveAutoTimerResource, AutoTimerChangeSettingsResource, AutoTimerSettingsResource, AutoTimerSimulateResource, AutoTimerTestResource, API_VERSION, ) except ImportError as ie: pass else: root = AutoTimerListAutoTimerResource() root.putChild("parse", AutoTimerDoParseResource()) root.putChild("remove", AutoTimerRemoveAutoTimerResource()) root.putChild("edit", AutoTimerAddOrEditAutoTimerResource()) root.putChild("get", AutoTimerSettingsResource()) root.putChild("set", AutoTimerChangeSettingsResource()) root.putChild("simulate", AutoTimerSimulateResource()) root.putChild("test", AutoTimerTestResource()) addExternalChild(("autotimer", root, "AutoTimer-Plugin", API_VERSION)) doLog("[AutoTimer] Use OpenWebif")
autotimer = AutoTimer() autopoller = None AUTOTIMER_VERSION = "4.3" NOTIFICATIONDOMAIN = "AutoTimer" #pragma mark - Help try: from Plugins.SystemPlugins.MPHelp import registerHelp, XMLHelpReader from Tools.Directories import resolveFilename, SCOPE_PLUGINS reader = XMLHelpReader(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/mphelp.xml"), translate=_) autotimerHelp = registerHelp(*reader) except Exception as e: doLog("[AutoTimer] Unable to initialize MPHelp:", e, "- Help not available!") autotimerHelp = None #pragma mark - # Notification-Domain from Tools.Notifications import notificationQueue try: notificationQueue.registerDomain(NOTIFICATIONDOMAIN, _("AutoTimer"), deferred_callable=True) except Exception as e: print("[AutoTimer] Error registering Notification-Domain:", e) # Autostart def autostart(reason, **kwargs):
def readXml(self, **kwargs): if "xml_string" in kwargs: # reset time self.configMtime = -1 # Parse Config try: configuration = cet_fromstring(kwargs["xml_string"]) except: doLog("[AutoTimer] fatal error, the xml_string not read") return else: # Abort if no config found if not os.path.exists(XML_CONFIG): doLog("[AutoTimer] No configuration file present") return # Parse if mtime differs from whats saved mtime = os.path.getmtime(XML_CONFIG) if mtime == self.configMtime: doLog("[AutoTimer] No changes in configuration, won't parse") return # Save current mtime self.configMtime = mtime # Parse Config try: configuration = cet_parse(XML_CONFIG).getroot() except: try: if os.path.exists(XML_CONFIG + "_old"): os.rename(XML_CONFIG + "_old", XML_CONFIG + "_old(1)") os.rename(XML_CONFIG, XML_CONFIG + "_old") doLog("[AutoTimer] autotimer.xml is corrupt rename file to /etc/enigma2/autotimer.xml_old") except: pass if Standby.inStandby is None: AddPopup(_("The autotimer file (/etc/enigma2/autotimer.xml) is corrupt and could not be loaded.") + "\n" +_("A new and empty config was created. A backup of the config can be found here (/etc/enigma2/autotimer.xml_old)."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "AutoTimerLoadFailed") self.timers = [] self.defaultTimer = preferredAutoTimerComponent( 0, # Id "", # Name "", # Match True # Enabled ) try: self.writeXml() configuration = cet_parse(XML_CONFIG).getroot() except: doLog("[AutoTimer] fatal error, the autotimer.xml cannot create") return # Empty out timers and reset Ids del self.timers[:] self.defaultTimer.clear(-1, True) parseConfig( configuration, self.timers, configuration.get("version"), 0, self.defaultTimer ) self.uniqueTimerId = len(self.timers)