don't apply enigma2 margin for recordings if a custom offset is set (update sample...
[enigma2-plugins.git] / autotimer / src / AutoTimer.py
1 # Plugins Config
2 from xml.dom.minidom import parse as minidom_parse
3 from Tools.XMLTools import stringToXML
4 from os import path as os_path
5
6 # Navigation (RecordTimer)
7 import NavigationInstance
8
9 # Timer
10 from ServiceReference import ServiceReference
11 from RecordTimer import RecordTimerEntry, AFTEREVENT
12 from Components.TimerSanityCheck import TimerSanityCheck
13
14 # Timespan
15 from time import localtime, time
16
17 # EPGCache & Event
18 from enigma import eEPGCache, eServiceReference
19
20 # Enigma2 Config
21 from Components.config import config
22
23 # AutoTimer Component
24 from AutoTimerComponent import AutoTimerComponent
25
26 XML_CONFIG = "/etc/enigma2/autotimer.xml"
27 CURRENT_CONFIG_VERSION = "5"
28
29 def getValue(definitions, default):
30         # Initialize Output
31         ret = ""
32
33         # How many definitions are present
34         try:
35                 childNodes = definitions.childNodes
36         except:
37                 Len = len(definitions)
38                 if Len > 0:
39                         childNodes = definitions[Len-1].childNodes
40                 else:
41                         childNodes = []
42
43         # Iterate through nodes of last one
44         for node in childNodes:
45                 # Append text if we have a text node
46                 if node.nodeType == node.TEXT_NODE:
47                         ret = ret + node.data
48
49         # Return stripped output or (if empty) default
50         return ret.strip() or default
51
52 def getTimeDiff(timer, begin, end):
53         if begin <= timer.begin <= end:
54                 return end - timer.begin
55         elif timer.begin <= begin <= timer.end:
56                 return timer.end - begin
57         return 0
58
59 class AutoTimerIgnoreTimerException(Exception):
60         def __init__(self, cause):
61                 self.cause = cause
62
63         def __str__(self):
64                 return "[AutoTimer] " + str(self.cause)
65
66         def __repr__(self):
67                 return str(type(self))
68
69 class AutoTimer:
70         """Read and save xml configuration, query EPGCache"""
71
72         def __init__(self):
73                 # Keep EPGCache
74                 self.epgcache = eEPGCache.getInstance()
75
76                 # Initialize
77                 self.timers = []
78                 self.configMtime = -1
79                 self.uniqueTimerId = 0
80
81         def readXml(self):
82                 # Abort if no config found
83                 if not os_path.exists(XML_CONFIG):
84                         return
85
86                 # Parse if mtime differs from whats saved
87                 mtime = os_path.getmtime(XML_CONFIG)
88                 if mtime == self.configMtime:
89                         print "[AutoTimer] No changes in configuration, won't parse"
90                         return
91
92                 # Save current mtime
93                 self.configMtime = mtime
94
95                 # Parse Config
96                 dom = minidom_parse(XML_CONFIG)
97                 
98                 # Empty out timers and reset Ids
99                 del self.timers[:]
100                 self.uniqueTimerId = 0
101                 self.defaultTimer = AutoTimerComponent(
102                         0,              # Id
103                         "",             # Name
104                         "",             # Match
105                         True    # Enabled
106                 )
107
108                 # Get Config Element
109                 for configuration in dom.getElementsByTagName("autotimer"):
110                         # Parse old configuration files
111                         if configuration.getAttribute("version") != CURRENT_CONFIG_VERSION:
112                                 from OldConfigurationParser import parseConfig
113                                 parseConfig(configuration, self.timers, configuration.getAttribute("version"), self.uniqueTimerId)
114                                 if not self.uniqueTimerId:
115                                         self.uniqueTimerId = len(self.timers)
116                                 continue
117                         # Read in defaults for a new timer
118                         for defaults in configuration.getElementsByTagName("defaults"):
119                                 # Read out timespan
120                                 start = defaults.getAttribute("from")
121                                 end = defaults.getAttribute("to")
122                                 if start and end:
123                                         start = [int(x) for x in start.split(':')]
124                                         end = [int(x) for x in end.split(':')]
125                                         self.defaultTimer.timespan = (start, end)
126
127                                 # Read out max length
128                                 maxduration = defaults.getAttribute("maxduration") or None
129                                 if maxduration:
130                                         self.defaultTimer.maxduration = int(maxlen)*60
131
132                                 # Read out recording path
133                                 self.defaultTimer.destination = defaults.getAttribute("location").encode("UTF-8") or None
134
135                                 # Read out offset
136                                 offset = defaults.getAttribute("offset") or None
137                                 if offset:
138                                         offset = offset.split(",")
139                                         if len(offset) == 1:
140                                                 before = after = int(offset[0] or 0) * 60
141                                         else:
142                                                 before = int(offset[0] or 0) * 60
143                                                 after = int(offset[1] or 0) * 60
144                                         self.defaultTimer.offset = (before, after)
145
146                                 # Read out counter
147                                 self.defaultTimer.matchCount = int(defaults.getAttribute("counter") or '0')
148                                 self.defaultTimer.matchFormatString = defaults.getAttribute("counterFormat")
149
150                                 # Read out justplay
151                                 justplay = int(defaults.getAttribute("justplay") or '0')
152
153                                 # Read out avoidDuplicateDescription
154                                 self.defaultTimer.avoidDuplicateDescription = bool(defaults.getAttribute("avoidDuplicateDescription") or False)
155
156                                 # Read out allowed services
157                                 servicelist = self.defaultTimer.services        
158                                 for service in defaults.getElementsByTagName("serviceref"):
159                                         value = getValue(service, None)
160                                         if value:
161                                                 # strip all after last :
162                                                 pos = value.rfind(':')
163                                                 if pos != -1:
164                                                         value = value[:pos+1]
165
166                                                 servicelist.append(value)
167                                 self.defaultTimer.services = servicelist # We might have got a dummy list above
168
169                                 # Read out allowed bouquets
170                                 bouquets = self.defaultTimer.bouquets
171                                 for bouquet in defaults.getElementsByTagName("bouquet"):
172                                         value = getValue(bouquet, None)
173                                         if value:
174                                                 bouquets.append(value)
175                                 self.defaultTimer.bouquets = bouquets
176
177                                 # Read out afterevent
178                                 idx = {"none": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY}
179                                 afterevent = self.defaultTimer.afterevent
180                                 for element in defaults.getElementsByTagName("afterevent"):
181                                         value = getValue(element, None)
182
183                                         try:
184                                                 value = idx[value]
185                                                 start = element.getAttribute("from")
186                                                 end = element.getAttribute("to")
187                                                 if start and end:
188                                                         start = [int(x) for x in start.split(':')]
189                                                         end = [int(x) for x in end.split(':')]
190                                                         afterevent.append((value, (start, end)))
191                                                 else:
192                                                         afterevent.append((value, None))
193                                         except KeyError, ke:
194                                                 print '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition'
195                                                 continue
196                                 self.defaultTimer.afterevent = afterevent
197
198                                 # Read out exclude
199                                 idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
200                                 excludes = (self.defaultTimer.getExcludedTitle(), self.defaultTimer.getExcludedShort(), self.defaultTimer.getExcludedDescription(), self.defaultTimer.getExcludedDays()) 
201                                 for exclude in defaults.getElementsByTagName("exclude"):
202                                         where = exclude.getAttribute("where")
203                                         value = getValue(exclude, None)
204                                         if not (value and where):
205                                                 continue
206
207                                         try:
208                                                 excludes[idx[where]].append(value.encode("UTF-8"))
209                                         except KeyError, ke:
210                                                 pass
211                                 self.defaultTimer.excludes = excludes
212
213                                 # Read out includes (use same idx)
214                                 includes = (self.defaultTimer.getIncludedTitle(), self.defaultTimer.getIncludedShort(), self.defaultTimer.getIncludedDescription(), self.defaultTimer.getIncludedDays())
215                                 for include in defaults.getElementsByTagName("include"):
216                                         where = include.getAttribute("where")
217                                         value = getValue(include, None)
218                                         if not (value and where):
219                                                 continue
220
221                                         try:
222                                                 includes[idx[where]].append(value.encode("UTF-8"))
223                                         except KeyError, ke:
224                                                 pass
225                                 self.defaultTimer.includes = includes
226
227                                 # Read out recording tags (needs my enhanced tag support patch)
228                                 tags = self.defaultTimer.tags
229                                 for tag in defaults.getElementsByTagName("tag"):
230                                         value = getValue(tag, None)
231                                         if not value:
232                                                 continue
233
234                                         tags.append(value.encode("UTF-8"))
235
236                         # Iterate Timers
237                         for timer in configuration.getElementsByTagName("timer"):
238                                 # Increment uniqueTimerId
239                                 self.uniqueTimerId += 1
240
241                                 # Read out match
242                                 match = timer.getAttribute("match").encode("UTF-8")
243                                 if not match:
244                                         print '[AutoTimer] Erroneous config is missing attribute "match", skipping entry'
245                                         continue
246
247                                 # Read out name
248                                 name = timer.getAttribute("name").encode("UTF-8")
249                                 if not name:
250                                         print '[AutoTimer] Timer is missing attribute "name", defaulting to match'
251                                         name = match
252
253                                 # Read out enabled
254                                 enabled = timer.getAttribute("enabled") or "yes"
255                                 if enabled == "no":
256                                         enabled = False
257                                 elif enabled == "yes":
258                                         enabled = True
259                                 else:
260                                         print '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', disabling'
261                                         enabled = False
262
263                                 # Read out timespan
264                                 start = timer.getAttribute("from")
265                                 end = timer.getAttribute("to")
266                                 if start and end:
267                                         start = [int(x) for x in start.split(':')]
268                                         end = [int(x) for x in end.split(':')]
269                                         timetuple = (start, end)
270                                 else:
271                                         timetuple = None
272
273                                 # Read out max length
274                                 maxlen = timer.getAttribute("maxduration") or None
275                                 if maxlen:
276                                         maxlen = int(maxlen)*60
277
278                                 # Read out recording path
279                                 destination = timer.getAttribute("location").encode("UTF-8") or None
280
281                                 # Read out offset
282                                 offset = timer.getAttribute("offset") or None
283                                 if offset:
284                                         offset = offset.split(",")
285                                         if len(offset) == 1:
286                                                 before = after = int(offset[0] or 0) * 60
287                                         else:
288                                                 before = int(offset[0] or 0) * 60
289                                                 after = int(offset[1] or 0) * 60
290                                         offset = (before, after)
291
292                                 # Read out counter
293                                 counter = int(timer.getAttribute("counter") or '0')
294                                 counterLeft = int(timer.getAttribute("left") or counter)
295                                 counterLimit = timer.getAttribute("lastActivation")
296                                 counterFormat = timer.getAttribute("counterFormat")
297                                 lastBegin = int(timer.getAttribute("lastBegin") or 0)
298
299                                 # Read out justplay
300                                 justplay = int(timer.getAttribute("justplay") or '0')
301
302                                 # Read out avoidDuplicateDescription
303                                 avoidDuplicateDescription = bool(timer.getAttribute("avoidDuplicateDescription") or False)
304
305                                 # Read out allowed services
306                                 servicelist = []                                        
307                                 for service in timer.getElementsByTagName("serviceref"):
308                                         value = getValue(service, None)
309                                         if value:
310                                                 # strip all after last :
311                                                 pos = value.rfind(':')
312                                                 if pos != -1:
313                                                         value = value[:pos+1]
314
315                                                 servicelist.append(value)
316
317                                 # Read out allowed bouquets
318                                 bouquets = []
319                                 for bouquet in timer.getElementsByTagName("bouquet"):
320                                         value = getValue(bouquet, None)
321                                         if value:
322                                                 bouquets.append(value)
323
324                                 # Read out afterevent
325                                 idx = {"none": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "shutdown": AFTEREVENT.DEEPSTANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY}
326                                 afterevent = []
327                                 for element in timer.getElementsByTagName("afterevent"):
328                                         value = getValue(element, None)
329
330                                         try:
331                                                 value = idx[value]
332                                                 start = element.getAttribute("from")
333                                                 end = element.getAttribute("to")
334                                                 if start and end:
335                                                         start = [int(x) for x in start.split(':')]
336                                                         end = [int(x) for x in end.split(':')]
337                                                         afterevent.append((value, (start, end)))
338                                                 else:
339                                                         afterevent.append((value, None))
340                                         except KeyError, ke:
341                                                 print '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition'
342                                                 continue
343
344                                 # Read out exclude
345                                 idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
346                                 excludes = ([], [], [], []) 
347                                 for exclude in timer.getElementsByTagName("exclude"):
348                                         where = exclude.getAttribute("where")
349                                         value = getValue(exclude, None)
350                                         if not (value and where):
351                                                 continue
352
353                                         try:
354                                                 excludes[idx[where]].append(value.encode("UTF-8"))
355                                         except KeyError, ke:
356                                                 pass
357
358                                 # Read out includes (use same idx)
359                                 includes = ([], [], [], []) 
360                                 for include in timer.getElementsByTagName("include"):
361                                         where = include.getAttribute("where")
362                                         value = getValue(include, None)
363                                         if not (value and where):
364                                                 continue
365
366                                         try:
367                                                 includes[idx[where]].append(value.encode("UTF-8"))
368                                         except KeyError, ke:
369                                                 pass
370
371                                 # Read out recording tags (needs my enhanced tag support patch)
372                                 tags = []
373                                 for tag in timer.getElementsByTagName("tag"):
374                                         value = getValue(tag, None)
375                                         if not value:
376                                                 continue
377
378                                         tags.append(value.encode("UTF-8"))
379
380                                 # Finally append tuple
381                                 self.timers.append(AutoTimerComponent(
382                                                 self.uniqueTimerId,
383                                                 name,
384                                                 match,
385                                                 enabled,
386                                                 timespan = timetuple,
387                                                 services = servicelist,
388                                                 offset = offset,
389                                                 afterevent = afterevent,
390                                                 exclude = excludes,
391                                                 include = includes,
392                                                 maxduration = maxlen,
393                                                 destination = destination,
394                                                 matchCount = counter,
395                                                 matchLeft = counterLeft,
396                                                 matchLimit = counterLimit,
397                                                 matchFormatString = counterFormat,
398                                                 lastBegin = lastBegin,
399                                                 justplay = justplay,
400                                                 avoidDuplicateDescription = avoidDuplicateDescription,
401                                                 bouquets = bouquets,
402                                                 tags = tags
403                                 ))
404
405         def getTimerList(self):
406                 return self.timers
407
408         def getEnabledTimerList(self):
409                 return [x for x in self.timers if x.enabled]
410
411         def getTupleTimerList(self):
412                 return [(x,) for x in self.timers]
413
414         def getUniqueId(self):
415                 self.uniqueTimerId += 1
416                 return self.uniqueTimerId
417
418         def add(self, timer):
419                 self.timers.append(timer)
420
421         def set(self, timer):
422                 idx = 0
423                 for stimer in self.timers:
424                         if stimer == timer:
425                                 self.timers[idx] = timer
426                                 return
427                         idx += 1
428                 self.timers.append(timer)
429
430         def remove(self, uniqueId):
431                 idx = 0
432                 for timer in self.timers:
433                         if timer.id == uniqueId:
434                                 self.timers.pop(idx)
435                                 return
436                         idx += 1
437
438         def writeXml(self):
439                 # Generate List in RAM
440                 list = ['<?xml version="1.0" ?>\n<autotimer version="', CURRENT_CONFIG_VERSION, '">\n\n']
441
442                 # XXX: we might want to make sure that we don't save empty default here
443                 list.extend([' <defaults'])
444
445                 # Timespan
446                 if self.defaultTimer.hasTimespan():
447                         list.extend([' from="', self.defaultTimer.getTimespanBegin(), '" to="', self.defaultTimer.getTimespanEnd(), '"'])
448
449                 # Duration
450                 if self.defaultTimer.hasDuration():
451                         list.extend([' maxduration="', str(self.defaultTimer.getDuration()), '"'])
452
453                 # Destination
454                 if self.defaultTimer.hasDestination():
455                         list.extend([' destination="', stringToXML(self.defaultTimer.destination), '"'])
456
457                 # Offset
458                 if self.defaultTimer.hasOffset():
459                         if self.defaultTimer.isOffsetEqual():
460                                 list.extend([' offset="', str(self.defaultTimer.getOffsetBegin()), '"'])
461                         else:
462                                 list.extend([' offset="', str(self.defaultTimer.getOffsetBegin()), ',', str(self.defaultTimer.getOffsetEnd()), '"'])
463
464                 # Counter
465                 if self.defaultTimer.hasCounter():
466                         list.extend([' counter="', str(self.defaultTimer.getCounter()), '"'])
467                         if self.defaultTimer.hasCounterFormatString():
468                                 list.extend([' counterFormat="', str(self.defaultTimer.getCounterFormatString()), '"'])
469
470                 # Duplicate Description
471                 if self.defaultTimer.getAvoidDuplicateDescription():
472                         list.append(' avoidDuplicateDescription="1" ')
473
474                 # Only display justplay if true
475                 if self.defaultTimer.justplay:
476                         list.extend([' justplay="', str(self.defaultTimer.getJustplay()), '"'])
477
478                 # Close still opened defaults tag
479                 list.append('>\n')
480
481                 # Services
482                 for serviceref in self.defaultTimer.getServices():
483                         list.extend(['  <serviceref>', serviceref, '</serviceref>'])
484                         ref = ServiceReference(str(serviceref))
485                         list.extend([' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'])
486
487                 # Bouquets
488                 for bouquet in self.defaultTimer.getBouquets():
489                         list.extend(['  <bouquet>', str(bouquet), '</bouquet>'])
490                         ref = ServiceReference(str(bouquet))
491                         list.extend([' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'])
492
493                 # AfterEvent
494                 if self.defaultTimer.hasAfterEvent():
495                         idx = {AFTEREVENT.NONE: "none", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "shutdown"}
496                         for afterevent in self.defaultTimer.getCompleteAfterEvent():
497                                 action, timespan = afterevent
498                                 list.append('  <afterevent')
499                                 if timespan[0] is not None:
500                                         list.append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
501                                 list.extend(['>', idx[action], '</afterevent>\n'])
502
503                 # Excludes
504                 for title in self.defaultTimer.getExcludedTitle():
505                         list.extend(['  <exclude where="title">', stringToXML(title), '</exclude>\n'])
506                 for short in self.defaultTimer.getExcludedShort():
507                         list.extend(['  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'])
508                 for desc in self.defaultTimer.getExcludedDescription():
509                         list.extend(['  <exclude where="description">', stringToXML(desc), '</exclude>\n'])
510                 for day in self.defaultTimer.getExcludedDays():
511                         list.extend(['  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'])
512
513                 # Includes
514                 for title in self.defaultTimer.getIncludedTitle():
515                         list.extend(['  <include where="title">', stringToXML(title), '</include>\n'])
516                 for short in self.defaultTimer.getIncludedShort():
517                         list.extend(['  <include where="shortdescription">', stringToXML(short), '</include>\n'])
518                 for desc in self.defaultTimer.getIncludedDescription():
519                         list.extend(['  <include where="description">', stringToXML(desc), '</include>\n'])
520                 for day in self.defaultTimer.getIncludedDays():
521                         list.extend(['  <include where="dayofweek">', stringToXML(day), '</include>\n'])
522
523                 # Tags
524                 for tag in self.defaultTimer.tags:
525                         list.extend(['  <tag>', stringToXML(tag), '</tag>\n'])
526
527                 # End of Timer
528                 list.append(' </defaults>\n\n')
529
530                 # Iterate timers
531                 for timer in self.timers:
532                         # Common attributes (match, enabled)
533                         list.extend([' <timer name="', stringToXML(timer.name), '" match="', stringToXML(timer.match), '" enabled="', timer.getEnabled(), '"'])
534
535                         # Timespan
536                         if timer.hasTimespan():
537                                 list.extend([' from="', timer.getTimespanBegin(), '" to="', timer.getTimespanEnd(), '"'])
538
539                         # Duration
540                         if timer.hasDuration():
541                                 list.extend([' maxduration="', str(timer.getDuration()), '"'])
542
543                         # Destination
544                         if timer.hasDestination():
545                                 list.extend([' destination="', stringToXML(timer.destination), '"'])
546
547                         # Offset
548                         if timer.hasOffset():
549                                 if timer.isOffsetEqual():
550                                         list.extend([' offset="', str(timer.getOffsetBegin()), '"'])
551                                 else:
552                                         list.extend([' offset="', str(timer.getOffsetBegin()), ',', str(timer.getOffsetEnd()), '"'])
553
554                         # Counter
555                         if timer.hasCounter():
556                                 list.extend([' lastBegin="', str(timer.getLastBegin()), '" counter="', str(timer.getCounter()), '" left="', str(timer.getCounterLeft()) ,'"'])
557                                 if timer.hasCounterFormatString():
558                                         list.extend([' lastActivation="', str(timer.getCounterLimit()), '"'])
559                                         list.extend([' counterFormat="', str(timer.getCounterFormatString()), '"'])
560
561                         # Duplicate Description
562                         if timer.getAvoidDuplicateDescription():
563                                 list.append(' avoidDuplicateDescription="1" ')
564
565                         # Only display justplay if true
566                         if timer.justplay:
567                                 list.extend([' justplay="', str(timer.getJustplay()), '"'])
568
569                         # Close still opened timer tag
570                         list.append('>\n')
571
572                         # Services
573                         for serviceref in timer.getServices():
574                                 list.extend(['  <serviceref>', serviceref, '</serviceref>'])
575                                 ref = ServiceReference(str(serviceref))
576                                 list.extend([' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'])
577
578                         # Bouquets
579                         for bouquet in timer.getBouquets():
580                                 list.extend(['  <bouquet>', str(bouquet), '</bouquet>'])
581                                 ref = ServiceReference(str(bouquet))
582                                 list.extend([' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'])
583
584                         # AfterEvent
585                         if timer.hasAfterEvent():
586                                 idx = {AFTEREVENT.NONE: "none", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "shutdown"}
587                                 for afterevent in timer.getCompleteAfterEvent():
588                                         action, timespan = afterevent
589                                         list.append('  <afterevent')
590                                         if timespan[0] is not None:
591                                                 list.append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
592                                         list.extend(['>', idx[action], '</afterevent>\n'])
593
594                         # Excludes
595                         for title in timer.getExcludedTitle():
596                                 list.extend(['  <exclude where="title">', stringToXML(title), '</exclude>\n'])
597                         for short in timer.getExcludedShort():
598                                 list.extend(['  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'])
599                         for desc in timer.getExcludedDescription():
600                                 list.extend(['  <exclude where="description">', stringToXML(desc), '</exclude>\n'])
601                         for day in timer.getExcludedDays():
602                                 list.extend(['  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'])
603
604                         # Includes
605                         for title in timer.getIncludedTitle():
606                                 list.extend(['  <include where="title">', stringToXML(title), '</include>\n'])
607                         for short in timer.getIncludedShort():
608                                 list.extend(['  <include where="shortdescription">', stringToXML(short), '</include>\n'])
609                         for desc in timer.getIncludedDescription():
610                                 list.extend(['  <include where="description">', stringToXML(desc), '</include>\n'])
611                         for day in timer.getIncludedDays():
612                                 list.extend(['  <include where="dayofweek">', stringToXML(day), '</include>\n'])
613
614                         # Tags
615                         for tag in timer.tags:
616                                 list.extend(['  <tag>', stringToXML(tag), '</tag>\n'])
617
618                         # End of Timer
619                         list.append(' </timer>\n\n')
620
621                 # End of Configuration
622                 list.append('</autotimer>\n')
623
624                 # Save to Flash
625                 file = open(XML_CONFIG, 'w')
626                 file.writelines(list)
627
628                 file.close()
629
630         def parseEPG(self, simulateOnly = False):
631                 if NavigationInstance.instance is None:
632                         print "[AutoTimer] Navigation is not available, can't parse EPG"
633                         return (0, 0, 0, [])
634
635                 total = 0
636                 new = 0
637                 modified = 0
638                 timers = []
639
640                 self.readXml()
641
642                 # Save Recordings in a dict to speed things up a little
643                 # We include processed timers as we might search for duplicate descriptions
644                 recorddict = {}
645                 for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
646                         if not recorddict.has_key(str(timer.service_ref)):
647                                 recorddict[str(timer.service_ref)] = [timer]
648                         else:
649                                 recorddict[str(timer.service_ref)].append(timer)
650
651                 # Iterate Timer
652                 for timer in self.getEnabledTimerList():
653                         # Search EPG, default to empty list
654                         ret = self.epgcache.search(('RI', 100, eEPGCache.PARTIAL_TITLE_SEARCH, timer.match, eEPGCache.NO_CASE_CHECK)) or []
655
656                         for serviceref, eit in ret:
657                                 eserviceref = eServiceReference(serviceref)
658
659                                 evt = self.epgcache.lookupEventId(eserviceref, eit)
660                                 if not evt:
661                                         print "[AutoTimer] Could not create Event!"
662                                         continue
663
664                                 # Try to determine real service (we always choose the last one)
665                                 n = evt.getNumOfLinkageServices()
666                                 if n > 0:
667                                         i = evt.getLinkageService(eserviceref, n-1)
668                                         serviceref = i.toString()
669
670                                 # Gather Information
671                                 name = evt.getEventName()
672                                 description = evt.getShortDescription()
673                                 begin = evt.getBeginTime()
674                                 duration = evt.getDuration()
675                                 end = begin + duration
676
677                                 # If event starts in less than 60 seconds skip it
678                                 if begin < time() + 60:
679                                         continue
680
681                                 # Convert begin time
682                                 timestamp = localtime(begin)
683
684                                 # Update timer
685                                 timer.update(begin, timestamp)
686
687                                 # Check Duration, Timespan and Excludes
688                                 if timer.checkServices(serviceref) \
689                                         or timer.checkDuration(duration) \
690                                         or timer.checkTimespan(timestamp) \
691                                         or timer.checkFilter(name, description,
692                                                 evt.getExtendedDescription(), str(timestamp.tm_wday)):
693                                         continue
694
695                                 if timer.hasOffset():
696                                         # Apply custom Offset
697                                         begin, end = timer.applyOffset(begin, end)
698                                 else:
699                                         # Apply E2 Offset
700                                         begin -= config.recording.margin_before.value * 60
701                                         end += config.recording.margin_after.value * 60
702
703
704                                 total += 1
705
706                                 # Append to timerlist and abort if simulating
707                                 timers.append((name, begin, end, serviceref, timer.name))
708                                 if simulateOnly:
709                                         continue
710
711                                 # Initialize
712                                 newEntry = None
713
714                                 # Check for double Timers
715                                 # We first check eit and if user wants us to guess event based on time
716                                 # we try this as backup. The allowed diff should be configurable though.
717                                 try:
718                                         for rtimer in recorddict.get(serviceref, []):
719                                                 if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
720                                                         newEntry = rtimer
721
722                                                         # Abort if we don't want to modify timers or timer is repeated
723                                                         if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
724                                                                 raise AutoTimerIgnoreTimerException("Won't modify existing timer because either no modification allowed or repeated timer")
725
726                                                         try:
727                                                                 if newEntry.isAutoTimer:
728                                                                         print "[AutoTimer] Modifying existing AutoTimer!"
729                                                         except AttributeError, ae:
730                                                                 if config.plugins.autotimer.refresh.value != "all":
731                                                                         raise AutoTimerIgnoreTimerException("Won't modify existing timer because it's no timer set by us")
732                                                                 print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"
733
734                                                         func = NavigationInstance.instance.RecordTimer.timeChanged
735                                                         modified += 1
736
737                                                         # Modify values saved in timer
738                                                         newEntry.name = name
739                                                         newEntry.description = description
740                                                         newEntry.begin = int(begin)
741                                                         newEntry.end = int(end)
742                                                         newEntry.service_ref = ServiceReference(serviceref)
743
744                                                         break
745                                                 elif timer.getAvoidDuplicateDescription() and rtimer.description == description:
746                                                         raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
747
748                                 except AutoTimerIgnoreTimerException, etite:
749                                         print etite
750                                         continue
751
752                                 # Event not yet in Timers
753                                 if newEntry is None:
754                                         if timer.checkCounter(timestamp):
755                                                 continue
756
757                                         new += 1
758
759                                         print "[AutoTimer] Adding an event."
760                                         newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit)
761                                         func = NavigationInstance.instance.RecordTimer.record
762
763                                         # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
764                                         newEntry.isAutoTimer = True
765
766                                 # Apply afterEvent
767                                 if timer.hasAfterEvent():
768                                         afterEvent = timer.getAfterEventTimespan(localtime(end))
769                                         if afterEvent is None:
770                                                 afterEvent = timer.getAfterEvent()
771                                         if afterEvent is not None:
772                                                 newEntry.afterEvent = afterEvent
773
774                                 newEntry.dirname = timer.destination
775                                 newEntry.justplay = timer.justplay
776                                 newEntry.tags = timer.tags # This needs my enhanced tag support patch to work
777  
778                                 # Do a sanity check, although it does not do much right now
779                                 timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, newEntry)
780                                 if not timersanitycheck.check():
781                                         print "[Autotimer] Sanity check failed"
782                                 else:
783                                         print "[Autotimer] Sanity check passed"
784
785                                 # Either add to List or change time
786                                 func(newEntry)
787
788                 return (total, new, modified, timers)