some more ugly hacks for renamed services (so much hasssle for something that useless...)
[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 # 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.append((action, timespan))
84                         elif timespan is None:
85                                 self._afterevent.append((action, (None,)))
86                         else:
87                                 self._afterevent.append((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                                                                 if value[pos-1] == ':'
405                                                                         pos -= 1
406                                                                 value = value[:pos+1]
407
408                                                         if value == check_service:
409                                                                 return False
410                                                 else:
411                                                         break
412                         return True
413                 return False
414
415         """
416         Return alternative service including a given ref.
417         Note that this only works for alternatives that the autotimer is restricted to.
418         """
419         def getAlternative(self, override_service):
420                 services = self.services
421                 if services:
422                         serviceHandler = eServiceCenter.getInstance()
423
424                         for service in services:
425                                 myref = eServiceReference(str(service))
426                                 if myref.flags & eServiceReference.isGroup:
427                                         mylist = serviceHandler.list(myref)
428                                         if mylist is not None:
429                                                 while 1:
430                                                         s = mylist.getNext()
431                                                         if s.valid():
432                                                                 # strip all after last :
433                                                                 value = s.toString()
434                                                                 pos = value.rfind(':')
435                                                                 if pos != -1:
436                                                                         if value[pos-1] == ':'
437                                                                                 pos -= 1
438                                                                         value = value[:pos+1]
439
440                                                                 if value == override_service:
441                                                                         return service
442                                                         else:
443                                                                 break
444                 return override_service
445
446         def checkTimespan(self, begin):
447                 return self.checkAnyTimespan(begin, *self.timespan)
448
449         def decrementCounter(self):
450                 if self.matchCount and self.matchLeft > 0:
451                         self.matchLeft -= 1
452
453         def getAfterEvent(self):
454                 for afterevent in self.afterevent:
455                         if afterevent[1][0] is None:
456                                 return afterevent[0]
457                 return None
458
459         def getAfterEventTimespan(self, end):
460                 for afterevent in self.afterevent:
461                         if not self.checkAnyTimespan(end, *afterevent[1]):
462                                 return afterevent[0]
463                 return None
464
465 ### Misc
466
467         def __copy__(self):
468                 return self.__class__(
469                         self.id,
470                         self.name,
471                         self.match,
472                         self.enabled,
473                         timespan = self.timespan,
474                         services = self.services,
475                         offset = self.offset,
476                         afterevent = self.afterevent,
477                         exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.getExcludedDays()),
478                         maxduration = self.maxduration,
479                         destination = self.destination,
480                         include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.getIncludedDays()),
481                         matchCount = self.matchCount,
482                         matchLeft = self.matchLeft,
483                         matchLimit = self.matchLimit,
484                         matchFormatString = self.matchFormatString,
485                         lastBegin = self.lastBegin,
486                         justplay = self.justplay,
487                         avoidDuplicateDescription = self.avoidDuplicateDescription,
488                         bouquets = self.bouquets,
489                         tags = self.tags,
490                         encoding = self.encoding,
491                         searchType = self.searchType,
492                         searchCase = self.searchCase,
493                         overrideAlternatives = self.overrideAlternatives
494                 )
495
496         def __deepcopy__(self, memo):
497                 return self.__class__(
498                         self.id,
499                         self.name,
500                         self.match,
501                         self.enabled,
502                         timespan = self.timespan,
503                         services = self.services[:],
504                         offset = self.offset and self.offset[:],
505                         afterevent = self.afterevent[:],
506                         exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.exclude[3][:]),
507                         maxduration = self.maxduration,
508                         destination = self.destination,
509                         include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.include[3][:]),
510                         matchCount = self.matchCount,
511                         matchLeft = self.matchLeft,
512                         matchLimit = self.matchLimit,
513                         matchFormatString = self.matchFormatString,
514                         lastBegin = self.lastBegin,
515                         justplay = self.justplay,
516                         avoidDuplicateDescription = self.avoidDuplicateDescription,
517                         bouquets = self.bouquets[:],
518                         tags = self.tags[:],
519                         encoding = self.encoding,
520                         searchType = self.searchType,
521                         searchCase = self.searchCase,
522                         overrideAlternatives = self.overrideAlternatives
523                 )
524
525         def __eq__(self, other):
526                 if isinstance(other, AutoTimerComponent):
527                         return self.id == other.id
528                 return False
529
530         def __lt__(self, other):
531                 if isinstance(other, AutoTimerComponent):
532                         return self.name.lower() < other.name.lower()
533                 return False
534
535         def __ne__(self, other):
536                 return not self.__eq__(other)
537
538         def __repr__(self):
539                 return ''.join((
540                         '<AutomaticTimer ',
541                         self.name,
542                         ' (',
543                         ', '.join((
544                                         str(self.match),
545                                         str(self.encoding),
546                                         str(self.searchCase),
547                                         str(self.searchType),
548                                         str(self.timespan),
549                                         str(self.services),
550                                         str(self.offset),
551                                         str(self.afterevent),
552                                         str(([x.pattern for x in self.exclude[0]],
553                                                 [x.pattern for x in self.exclude[1]],
554                                                 [x.pattern for x in self.exclude[2]],
555                                                 self.exclude[3]
556                                         )),
557                                         str(([x.pattern for x in self.include[0]],
558                                                 [x.pattern for x in self.include[1]],
559                                                 [x.pattern for x in self.include[2]],
560                                                 self.include[3]
561                                         )),
562                                         str(self.maxduration),
563                                         str(self.enabled),
564                                         str(self.destination),
565                                         str(self.matchCount),
566                                         str(self.matchLeft),
567                                         str(self.matchLimit),
568                                         str(self.matchFormatString),
569                                         str(self.lastBegin),
570                                         str(self.justplay),
571                                         str(self.avoidDuplicateDescription),
572                                         str(self.bouquets),
573                                         str(self.tags),
574                                         str(self.overrideAlternatives),
575                          )),
576                          ")>"
577                 ))
578