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