autotimer: more control over duplicate search
[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         # Read out encoding (won't change if no value is set)
110         baseTimer.encoding = element.get("encoding")
111
112         # Read out search type/case
113         baseTimer.searchType = element.get("searchType", baseTimer.searchType)
114         baseTimer.searchCase = element.get("searchCase", baseTimer.searchCase)
115
116         # Read out if we should change to alternative services
117         baseTimer.overrideAlternatives = int(element.get("overrideAlternatives", baseTimer.overrideAlternatives))
118
119         # Read out timespan
120         start = element.get("from")
121         end = element.get("to")
122         if start and end:
123                 start = [int(x) for x in start.split(':')]
124                 end = [int(x) for x in end.split(':')]
125                 baseTimer.timespan = (start, end)
126
127         # Read out max length
128         maxduration = element.get("maxduration")
129         if maxduration:
130                 baseTimer.maxduration = int(maxduration)*60
131
132         # Read out recording path
133         default = baseTimer.destination or ""
134         baseTimer.destination = element.get("location", default).encode("UTF-8") or None
135
136         # Read out offset
137         offset = element.get("offset")
138         if offset:
139                 offset = offset.split(",")
140                 if len(offset) == 1:
141                         before = after = int(offset[0] or 0) * 60
142                 else:
143                         before = int(offset[0] or 0) * 60
144                         after = int(offset[1] or 0) * 60
145                 baseTimer.offset = (before, after)
146
147         # Read out counter
148         baseTimer.matchCount = int(element.get("counter", 0))
149         baseTimer.matchFormatString = element.get("counterFormat", "")
150         if not defaults:
151                 baseTimer.matchLeft = int(element.get("left", baseTimer.matchCount))
152                 baseTimer.matchLimit = element.get("lastActivation", "")
153                 baseTimer.lastBegin = int(element.get("lastBegin", 0))
154
155         # Read out justplay
156         baseTimer.justplay = int(element.get("justplay", 0))
157
158         # Read out avoidDuplicateDescription
159         baseTimer.avoidDuplicateDescription = int(element.get("avoidDuplicateDescription", 0))
160         baseTimer.searchForDuplicateDescription = int(element.get("searchForDuplicateDescription", 3))
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
408                 # Read out avoidDuplicateDescription
409                 avoidDuplicateDescription = int(timer.get("avoidDuplicateDescription", 0))
410                 searchForDuplicateDescription = int(timer.get("searchForDuplicateDescription", 3))
411
412                 # Read out afterevent (compatible to V* though behaviour for V3- is different as V4+ allows multiple afterevents while the last definication was chosen before)
413                 idx = {
414                         "none": AFTEREVENT.NONE,
415                         "deepstandby": AFTEREVENT.DEEPSTANDBY,
416                         "shutdown": AFTEREVENT.DEEPSTANDBY,
417                         "standby": AFTEREVENT.STANDBY,
418                         "auto": AFTEREVENT.AUTO
419                 }
420                 afterevent = []
421                 for element in timer.findall("afterevent"):
422                         value = element.text
423
424                         if value in idx:
425                                 value = idx[value]
426                         else:
427                                 print('[AutoTimer] Erroneous config contains invalid value for "afterevent":', afterevent,', ignoring definition')
428                                 continue
429
430                         start = element.get("from")
431                         end = element.get("to")
432                         if start and end:
433                                 start = [int(x) for x in start.split(':')]
434                                 end = [int(x) for x in end.split(':')]
435                                 afterevent.append((value, (start, end)))
436                         else:
437                                 afterevent.append((value, None))
438
439                 # Read out exclude (V*)
440                 idx = {"title": 0, "shortdescription": 1, "description": 2, "dayofweek": 3}
441                 excludes = ([], [], [], [])
442                 for exclude in timer.findall("exclude"):
443                         where = exclude.get("where")
444                         value = exclude.text
445                         if not (value and where):
446                                 continue
447
448                         if where in idx:
449                                 excludes[idx[where]].append(value.encode("UTF-8"))
450
451                 # Read out includes (use same idx) (V4+ feature, should not harm V3-)
452                 includes = ([], [], [], [])
453                 for include in timer.findall("include"):
454                         where = include.get("where")
455                         value = include.text
456                         if not (value and where):
457                                 continue
458
459                         if where in idx:
460                                 includes[idx[where]].append(value.encode("UTF-8"))
461
462                 # Read out max length (V4+)
463                 maxlen = timer.get("maxduration")
464                 if maxlen:
465                         maxlen = int(maxlen)*60
466                 # V3-
467                 else:
468                         elements = timer.findall("maxduration")
469                         if elements:
470                                 maxlen = getValue(elements, None)
471                                 if maxlen is not None:
472                                         maxlen = int(maxlen)*60
473                         else:
474                                 maxlen = None
475
476                 # Read out recording path
477                 destination = timer.get("destination", "").encode("UTF-8") or None
478
479                 # Read out recording tags
480                 tags = []
481                 for tag in timer.findall("tag"):
482                         value = tag.text
483                         if not value:
484                                 continue
485
486                         tags.append(value.encode("UTF-8"))
487
488                 # Finally append timer
489                 list.append(preferredAutoTimerComponent(
490                                 uniqueTimerId,
491                                 name,
492                                 match,
493                                 enabled,
494                                 timespan = timetuple,
495                                 services = servicelist,
496                                 offset = offset,
497                                 afterevent = afterevent,
498                                 exclude = excludes,
499                                 include = includes,
500                                 maxduration = maxlen,
501                                 destination = destination,
502                                 matchCount = counter,
503                                 matchLeft = counterLeft,
504                                 matchLimit = counterLimit,
505                                 matchFormatString = counterFormat,
506                                 lastBegin = lastBegin,
507                                 justplay = justplay,
508                                 avoidDuplicateDescription = avoidDuplicateDescription,
509                                 searchForDuplicateDescription = searchForDuplicateDescription,
510                                 bouquets = bouquets,
511                                 tags = tags
512                 ))
513
514 def buildConfig(defaultTimer, timers, webif = False):
515         # Generate List in RAM
516         list = ['<?xml version="1.0" ?>\n<autotimer version="', CURRENT_CONFIG_VERSION, '">\n\n']
517         append = list.append
518         extend = list.extend
519         defaultEncoding = getDefaultEncoding()
520
521         # This gets deleted afterwards if we do not have set any defaults
522         append(' <defaults')
523         if webif:
524                 extend((' id="', str(defaultTimer.getId()),'"'))
525
526         # Timespan
527         if defaultTimer.hasTimespan():
528                 extend((' from="', defaultTimer.getTimespanBegin(), '" to="', defaultTimer.getTimespanEnd(), '"'))
529
530         # Duration
531         if defaultTimer.hasDuration():
532                 extend((' maxduration="', str(defaultTimer.getDuration()), '"'))
533
534         # Destination
535         if defaultTimer.hasDestination():
536                 extend((' location="', stringToXML(defaultTimer.destination), '"'))
537
538         # Offset
539         if defaultTimer.hasOffset():
540                 if defaultTimer.isOffsetEqual():
541                         extend((' offset="', str(defaultTimer.getOffsetBegin()), '"'))
542                 else:
543                         extend((' offset="', str(defaultTimer.getOffsetBegin()), ',', str(defaultTimer.getOffsetEnd()), '"'))
544
545         # Counter
546         if defaultTimer.hasCounter():
547                 extend((' counter="', str(defaultTimer.getCounter()), '"'))
548                 if defaultTimer.hasCounterFormatString():
549                         extend((' counterFormat="', str(defaultTimer.getCounterFormatString()), '"'))
550
551         # Duplicate Description
552         if defaultTimer.getAvoidDuplicateDescription():
553                 extend((' avoidDuplicateDescription="', str(defaultTimer.getAvoidDuplicateDescription()), '"'))
554
555                 if defaultTimer.getAvoidDuplicateDescription() > 0:
556                         if defaultTimer.getSearchForDuplicateDescription() != 3:
557                                 extend((' searchForDuplicateDescription="', str(defaultTimer.getSearchForDuplicateDescription()), '"'))
558         # Only display justplay if true
559         if defaultTimer.justplay:
560                 extend((' justplay="', str(defaultTimer.getJustplay()), '"'))
561
562         # Only display encoding if != utf-8
563         if defaultTimer.encoding != defaultEncoding or webif:
564                 extend((' encoding="', str(defaultTimer.encoding), '"'))
565
566         # SearchType
567         if defaultTimer.searchType != "partial":
568                 extend((' searchType="', str(defaultTimer.searchType), '"'))
569
570         # Only display searchCase if sensitive
571         if defaultTimer.searchCase == "sensitive":
572                 extend((' searchCase="', str(defaultTimer.searchCase), '"'))
573
574         # Close still opened defaults tag
575         append('>\n')
576
577         if webif:
578                 # Services + Bouquets
579                 for serviceref in defaultTimer.services + defaultTimer.bouquets:
580                         ref = ServiceReference(str(serviceref))
581                         extend((
582                                 '  <e2service>\n',
583                                 '   <e2servicereference>', str(serviceref), '</e2servicereference>\n',
584                                 '   <e2servicename>', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '</e2servicename>\n',
585                                 '  </e2service>\n',
586                         ))
587         else:
588                 # Services
589                 for serviceref in defaultTimer.services:
590                         ref = ServiceReference(str(serviceref))
591                         extend(('  <serviceref>', serviceref, '</serviceref>',
592                                                 ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
593                         ))
594
595                 # Bouquets
596                 for bouquet in defaultTimer.bouquets:
597                         ref = ServiceReference(str(bouquet))
598                         extend(('  <bouquet>', str(bouquet), '</bouquet>',
599                                                 ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
600                         ))
601
602         # AfterEvent
603         if defaultTimer.hasAfterEvent():
604                 idx = {
605                         AFTEREVENT.NONE: "none",
606                         AFTEREVENT.STANDBY: "standby",
607                         AFTEREVENT.DEEPSTANDBY: "shutdown",
608                         AFTEREVENT.AUTO: "auto"
609                 }
610                 for afterevent in defaultTimer.afterevent:
611                         action, timespan = afterevent
612                         append('  <afterevent')
613                         if timespan[0] is not None:
614                                 append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
615                         extend(('>', idx[action], '</afterevent>\n'))
616
617         # Excludes
618         for title in defaultTimer.getExcludedTitle():
619                 extend(('  <exclude where="title">', stringToXML(title), '</exclude>\n'))
620         for short in defaultTimer.getExcludedShort():
621                 extend(('  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'))
622         for desc in defaultTimer.getExcludedDescription():
623                 extend(('  <exclude where="description">', stringToXML(desc), '</exclude>\n'))
624         for day in defaultTimer.getExcludedDays():
625                 extend(('  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'))
626
627         # Includes
628         for title in defaultTimer.getIncludedTitle():
629                 extend(('  <include where="title">', stringToXML(title), '</include>\n'))
630         for short in defaultTimer.getIncludedShort():
631                 extend(('  <include where="shortdescription">', stringToXML(short), '</include>\n'))
632         for desc in defaultTimer.getIncludedDescription():
633                 extend(('  <include where="description">', stringToXML(desc), '</include>\n'))
634         for day in defaultTimer.getIncludedDays():
635                 extend(('  <include where="dayofweek">', stringToXML(day), '</include>\n'))
636
637         # Tags
638         if webif and defaultTimer.tags:
639                 extend(('  <e2tags>', stringToXML(' '.join(defaultTimer.tags)), '</e2tags>\n'))
640         else:
641                 for tag in defaultTimer.tags:
642                         extend(('  <tag>', stringToXML(tag), '</tag>\n'))
643
644         # Keep the list clean
645         if len(list) == 5:
646                 list.pop() # >
647                 list.pop() # <defaults
648         else:
649                 append(' </defaults>\n\n')
650
651         # Iterate timers
652         for timer in timers:
653                 # Common attributes (match, enabled)
654                 extend((' <timer name="', stringToXML(timer.name), '" match="', stringToXML(timer.match), '" enabled="', timer.getEnabled(), '"'))
655                 if webif:
656                         extend((' id="', str(timer.getId()),'"'))
657
658                 # Timespan
659                 if timer.hasTimespan():
660                         extend((' from="', timer.getTimespanBegin(), '" to="', timer.getTimespanEnd(), '"'))
661
662                 # Timeframe
663                 if timer.hasTimeframe():
664                         extend((' after="', str(timer.getTimeframeBegin()), '" before="', str(timer.getTimeframeEnd()), '"'))
665
666                 # Duration
667                 if timer.hasDuration():
668                         extend((' maxduration="', str(timer.getDuration()), '"'))
669
670                 # Destination
671                 if timer.hasDestination():
672                         extend((' location="', stringToXML(timer.destination), '"'))
673
674                 # Offset
675                 if timer.hasOffset():
676                         if timer.isOffsetEqual():
677                                 extend((' offset="', str(timer.getOffsetBegin()), '"'))
678                         else:
679                                 extend((' offset="', str(timer.getOffsetBegin()), ',', str(timer.getOffsetEnd()), '"'))
680
681                 # Counter
682                 if timer.hasCounter():
683                         extend((' lastBegin="', str(timer.getLastBegin()), '" counter="', str(timer.getCounter()), '" left="', str(timer.getCounterLeft()) ,'"'))
684                         if timer.hasCounterFormatString():
685                                 extend((' lastActivation="', str(timer.getCounterLimit()), '"'))
686                                 extend((' counterFormat="', str(timer.getCounterFormatString()), '"'))
687
688                 # Duplicate Description
689                 if timer.getAvoidDuplicateDescription():
690                         extend((' avoidDuplicateDescription="', str(timer.getAvoidDuplicateDescription()), '"'))
691                         if timer.getSearchForDuplicateDescription() != 3:
692                                 extend((' searchForDuplicateDescription="', str(timer.getSearchForDuplicateDescription()), '"'))
693
694                 # Only display justplay if true
695                 if timer.justplay:
696                         extend((' justplay="', str(timer.getJustplay()), '"'))
697
698                 # Only display encoding if != utf-8
699                 if timer.encoding != defaultEncoding or webif:
700                         extend((' encoding="', str(timer.encoding), '"'))
701
702                 # SearchType
703                 if timer.searchType != "partial":
704                         extend((' searchType="', str(timer.searchType), '"'))
705
706                 # Only display searchCase if sensitive
707                 if timer.searchCase == "sensitive":
708                         extend((' searchCase="', str(timer.searchCase), '"'))
709
710                 # Only display overrideAlternatives if true
711                 if timer.overrideAlternatives:
712                         extend((' overrideAlternatives="', str(timer.getOverrideAlternatives()), '"'))
713
714                 # Only add vps related entries if true
715                 if timer.vps_enabled:
716                         append(' vps_enabled="yes"')
717                         if timer.vps_overwrite:
718                                 append(' vps_overwrite="yes"')
719
720                 # Close still opened timer tag
721                 append('>\n')
722
723                 if webif:
724                         # Services + Bouquets
725                         for serviceref in timer.services + timer.bouquets:
726                                 ref = ServiceReference(str(serviceref))
727                                 extend((
728                                         '  <e2service>\n',
729                                         '   <e2servicereference>', str(serviceref), '</e2servicereference>\n',
730                                         '   <e2servicename>', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), '</e2servicename>\n',
731                                         '  </e2service>\n',
732                                 ))
733                 else:
734                         # Services
735                         for serviceref in timer.services:
736                                 ref = ServiceReference(str(serviceref))
737                                 extend(('  <serviceref>', serviceref, '</serviceref>',
738                                                         ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
739                                 ))
740
741                         # Bouquets
742                         for bouquet in timer.bouquets:
743                                 ref = ServiceReference(str(bouquet))
744                                 extend(('  <bouquet>', str(bouquet), '</bouquet>',
745                                                         ' <!-- ', stringToXML(ref.getServiceName().replace('\xc2\x86', '').replace('\xc2\x87', '')), ' -->\n',
746                                 ))
747
748                 # AfterEvent
749                 if timer.hasAfterEvent():
750                         idx = {
751                                 AFTEREVENT.NONE: "none",
752                                 AFTEREVENT.STANDBY: "standby",
753                                 AFTEREVENT.DEEPSTANDBY: "shutdown",
754                                 AFTEREVENT.AUTO: "auto"
755                         }
756                         for afterevent in timer.afterevent:
757                                 action, timespan = afterevent
758                                 append('  <afterevent')
759                                 if timespan[0] is not None:
760                                         append(' from="%02d:%02d" to="%02d:%02d"' % (timespan[0][0], timespan[0][1], timespan[1][0], timespan[1][1]))
761                                 extend(('>', idx[action], '</afterevent>\n'))
762
763                 # Excludes
764                 for title in timer.getExcludedTitle():
765                         extend(('  <exclude where="title">', stringToXML(title), '</exclude>\n'))
766                 for short in timer.getExcludedShort():
767                         extend(('  <exclude where="shortdescription">', stringToXML(short), '</exclude>\n'))
768                 for desc in timer.getExcludedDescription():
769                         extend(('  <exclude where="description">', stringToXML(desc), '</exclude>\n'))
770                 for day in timer.getExcludedDays():
771                         extend(('  <exclude where="dayofweek">', stringToXML(day), '</exclude>\n'))
772
773                 # Includes
774                 for title in timer.getIncludedTitle():
775                         extend(('  <include where="title">', stringToXML(title), '</include>\n'))
776                 for short in timer.getIncludedShort():
777                         extend(('  <include where="shortdescription">', stringToXML(short), '</include>\n'))
778                 for desc in timer.getIncludedDescription():
779                         extend(('  <include where="description">', stringToXML(desc), '</include>\n'))
780                 for day in timer.getIncludedDays():
781                         extend(('  <include where="dayofweek">', stringToXML(day), '</include>\n'))
782
783                 # Tags
784                 if webif and timer.tags:
785                         extend(('  <e2tags>', stringToXML(' '.join(timer.tags)), '</e2tags>\n'))
786                 else:
787                         for tag in timer.tags:
788                                 extend(('  <tag>', stringToXML(tag), '</tag>\n'))
789
790                 # End of Timer
791                 append(' </timer>\n\n')
792
793         # End of Configuration
794         append('</autotimer>\n')
795
796         return list
797