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