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, \
47 offset=None, afterevent=[], exclude=None, maxduration=None, \
48 destination=None, include=None, matchCount=0, matchLeft=0, \
49 matchLimit='', matchFormatString='', lastBegin=0, justplay=False, \
50 avoidDuplicateDescription=0, searchForDuplicateDescription=2, bouquets=None, \
51 tags=None, searchType="partial", searchCase="insensitive", \
52 overrideAlternatives=False, timeframe=None, vps_enabled=False, \
53 vps_overwrite=False, setEndtime=False, series_labeling=False):
56 self.enabled = enabled
57 self.timespan = timespan
58 self.services = services
60 self.afterevent = afterevent
61 self.exclude = exclude
62 self.maxduration = maxduration
63 self.destination = destination
64 self.include = include
65 self.matchCount = matchCount
66 self.matchLeft = matchLeft
67 self.matchLimit = matchLimit
68 self.matchFormatString = matchFormatString
69 self.lastBegin = lastBegin
70 self.justplay = justplay
71 self.avoidDuplicateDescription = avoidDuplicateDescription
72 self.searchForDuplicateDescription = searchForDuplicateDescription
73 self.bouquets = bouquets
74 self.tags = tags or []
75 self.searchType = searchType
76 self.searchCase = searchCase
77 self.overrideAlternatives = overrideAlternatives
78 self.timeframe = timeframe
79 self.vps_enabled = vps_enabled
80 self.vps_overwrite = vps_overwrite
81 self.series_labeling = series_labeling
82 self.setEndtime = setEndtime
84 ### Attributes / Properties
86 def setAfterEvent(self, afterevent):
87 if afterevent is not self._afterevent:
88 del self._afterevent[:]
92 for action, timespan in afterevent:
93 if timespan is None or timespan[0] is None:
94 self._afterevent.append((action, (None,)))
96 self._afterevent.append((action, self.calculateDayspan(*timespan)))
98 afterevent = property(lambda self: self._afterevent, setAfterEvent)
100 def setBouquets(self, bouquets):
102 self._bouquets = bouquets
106 bouquets = property(lambda self: self._bouquets , setBouquets)
108 def setExclude(self, exclude):
111 [re_compile(x) for x in exclude[0]],
112 [re_compile(x) for x in exclude[1]],
113 [re_compile(x) for x in exclude[2]],
117 self._exclude = ([], [], [], [])
119 exclude = property(lambda self: self._exclude, setExclude)
121 def setInclude(self, include):
124 [re_compile(x) for x in include[0]],
125 [re_compile(x) for x in include[1]],
126 [re_compile(x) for x in include[2]],
130 self._include = ([], [], [], [])
132 include = property(lambda self: self._include, setInclude)
134 def setSearchCase(self, case):
135 assert case in ("sensitive", "insensitive"), "search case must be sensitive or insensitive"
136 self._searchCase = case
138 searchCase = property(lambda self: self._searchCase, setSearchCase)
140 def setSearchType(self, type):
141 assert type in ("exact", "partial", "description"), "search type must be exact, partial or description"
142 self._searchType = type
144 searchType = property(lambda self: self._searchType, setSearchType)
146 def setServices(self, services):
148 self._services = services
152 services = property(lambda self: self._services, setServices)
154 def setTimespan(self, timespan):
155 if timespan is None or timespan and timespan[0] is None:
156 self._timespan = (None,)
158 self._timespan = self.calculateDayspan(*timespan)
160 timespan = property(lambda self: self._timespan, setTimespan)
162 ### See if Attributes are set
164 def hasAfterEvent(self):
165 return len(self.afterevent)
167 def hasAfterEventTimespan(self):
168 for afterevent in self.afterevent:
169 if afterevent[1][0] is not None:
173 def hasCounter(self):
174 return self.matchCount != 0
176 def hasCounterFormatString(self):
177 return self.matchFormatString != ''
179 def hasDestination(self):
180 return self.destination is not None
182 def hasDuration(self):
183 return self.maxduration is not None
186 return len(self.tags)
188 def hasTimespan(self):
189 return self.timespan[0] is not None
192 return self.offset is not None
194 def hasTimeframe(self):
195 return self.timeframe is not None
200 Returns a tulple of (input begin, input end, begin earlier than end)
202 def calculateDayspan(self, begin, end, ignore = None):
203 if end[0] < begin[0] or (end[0] == begin[0] and end[1] <= begin[1]):
204 return (begin, end, True)
206 return (begin, end, False)
209 Returns if a given timestruct is in a timespan
211 def checkAnyTimespan(self, time, begin = None, end = None, haveDayspan = False):
215 # Check if we span a day
217 # Check if begin of event is later than our timespan starts
218 if time.tm_hour > begin[0] or (time.tm_hour == begin[0] and time.tm_min >= begin[1]):
219 # If so, event is in our timespan
221 # Check if begin of event is earlier than our timespan end
222 if time.tm_hour < end[0] or (time.tm_hour == end[0] and time.tm_min <= end[1]):
223 # If so, event is in our timespan
227 # Check if event begins earlier than our timespan starts
228 if time.tm_hour < begin[0] or (time.tm_hour == begin[0] and time.tm_min < begin[1]):
229 # Its out of our timespan then
231 # Check if event begins later than our timespan ends
232 if time.tm_hour > end[0] or (time.tm_hour == end[0] and time.tm_min > end[1]):
233 # Its out of our timespan then
238 Called when a timer based on this component was added
240 def update(self, begin, timestamp):
241 # Only update limit when we have new begin
242 if begin > self.lastBegin:
243 self.lastBegin = begin
246 # %m is Month, %U is week (sunday), %W is week (monday)
247 newLimit = strftime(self.matchFormatString, timestamp)
249 if newLimit != self.matchLimit:
250 self.matchLeft = self.matchCount
251 self.matchLimit = newLimit
253 ### Makes saving Config easier
255 getAvoidDuplicateDescription = lambda self: self.avoidDuplicateDescription
257 getBouquets = lambda self: self._bouquets
259 getCompleteAfterEvent = lambda self: self._afterevent
261 getCounter = lambda self: self.matchCount
262 getCounterFormatString = lambda self: self.matchFormatString
263 getCounterLeft = lambda self: self.matchLeft
264 getCounterLimit = lambda self: self.matchLimit
266 # 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 ;-)
267 getDestination = lambda self: self.destination is not None
269 getDuration = lambda self: self.maxduration/60
271 getEnabled = lambda self: self.enabled and "yes" or "no"
273 getExclude = lambda self: self._exclude
274 getExcludedDays = lambda self: self.exclude[3]
275 getExcludedDescription = lambda self: [x.pattern for x in self.exclude[2]]
276 getExcludedShort = lambda self: [x.pattern for x in self.exclude[1]]
277 getExcludedTitle = lambda self: [x.pattern for x in self.exclude[0]]
279 getId = lambda self: self.id
281 getInclude = lambda self: self._include
282 getIncludedTitle = lambda self: [x.pattern for x in self.include[0]]
283 getIncludedShort = lambda self: [x.pattern for x in self.include[1]]
284 getIncludedDescription = lambda self: [x.pattern for x in self.include[2]]
285 getIncludedDays = lambda self: self.include[3]
287 getJustplay = lambda self: self.justplay and "1" or "0"
289 getLastBegin = lambda self: self.lastBegin
291 getMatch = lambda self: self.match
292 getName = lambda self: self.name
294 getOffsetBegin = lambda self: self.offset[0]/60
295 getOffsetEnd = lambda self: self.offset[1]/60
297 getOverrideAlternatives = lambda self: self.overrideAlternatives and "1" or "0"
299 getServices = lambda self: self._services
301 getTags = lambda self: self.tags
303 getTimespan = lambda self: self._timespan
304 getTimespanBegin = lambda self: '%02d:%02d' % (self.timespan[0][0], self.timespan[0][1])
305 getTimespanEnd = lambda self: '%02d:%02d' % (self.timespan[1][0], self.timespan[1][1])
307 getTimeframe = lambda self: self.timeframe
308 getTimeframeBegin = lambda self: int(self.timeframe[0])
309 getTimeframeEnd = lambda self: int(self.timeframe[1])
311 isOffsetEqual = lambda self: self.offset[0] == self.offset[1]
313 ### Actual functionality
315 def applyOffset(self, begin, end):
316 if self.offset is None:
318 return (begin - self.offset[0], end + self.offset[1])
320 def checkCounter(self, timestamp):
321 # 0-Count is considered "unset"
322 if self.matchCount == 0:
325 # Check if event is in current timespan (we can only manage one!)
326 limit = strftime(self.matchFormatString, timestamp)
327 if limit != self.matchLimit:
330 if self.matchLeft > 0:
334 def checkDuration(self, length):
335 if self.maxduration is None:
337 return length > self.maxduration
339 def checkExcluded(self, title, short, extended, dayofweek):
340 if dayofweek and self.exclude[3]:
341 list = self.exclude[3]
342 if dayofweek in list:
344 if "weekend" in list and dayofweek in ("5", "6"):
346 if "weekday" in list and dayofweek in ("0", "1", "2", "3", "4"):
349 for exclude in self.exclude[0]:
350 if exclude.search(title) is not None:
352 for exclude in self.exclude[1]:
353 if exclude.search(short) is not None:
355 for exclude in self.exclude[2]:
356 if exclude.search(extended) is not None:
360 def checkFilter(self, title, short, extended, dayofweek):
361 if self.checkExcluded(title, short, extended, dayofweek):
364 return self.checkIncluded(title, short, extended, dayofweek)
366 def checkIncluded(self, title, short, extended, dayofweek):
367 if dayofweek and self.include[3]:
368 list = self.include[3][:]
369 if "weekend" in list:
370 list.extend(("5", "6"))
371 if "weekday" in list:
372 list.extend(("0", "1", "2", "3", "4"))
373 if dayofweek not in list:
377 for include in self.include[0]:
378 if include.search(title) is not None:
383 for include in self.include[1]:
384 if include.search(short) is not None:
389 for include in self.include[2]:
390 if include.search(extended) is not None:
396 def checkServices(self, check_service):
397 services = self.services
398 bouquets = self.bouquets
399 if services or bouquets:
402 for service in services:
403 if service == check_service:
406 myref = eServiceReference(str(service))
407 if myref.flags & eServiceReference.isGroup:
408 addbouquets.append(service)
410 serviceHandler = eServiceCenter.getInstance()
411 for bouquet in bouquets + addbouquets:
412 myref = eServiceReference(str(bouquet))
413 mylist = serviceHandler.list(myref)
414 if mylist is not None:
417 # TODO: I wonder if its sane to assume we get services here (and not just new lists)
418 # We can ignore markers & directorys here because they won't match any event's service :-)
420 # strip all after last :
422 pos = value.rfind(':')
424 if value[pos-1] == ':':
426 value = value[:pos+1]
428 if value == check_service:
436 Return alternative service including a given ref.
437 Note that this only works for alternatives that the autotimer is restricted to.
439 def getAlternative(self, override_service):
440 services = self.services
442 serviceHandler = eServiceCenter.getInstance()
444 for service in services:
445 myref = eServiceReference(str(service))
446 if myref.flags & eServiceReference.isGroup:
447 mylist = serviceHandler.list(myref)
448 if mylist is not None:
452 # strip all after last :
454 pos = value.rfind(':')
456 if value[pos-1] == ':':
458 value = value[:pos+1]
460 if value == override_service:
464 return override_service
466 def checkTimespan(self, begin):
467 return self.checkAnyTimespan(begin, *self.timespan)
469 def decrementCounter(self):
470 if self.matchCount and self.matchLeft > 0:
473 def getAfterEvent(self):
474 for afterevent in self.afterevent:
475 if afterevent[1][0] is None:
479 def getAfterEventTimespan(self, end):
480 for afterevent in self.afterevent:
481 if not self.checkAnyTimespan(end, *afterevent[1]):
485 def checkTimeframe(self, begin):
486 timerframe = self.timeframe
487 if timeframe is not None:
488 start, end = timeframe
489 if start == end: # NOTE: by convention start == end indicates open ended from begin
491 if begin > start and begin < end:
499 return self.__class__(
504 timespan = self.timespan,
505 services = self.services,
506 offset = self.offset,
507 afterevent = self.afterevent,
508 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.getExcludedDays()),
509 maxduration = self.maxduration,
510 destination = self.destination,
511 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.getIncludedDays()),
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 searchForDuplicateDescription = self.searchForDuplicateDescription,
520 bouquets = self.bouquets,
522 searchType = self.searchType,
523 searchCase = self.searchCase,
524 overrideAlternatives = self.overrideAlternatives,
525 timeframe = self.timeframe,
526 vps_enabled = self.vps_enabled,
527 vps_overwrite = self.vps_overwrite,
528 series_labeling = self.series_labeling,
531 def __deepcopy__(self, memo):
532 return self.__class__(
537 timespan = self.timespan,
538 services = self.services[:],
539 offset = self.offset and self.offset[:],
540 afterevent = self.afterevent[:],
541 exclude = (self.getExcludedTitle(), self.getExcludedShort(), self.getExcludedDescription(), self.exclude[3][:]),
542 maxduration = self.maxduration,
543 destination = self.destination,
544 include = (self.getIncludedTitle(), self.getIncludedShort(), self.getIncludedDescription(), self.include[3][:]),
545 matchCount = self.matchCount,
546 matchLeft = self.matchLeft,
547 matchLimit = self.matchLimit,
548 matchFormatString = self.matchFormatString,
549 lastBegin = self.lastBegin,
550 justplay = self.justplay,
551 avoidDuplicateDescription = self.avoidDuplicateDescription,
552 searchForDuplicateDescription = self.searchForDuplicateDescription,
553 bouquets = self.bouquets[:],
555 searchType = self.searchType,
556 searchCase = self.searchCase,
557 overrideAlternatives = self.overrideAlternatives,
558 timeframe = self.timeframe,
559 vps_enabled = self.vps_enabled,
560 vps_overwrite = self.vps_overwrite,
561 series_labeling = self.series_labeling,
564 def __eq__(self, other):
565 if isinstance(other, AutoTimerComponent):
566 return self.id == other.id
569 def __lt__(self, other):
570 if isinstance(other, AutoTimerComponent):
571 return self.name.lower() < other.name.lower()
574 def __ne__(self, other):
575 return not self.__eq__(other)
584 str(self.searchCase),
585 str(self.searchType),
589 str(self.afterevent),
590 str(([x.pattern for x in self.exclude[0]],
591 [x.pattern for x in self.exclude[1]],
592 [x.pattern for x in self.exclude[2]],
595 str(([x.pattern for x in self.include[0]],
596 [x.pattern for x in self.include[1]],
597 [x.pattern for x in self.include[2]],
600 str(self.maxduration),
602 str(self.destination),
603 str(self.matchCount),
605 str(self.matchLimit),
606 str(self.matchFormatString),
609 str(self.avoidDuplicateDescription),
610 str(self.searchForDuplicateDescription),
613 str(self.overrideAlternatives),
615 str(self.vps_enabled),
616 str(self.vps_overwrite),
617 str(self.series_labeling),
622 class AutoTimerFastscanComponent(AutoTimerComponent):
623 def __init__(self, *args, **kwargs):
624 AutoTimerComponent.__init__(self, *args, **kwargs)
625 self._fastServices = None
627 def setBouquets(self, bouquets):
628 AutoTimerComponent.setBouquets(self, bouquets)
629 self._fastServices = None
631 def setServices(self, services):
632 AutoTimerComponent.setServices(self, services)
633 self._fastServices = None
635 def getFastServices(self):
636 if self._fastServices is None:
638 append = fastServices.append
640 for service in self.services:
641 myref = eServiceReference(str(service))
642 if myref.flags & eServiceReference.isGroup:
643 addbouquets.append(service)
645 comp = service.split(':')
646 append(':'.join(comp[3:]))
648 serviceHandler = eServiceCenter.getInstance()
649 for bouquet in self.bouquets + addbouquets:
650 myref = eServiceReference(str(bouquet))
651 mylist = serviceHandler.list(myref)
652 if mylist is not None:
655 # TODO: I wonder if its sane to assume we get services here (and not just new lists)
656 # We can ignore markers & directorys here because they won't match any event's service :-)
658 # strip all after last :
660 pos = value.rfind(':')
662 if value[pos-1] == ':':
664 value = value[:pos+1]
666 comp = value.split(':')
667 append(':'.join(value[3:]))
670 self._fastServices = fastServices
671 return self._fastServices
673 def checkServices(self, check_service):
674 services = self.getFastServices()
676 check = ':'.join(check_service.split(':')[3:])
677 for service in services:
679 return False # included
680 return True # not included
681 return False # no restriction
683 def getAlternative(self, override_service):
684 services = self.services
686 override = ':'.join(override_service.split(':')[3:])
687 serviceHandler = eServiceCenter.getInstance()
689 for service in services:
690 myref = eServiceReference(str(service))
691 if myref.flags & eServiceReference.isGroup:
692 mylist = serviceHandler.list(myref)
693 if mylist is not None:
697 # strip all after last :
699 pos = value.rfind(':')
701 if value[pos-1] == ':':
703 value = value[:pos+1]
705 if ':'.join(value.split(':')[3:]) == override:
709 return override_service
711 # very basic factory ;-)
712 preferredAutoTimerComponent = lambda *args, **kwargs: AutoTimerFastscanComponent(*args, **kwargs) if config.plugins.autotimer.fastscan.value else AutoTimerComponent(*args, **kwargs)