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