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