2 from time import strftime
5 from re import compile as re_compile
7 # Alternatives and service restriction
8 from enigma import eServiceReference, eServiceCenter
10 class AutoTimerComponent(object):
11 """AutoTimer Component which also handles validity checks"""
16 def __init__(self, id, name, match, enabled, *args, **kwargs):
19 self.setValues(name, match, enabled, *args, **kwargs)
24 def clear(self, id = -1, enabled = False):
26 self.setValues('', '', enabled)
29 Create a deep copy of this instance
32 return self.__deepcopy__({})
41 Keeps init small and helps setting many values at once
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):
51 self.enabled = enabled
52 self.timespan = timespan
53 self.services = services
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
74 ### Attributes / Properties
76 def setAfterEvent(self, afterevent):
77 if afterevent is not self._afterevent:
78 del self._afterevent[:]
80 for action, timespan in afterevent:
82 self._afterevent.append((action, (None,)))
84 self._afterevent.append((action, self.calculateDayspan(*timespan)))
86 afterevent = property(lambda self: self._afterevent, setAfterEvent)
88 def setBouquets(self, bouquets):
90 self._bouquets = bouquets
94 bouquets = property(lambda self: self._bouquets , setBouquets)
96 def setEncoding(self, encoding):
98 self._encoding = encoding
100 encoding = property(lambda self: self._encoding, setEncoding)
102 def setExclude(self, exclude):
105 [re_compile(x) for x in exclude[0]],
106 [re_compile(x) for x in exclude[1]],
107 [re_compile(x) for x in exclude[2]],
111 self._exclude = ([], [], [], [])
113 exclude = property(lambda self: self._exclude, setExclude)
115 def setInclude(self, include):
118 [re_compile(x) for x in include[0]],
119 [re_compile(x) for x in include[1]],
120 [re_compile(x) for x in include[2]],
124 self._include = ([], [], [], [])
126 include = property(lambda self: self._include, setInclude)
128 def setSearchCase(self, case):
129 assert case in ("sensitive", "insensitive"), "search case must be sensitive or insensitive"
130 self._searchCase = case
132 searchCase = property(lambda self: self._searchCase, setSearchCase)
134 def setSearchType(self, type):
135 assert type in ("exact", "partial"), "search type must be exact or partial"
136 self._searchType = type
138 searchType = property(lambda self: self._searchType, setSearchType)
140 def setServices(self, services):
142 self._services = services
146 services = property(lambda self: self._services, setServices)
148 def setTimespan(self, timespan):
149 if timespan is None or timespan and timespan[0] is None:
150 self._timespan = (None,)
152 self._timespan = self.calculateDayspan(*timespan)
154 timespan = property(lambda self: self._timespan, setTimespan)
156 ### See if Attributes are set
158 def hasAfterEvent(self):
159 return len(self.afterevent)
161 def hasAfterEventTimespan(self):
162 for afterevent in self.afterevent:
163 if afterevent[1][0] is not None:
167 def hasCounter(self):
168 return self.matchCount != 0
170 def hasCounterFormatString(self):
171 return self.matchFormatString != ''
173 def hasDestination(self):
174 return self.destination is not None
176 def hasDuration(self):
177 return self.maxduration is not None
180 return len(self.tags)
182 def hasTimespan(self):
183 return self.timespan[0] is not None
186 return self.offset is not None
191 Returns a tulple of (input begin, input end, begin earlier than end)
193 def calculateDayspan(self, begin, end, ignore = None):
194 if end[0] < begin[0] or (end[0] == begin[0] and end[1] <= begin[1]):
195 return (begin, end, True)
197 return (begin, end, False)
200 Returns if a given timestruct is in a timespan
202 def checkAnyTimespan(self, time, begin = None, end = None, haveDayspan = False):
206 # Check if we span a day
208 # Check if begin of event is later than our timespan starts
209 if time.tm_hour > begin[0] or (time.tm_hour == begin[0] and time.tm_min >= begin[1]):
210 # If so, event is in our timespan
212 # Check if begin of event is earlier than our timespan end
213 if time.tm_hour < end[0] or (time.tm_hour == end[0] and time.tm_min <= end[1]):
214 # If so, event is in our timespan
218 # Check if event begins earlier than our timespan starts
219 if time.tm_hour < begin[0] or (time.tm_hour == begin[0] and time.tm_min < begin[1]):
220 # Its out of our timespan then
222 # Check if event begins later than our timespan ends
223 if time.tm_hour > end[0] or (time.tm_hour == end[0] and time.tm_min > end[1]):
224 # Its out of our timespan then
229 Called when a timer based on this component was added
231 def update(self, begin, timestamp):
232 # Only update limit when we have new begin
233 if begin > self.lastBegin:
234 self.lastBegin = begin
237 # %m is Month, %U is week (sunday), %W is week (monday)
238 newLimit = strftime(self.matchFormatString, timestamp)
240 if newLimit != self.matchLimit:
241 self.matchLeft = self.matchCount
242 self.matchLimit = newLimit
244 ### Makes saving Config easier
246 getAvoidDuplicateDescription = lambda self: self.avoidDuplicateDescription
248 getBouquets = lambda self: self._bouquets
250 getCompleteAfterEvent = lambda self: self._afterevent
252 getCounter = lambda self: self.matchCount
253 getCounterFormatString = lambda self: self.matchFormatString
254 getCounterLeft = lambda self: self.matchLeft
255 getCounterLimit = lambda self: self.matchLimit
257 # XXX: as this function was not added by me (ritzMo) i'll leave it like this but i'm not really sure if this is right ;-)
258 getDestination = lambda self: self.destination is not None
260 getDuration = lambda self: self.maxduration/60
262 getEnabled = lambda self: self.enabled and "yes" or "no"
264 getExclude = lambda self: self._exclude
265 getExcludedDays = lambda self: self.exclude[3]
266 getExcludedDescription = lambda self: [x.pattern for x in self.exclude[2]]
267 getExcludedShort = lambda self: [x.pattern for x in self.exclude[1]]
268 getExcludedTitle = lambda self: [x.pattern for x in self.exclude[0]]
270 getInclude = lambda self: self._include
271 getIncludedTitle = lambda self: [x.pattern for x in self.include[0]]
272 getIncludedShort = lambda self: [x.pattern for x in self.include[1]]
273 getIncludedDescription = lambda self: [x.pattern for x in self.include[2]]
274 getIncludedDays = lambda self: self.include[3]
276 getJustplay = lambda self: self.justplay and "1" or "0"
278 getLastBegin = lambda self: self.lastBegin
280 getMatch = lambda self: self.match
281 getName = lambda self: self.name
283 getOffsetBegin = lambda self: self.offset[0]/60
284 getOffsetEnd = lambda self: self.offset[1]/60
286 getOverrideAlternatives = lambda self: self.overrideAlternatives and "1" or "0"
288 getServices = lambda self: self._services
290 getTags = lambda self: self.tags
292 getTimespan = lambda self: self._timespan
293 getTimespanBegin = lambda self: '%02d:%02d' % (self.timespan[0][0], self.timespan[0][1])
294 getTimespanEnd = lambda self: '%02d:%02d' % (self.timespan[1][0], self.timespan[1][1])
296 isOffsetEqual = lambda self: self.offset[0] == self.offset[1]
298 ### Actual functionality
300 def applyOffset(self, begin, end):
301 if self.offset is None:
303 return (begin - self.offset[0], end + self.offset[1])
305 def checkCounter(self, timestamp):
306 # 0-Count is considered "unset"
307 if self.matchCount == 0:
310 # Check if event is in current timespan (we can only manage one!)
311 limit = strftime(self.matchFormatString, timestamp)
312 if limit != self.matchLimit:
315 if self.matchLeft > 0:
319 def checkDuration(self, length):
320 if self.maxduration is None:
322 return length > self.maxduration
324 def checkExcluded(self, title, short, extended, dayofweek):
326 list = self.exclude[3]
327 if dayofweek in list:
329 if "weekend" in list and dayofweek in ("5", "6"):
331 if "weekday" in list and dayofweek in ("0", "1", "2", "3", "4"):
334 for exclude in self.exclude[0]:
335 if exclude.search(title):
337 for exclude in self.exclude[1]:
338 if exclude.search(short):
340 for exclude in self.exclude[2]:
341 if exclude.search(extended):
345 def checkFilter(self, title, short, extended, dayofweek):
346 if self.checkExcluded(title, short, extended, dayofweek):
349 return self.checkIncluded(title, short, extended, dayofweek)
351 def checkIncluded(self, title, short, extended, dayofweek):
353 list = self.include[3][:]
354 if "weekend" in list:
355 list.extend(("5", "6"))
356 if "weekday" in list:
357 list.extend(("0", "1", "2", "3", "4"))
358 if dayofweek not in list:
361 for include in self.include[0]:
362 if not include.search(title):
364 for include in self.include[1]:
365 if not include.search(short):
367 for include in self.include[2]:
368 if not include.search(extended):
373 def checkServices(self, check_service):
374 services = self.services
375 bouquets = self.bouquets
376 if services or bouquets:
379 for service in services:
380 if service == check_service:
383 myref = eServiceReference(str(service))
384 if myref.flags & eServiceReference.isGroup:
385 addbouquets.append(service)
387 serviceHandler = eServiceCenter.getInstance()
388 for bouquet in bouquets + addbouquets:
389 myref = eServiceReference(str(bouquet))
390 mylist = serviceHandler.list(myref)
391 if mylist is not None:
394 # TODO: I wonder if its sane to assume we get services here (and not just new lists)
395 # We can ignore markers & directorys here because they won't match any event's service :-)
397 # strip all after last :
399 pos = value.rfind(':')
401 if value[pos-1] == ':':
403 value = value[:pos+1]
405 if value == check_service:
413 Return alternative service including a given ref.
414 Note that this only works for alternatives that the autotimer is restricted to.
416 def getAlternative(self, override_service):
417 services = self.services
419 serviceHandler = eServiceCenter.getInstance()
421 for service in services:
422 myref = eServiceReference(str(service))
423 if myref.flags & eServiceReference.isGroup:
424 mylist = serviceHandler.list(myref)
425 if mylist is not None:
429 # strip all after last :
431 pos = value.rfind(':')
433 if value[pos-1] == ':':
435 value = value[:pos+1]
437 if value == override_service:
441 return override_service
443 def checkTimespan(self, begin):
444 return self.checkAnyTimespan(begin, *self.timespan)
446 def decrementCounter(self):
447 if self.matchCount and self.matchLeft > 0:
450 def getAfterEvent(self):
451 for afterevent in self.afterevent:
452 if afterevent[1][0] is None:
456 def getAfterEventTimespan(self, end):
457 for afterevent in self.afterevent:
458 if not self.checkAnyTimespan(end, *afterevent[1]):
465 return self.__class__(
470 timespan = self.timespan,
471 services = self.services,
472 offset = self.offset,
473 afterevent = self.afterevent,
474 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.getExcludedDays()),
475 maxduration = self.maxduration,
476 destination = self.destination,
477 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.getIncludedDays()),
478 matchCount = self.matchCount,
479 matchLeft = self.matchLeft,
480 matchLimit = self.matchLimit,
481 matchFormatString = self.matchFormatString,
482 lastBegin = self.lastBegin,
483 justplay = self.justplay,
484 avoidDuplicateDescription = self.avoidDuplicateDescription,
485 bouquets = self.bouquets,
487 encoding = self.encoding,
488 searchType = self.searchType,
489 searchCase = self.searchCase,
490 overrideAlternatives = self.overrideAlternatives
493 def __deepcopy__(self, memo):
494 return self.__class__(
499 timespan = self.timespan,
500 services = self.services[:],
501 offset = self.offset and self.offset[:],
502 afterevent = self.afterevent[:],
503 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.exclude[3][:]),
504 maxduration = self.maxduration,
505 destination = self.destination,
506 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.include[3][:]),
507 matchCount = self.matchCount,
508 matchLeft = self.matchLeft,
509 matchLimit = self.matchLimit,
510 matchFormatString = self.matchFormatString,
511 lastBegin = self.lastBegin,
512 justplay = self.justplay,
513 avoidDuplicateDescription = self.avoidDuplicateDescription,
514 bouquets = self.bouquets[:],
516 encoding = self.encoding,
517 searchType = self.searchType,
518 searchCase = self.searchCase,
519 overrideAlternatives = self.overrideAlternatives
522 def __eq__(self, other):
523 if isinstance(other, AutoTimerComponent):
524 return self.id == other.id
527 def __lt__(self, other):
528 if isinstance(other, AutoTimerComponent):
529 return self.name.lower() < other.name.lower()
532 def __ne__(self, other):
533 return not self.__eq__(other)
543 str(self.searchCase),
544 str(self.searchType),
548 str(self.afterevent),
549 str(([x.pattern for x in self.exclude[0]],
550 [x.pattern for x in self.exclude[1]],
551 [x.pattern for x in self.exclude[2]],
554 str(([x.pattern for x in self.include[0]],
555 [x.pattern for x in self.include[1]],
556 [x.pattern for x in self.include[2]],
559 str(self.maxduration),
561 str(self.destination),
562 str(self.matchCount),
564 str(self.matchLimit),
565 str(self.matchFormatString),
568 str(self.avoidDuplicateDescription),
571 str(self.overrideAlternatives),