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