autotimer: interpret timeframe after==begin as open ended
[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
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 = "8"
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                 ret = definitions[Len-1].text if Len else ""
35         else:
36                 ret = definitions.text
37
38         # Return stripped output or (if empty) default
39         return ret.strip() or default
40
41 def parseConfig(configuration, list, version = None, uniqueTimerId = 0, defaultTimer = None):
42         try:
43                 intVersion = int(version)
44         except ValueError:
45                 print('[AutoTimer] Config version "%s" is not a valid integer, assuming old version' % version)
46                 intVersion = -1
47
48         if intVersion < 5:
49                 parseConfigOld(configuration, list, uniqueTimerId)
50                 return
51
52         if defaultTimer is not None:
53                 # Read in defaults for a new timer
54                 for defaults in configuration.findall("defaults"):
55                         parseEntry(defaults, defaultTimer, True)
56
57         for timer in configuration.findall("timer"):
58                 uniqueTimerId += 1
59                 baseTimer = preferredAutoTimerComponent(
60                         uniqueTimerId,
61                         '',
62                         '',
63                         True
64                 )
65
66                 if parseEntry(timer, baseTimer):
67                         list.append(baseTimer)
68
69 def parseEntry(element, baseTimer, defaults = False):
70         if not defaults:
71                 # Read out match
72                 baseTimer.match = element.get("match", "").encode("UTF-8")
73                 if not baseTimer.match:
74                         print('[AutoTimer] Erroneous config is missing attribute "match", skipping entry')
75                         return False
76
77                 # Read out name
78                 baseTimer.name = element.get("name", "").encode("UTF-8")
79                 if not baseTimer.name:
80                         print('[AutoTimer] Timer is missing attribute "name", defaulting to match')
81                         baseTimer.name = baseTimer.match
82
83                 # Read out enabled
84                 enabled = element.get("enabled", "yes")
85                 if enabled == "no":
86                         baseTimer.enabled = False
87                 elif enabled == "yes":
88                         baseTimer.enabled = True
89                 else:
90                         print('[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', disabling')
91                         baseTimer.enabled = False
92
93                 # Read timeframe
94                 before = element.get("before")
95                 after = element.get("after")
96                 if before and after:
97                         baseTimer.timeframe = (int(after), int(before))
98
99         # VPS-Plugin settings
100         vps_enabled = element.get("vps_enabled", "no")
101         vps_overwrite = element.get("vps_overwrite", "no")
102         baseTimer.vps_enabled = True if vps_enabled == "yes" else False
103         baseTimer.vps_overwrite = True if vps_overwrite == "yes" else False
104         del vps_enabled, vps_overwrite
105
106         # SeriesPlugin settings
107         series_labeling = element.get("series_labeling", "no")
108         baseTimer.series_labeling = True if series_labeling == "yes" else False
109         del series_labeling
110
111         # Read out search type/case
112         baseTimer.searchType = element.get("searchType", baseTimer.searchType)
113         baseTimer.searchCase = element.get("searchCase", baseTimer.searchCase)
114
115         # Read out if we should change to alternative services
116         baseTimer.overrideAlternatives = int(element.get("overrideAlternatives", baseTimer.overrideAlternatives))
117
118         # Read out timespan
119         start = element.get("from")
120         end = element.get("to")
121         if start and end:
122                 start = [int(x) for x in start.split(':')]
123                 end = [int(x) for x in end.split(':')]
124                 baseTimer.timespan = (start, end)
125
126         # Read out max length
127         maxduration = element.get("maxduration")
128         if maxduration:
129                 baseTimer.maxduration = int(maxduration)*60
130
131         # Read out recording path
132         default = baseTimer.destination or ""
133         baseTimer.destination = element.get("location", default).encode("UTF-8") or None
134
135         # Read out offset
136         offset = element.get("offset")
137         if offset:
138                 offset = offset.split(",")
139                 if len(offset) == 1:
140                         before = after = int(offset[0] or 0) * 60
141                 else:
142                         before = int(offset[0] or 0) * 60
143                         after = int(offset[1] or 0) * 60
144                 baseTimer.offset = (before, after)
145
146         # Read out counter
147         baseTimer.matchCount = int(element.get("counter", 0))
148         baseTimer.matchFormatString = element.get("counterFormat", "")
149         if not defaults:
150                 baseTimer.matchLeft = int(element.get("left", baseTimer.matchCount))
151                 baseTimer.matchLimit = element.get("lastActivation", "")
152                 baseTimer.lastBegin = int(element.get("lastBegin", 0))
153
154         # Read out justplay
155         baseTimer.justplay = int(element.get("justplay", 0))
156         baseTimer.setEndtime = int(element.get("setEndtime", 1))
157
158         # Read out avoidDuplicateDescription
159         baseTimer.avoidDuplicateDescription = int(element.get("avoidDuplicateDescription", 0))
160         baseTimer.searchForDuplicateDescription = int(element.get("searchForDuplicateDescription", 2))
161
162         # Read out allowed services
163         l = element.findall("serviceref")
164         if l:
165                 servicelist = []
166
167                 for service in l:
168                         value = service.text
169                         if value:
170                                 myref = eServiceReference(str(value))
171                                 if not (myref.flags & eServiceReference.isGroup):
172                                         # strip all after last :
173                                         pos = value.rfind(':')
174                                         if pos != -1:
175                                                 if value[pos-1] == ':':
176                                                         pos -= 1
177                                                 value = value[:pos+1]
178
179                                 servicelist.append(value)
180                 baseTimer.services = servicelist
181
182         # Read out allowed bouquets
183         l = element.findall("bouquet")
184         if l:
185                 bouquets = []
186                 for bouquet in l:
187                         value = bouquet.text
188                         if value:
189                                 bouquets.append(value)
190                 baseTimer.bouquets = bouquets
191
192         # Read out afterevent
193         l = element.findall("afterevent")
194         if l:
195                 idx = {
196                         "none": AFTEREVENT.NONE,
197                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
198                         "shutdown": AFTEREVENT.DEEPSTANDBY,
199                         "standby": AFTEREVENT.STANDBY,
200                         "auto": AFTEREVENT.AUTO
201                 }
202                 afterevents = []
203                 for afterevent in l:
204                         value = afterevent.text
205
206                         if value in idx:
207                                 value = idx[value]
208                         else:
209                                 print('[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition')
210                                 continue
211
212                         start = afterevent.get("from")
213                         end = afterevent.get("to")
214                         if start and end:
215                                 start = [int(x) for x in start.split(':')]
216                                 end = [int(x) for x in end.split(':')]
217                                 afterevents.append((value, (start, end)))
218                         else:
219                                 afterevents.append((value, None))
220                 baseTimer.afterevent = afterevents
221
222         # Read out exclude
223         l = element.findall("exclude")
224         idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
225         if l:
226                 excludes = ([], [], [], [])
227                 for exclude in l:
228                         where = exclude.get("where")
229                         value = exclude.text
230                         if not (value and where):
231                                 continue
232
233                         if where in idx:
234                                 excludes[idx[where]].append(value.encode("UTF-8"))
235                 baseTimer.exclude = excludes
236
237         # Read out includes (use same idx)
238         l = element.findall("include")
239         if l:
240                 includes = ([], [], [], [])
241                 for include in l:
242                         where = include.get("where")
243                         value = include.text
244                         if not (value and where):
245                                 continue
246
247                         if where in idx:
248                                 includes[idx[where]].append(value.encode("UTF-8"))
249                 baseTimer.include = includes
250
251         # Read out recording tags
252         l =  element.findall("tag")
253         if l:
254                 tags = []
255                 for tag in l:
256                         value = tag.text
257                         if not value:
258                                 continue
259
260                         tags.append(value.encode("UTF-8"))
261                 baseTimer.tags = tags
262
263         return True
264
265 def parseConfigOld(configuration, list, uniqueTimerId = 0):
266         print("[AutoTimer] Trying to parse old config")
267
268         # Iterate Timers
269         for timer in configuration.findall("timer"):
270                 # Increment uniqueTimerId
271                 uniqueTimerId += 1
272
273                 # Get name (V2+)
274                 name = timer.get("name")
275                 if name:
276                         name = name.encode("UTF-8")
277                 # Get name (= match) (V1)
278                 else:
279                         # Read out name
280                         name = getValue(timer.findall("name"), "").encode("UTF-8")
281
282                 if not name:
283                         print('[AutoTimer] Erroneous config is missing attribute "name", skipping entry')
284                         continue
285
286                 # Read out match (V3+)
287                 match = timer.get("match")
288                 if match:
289                         # Read out match
290                         match = match.encode("UTF-8")
291                         if not match:
292                                 print('[AutoTimer] Erroneous config contains empty attribute "match", skipping entry')
293                                 continue
294                 # V2-
295                 else:
296                         # Setting match to name
297                         match = name
298
299
300                 # See if Timer is ensabled (V2+)
301                 enabled = timer.get("enabled")
302                 if enabled:
303                         if enabled == "no":
304                                 enabled = False
305                         elif enabled == "yes":
306                                 enabled = True
307                         else:
308                                 print('[AutoTimer] Erroneous config contains invalid value for "enabled":', enabled,', skipping entry')
309                                 enabled = False
310                 # V1
311                 else:
312                         elements = timer.findall("enabled")
313                         if elements:
314                                 if getValue(elements, "yes") == "no":
315                                         enabled = False
316                                 else:
317                                         enabled = True
318                         else:
319                                 enabled = True
320
321                 # Read out timespan (V4+; Falling back on missing definition should be OK)
322                 start = timer.get("from")
323                 end = timer.get("to")
324                 if start and end:
325                         start = [int(x) for x in start.split(':')]
326                         end = [int(x) for x in end.split(':')]
327                         timetuple = (start, end)
328                 # V3-
329                 else:
330                         elements = timer.findall("timespan")
331                         Len = len(elements)
332                         if Len:
333                                 # Read out last definition
334                                 start = elements[Len-1].get("from")
335                                 end = elements[Len-1].get("to")
336                                 if start and end:
337                                         start = [int(x) for x in start.split(':')]
338                                         end = [int(x) for x in end.split(':')]
339                                         timetuple = (start, end)
340                                 else:
341                                         print('[AutoTimer] Erroneous config contains invalid definition of "timespan", ignoring definition')
342                                         timetuple = None
343                         else:
344                                 timetuple = None
345
346                 # Read out allowed services (V*)
347                 elements = timer.findall("serviceref")
348                 if elements:
349                         servicelist = []
350                         for service in elements:
351                                 value = service.text
352                                 if value:
353                                         myref = eServiceReference(str(value))
354                                         if not (myref.flags & eServiceReference.isGroup):
355                                                 # strip all after last :
356                                                 pos = value.rfind(':')
357                                                 if pos != -1:
358                                                         if value[pos-1] == ':':
359                                                                 pos -= 1
360                                                         value = value[:pos+1]
361
362                                         servicelist.append(value)
363                 else:
364                         servicelist = None
365
366                 # Read out allowed bouquets (V* though officially supported since V4)
367                 bouquets = []
368                 for bouquet in timer.findall("bouquet"):
369                         value = bouquet.text
370                         if value:
371                                 bouquets.append(value)
372
373                 # Read out offset (V4+)
374                 offset = timer.get("offset")
375                 if offset:
376                         offset = offset.split(",")
377                         if len(offset) == 1:
378                                 before = after = int(offset[0] or 0) * 60
379                         else:
380                                 before = int(offset[0] or 0) * 60
381                                 after = int(offset[1] or 0) * 60
382                         offset = (before, after)
383                 # V3-
384                 else:
385                         elements = timer.findall("offset")
386                         Len = len(elements)
387                         if Len:
388                                 value = elements[Len-1].get("both")
389                                 if value == '':
390                                         before = int(elements[Len-1].get("before", 0)) * 60
391                                         after = int(elements[Len-1].get("after", 0)) * 60
392                                 else:
393                                         before = after = int(value) * 60
394                                 offset = (before, after)
395                         else:
396                                 offset = None
397
398                 # Read out counter
399                 counter = int(timer.get("counter", '0'))
400                 counterLeft = int(timer.get("left", counter))
401                 counterLimit = timer.get("lastActivation")
402                 counterFormat = timer.get("counterFormat", "")
403                 lastBegin = int(timer.get("lastBegin", 0))
404
405                 # Read out justplay
406                 justplay = int(timer.get("justplay", '0'))
407                 setEndtime = int(timer.get("setEndtime", '1'))
408
409                 # Read out avoidDuplicateDescription
410                 avoidDuplicateDescription = int(timer.get("avoidDuplicateDescription", 0))
411                 searchForDuplicateDescription = int(timer.get("searchForDuplicateDescription", 3)) - 1
412                 if searchForDuplicateDescription < 0 or searchForDuplicateDescription > 2:
413                         searchForDuplicateDescription = 2
414
415                 # Read out afterevent (compatible to V* though behaviour for V3- is different as V4+ allows multiple afterevents while the last definication was chosen before)
416                 idx = {
417                         "none": AFTEREVENT.NONE,
418                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
419                         "shutdown": AFTEREVENT.DEEPSTANDBY,
420                         "standby": AFTEREVENT.STANDBY,
421                         "auto": AFTEREVENT.AUTO
422                 }
423                 afterevent = []
424                 for element in timer.findall("afterevent"):
425                         value = element.text
426
427                         if value in idx:
428                                 value = idx[value]
429                         else:
430                                 print('[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition')
431                                 continue
432
433                         start = element.get("from")
434                         end = element.get("to")
435                         if start and end:
436                                 start = [int(x) for x in start.split(':')]
437                                 end = [int(x) for x in end.split(':')]
438                                 afterevent.append((value, (start, end)))
439                         else:
440                                 afterevent.append((value, None))
441
442                 # Read out exclude (V*)
443                 idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
444                 excludes = ([], [], [], [])
445                 for exclude in timer.findall("exclude"):
446                         where = exclude.get("where")
447                         value = exclude.text
448                         if not (value and where):
449                                 continue
450
451                         if where in idx:
452                                 excludes[idx[where]].append(value.encode("UTF-8"))
453
454                 # Read out includes (use same idx) (V4+ feature, should not harm V3-)
455                 includes = ([], [], [], [])
456                 for include in timer.findall("include"):
457                         where = include.get("where")
458                         value = include.text
459                         if not (value and where):
460                                 continue
461
462                         if where in idx:
463                                 includes[idx[where]].append(value.encode("UTF-8"))
464
465                 # Read out max length (V4+)
466                 maxlen = timer.get("maxduration")
467                 if maxlen:
468                         maxlen = int(maxlen)*60
469                 # V3-
470                 else:
471                         elements = timer.findall("maxduration")
472                         if elements:
473                                 maxlen = getValue(elements, None)
474                                 if maxlen is not None:
475                                         maxlen = int(maxlen)*60
476                         else:
477                                 maxlen = None
478
479                 # Read out recording path
480                 destination = timer.get("destination", "").encode("UTF-8") or None
481
482                 # Read out recording tags
483                 tags = []
484                 for tag in timer.findall("tag"):
485                         value = tag.text
486                         if not value:
487                                 continue
488
489                         tags.append(value.encode("UTF-8"))
490
491                 # Finally append timer
492                 list.append(preferredAutoTimerComponent(
493                                 uniqueTimerId,
494                                 name,
495                                 match,
496                                 enabled,
497                                 timespan = timetuple,
498                                 services = servicelist,
499                                 offset = offset,
500                                 afterevent = afterevent,
501                                 exclude = excludes,
502                                 include = includes,
503                                 maxduration = maxlen,
504                                 destination = destination,
505                                 matchCount = counter,
506                                 matchLeft = counterLeft,
507                                 matchLimit = counterLimit,
508                                 matchFormatString = counterFormat,
509                                 lastBegin = lastBegin,
510                                 justplay = justplay,
511                                 setEndtime = setEndtime,
512                                 avoidDuplicateDescription = avoidDuplicateDescription,
513                                 searchForDuplicateDescription = searchForDuplicateDescription,
514                                 bouquets = bouquets,
515                                 tags = tags
516                 ))
517
518 def buildConfig(defaultTimer, timers, webif = False):
519         # Generate List in RAM
520         list = ['<?xml version="1.0" ?>\n<autotimer version="', CURRENT_CONFIG_VERSION, '">\n\n']
521         append = list.append
522         extend = list.extend
523
524         # This gets deleted afterwards if we do not have set any defaults
525         append(' <defaults')
526         if webif:
527                 extend((' id="', str(defaultTimer.getId()),'"'))
528
529         # Timespan
530         if defaultTimer.hasTimespan():
531                 extend((' from="', defaultTimer.getTimespanBegin(), '" to="', defaultTimer.getTimespanEnd(), '"'))
532
533         # Duration
534         if defaultTimer.hasDuration():
535                 extend((' maxduration="', str(defaultTimer.getDuration()), '"'))
536
537         # Destination
538         if defaultTimer.hasDestination():
539                 extend((' location="', stringToXML(defaultTimer.destination), '"'))
540
541         # Offset
542         if defaultTimer.hasOffset():
543                 if defaultTimer.isOffsetEqual():
544                         extend((' offset="', str(defaultTimer.getOffsetBegin()), '"'))
545                 else:
546                         extend((' offset="', str(defaultTimer.getOffsetBegin()), ',', str(defaultTimer.getOffsetEnd()), '"'))
547
548         # Counter
549         if defaultTimer.hasCounter():
550                 extend((' counter="', str(defaultTimer.getCounter()), '"'))
551                 if defaultTimer.hasCounterFormatString():
552                         extend((' counterFormat="', str(defaultTimer.getCounterFormatString()), '"'))
553
554         # Duplicate Description
555         if defaultTimer.getAvoidDuplicateDescription():
556                 extend((' avoidDuplicateDescription="', str(defaultTimer.getAvoidDuplicateDescription()), '"'))
557
558                 if defaultTimer.getAvoidDuplicateDescription() > 0:
559                         if defaultTimer.searchForDuplicateDescription != 2:
560                                 extend((' searchForDuplicateDescription="', str(defaultTimer.searchForDuplicateDescription), '"'))
561
562         # Only display justplay if true
563         if defaultTimer.justplay:
564                 extend((' justplay="', str(defaultTimer.getJustplay()), '"'))
565                 if not defaultTimer.setEndtime:
566                         append(' setEndtime="0"')
567
568         # SearchType
569         if defaultTimer.searchType != "partial":
570                 extend((' searchType="', str(defaultTimer.searchType), '"'))
571
572         # Only display searchCase if sensitive
573         if defaultTimer.searchCase == "sensitive":
574                 extend((' searchCase="', str(defaultTimer.searchCase), '"'))
575
576         # Only add vps related entries if true
577         if defaultTimer.vps_enabled:
578                 append(' vps_enabled="yes"')
579                 if defaultTimer.vps_overwrite:
580                         append(' vps_overwrite="yes"')
581
582         # Only add seriesplugin related entry if true
583         if defaultTimer.series_labeling:
584                 append(' series_labeling="yes"')
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                 # SearchType
713                 if timer.searchType != "partial":
714                         extend((' searchType="', str(timer.searchType), '"'))
715
716                 # Only display searchCase if sensitive
717                 if timer.searchCase == "sensitive":
718                         extend((' searchCase="', str(timer.searchCase), '"'))
719
720                 # Only display overrideAlternatives if true
721                 if timer.overrideAlternatives:
722                         extend((' overrideAlternatives="', str(timer.getOverrideAlternatives()), '"'))
723
724                 # Only add vps related entries if true
725                 if timer.vps_enabled:
726                         append(' vps_enabled="yes"')
727                         if timer.vps_overwrite:
728                                 append(' vps_overwrite="yes"')
729
730                 # Only add seriesplugin related entry if true
731                 if timer.series_labeling:
732                         append(' series_labeling="yes"')
733
734                 # Close still opened timer tag
735                 append('>\n')
736
737                 if webif:
738                         # Services + Bouquets
739                         for serviceref in timer.services + timer.bouquets:
740                                 ref = ServiceReference(str(serviceref))
741                                 extend((
742                                         '  <e2service>\n',
743                                         '   <e2servicereference>', str(serviceref), '</e2servicereference>\n',
744                                         '   <e2servicename>', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '</e2servicename>\n',
745                                         '  </e2service>\n',
746                                 ))
747                 else:
748                         # Services
749                         for serviceref in timer.services:
750                                 ref = ServiceReference(str(serviceref))
751                                 extend(('  <serviceref>', serviceref, '</serviceref>',
752                                                         ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
753                                 ))
754
755                         # Bouquets
756                         for bouquet in timer.bouquets:
757                                 ref = ServiceReference(str(bouquet))
758                                 extend(('  <bouquet>', str(bouquet), '</bouquet>',
759                                                         ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
760                                 ))
761
762                 # AfterEvent
763                 if timer.hasAfterEvent():
764                         idx = {
765                                 AFTEREVENT.NONE: "none",
766                                 AFTEREVENT.STANDBY: "standby",
767                                 AFTEREVENT.DEEPSTANDBY: "shutdown",
768                                 AFTEREVENT.AUTO: "auto"
769                         }
770                         for afterevent in timer.afterevent:
771                                 action, timespan = afterevent
772                                 append('  <afterevent')
773                                 if timespan[0] is not None:
774                                         append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
775                                 extend(('>', idx[action], '</afterevent>\n'))
776
777                 # Excludes
778                 for title in timer.getExcludedTitle():
779                         extend(('  <exclude where="title">', stringToXML(title), '</exclude>\n'))
780                 for short in timer.getExcludedShort():
781                         extend(('  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'))
782                 for desc in timer.getExcludedDescription():
783                         extend(('  <exclude where="description">', stringToXML(desc), '</exclude>\n'))
784                 for day in timer.getExcludedDays():
785                         extend(('  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'))
786
787                 # Includes
788                 for title in timer.getIncludedTitle():
789                         extend(('  <include where="title">', stringToXML(title), '</include>\n'))
790                 for short in timer.getIncludedShort():
791                         extend(('  <include where="shortdescription">', stringToXML(short), '</include>\n'))
792                 for desc in timer.getIncludedDescription():
793                         extend(('  <include where="description">', stringToXML(desc), '</include>\n'))
794                 for day in timer.getIncludedDays():
795                         extend(('  <include where="dayofweek">', stringToXML(day), '</include>\n'))
796
797                 # Tags
798                 if webif and timer.tags:
799                         extend(('  <e2tags>', stringToXML(' '.join(timer.tags)), '</e2tags>\n'))
800                 else:
801                         for tag in timer.tags:
802                                 extend(('  <tag>', stringToXML(tag), '</tag>\n'))
803
804                 # End of Timer
805                 append(' </timer>\n\n')
806
807         # End of Configuration
808         append('</autotimer>\n')
809
810         return list
811