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