# Plugins Config
-from xml.dom.minidom import parse as minidom_parse
+from xml.etree.cElementTree import parse as cet_parse
from os import path as os_path
+from AutoTimerConfiguration import parseConfig, buildConfig
# Navigation (RecordTimer)
import NavigationInstance
# Timer
from ServiceReference import ServiceReference
-from RecordTimer import RecordTimerEntry, AFTEREVENT
+from RecordTimer import RecordTimerEntry
from Components.TimerSanityCheck import TimerSanityCheck
# Timespan
from Components.config import config
# AutoTimer Component
-from AutoTimerComponent import AutoTimerComponent
+from AutoTimerComponent import preferredAutoTimerComponent
XML_CONFIG = "/etc/enigma2/autotimer.xml"
-CURRENT_CONFIG_VERSION = "4"
-
-def getValue(definitions, default, isList = True):
- # Initialize Output
- ret = ""
-
- # How many definitions are present
- if isList:
- Len = len(definitions)
- if Len > 0:
- childNodes = definitions[Len-1].childNodes
- else:
- childNodes = []
- else:
- childNodes = definitions.childNodes
-
- # Iterate through nodes of last one
- for node in childNodes:
- # Append text if we have a text node
- if node.nodeType == node.TEXT_NODE:
- ret = ret + node.data
-
- # Return stripped output or (if empty) default
- return ret.strip() or default
def getTimeDiff(timer, begin, end):
if begin <= timer.begin <= end:
return timer.end - begin
return 0
+typeMap = {
+ "exact": eEPGCache.EXAKT_TITLE_SEARCH,
+ "partial": eEPGCache.PARTIAL_TITLE_SEARCH
+}
+
+caseMap = {
+ "sensitive": eEPGCache.CASE_CHECK,
+ "insensitive": eEPGCache.NO_CASE_CHECK
+}
+
class AutoTimerIgnoreTimerException(Exception):
def __init__(self, cause):
self.cause = cause
"""Read and save xml configuration, query EPGCache"""
def __init__(self):
- # Keep EPGCache
- self.epgcache = eEPGCache.getInstance()
-
# Initialize
self.timers = []
self.configMtime = -1
self.uniqueTimerId = 0
+ self.defaultTimer = preferredAutoTimerComponent(
+ 0, # Id
+ "", # Name
+ "", # Match
+ True # Enabled
+ )
+
+# Configuration
def readXml(self):
# Abort if no config found
if not os_path.exists(XML_CONFIG):
+ print "[AutoTimer] No configuration file present"
return
# Parse if mtime differs from whats saved
self.configMtime = mtime
# Parse Config
- dom = minidom_parse(XML_CONFIG)
-
+ configuration = cet_parse(XML_CONFIG).getroot()
+
# Empty out timers and reset Ids
del self.timers[:]
- self.uniqueTimerId = 0
+ self.defaultTimer.clear(-1, True)
- # Get Config Element
- for configuration in dom.getElementsByTagName("autotimer"):
- # Parse old configuration files
- if configuration.getAttribute("version") != CURRENT_CONFIG_VERSION:
- from OldConfigurationParser import parseConfig
- parseConfig(configuration, self.timers, configuration.getAttribute("version"), self.uniqueTimerId)
- if not self.uniqueTimerId:
- self.uniqueTimerId = len(self.timers)
- continue
- # Iterate Timers
- for timer in configuration.getElementsByTagName("timer"):
- # Timers are saved as tuple (name, allowedtime (from, to) or None, list of services or None, timeoffset in m (before, after) or None, afterevent)
-
- # Increment uniqueTimerId
- self.uniqueTimerId += 1
-
- # Read out match
- match = timer.getAttribute("match").encode("UTF-8")
- if not match:
- print '[AutoTimer] Erroneous config is missing attribute "match", skipping entry'
- continue
+ parseConfig(
+ configuration,
+ self.timers,
+ configuration.get("version"),
+ 0,
+ self.defaultTimer
+ )
+ self.uniqueTimerId = len(self.timers)
- # Read out name
- name = timer.getAttribute("name").encode("UTF-8")
- if not name:
- print '[AutoTimer] Timer is missing attribute "name", defaulting to match'
- name = match
-
- # Read out enabled
- enabled = timer.getAttribute("enabled") or "yes"
- if enabled == "no":
- enabled = False
- elif enabled == "yes":
- enabled = True
- else:
- print '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', disabling'
- enabled = False
-
- # Read out timespan
- start = timer.getAttribute("from")
- end = timer.getAttribute("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:
- timetuple = None
-
- # Read out max length
- maxlen = timer.getAttribute("maxduration") or None
- if maxlen:
- maxlen = int(maxlen)*60
-
- # Read out recording path (needs my Location-select patch)
- destination = timer.getAttribute("destination").encode("UTF-8") or None
-
- # Read out offset
- offset = timer.getAttribute("offset") or None
- 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)
-
- # Read out counter
- counter = int(timer.getAttribute("counter") or '0')
- counterLeft = int(timer.getAttribute("left") or counter)
- counterLimit = timer.getAttribute("lastActivation")
- counterFormat = timer.getAttribute("counterFormat")
- lastBegin = int(timer.getAttribute("lastBegin") or 0)
-
- # Read out justplay
- justplay = int(timer.getAttribute("justplay") or '0')
-
- # Read out avoidDuplicateDescription
- avoidDuplicateDescription = bool(timer.getAttribute("avoidDuplicateDescription") or False)
-
- # Read out allowed services
- servicelist = []
- for service in timer.getElementsByTagName("serviceref"):
- value = getValue(service, None, False)
- if value:
- # strip all after last :
- pos = value.rfind(':')
- if pos != -1:
- value = value[:pos+1]
-
- servicelist.append(value)
-
- # Read out afterevent
- idx = {"none": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY}
- afterevent = []
- for element in timer.getElementsByTagName("afterevent"):
- value = getValue(element, None, False)
-
- try:
- value = idx[value]
- start = element.getAttribute("from")
- end = element.getAttribute("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))
- except KeyError, ke:
- print '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition'
- continue
-
- # Read out exclude
- idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
- excludes = ([], [], [], [])
- for exclude in timer.getElementsByTagName("exclude"):
- where = exclude.getAttribute("where")
- value = getValue(exclude, None, False)
- if not (value and where):
- continue
+ def getXml(self):
+ return buildConfig(self.defaultTimer, self.timers, webif = True)
- try:
- excludes[idx[where]].append(value.encode("UTF-8"))
- except KeyError, ke:
- pass
-
- # Read out includes (use same idx)
- includes = ([], [], [], [])
- for include in timer.getElementsByTagName("include"):
- where = include.getAttribute("where")
- value = getValue(include, None, False)
- if not (value and where):
- continue
+ def writeXml(self):
+ file = open(XML_CONFIG, 'w')
+ file.writelines(buildConfig(self.defaultTimer, self.timers))
+ file.close()
- try:
- includes[idx[where]].append(value.encode("UTF-8"))
- except KeyError, ke:
- pass
-
- # Finally append tuple
- self.timers.append(AutoTimerComponent(
- self.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,
- avoidDuplicateDescription = avoidDuplicateDescription
- ))
+# Manage List
- def getTimerList(self):
- return self.timers
+ def add(self, timer):
+ self.timers.append(timer)
def getEnabledTimerList(self):
return [x for x in self.timers if x.enabled]
+ def getTimerList(self):
+ return self.timers
+
def getTupleTimerList(self):
- return [(x,) for x in self.timers]
+ list = self.timers
+ return [(x,) for x in list]
+
+ def getSortedTupleTimerList(self):
+ list = self.timers[:]
+ list.sort()
+ return [(x,) for x in list]
def getUniqueId(self):
self.uniqueTimerId += 1
return self.uniqueTimerId
- def add(self, timer):
- self.timers.append(timer)
+ def remove(self, uniqueId):
+ idx = 0
+ for timer in self.timers:
+ if timer.id == uniqueId:
+ self.timers.pop(idx)
+ return
+ idx += 1
def set(self, timer):
idx = 0
idx += 1
self.timers.append(timer)
- def remove(self, uniqueId):
- idx = 0
- for timer in self.timers:
- if timer.id == uniqueId:
- self.timers.pop(idx)
- return
- idx += 1
-
- def writeXml(self):
- # Generate List in RAM
- list = ['<?xml version="1.0" ?>\n<autotimer version="', CURRENT_CONFIG_VERSION, '">\n\n']
-
- # Iterate timers
- for timer in self.timers:
- # Common attributes (match, enabled)
- list.extend([' <timer name="', timer.name, '" match="', timer.match, '" enabled="', timer.getEnabled(), '"'])
-
- # Timespan
- if timer.hasTimespan():
- list.extend([' from="', timer.getTimespanBegin(), '" to="', timer.getTimespanEnd(), '"'])
-
- # Duration
- if timer.hasDuration():
- list.extend([' maxduration="', str(timer.getDuration()), '"'])
-
- # Destination (needs my Location-select patch)
- if timer.hasDestination():
- list.extend([' destination="', str(timer.destination), '"'])
-
- # Offset
- if timer.hasOffset():
- if timer.isOffsetEqual():
- list.extend([' offset="', str(timer.getOffsetBegin()), '"'])
- else:
- list.extend([' offset="', str(timer.getOffsetBegin()), ',', str(timer.getOffsetEnd()), '"'])
-
- # Counter
- if timer.hasCounter():
- list.extend([' lastBegin="', str(timer.getLastBegin()), '" counter="', str(timer.getCounter()), '" left="', str(timer.getCounterLeft()) ,'"'])
- if timer.hasCounterFormatString():
- list.extend([' lastActivation="', str(timer.getCounterLimit()), '"'])
- list.extend([' counterFormat="', str(timer.getCounterFormatString()), '"'])
-
- # Duplicate Description
- if timer.getAvoidDuplicateDescription():
- list.append(' avoidDuplicateDescription="1" ')
-
- # Only display justplay if true
- if timer.justplay:
- list.extend([' justplay="', str(timer.getJustplay()), '"'])
-
- # Close still opened timer tag
- list.append('>\n')
-
- # Services
- for serviceref in timer.getServices():
- list.extend([' <serviceref>', serviceref, '</serviceref>'])
- ref = ServiceReference(str(serviceref))
- list.extend([' <!-- ', ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', ''), ' -->\n'])
-
- # AfterEvent
- if timer.hasAfterEvent():
- idx = {AFTEREVENT.NONE: "none", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "shutdown"}
- for afterevent in timer.getCompleteAfterEvent():
- action, timespan = afterevent
- list.append(' <afterevent')
- if timespan[0] is not None:
- list.append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
- list.extend(['>', idx[action], '</afterevent>\n'])
-
- # Excludes
- for title in timer.getExcludedTitle():
- list.extend([' <exclude where="title">', title, '</exclude>\n'])
- for short in timer.getExcludedShort():
- list.extend([' <exclude where="shortdescription">', short, '</exclude>\n'])
- for desc in timer.getExcludedDescription():
- list.extend([' <exclude where="description">', desc, '</exclude>\n'])
- for day in timer.getExcludedDays():
- list.extend([' <exclude where="dayofweek">', day, '</exclude>\n'])
-
- # Includes
- for title in timer.getIncludedTitle():
- list.extend([' <include where="title">', title, '</include>\n'])
- for short in timer.getIncludedShort():
- list.extend([' <include where="shortdescription">', short, '</include>\n'])
- for desc in timer.getIncludedDescription():
- list.extend([' <include where="description">', desc, '</include>\n'])
- for day in timer.getIncludedDays():
- list.extend([' <include where="dayofweek">', day, '</include>\n'])
-
- # End of Timer
- list.append(' </timer>\n\n')
-
- # End of Configuration
- list.append('</autotimer>\n')
-
- # Try Saving to Flash
- file = None
- try:
- file = open(XML_CONFIG, 'w')
- file.writelines(list)
-
- # FIXME: This should actually be placed inside a finally-block but python 2.4 does not support this - waiting for some images to upgrade
- file.close()
- except Exception, e:
- print "[AutoTimer] Error Saving Timer List:", e
+# Main function
def parseEPG(self, simulateOnly = False):
if NavigationInstance.instance is None:
self.readXml()
# Save Recordings in a dict to speed things up a little
+ # We include processed timers as we might search for duplicate descriptions
recorddict = {}
- for timer in NavigationInstance.instance.RecordTimer.timer_list:
- if not recorddict.has_key(str(timer.service_ref)):
- recorddict[str(timer.service_ref)] = [timer]
- else:
- recorddict[str(timer.service_ref)].append(timer)
+ for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
+ recorddict.setdefault(str(timer.service_ref), []).append(timer)
# Iterate Timer
for timer in self.getEnabledTimerList():
+ # Workaround to allow search for umlauts if we know the encoding
+ match = timer.match
+ if timer.encoding != 'UTF-8':
+ try:
+ match = match.decode('UTF-8').encode(timer.encoding)
+ except UnicodeDecodeError:
+ pass
+
# Search EPG, default to empty list
- ret = self.epgcache.search(('RI', 100, eEPGCache.PARTIAL_TITLE_SEARCH, timer.match, eEPGCache.NO_CASE_CHECK)) or []
+ epgcache = eEPGCache.getInstance()
+ ret = epgcache.search(('RI', 100, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or ()
for serviceref, eit in ret:
eserviceref = eServiceReference(serviceref)
- evt = self.epgcache.lookupEventId(eserviceref, eit)
+ evt = epgcache.lookupEventId(eserviceref, eit)
if not evt:
print "[AutoTimer] Could not create Event!"
continue
timer.update(begin, timestamp)
# Check Duration, Timespan and Excludes
- if timer.checkServices(serviceref) or timer.checkDuration(duration) or timer.checkTimespan(timestamp) or timer.checkFilter(name, description, evt.getExtendedDescription(), str(timestamp.tm_wday)):
+ if timer.checkServices(serviceref) \
+ or timer.checkDuration(duration) \
+ or timer.checkTimespan(timestamp) \
+ or timer.checkFilter(name, description,
+ evt.getExtendedDescription(), str(timestamp.tm_wday)):
continue
- # Apply E2 Offset
- begin -= config.recording.margin_before.value * 60
- end += config.recording.margin_after.value * 60
-
- # Apply custom Offset
- begin, end = timer.applyOffset(begin, end)
+ 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
+
+ # Eventually change service to alternative
+ if timer.overrideAlternatives:
+ serviceref = timer.getAlternative(serviceref)
total += 1
# Initialize
newEntry = None
+ oldExists = False
# 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.
- try:
- for rtimer in recorddict.get(serviceref, []):
- if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
- newEntry = rtimer
-
- # Abort if we don't want to modify timers or timer is repeated
- if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
- raise AutoTimerIgnoreTimerException("Won't modify existing timer because either no modification allowed or repeated timer")
-
- try:
- if newEntry.isAutoTimer:
- print "[AutoTimer] Modifying existing AutoTimer!"
- except AttributeError, ae:
- if config.plugins.autotimer.refresh.value != "all":
- raise AutoTimerIgnoreTimerException("Won't modify existing timer because it's no timer set by us")
- print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"
-
- func = NavigationInstance.instance.RecordTimer.timeChanged
- modified += 1
-
- # Modify values saved in timer
- newEntry.name = name
- newEntry.description = description
- newEntry.begin = int(begin)
- newEntry.end = int(end)
- newEntry.service_ref = ServiceReference(serviceref)
+ for rtimer in recorddict.get(serviceref, ()):
+ if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
+ oldExists = True
+ # Abort if we don't want to modify timers or timer is repeated
+ if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
+ print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer"
break
- elif timer.getAvoidDuplicateDescription() and rtimer.description == description:
- raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
- except AutoTimerIgnoreTimerException, etite:
- print etite
- continue
+ if hasattr(rtimer, "isAutoTimer"):
+ print "[AutoTimer] Modifying existing AutoTimer!"
+ else:
+ if config.plugins.autotimer.refresh.value != "all":
+ print "[AutoTimer] Won't modify existing timer because it's no timer set by us"
+ break
+
+ print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"
+
+ newEntry = rtimer
+ modified += 1
+
+ # Modify values saved in timer
+ newEntry.name = name
+ newEntry.description = description
+ newEntry.begin = int(begin)
+ newEntry.end = int(end)
+ newEntry.service_ref = ServiceReference(serviceref)
- # Event not yet in Timers
+ break
+ elif timer.avoidDuplicateDescription == 1 and rtimer.description == description:
+ oldExists = True
+ print "[AutoTimer] We found a timer with same description, skipping event"
+ break
+
+ # We found no timer we want to edit
if newEntry is None:
- if timer.checkCounter(timestamp):
+ # But there is a match
+ if oldExists:
continue
- new += 1
+ # We want to search for possible doubles
+ if timer.avoidDuplicateDescription == 2:
+ # I thinks thats the fastest way to do this, though it's a little ugly
+ try:
+ for list in recorddict.values():
+ for rtimer in list:
+ if rtimer.description == description:
+ raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
+ except AutoTimerIgnoreTimerException, etite:
+ print etite
+ continue
+
+ if timer.checkCounter(timestamp):
+ continue
print "[AutoTimer] Adding an event."
newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit)
- func = NavigationInstance.instance.RecordTimer.record
# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
newEntry.isAutoTimer = True
if afterEvent is not None:
newEntry.afterEvent = afterEvent
- # Set custom destination directory (needs my Location-select patch)
- if timer.hasDestination():
- # TODO: add warning when patch not installed?
- newEntry.dirname = timer.destination
-
- # Make this timer a zap-timer if wanted
- newEntry.justplay = timer.justplay
-
- # Do a sanity check, although it does not do much right now
- timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, newEntry)
- if not timersanitycheck.check():
- print "[Autotimer] Sanity check failed"
- else:
- print "[Autotimer] Sanity check passed"
-
- # Either add to List or change time
- func(newEntry)
-
- return (total, new, modified, timers)
\ No newline at end of file
+ newEntry.dirname = timer.destination
+ newEntry.justplay = timer.justplay
+ newEntry.tags = timer.tags
+
+ if oldExists:
+ # XXX: this won't perform a sanity check, but do we actually want to do so?
+ NavigationInstance.instance.RecordTimer.timeChanged(newEntry)
+ else:
+ conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
+ if conflicts and config.plugins.autotimer.disabled_on_conflict.value:
+ 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 = NavigationInstance.instance.RecordTimer.record(newEntry)
+ if conflicts is None:
+ timer.decrementCounter()
+ new += 1
+ recorddict.setdefault(serviceref, []).append(newEntry)
+
+ return (total, new, modified, timers)
+