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