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