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