Configuration parser factored out to AutoTimerConfiguration, adjust to previous commit...
[enigma2-plugins.git] / autotimer / src / AutoTimerComponent.py
1 # Format Counter
2 from time import strftime
3
4 # regular expression
5 from re import compile as re_compile
6
7 class AutoTimerComponent(object):
8         """AutoTimer Component which also handles validity checks"""
9
10         """
11          Initiate
12         """
13         def __init__(self, id, name, match, enabled, *args, **kwargs):
14                 self.id = id
15                 self._afterevent = []
16                 self.setValues(name, match, enabled, *args, **kwargs)
17
18         """
19          Unsets all Attributes
20         """
21         def clear(self, id = -1, enabled = False):
22                 self.id = id
23                 self.setValues('', '', enabled)
24
25         """
26          Create a deep copy of this instance 
27         """
28         def clone(self):
29                 return self.__deepcopy__({})
30
31         """
32          Hook needed for WebIf
33         """
34         def getEntry(self):
35                 return self
36
37         """
38          Keeps init small and helps setting many values at once
39         """
40         def setValues(self, name, match, enabled, timespan = None, services = None, offset = None, \
41                         afterevent = [], exclude = None, maxduration = None, destination = None, \
42                         include = None, matchCount = 0, matchLeft = 0, matchLimit = '', matchFormatString = '', \
43                         lastBegin = 0, justplay = False, avoidDuplicateDescription = False, bouquets = None, \
44                         tags = None):
45                 self.name = name
46                 self.match = match
47                 self.enabled = enabled
48                 self.timespan = timespan
49                 self.services = services
50                 self.offset = offset
51                 self.afterevent = afterevent
52                 self.exclude = exclude
53                 self.maxduration = maxduration
54                 self.destination = destination
55                 self.include = include
56                 self.matchCount = matchCount
57                 self.matchLeft = matchLeft
58                 self.matchLimit = matchLimit
59                 self.matchFormatString = matchFormatString
60                 self.lastBegin = lastBegin
61                 self.justplay = justplay
62                 self.avoidDuplicateDescription = avoidDuplicateDescription
63                 self.bouquets = bouquets
64                 self.tags = tags or []
65
66 ### Attributes / Properties
67
68         def getCompleteAfterEvent(self):
69                 return self._afterevent
70
71         def setAfterEvent(self, afterevent):
72                 del self._afterevent[:]
73                 if len(afterevent):
74                         for definition in afterevent:
75                                 action, timespan = definition
76                                 if timespan is None:
77                                         self._afterevent.append((action, (None,)))
78                                 else:
79                                         self._afterevent.append((action, self.calculateDayspan(*timespan)))
80
81         afterevent = property(getCompleteAfterEvent, setAfterEvent)
82
83         def setBouquets(self, bouquets):
84                 if bouquets:
85                         self._bouquets = bouquets
86                 else:
87                         self._bouquets = []
88
89         def getBouquets(self):
90                 return self._bouquets
91
92         bouquets = property(getBouquets, setBouquets)
93
94         def setExclude(self, exclude):
95                 if exclude:
96                         self._exclude = (
97                                 [re_compile(x) for x in exclude[0]],
98                                 [re_compile(x) for x in exclude[1]],
99                                 [re_compile(x) for x in exclude[2]],
100                                 exclude[3]
101                         )
102                 else:
103                         self._exclude = ([], [], [], [])
104
105         def getExclude(self):
106                 return self._exclude
107
108         exclude = property(getExclude, setExclude)
109
110         def setInclude(self, include):
111                 if include:
112                         self._include = (
113                                 [re_compile(x) for x in include[0]],
114                                 [re_compile(x) for x in include[1]],
115                                 [re_compile(x) for x in include[2]],
116                                 include[3]
117                         )
118                 else:
119                         self._include = ([], [], [], [])
120
121         def getInclude(self):
122                 return self._include
123
124         include = property(getInclude, setInclude)
125
126         def setName(self, name):
127                 self.name = name
128
129         def getMatch(self):
130                 return self._match
131
132         def setMatch(self, match):
133                 # XXX: a sanity check might be useful...
134                 self._match = match
135
136         match = property(getMatch, setMatch)
137
138         def getServices(self):
139                 return self._services
140
141         def setServices(self, services):
142                 if services:
143                         self._services = services
144                 else:
145                         self._services = []
146
147         services = property(getServices, setServices)
148
149         def getTimespan(self):
150                 return self._timespan
151
152         def setTimespan(self, timespan):
153                 if timespan is None or len(timespan) and timespan[0] is None:
154                         self._timespan = (None,)
155                 else:
156                         self._timespan = self.calculateDayspan(*timespan)
157
158         timespan = property(getTimespan, setTimespan)
159
160 ### See if Attributes are set
161
162         def hasAfterEvent(self):
163                 return len(self.afterevent)
164
165         def hasAfterEventTimespan(self):
166                 for afterevent in self.afterevent:
167                         if afterevent[1][0] is not None:
168                                 return True
169                 return False
170
171         def hasCounter(self):
172                 return self.matchCount != 0
173
174         def hasCounterFormatString(self):
175                 return self.matchFormatString != ''
176
177         def hasDestination(self):
178                 return self.destination is not None
179
180         def hasDuration(self):
181                 return self.maxduration is not None
182
183         def hasTags(self):
184                 return len(self.tags) > 0
185
186         def hasTimespan(self):
187                 return self.timespan[0] is not None
188
189         def hasOffset(self):
190                 return self.offset is not None
191
192 ### Helper
193
194         """
195          Returns a tulple of (input begin, input end, begin earlier than end)
196         """
197         def calculateDayspan(self, begin, end, ignore = None):
198                 if end[0] < begin[0] or (end[0] == begin[0] and end[1] <= begin[1]):
199                         return (begin, end, True)
200                 else:
201                         return (begin, end, False)
202
203         """
204          Returns if a given timestruct is in a timespan
205         """
206         def checkAnyTimespan(self, time, begin = None, end = None, haveDayspan = False):
207                 if begin is None:
208                         return False
209
210                 # Check if we span a day
211                 if haveDayspan:
212                         # Check if begin of event is later than our timespan starts
213                         if time.tm_hour > begin[0] or (time.tm_hour == begin[0] and time.tm_min >= begin[1]):
214                                 # If so, event is in our timespan
215                                 return False
216                         # Check if begin of event is earlier than our timespan end
217                         if time.tm_hour < end[0] or (time.tm_hour == end[0] and time.tm_min <= end[1]):
218                                 # If so, event is in our timespan
219                                 return False
220                         return True
221                 else:
222                         # Check if event begins earlier than our timespan starts 
223                         if time.tm_hour < begin[0] or (time.tm_hour == begin[0] and time.tm_min < begin[1]):
224                                 # Its out of our timespan then
225                                 return True
226                         # Check if event begins later than our timespan ends
227                         if time.tm_hour > end[0] or (time.tm_hour == end[0] and time.tm_min > end[1]):
228                                 # Its out of our timespan then
229                                 return True
230                         return False
231
232         """
233          Returns a list of all allowed services by listing the bouquets
234         """
235         def getFullServices(self):
236                 list = self.services[:]
237
238                 from enigma import eServiceReference, eServiceCenter
239                 serviceHandler = eServiceCenter.getInstance()
240                 for bouquet in self.bouquets:
241                         myref = eServiceReference(str(bouquet))
242                         mylist = serviceHandler.list(myref)
243                         if mylist is not None:
244                                 while 1:
245                                         s = mylist.getNext()
246                                         # TODO: I wonder if its sane to assume we get services here (and not just new lists)
247                                         # We can ignore markers & directorys here because they won't match any event's service :-)
248                                         if s.valid():
249                                                 # strip all after last :
250                                                 value = s.toString()
251                                                 pos = value.rfind(':')
252                                                 if pos != -1:
253                                                         value = value[:pos+1]
254
255                                                 list.append(value)
256                                         else:
257                                                 break
258
259                 return list
260
261         """
262          Called when a timer based on this component was added
263         """
264         def update(self, begin, timestamp):
265                 # Only update limit when we have new begin
266                 if begin > self.lastBegin:
267                         self.lastBegin = begin
268
269                         # Update Counter:
270                         # %m is Month, %U is week (sunday), %W is week (monday)
271                         newLimit = strftime(self.matchFormatString, timestamp)
272
273                         if newLimit != self.matchLimit:
274                                 self.matchLeft = self.matchCount
275                                 self.matchLimit = newLimit
276
277 ### Makes saving Config easier
278
279         def getAvoidDuplicateDescription(self):
280                 return self.avoidDuplicateDescription
281
282         def getCounter(self):
283                 return self.matchCount
284
285         def getCounterFormatString(self):
286                 return self.matchFormatString
287
288         def getCounterLeft(self):
289                 return self.matchLeft
290
291         def getCounterLimit(self):
292                 return self.matchLimit
293
294         def getDestination(self):
295                 # XXX: as this function was not added by me (ritzMo) i'll leave it like this but i'm not really sure if this is right ;-)
296                 return self.destination is not None
297
298         def getDuration(self):
299                 return self.maxduration/60
300
301         def getEnabled(self):
302                 return self.enabled and "yes" or "no"
303
304         def getExcludedDays(self):
305                 return self.exclude[3]
306
307         def getExcludedDescription(self):
308                 return [x.pattern for x in self.exclude[2]]
309
310         def getExcludedShort(self):
311                 return [x.pattern for x in self.exclude[1]]
312
313         def getExcludedTitle(self):
314                 return [x.pattern for x in self.exclude[0]]     
315
316         def getIncludedTitle(self):
317                 return [x.pattern for x in self.include[0]]
318
319         def getIncludedShort(self):
320                 return [x.pattern for x in self.include[1]]
321
322         def getIncludedDescription(self):
323                 return [x.pattern for x in self.include[2]]
324
325         def getIncludedDays(self):
326                 return self.include[3]
327
328         def getJustplay(self):
329                 return self.justplay and "1" or "0"
330
331         def getLastBegin(self):
332                 return self.lastBegin
333
334         def getName(self):
335                 return self.name
336
337         def getOffsetBegin(self):
338                 return self.offset[0]/60
339
340         def getOffsetEnd(self):
341                 return self.offset[1]/60
342
343         def getTags(self):
344                 return self.tags
345
346         def getTimespanBegin(self):
347                 return '%02d:%02d' % (self.timespan[0][0], self.timespan[0][1])
348
349         def getTimespanEnd(self):
350                 return '%02d:%02d' % (self.timespan[1][0], self.timespan[1][1])
351
352         def isOffsetEqual(self):
353                 return self.offset[0] == self.offset[1]
354         
355 ### Actual functionality
356
357         def applyOffset(self, begin, end):
358                 if self.offset is None:
359                         return (begin, end)
360                 return (begin - self.offset[0], end + self.offset[1])
361
362         def checkCounter(self, timestamp):
363                 # 0-Count is considered "unset"
364                 if self.matchCount == 0:
365                         return False
366
367                 # Check if event is in current timespan (we can only manage one!)
368                 limit = strftime(self.matchFormatString, timestamp)
369                 if limit != self.matchLimit:
370                         return True
371
372                 if self.matchLeft > 0:
373                         self.matchLeft -= 1
374                         return False
375                 return True
376
377         def checkDuration(self, length):
378                 if self.maxduration is None:
379                         return False
380                 return length > self.maxduration
381         
382         def checkExcluded(self, title, short, extended, dayofweek):
383                 if len(self.exclude[3]):
384                         list = [x for x in self.exclude[3]]
385                         if "weekend" in list:
386                                 list.extend(["5", "6"])
387                         if "weekday" in list:
388                                 list.extend(["0", "1", "2", "3", "4"])
389                         if dayofweek in list:
390                                 return True
391
392                 for exclude in self.exclude[0]:
393                         if exclude.search(title):
394                                 return True
395                 for exclude in self.exclude[1]:
396                         if exclude.search(short):
397                                 return True
398                 for exclude in self.exclude[2]:
399                         if exclude.search(extended):
400                                 return True
401                 return False
402
403         def checkFilter(self, title, short, extended, dayofweek):
404                 if self.checkExcluded(title, short, extended, dayofweek):
405                         return True
406
407                 return self.checkIncluded(title, short, extended, dayofweek)
408
409         def checkIncluded(self, title, short, extended, dayofweek):
410                 if len(self.include[3]):
411                         list = [x for x in self.include[3]]
412                         if "weekend" in list:
413                                 list.extend(["5", "6"])
414                         if "weekday" in list:
415                                 list.extend(["0", "1", "2", "3", "4"])
416                         if dayofweek not in list:
417                                 return True
418
419                 for include in self.include[0]:
420                         if not include.search(title):
421                                 return True
422                 for include in self.include[1]:
423                         if not include.search(short):
424                                 return True
425                 for include in self.include[2]:
426                         if not include.search(extended):
427                                 return True
428
429                 return False
430
431         def checkServices(self, service):
432                 if len(self.services) or len(self.bouquets): 
433                         return service not in self.getFullServices()
434                 return False
435
436         def checkTimespan(self, begin):
437                 return self.checkAnyTimespan(begin, *self.timespan)
438
439         def getAfterEvent(self):
440                 for afterevent in self.afterevent:
441                         if afterevent[1][0] is None:
442                                 return afterevent[0]
443                 return None
444
445         def getAfterEventTimespan(self, end):
446                 for afterevent in self.afterevent:
447                         if not self.checkAnyTimespan(end, *afterevent[1]):
448                                 return afterevent[0]
449                 return None
450
451 ### Misc
452
453         def __copy__(self):
454                 return self.__class__(
455                         self.id,
456                         self.name,
457                         self.match,
458                         self.enabled,
459                         timespan = self.timespan,
460                         services = self.services,
461                         offset = self.offset,
462                         afterevent = self.afterevent,
463                         exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.getExcludedDays()),
464                         maxduration = self.maxduration,
465                         destination = self.destination,
466                         include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.getIncludedDays()),
467                         matchCount = self.matchCount,
468                         matchLeft = self.matchLeft,
469                         matchLimit = self.matchLimit,
470                         matchFormatString = self.matchFormatString,
471                         lastBegin = self.lastBegin,
472                         justplay = self.justplay,
473                         avoidDuplicateDescription = self.avoidDuplicateDescription,
474                         bouquets = self.bouquets,
475                         tags = self.tags
476                 )
477
478         def __deepcopy__(self, memo):
479                 return self.__class__(
480                         self.id,
481                         self.name,
482                         self.match,
483                         self.enabled,
484                         timespan = self.timespan,
485                         services = self.services[:],
486                         offset = self.offset and self.offset[:],
487                         afterevent = self.afterevent[:],
488                         exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.exclude[3][:]),
489                         maxduration = self.maxduration,
490                         destination = self.destination,
491                         include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.include[3][:]),
492                         matchCount = self.matchCount,
493                         matchLeft = self.matchLeft,
494                         matchLimit = self.matchLimit,
495                         matchFormatString = self.matchFormatString,
496                         lastBegin = self.lastBegin,
497                         justplay = self.justplay,
498                         avoidDuplicateDescription = self.avoidDuplicateDescription,
499                         bouquets = self.bouquets[:],
500                         tags = self.tags[:]
501                 )
502
503         def __eq__(self, other):
504                 try:
505                         return self.id == other.id
506                 except AttributeError:
507                         return False
508
509         def __ne__(self, other):
510                 return not self.__eq__(other)
511
512         def __repr__(self):
513                 return ''.join([
514                         '<AutomaticTimer ',
515                         self.name,
516                         ' (',
517                         ', '.join([
518                                         str(self.match),
519                                         str(self.timespan),
520                                         str(self.services),
521                                         str(self.offset),
522                                         str(self.afterevent),
523                                         str(([x.pattern for x in self.exclude[0]],
524                                                 [x.pattern for x in self.exclude[1]],
525                                                 [x.pattern for x in self.exclude[2]],
526                                                 self.exclude[3]
527                                         )),
528                                         str(([x.pattern for x in self.include[0]],
529                                                 [x.pattern for x in self.include[1]],
530                                                 [x.pattern for x in self.include[2]],
531                                                 self.include[3]
532                                         )),
533                                         str(self.maxduration),
534                                         str(self.enabled),
535                                         str(self.destination),
536                                         str(self.matchCount),
537                                         str(self.matchLeft),
538                                         str(self.matchLimit),
539                                         str(self.matchFormatString),
540                                         str(self.lastBegin),
541                                         str(self.justplay),
542                                         str(self.avoidDuplicateDescription),
543                                         str(self.bouquets),
544                                         str(self.tags)
545                          ]),
546                          ")>"
547                 ])