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 # To get preferred component
11 from Components.config import config
13 class AutoTimerComponent(object):
14 """AutoTimer Component which also handles validity checks"""
19 def __init__(self, id, name, match, enabled, *args, **kwargs):
22 self.setValues(name, match, enabled, *args, **kwargs)
27 def clear(self, id = -1, enabled = False):
29 self.setValues('', '', enabled)
32 Create a deep copy of this instance
35 return self.__deepcopy__({})
44 Keeps init small and helps setting many values at once
46 def setValues(self, name, match, enabled, timespan = None, services = None, offset = None, \
47 afterevent = [], exclude = None, maxduration = None, destination = None, \
48 include = None, matchCount = 0, matchLeft = 0, matchLimit = '', matchFormatString = '', \
49 lastBegin = 0, justplay = False, avoidDuplicateDescription = 0, bouquets = None, \
50 tags = None, encoding = 'UTF-8', searchType = "partial", searchCase = "insensitive", \
51 overrideAlternatives = False):
54 self.enabled = enabled
55 self.timespan = timespan
56 self.services = services
58 self.afterevent = afterevent
59 self.exclude = exclude
60 self.maxduration = maxduration
61 self.destination = destination
62 self.include = include
63 self.matchCount = matchCount
64 self.matchLeft = matchLeft
65 self.matchLimit = matchLimit
66 self.matchFormatString = matchFormatString
67 self.lastBegin = lastBegin
68 self.justplay = justplay
69 self.avoidDuplicateDescription = avoidDuplicateDescription
70 self.bouquets = bouquets
71 self.tags = tags or []
72 self.encoding = encoding
73 self.searchType = searchType
74 self.searchCase = searchCase
75 self.overrideAlternatives = overrideAlternatives
77 ### Attributes / Properties
79 def setAfterEvent(self, afterevent):
80 if afterevent is not self._afterevent:
81 del self._afterevent[:]
85 for action, timespan in afterevent:
86 if timespan is None or timespan[0] is None:
87 self._afterevent.append((action, (None,)))
89 self._afterevent.append((action, self.calculateDayspan(*timespan)))
91 afterevent = property(lambda self: self._afterevent, setAfterEvent)
93 def setBouquets(self, bouquets):
95 self._bouquets = bouquets
99 bouquets = property(lambda self: self._bouquets , setBouquets)
101 def setEncoding(self, encoding):
103 self._encoding = encoding
105 encoding = property(lambda self: self._encoding, setEncoding)
107 def setExclude(self, exclude):
110 [re_compile(x) for x in exclude[0]],
111 [re_compile(x) for x in exclude[1]],
112 [re_compile(x) for x in exclude[2]],
116 self._exclude = ([], [], [], [])
118 exclude = property(lambda self: self._exclude, setExclude)
120 def setInclude(self, include):
123 [re_compile(x) for x in include[0]],
124 [re_compile(x) for x in include[1]],
125 [re_compile(x) for x in include[2]],
129 self._include = ([], [], [], [])
131 include = property(lambda self: self._include, setInclude)
133 def setSearchCase(self, case):
134 assert case in ("sensitive", "insensitive"), "search case must be sensitive or insensitive"
135 self._searchCase = case
137 searchCase = property(lambda self: self._searchCase, setSearchCase)
139 def setSearchType(self, type):
140 assert type in ("exact", "partial"), "search type must be exact or partial"
141 self._searchType = type
143 searchType = property(lambda self: self._searchType, setSearchType)
145 def setServices(self, services):
147 self._services = services
151 services = property(lambda self: self._services, setServices)
153 def setTimespan(self, timespan):
154 if timespan is None or timespan and timespan[0] is None:
155 self._timespan = (None,)
157 self._timespan = self.calculateDayspan(*timespan)
159 timespan = property(lambda self: self._timespan, setTimespan)
161 ### See if Attributes are set
163 def hasAfterEvent(self):
164 return len(self.afterevent)
166 def hasAfterEventTimespan(self):
167 for afterevent in self.afterevent:
168 if afterevent[1][0] is not None:
172 def hasCounter(self):
173 return self.matchCount != 0
175 def hasCounterFormatString(self):
176 return self.matchFormatString != ''
178 def hasDestination(self):
179 return self.destination is not None
181 def hasDuration(self):
182 return self.maxduration is not None
185 return len(self.tags)
187 def hasTimespan(self):
188 return self.timespan[0] is not None
191 return self.offset is not None
196 Returns a tulple of (input begin, input end, begin earlier than end)
198 def calculateDayspan(self, begin, end, ignore = None):
199 if end[0] < begin[0] or (end[0] == begin[0] and end[1] <= begin[1]):
200 return (begin, end, True)
202 return (begin, end, False)
205 Returns if a given timestruct is in a timespan
207 def checkAnyTimespan(self, time, begin = None, end = None, haveDayspan = False):
211 # Check if we span a day
213 # Check if begin of event is later than our timespan starts
214 if time.tm_hour > begin[0] or (time.tm_hour == begin[0] and time.tm_min >= begin[1]):
215 # If so, event is in our timespan
217 # Check if begin of event is earlier than our timespan end
218 if time.tm_hour < end[0] or (time.tm_hour == end[0] and time.tm_min <= end[1]):
219 # If so, event is in our timespan
223 # Check if event begins earlier than our timespan starts
224 if time.tm_hour < begin[0] or (time.tm_hour == begin[0] and time.tm_min < begin[1]):
225 # Its out of our timespan then
227 # Check if event begins later than our timespan ends
228 if time.tm_hour > end[0] or (time.tm_hour == end[0] and time.tm_min > end[1]):
229 # Its out of our timespan then
234 Called when a timer based on this component was added
236 def update(self, begin, timestamp):
237 # Only update limit when we have new begin
238 if begin > self.lastBegin:
239 self.lastBegin = begin
242 # %m is Month, %U is week (sunday), %W is week (monday)
243 newLimit = strftime(self.matchFormatString, timestamp)
245 if newLimit != self.matchLimit:
246 self.matchLeft = self.matchCount
247 self.matchLimit = newLimit
249 ### Makes saving Config easier
251 getAvoidDuplicateDescription = lambda self: self.avoidDuplicateDescription
253 getBouquets = lambda self: self._bouquets
255 getCompleteAfterEvent = lambda self: self._afterevent
257 getCounter = lambda self: self.matchCount
258 getCounterFormatString = lambda self: self.matchFormatString
259 getCounterLeft = lambda self: self.matchLeft
260 getCounterLimit = lambda self: self.matchLimit
262 # 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 ;-)
263 getDestination = lambda self: self.destination is not None
265 getDuration = lambda self: self.maxduration/60
267 getEnabled = lambda self: self.enabled and "yes" or "no"
269 getExclude = lambda self: self._exclude
270 getExcludedDays = lambda self: self.exclude[3]
271 getExcludedDescription = lambda self: [x.pattern for x in self.exclude[2]]
272 getExcludedShort = lambda self: [x.pattern for x in self.exclude[1]]
273 getExcludedTitle = lambda self: [x.pattern for x in self.exclude[0]]
275 getInclude = lambda self: self._include
276 getIncludedTitle = lambda self: [x.pattern for x in self.include[0]]
277 getIncludedShort = lambda self: [x.pattern for x in self.include[1]]
278 getIncludedDescription = lambda self: [x.pattern for x in self.include[2]]
279 getIncludedDays = lambda self: self.include[3]
281 getJustplay = lambda self: self.justplay and "1" or "0"
283 getLastBegin = lambda self: self.lastBegin
285 getMatch = lambda self: self.match
286 getName = lambda self: self.name
288 getOffsetBegin = lambda self: self.offset[0]/60
289 getOffsetEnd = lambda self: self.offset[1]/60
291 getOverrideAlternatives = lambda self: self.overrideAlternatives and "1" or "0"
293 getServices = lambda self: self._services
295 getTags = lambda self: self.tags
297 getTimespan = lambda self: self._timespan
298 getTimespanBegin = lambda self: '%02d:%02d' % (self.timespan[0][0], self.timespan[0][1])
299 getTimespanEnd = lambda self: '%02d:%02d' % (self.timespan[1][0], self.timespan[1][1])
301 isOffsetEqual = lambda self: self.offset[0] == self.offset[1]
303 ### Actual functionality
305 def applyOffset(self, begin, end):
306 if self.offset is None:
308 return (begin - self.offset[0], end + self.offset[1])
310 def checkCounter(self, timestamp):
311 # 0-Count is considered "unset"
312 if self.matchCount == 0:
315 # Check if event is in current timespan (we can only manage one!)
316 limit = strftime(self.matchFormatString, timestamp)
317 if limit != self.matchLimit:
320 if self.matchLeft > 0:
324 def checkDuration(self, length):
325 if self.maxduration is None:
327 return length > self.maxduration
329 def checkExcluded(self, title, short, extended, dayofweek):
331 list = self.exclude[3]
332 if dayofweek in list:
334 if "weekend" in list and dayofweek in ("5", "6"):
336 if "weekday" in list and dayofweek in ("0", "1", "2", "3", "4"):
339 for exclude in self.exclude[0]:
340 if exclude.search(title):
342 for exclude in self.exclude[1]:
343 if exclude.search(short):
345 for exclude in self.exclude[2]:
346 if exclude.search(extended):
350 def checkFilter(self, title, short, extended, dayofweek):
351 if self.checkExcluded(title, short, extended, dayofweek):
354 return self.checkIncluded(title, short, extended, dayofweek)
356 def checkIncluded(self, title, short, extended, dayofweek):
358 list = self.include[3][:]
359 if "weekend" in list:
360 list.extend(("5", "6"))
361 if "weekday" in list:
362 list.extend(("0", "1", "2", "3", "4"))
363 if dayofweek not in list:
366 for include in self.include[0]:
367 if not include.search(title):
369 for include in self.include[1]:
370 if not include.search(short):
372 for include in self.include[2]:
373 if not include.search(extended):
378 def checkServices(self, check_service):
379 services = self.services
380 bouquets = self.bouquets
381 if services or bouquets:
384 for service in services:
385 if service == check_service:
388 myref = eServiceReference(str(service))
389 if myref.flags & eServiceReference.isGroup:
390 addbouquets.append(service)
392 serviceHandler = eServiceCenter.getInstance()
393 for bouquet in bouquets + addbouquets:
394 myref = eServiceReference(str(bouquet))
395 mylist = serviceHandler.list(myref)
396 if mylist is not None:
399 # TODO: I wonder if its sane to assume we get services here (and not just new lists)
400 # We can ignore markers & directorys here because they won't match any event's service :-)
402 # strip all after last :
404 pos = value.rfind(':')
406 if value[pos-1] == ':':
408 value = value[:pos+1]
410 if value == check_service:
418 Return alternative service including a given ref.
419 Note that this only works for alternatives that the autotimer is restricted to.
421 def getAlternative(self, override_service):
422 services = self.services
424 serviceHandler = eServiceCenter.getInstance()
426 for service in services:
427 myref = eServiceReference(str(service))
428 if myref.flags & eServiceReference.isGroup:
429 mylist = serviceHandler.list(myref)
430 if mylist is not None:
434 # strip all after last :
436 pos = value.rfind(':')
438 if value[pos-1] == ':':
440 value = value[:pos+1]
442 if value == override_service:
446 return override_service
448 def checkTimespan(self, begin):
449 return self.checkAnyTimespan(begin, *self.timespan)
451 def decrementCounter(self):
452 if self.matchCount and self.matchLeft > 0:
455 def getAfterEvent(self):
456 for afterevent in self.afterevent:
457 if afterevent[1][0] is None:
461 def getAfterEventTimespan(self, end):
462 for afterevent in self.afterevent:
463 if not self.checkAnyTimespan(end, *afterevent[1]):
470 return self.__class__(
475 timespan = self.timespan,
476 services = self.services,
477 offset = self.offset,
478 afterevent = self.afterevent,
479 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.getExcludedDays()),
480 maxduration = self.maxduration,
481 destination = self.destination,
482 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.getIncludedDays()),
483 matchCount = self.matchCount,
484 matchLeft = self.matchLeft,
485 matchLimit = self.matchLimit,
486 matchFormatString = self.matchFormatString,
487 lastBegin = self.lastBegin,
488 justplay = self.justplay,
489 avoidDuplicateDescription = self.avoidDuplicateDescription,
490 bouquets = self.bouquets,
492 encoding = self.encoding,
493 searchType = self.searchType,
494 searchCase = self.searchCase,
495 overrideAlternatives = self.overrideAlternatives
498 def __deepcopy__(self, memo):
499 return self.__class__(
504 timespan = self.timespan,
505 services = self.services[:],
506 offset = self.offset and self.offset[:],
507 afterevent = self.afterevent[:],
508 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.exclude[3][:]),
509 maxduration = self.maxduration,
510 destination = self.destination,
511 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.include[3][:]),
512 matchCount = self.matchCount,
513 matchLeft = self.matchLeft,
514 matchLimit = self.matchLimit,
515 matchFormatString = self.matchFormatString,
516 lastBegin = self.lastBegin,
517 justplay = self.justplay,
518 avoidDuplicateDescription = self.avoidDuplicateDescription,
519 bouquets = self.bouquets[:],
521 encoding = self.encoding,
522 searchType = self.searchType,
523 searchCase = self.searchCase,
524 overrideAlternatives = self.overrideAlternatives
527 def __eq__(self, other):
528 if isinstance(other, AutoTimerComponent):
529 return self.id == other.id
532 def __lt__(self, other):
533 if isinstance(other, AutoTimerComponent):
534 return self.name.lower() < other.name.lower()
537 def __ne__(self, other):
538 return not self.__eq__(other)
548 str(self.searchCase),
549 str(self.searchType),
553 str(self.afterevent),
554 str(([x.pattern for x in self.exclude[0]],
555 [x.pattern for x in self.exclude[1]],
556 [x.pattern for x in self.exclude[2]],
559 str(([x.pattern for x in self.include[0]],
560 [x.pattern for x in self.include[1]],
561 [x.pattern for x in self.include[2]],
564 str(self.maxduration),
566 str(self.destination),
567 str(self.matchCount),
569 str(self.matchLimit),
570 str(self.matchFormatString),
573 str(self.avoidDuplicateDescription),
576 str(self.overrideAlternatives),
581 class AutoTimerFastscanComponent(AutoTimerComponent):
582 def __init__(self, *args, **kwargs):
583 AutoTimerComponent.__init__(self, *args, **kwargs)
584 self._fastServices = None
586 def setBouquets(self, bouquets):
587 AutoTimerComponent.setBouquets(self, bouquets)
588 self._fastServices = None
590 def setServices(self, services):
591 AutoTimerComponent.setServices(self, services)
592 self._fastServices = None
594 def getFastServices(self):
595 if self._fastServices is None:
597 append = fastServices.append
599 for service in self.services:
600 myref = eServiceReference(str(service))
601 if myref.flags & eServiceReference.isGroup:
602 addbouquets.append(service)
604 comp = service.split(':')
605 append(':'.join(comp[3:]))
607 serviceHandler = eServiceCenter.getInstance()
608 for bouquet in bouquets + addbouquets:
609 myref = eServiceReference(str(bouquet))
610 mylist = serviceHandler.list(myref)
611 if mylist is not None:
614 # TODO: I wonder if its sane to assume we get services here (and not just new lists)
615 # We can ignore markers & directorys here because they won't match any event's service :-)
617 # strip all after last :
619 pos = value.rfind(':')
621 if value[pos-1] == ':':
623 value = value[:pos+1]
625 comp = value.split(':')
626 append(':'.join(value[3:]))
629 self._fastServices = fastServices
630 return self._fastServices
632 def checkServices(self, check_service):
633 services = self.getFastServices()
635 check = ':'.join(check_service.split(':')[3:])
636 for service in services:
638 return False # included
639 return True # not included
640 return False # no restriction
642 def getAlternative(self, override_service):
643 services = self.services
645 override = ':'.join(override_service.split(':')[3:])
646 serviceHandler = eServiceCenter.getInstance()
648 for service in services:
649 myref = eServiceReference(str(service))
650 if myref.flags & eServiceReference.isGroup:
651 mylist = serviceHandler.list(myref)
652 if mylist is not None:
656 # strip all after last :
658 pos = value.rfind(':')
660 if value[pos-1] == ':':
662 value = value[:pos+1]
664 if ':'.join(value.split(':')[3:]) == override:
668 return override_service
670 # very basic factory ;-)
671 preferredAutoTimerComponent = lambda *args, **kwargs: AutoTimerFastscanComponent(*args, **kwargs) if config.plugins.autotimer.fastscan.value else AutoTimerComponent(*args, **kwargs)