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