some more ugly hacks for renamed services (so much hasssle for something that useless...)
[enigma2-plugins.git] / autotimer / src / AutoTimerConfiguration.py
1 # -*- coding: UTF-8 -*-
2 # for localized messages
3 from . import _
4
5 from AutoTimerComponent import AutoTimerComponent
6 from RecordTimer import AFTEREVENT
7 from Tools.XMLTools import stringToXML
8 from ServiceReference import ServiceReference
9
10 from enigma import eServiceReference
11
12 CURRENT_CONFIG_VERSION = "5"
13
14 def getValue(definitions, default):
15         # Initialize Output
16         ret = ""
17
18         # How many definitions are present
19         if isinstance(definitions, list):
20                 Len = len(definitions)
21                 if Len > 0:
22                         childNodes = definitions[Len-1].text
23                 else:
24                         childNodes = ""
25         else:
26                 ret = definitions.text
27
28         # Return stripped output or (if empty) default
29         return ret.strip() or default
30
31 def parseConfig(configuration, list, version = None, uniqueTimerId = 0, defaultTimer = None):
32         if version != CURRENT_CONFIG_VERSION:
33                 parseConfigOld(configuration, list, uniqueTimerId)
34                 return
35
36         if defaultTimer is not None:
37                 # Read in defaults for a new timer
38                 for defaults in configuration.findall("defaults"):
39                         parseEntry(defaults, defaultTimer, True)
40
41         for timer in configuration.findall("timer"):
42                 uniqueTimerId += 1
43                 baseTimer = AutoTimerComponent(
44                         uniqueTimerId,
45                         '',
46                         '',
47                         True
48                 )
49
50                 if parseEntry(timer, baseTimer):
51                         list.append(baseTimer)
52
53 def parseEntry(element, baseTimer, defaults = False):
54         if not defaults:
55                 # Read out match
56                 baseTimer.match = element.get("match", "").encode("UTF-8")
57                 if not baseTimer.match:
58                         print '[AutoTimer] Erroneous config is missing attribute "match", skipping entry'
59                         return False
60
61                 # Read out name
62                 baseTimer.name = element.get("name", "").encode("UTF-8")
63                 if not baseTimer.name:
64                         print '[AutoTimer] Timer is missing attribute "name", defaulting to match'
65                         baseTimer.name = baseTimer.match
66
67                 # Read out enabled
68                 enabled = element.get("enabled", "yes")
69                 if enabled == "no":
70                         baseTimer.enabled = False
71                 elif enabled == "yes":
72                         baseTimer.enabled = True
73                 else:
74                         print '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', disabling'
75                         baseTimer.enabled = False
76
77         # Read out encoding (won't change if no value is set)
78         baseTimer.encoding = element.get("encoding")
79
80         # Read out search type/case
81         baseTimer.searchType = element.get("searchType", baseTimer.searchType)
82         baseTimer.searchCase = element.get("searchCase", baseTimer.searchCase)
83
84         # Read out if we should change to alternative services
85         baseTimer.overrideAlternatives = int(element.get("overrideAlternatives", baseTimer.overrideAlternatives))
86
87         # Read out timespan
88         start = element.get("from")
89         end = element.get("to")
90         if start and end:
91                 start = [int(x) for x in start.split(':')]
92                 end = [int(x) for x in end.split(':')]
93                 baseTimer.timespan = (start, end)
94
95         # Read out max length
96         maxduration = element.get("maxduration")
97         if maxduration:
98                 baseTimer.maxduration = int(maxduration)*60
99
100         # Read out recording path
101         default = baseTimer.destination or ""
102         baseTimer.destination = element.get("location", default).encode("UTF-8") or None
103
104         # Read out offset
105         offset = element.get("offset")
106         if offset:
107                 offset = offset.split(",")
108                 if len(offset) == 1:
109                         before = after = int(offset[0] or 0) * 60
110                 else:
111                         before = int(offset[0] or 0) * 60
112                         after = int(offset[1] or 0) * 60
113                 baseTimer.offset = (before, after)
114
115         # Read out counter
116         baseTimer.matchCount = int(element.get("counter", 0))
117         baseTimer.matchFormatString = element.get("counterFormat", "")
118         if not defaults:
119                 baseTimer.matchLeft = int(element.get("left", baseTimer.matchCount))
120                 baseTimer.matchLimit = element.get("lastActivation", "")
121                 baseTimer.lastBegin = int(element.get("lastBegin", 0))
122
123         # Read out justplay
124         baseTimer.justplay = int(element.get("justplay", 0))
125
126         # Read out avoidDuplicateDescription
127         baseTimer.avoidDuplicateDescription = int(element.get("avoidDuplicateDescription", 0))
128
129         # Read out allowed services
130         l = element.findall("serviceref")
131         if l:
132                 servicelist = []
133
134                 for service in l:
135                         value = service.text
136                         if value:
137                                 myref = eServiceReference(str(value))
138                                 if not (myref.flags & eServiceReference.isGroup):
139                                         # strip all after last :
140                                         pos = value.rfind(':')
141                                         if pos != -1:
142                                                 if value[pos-1] == ':'
143                                                         pos -= 1
144                                                 value = value[:pos+1]
145
146                                 servicelist.append(value)
147                 baseTimer.services = servicelist
148
149         # Read out allowed bouquets
150         l = element.findall("bouquet")
151         if l:
152                 bouquets = []
153                 for bouquet in l:
154                         value = bouquet.text
155                         if value:
156                                 bouquets.append(value)
157                 baseTimer.bouquets = bouquets
158
159         # Read out afterevent
160         l = element.findall("afterevent")
161         if l:
162                 idx = {
163                         "none": AFTEREVENT.NONE,
164                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
165                         "shutdown": AFTEREVENT.DEEPSTANDBY,
166                         "standby": AFTEREVENT.STANDBY,
167                         "auto": AFTEREVENT.AUTO
168                 }
169                 afterevents = []
170                 for afterevent in l:
171                         value = afterevent.text
172
173                         if idx.has_key(value):
174                                 value = idx[value]
175                         else:
176                                 print '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition'
177                                 continue
178
179                         start = afterevent.get("from")
180                         end = afterevent.get("to")
181                         if start and end:
182                                 start = [int(x) for x in start.split(':')]
183                                 end = [int(x) for x in end.split(':')]
184                                 afterevents.append((value, (start, end)))
185                         else:
186                                 afterevents.append((value, None))
187                 baseTimer.afterevent = afterevents
188
189         # Read out exclude
190         l = element.findall("exclude")
191         idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
192         if l:
193                 excludes = ([], [], [], [])
194                 for exclude in l:
195                         where = exclude.get("where")
196                         value = exclude.text
197                         if not (value and where):
198                                 continue
199
200                         if idx.has_key(where):
201                                 excludes[idx[where]].append(value.encode("UTF-8"))
202                 baseTimer.exclude = excludes
203
204         # Read out includes (use same idx)
205         l = element.findall("include")
206         if l:
207                 includes = ([], [], [], [])
208                 for include in l:
209                         where = include.get("where")
210                         value = include.text
211                         if not (value and where):
212                                 continue
213
214                         if idx.has_key(where):
215                                 includes[idx[where]].append(value.encode("UTF-8"))
216                 baseTimer.include = includes
217
218         # Read out recording tags
219         l =  element.findall("tag")
220         if l:
221                 tags = []
222                 for tag in l:
223                         value = tag.text
224                         if not value:
225                                 continue
226
227                         tags.append(value.encode("UTF-8"))
228                 baseTimer.tags = tags
229
230         return True
231
232 def parseConfigOld(configuration, list, uniqueTimerId = 0):
233         print "[AutoTimer] Trying to parse old config"
234
235         # Iterate Timers
236         for timer in configuration.findall("timer"):
237                 # Increment uniqueTimerId
238                 uniqueTimerId += 1
239
240                 # Get name (V2+)
241                 name = timer.get("name")
242                 if name:
243                         name = name.encode("UTF-8")
244                 # Get name (= match) (V1)
245                 else:
246                         # Read out name
247                         name = getValue(timer.findall("name"), "").encode("UTF-8")
248
249                 if not name:
250                         print '[AutoTimer] Erroneous config is missing attribute "name", skipping entry'
251                         continue
252
253                 # Read out match (V3+)
254                 match = timer.get("match")
255                 if match:
256                         # Read out match
257                         match = match.encode("UTF-8")
258                         if not match:
259                                 print '[AutoTimer] Erroneous config contains empty attribute "match", skipping entry'
260                                 continue
261                 # V2-
262                 else:
263                         # Setting match to name
264                         match = name
265
266
267                 # See if Timer is ensabled (V2+)
268                 enabled = timer.get("enabled")
269                 if enabled:
270                         if enabled == "no":
271                                 enabled = False
272                         elif enabled == "yes":
273                                 enabled = True
274                         else:
275                                 print '[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', skipping entry'
276                                 enabled = False
277                 # V1
278                 else:
279                         elements = timer.findall("enabled")
280                         if elements:
281                                 if getValue(elements, "yes") == "no":
282                                         enabled = False
283                                 else:
284                                         enabled = True
285                         else:
286                                 enabled = True
287
288                 # Read out timespan (V4+; Falling back on missing definition should be OK)
289                 start = timer.get("from")
290                 end = timer.get("to")
291                 if start and end:
292                         start = [int(x) for x in start.split(':')]
293                         end = [int(x) for x in end.split(':')]
294                         timetuple = (start, end)
295                 # V3-
296                 else:
297                         elements = timer.findall("timespan")
298                         Len = len(elements)
299                         if Len:
300                                 # Read out last definition
301                                 start = elements[Len-1].get("from")
302                                 end = elements[Len-1].get("to")
303                                 if start and end:
304                                         start = [int(x) for x in start.split(':')]
305                                         end = [int(x) for x in end.split(':')]
306                                         timetuple = (start, end)
307                                 else:
308                                         print '[AutoTimer] Erroneous config contains invalid definition of "timespan", ignoring definition'
309                                         timetuple = None
310                         else:
311                                 timetuple = None
312
313                 # Read out allowed services (V*)
314                 elements = timer.findall("serviceref")
315                 if elements:
316                         servicelist = []
317                         for service in elements:
318                                 value = service.text
319                                 if value:
320                                         myref = eServiceReference(str(value))
321                                         if not (myref.flags & eServiceReference.isGroup):
322                                                 # strip all after last :
323                                                 pos = value.rfind(':')
324                                                 if pos != -1:
325                                                         if value[pos-1] == ':'
326                                                                 pos -= 1
327                                                         value = value[:pos+1]
328
329                                         servicelist.append(value)
330                 else:
331                         servicelist = None
332
333                 # Read out allowed bouquets (V* though officially supported since V4)
334                 bouquets = []
335                 for bouquet in timer.findall("bouquet"):
336                         value = bouquet.text
337                         if value:
338                                 bouquets.append(value)
339
340                 # Read out offset (V4+)
341                 offset = timer.get("offset")
342                 if offset:
343                         offset = offset.split(",")
344                         if len(offset) == 1:
345                                 before = after = int(offset[0] or 0) * 60
346                         else:
347                                 before = int(offset[0] or 0) * 60
348                                 after = int(offset[1] or 0) * 60
349                         offset = (before, after)
350                 # V3-
351                 else:
352                         elements = timer.findall("offset")
353                         Len = len(elements)
354                         if Len:
355                                 value = elements[Len-1].get("both")
356                                 if value == '':
357                                         before = int(elements[Len-1].get("before", 0)) * 60
358                                         after = int(elements[Len-1].get("after", 0)) * 60
359                                 else:
360                                         before = after = int(value) * 60
361                                 offset = (before, after)
362                         else:
363                                 offset = None
364
365                 # Read out counter
366                 counter = int(timer.get("counter", '0'))
367                 counterLeft = int(timer.get("left", counter))
368                 counterLimit = timer.get("lastActivation")
369                 counterFormat = timer.get("counterFormat", "")
370                 lastBegin = int(timer.get("lastBegin", 0))
371
372                 # Read out justplay
373                 justplay = int(timer.get("justplay", '0'))
374
375                 # Read out avoidDuplicateDescription
376                 avoidDuplicateDescription = int(timer.get("avoidDuplicateDescription", 0))
377
378                 # Read out afterevent (compatible to V* though behaviour for V3- is different as V4+ allows multiple afterevents while the last definication was chosen before)
379                 idx = {
380                         "none": AFTEREVENT.NONE,
381                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
382                         "shutdown": AFTEREVENT.DEEPSTANDBY,
383                         "standby": AFTEREVENT.STANDBY,
384                         "auto": AFTEREVENT.AUTO
385                 }
386                 afterevent = []
387                 for element in timer.findall("afterevent"):
388                         value = element.text
389
390                         if idx.has_key(value):
391                                 value = idx[value]
392                         else:
393                                 print '[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition'
394                                 continue
395
396                         start = element.get("from")
397                         end = element.get("to")
398                         if start and end:
399                                 start = [int(x) for x in start.split(':')]
400                                 end = [int(x) for x in end.split(':')]
401                                 afterevent.append((value, (start, end)))
402                         else:
403                                 afterevent.append((value, None))
404
405                 # Read out exclude (V*)
406                 idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
407                 excludes = ([], [], [], [])
408                 for exclude in timer.findall("exclude"):
409                         where = exclude.get("where")
410                         value = exclude.text
411                         if not (value and where):
412                                 continue
413
414                         if idx.has_key(where):
415                                 excludes[idx[where]].append(value.encode("UTF-8"))
416
417                 # Read out includes (use same idx) (V4+ feature, should not harm V3-)
418                 includes = ([], [], [], [])
419                 for include in timer.findall("include"):
420                         where = include.get("where")
421                         value = include.text
422                         if not (value and where):
423                                 continue
424
425                         if idx.has_key(where):
426                                 includes[idx[where]].append(value.encode("UTF-8"))
427
428                 # Read out max length (V4+)
429                 maxlen = timer.get("maxduration")
430                 if maxlen:
431                         maxlen = int(maxlen)*60
432                 # V3-
433                 else:
434                         elements = timer.findall("maxduration")
435                         if elements:
436                                 maxlen = getValue(elements, None)
437                                 if maxlen is not None:
438                                         maxlen = int(maxlen)*60
439                         else:
440                                 maxlen = None
441
442                 # Read out recording path
443                 destination = timer.get("destination", "").encode("UTF-8") or None
444
445                 # Read out recording tags
446                 tags = []
447                 for tag in timer.findall("tag"):
448                         value = tag.text
449                         if not value:
450                                 continue
451
452                         tags.append(value.encode("UTF-8"))
453
454                 # Finally append timer
455                 list.append(AutoTimerComponent(
456                                 uniqueTimerId,
457                                 name,
458                                 match,
459                                 enabled,
460                                 timespan = timetuple,
461                                 services = servicelist,
462                                 offset = offset,
463                                 afterevent = afterevent,
464                                 exclude = excludes,
465                                 include = includes,
466                                 maxduration = maxlen,
467                                 destination = destination,
468                                 matchCount = counter,
469                                 matchLeft = counterLeft,
470                                 matchLimit = counterLimit,
471                                 matchFormatString = counterFormat,
472                                 lastBegin = lastBegin,
473                                 justplay = justplay,
474                                 avoidDuplicateDescription = avoidDuplicateDescription,
475                                 bouquets = bouquets,
476                                 tags = tags
477                 ))
478
479 def writeConfig(filename, defaultTimer, timers):
480         # Generate List in RAM
481         list = ['<?xml version="1.0" ?>\n<autotimer version="', CURRENT_CONFIG_VERSION, '">\n\n']
482
483         # This gets deleted afterwards if we do not have set any defaults
484         list.append(' <defaults')
485
486         # Timespan
487         if defaultTimer.hasTimespan():
488                 list.extend((' from="', defaultTimer.getTimespanBegin(), '" to="', defaultTimer.getTimespanEnd(), '"'))
489
490         # Duration
491         if defaultTimer.hasDuration():
492                 list.extend((' maxduration="', str(defaultTimer.getDuration()), '"'))
493
494         # Destination
495         if defaultTimer.hasDestination():
496                 list.extend((' location="', stringToXML(defaultTimer.destination), '"'))
497
498         # Offset
499         if defaultTimer.hasOffset():
500                 if defaultTimer.isOffsetEqual():
501                         list.extend((' offset="', str(defaultTimer.getOffsetBegin()), '"'))
502                 else:
503                         list.extend((' offset="', str(defaultTimer.getOffsetBegin()), ',', str(defaultTimer.getOffsetEnd()), '"'))
504
505         # Counter
506         if defaultTimer.hasCounter():
507                 list.extend((' counter="', str(defaultTimer.getCounter()), '"'))
508                 if defaultTimer.hasCounterFormatString():
509                         list.extend((' counterFormat="', str(defaultTimer.getCounterFormatString()), '"'))
510
511         # Duplicate Description
512         if defaultTimer.getAvoidDuplicateDescription():
513                 list.append(' avoidDuplicateDescription="1" ')
514
515         # Only display justplay if true
516         if defaultTimer.justplay:
517                 list.extend((' justplay="', str(defaultTimer.getJustplay()), '"'))
518
519         # Only display encoding if != utf-8
520         if defaultTimer.encoding != 'UTF-8':
521                 list.extend((' encoding="', str(defaultTimer.encoding), '"'))
522
523         # Only display searchType if exact
524         if defaultTimer.searchType == "exact":
525                 list.extend((' searchType="', str(defaultTimer.searchType), '"'))
526
527         # Only display searchCase if sensitive
528         if defaultTimer.searchCase == "sensitive":
529                 list.extend((' searchCase="', str(defaultTimer.searchCase), '"'))
530
531         # Close still opened defaults tag
532         list.append('>\n')
533
534         # Services
535         for serviceref in defaultTimer.services:
536                 list.extend(('  <serviceref>', serviceref, '</serviceref>'))
537                 ref = ServiceReference(str(serviceref))
538                 list.extend((' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'))
539
540         # Bouquets
541         for bouquet in defaultTimer.bouquets:
542                 list.extend(('  <bouquet>', str(bouquet), '</bouquet>'))
543                 ref = ServiceReference(str(bouquet))
544                 list.extend((' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'))
545
546         # AfterEvent
547         if defaultTimer.hasAfterEvent():
548                 idx = {
549                         AFTEREVENT.NONE: "none",
550                         AFTEREVENT.STANDBY: "standby",
551                         AFTEREVENT.DEEPSTANDBY: "shutdown",
552                         AFTEREVENT.AUTO: "auto"
553                 }
554                 for afterevent in defaultTimer.afterevent:
555                         action, timespan = afterevent
556                         list.append('  <afterevent')
557                         if timespan[0] is not None:
558                                 list.append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
559                         list.extend(('>', idx[action], '</afterevent>\n'))
560
561         # Excludes
562         for title in defaultTimer.getExcludedTitle():
563                 list.extend(('  <exclude where="title">', stringToXML(title), '</exclude>\n'))
564         for short in defaultTimer.getExcludedShort():
565                 list.extend(('  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'))
566         for desc in defaultTimer.getExcludedDescription():
567                 list.extend(('  <exclude where="description">', stringToXML(desc), '</exclude>\n'))
568         for day in defaultTimer.getExcludedDays():
569                 list.extend(('  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'))
570
571         # Includes
572         for title in defaultTimer.getIncludedTitle():
573                 list.extend(('  <include where="title">', stringToXML(title), '</include>\n'))
574         for short in defaultTimer.getIncludedShort():
575                 list.extend(('  <include where="shortdescription">', stringToXML(short), '</include>\n'))
576         for desc in defaultTimer.getIncludedDescription():
577                 list.extend(('  <include where="description">', stringToXML(desc), '</include>\n'))
578         for day in defaultTimer.getIncludedDays():
579                 list.extend(('  <include where="dayofweek">', stringToXML(day), '</include>\n'))
580
581         # Tags
582         for tag in defaultTimer.tags:
583                 list.extend(('  <tag>', stringToXML(tag), '</tag>\n'))
584
585         # Keep the list clean
586         if len(list) == 5:
587                 list.pop() # >
588                 list.pop() # <defaults
589         else:
590                 list.append(' </defaults>\n\n')
591
592         # Iterate timers
593         for timer in timers:
594                 # Common attributes (match, enabled)
595                 list.extend((' <timer name="', stringToXML(timer.name), '" match="', stringToXML(timer.match), '" enabled="', timer.getEnabled(), '"'))
596
597                 # Timespan
598                 if timer.hasTimespan():
599                         list.extend((' from="', timer.getTimespanBegin(), '" to="', timer.getTimespanEnd(), '"'))
600
601                 # Duration
602                 if timer.hasDuration():
603                         list.extend((' maxduration="', str(timer.getDuration()), '"'))
604
605                 # Destination
606                 if timer.hasDestination():
607                         list.extend((' location="', stringToXML(timer.destination), '"'))
608
609                 # Offset
610                 if timer.hasOffset():
611                         if timer.isOffsetEqual():
612                                 list.extend((' offset="', str(timer.getOffsetBegin()), '"'))
613                         else:
614                                 list.extend((' offset="', str(timer.getOffsetBegin()), ',', str(timer.getOffsetEnd()), '"'))
615
616                 # Counter
617                 if timer.hasCounter():
618                         list.extend((' lastBegin="', str(timer.getLastBegin()), '" counter="', str(timer.getCounter()), '" left="', str(timer.getCounterLeft()) ,'"'))
619                         if timer.hasCounterFormatString():
620                                 list.extend((' lastActivation="', str(timer.getCounterLimit()), '"'))
621                                 list.extend((' counterFormat="', str(timer.getCounterFormatString()), '"'))
622
623                 # Duplicate Description
624                 if timer.getAvoidDuplicateDescription():
625                         list.extend((' avoidDuplicateDescription="', str(timer.getAvoidDuplicateDescription()), '"'))
626
627                 # Only display justplay if true
628                 if timer.justplay:
629                         list.extend((' justplay="', str(timer.getJustplay()), '"'))
630
631                 # Only display encoding if != utf-8
632                 if timer.encoding != 'UTF-8':
633                         list.extend((' encoding="', str(timer.encoding), '"'))
634
635                 # Only display searchType if exact
636                 if timer.searchType == "exact":
637                         list.extend((' searchType="', str(timer.searchType), '"'))
638
639                 # Only display searchCase if sensitive
640                 if timer.searchCase == "sensitive":
641                         list.extend((' searchCase="', str(timer.searchCase), '"'))
642
643                 # Only display overrideAlternatives if true
644                 if timer.overrideAlternatives:
645                         list.extend((' overrideAlternatives="', str(timer.getOverrideAlternatives()), '"'))
646
647                 # Close still opened timer tag
648                 list.append('>\n')
649
650                 # Services
651                 for serviceref in timer.services:
652                         list.extend(('  <serviceref>', serviceref, '</serviceref>'))
653                         ref = ServiceReference(str(serviceref))
654                         list.extend((' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'))
655
656                 # Bouquets
657                 for bouquet in timer.bouquets:
658                         list.extend(('  <bouquet>', str(bouquet), '</bouquet>'))
659                         ref = ServiceReference(str(bouquet))
660                         list.extend((' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n'))
661
662                 # AfterEvent
663                 if timer.hasAfterEvent():
664                         idx = {
665                                 AFTEREVENT.NONE: "none",
666                                 AFTEREVENT.STANDBY: "standby",
667                                 AFTEREVENT.DEEPSTANDBY: "shutdown",
668                                 AFTEREVENT.AUTO: "auto"
669                         }
670                         for afterevent in timer.afterevent:
671                                 action, timespan = afterevent
672                                 list.append('  <afterevent')
673                                 if timespan[0] is not None:
674                                         list.append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
675                                 list.extend(('>', idx[action], '</afterevent>\n'))
676
677                 # Excludes
678                 for title in timer.getExcludedTitle():
679                         list.extend(('  <exclude where="title">', stringToXML(title), '</exclude>\n'))
680                 for short in timer.getExcludedShort():
681                         list.extend(('  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'))
682                 for desc in timer.getExcludedDescription():
683                         list.extend(('  <exclude where="description">', stringToXML(desc), '</exclude>\n'))
684                 for day in timer.getExcludedDays():
685                         list.extend(('  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'))
686
687                 # Includes
688                 for title in timer.getIncludedTitle():
689                         list.extend(('  <include where="title">', stringToXML(title), '</include>\n'))
690                 for short in timer.getIncludedShort():
691                         list.extend(('  <include where="shortdescription">', stringToXML(short), '</include>\n'))
692                 for desc in timer.getIncludedDescription():
693                         list.extend(('  <include where="description">', stringToXML(desc), '</include>\n'))
694                 for day in timer.getIncludedDays():
695                         list.extend(('  <include where="dayofweek">', stringToXML(day), '</include>\n'))
696
697                 # Tags
698                 for tag in timer.tags:
699                         list.extend(('  <tag>', stringToXML(tag), '</tag>\n'))
700
701                 # End of Timer
702                 list.append(' </timer>\n\n')
703
704         # End of Configuration
705         list.append('</autotimer>\n')
706
707         # Save to Flash
708         file = open(filename, 'w')
709         file.writelines(list)
710
711         file.close()
712