2 from enigma import eEPGCache, getBestPlayableServiceReference, \
3 eServiceReference, iRecordableService, quitMainloop, eActionMap, setPreferredTuner
5 from Components.config import config
6 from Components.UsageConfig import defaultMoviePath
7 from Components.TimerSanityCheck import TimerSanityCheck
9 from Screens.MessageBox import MessageBox
10 import Screens.Standby
11 import Screens.InfoBar
12 from Tools import Directories, Notifications, ASCIItranslit, Trashcan
13 from Tools.XMLTools import stringToXML
16 import xml.etree.cElementTree
17 import NavigationInstance
18 from ServiceReference import ServiceReference
20 from time import localtime, strftime, ctime, time
21 from bisect import insort
22 from sys import maxint
24 # ok, for descriptions etc we have:
25 # service reference (to get the service name)
27 # description (description)
28 # event data (ONLY for time adjustments etc.)
31 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
32 # begin and end will be corrected
33 def parseEvent(ev, description = True):
35 name = ev.getEventName()
36 description = ev.getShortDescription()
38 description = ev.getExtendedDescription()
42 begin = ev.getBeginTime()
43 end = begin + ev.getDuration()
45 begin -= config.recording.margin_before.value * 60
46 end += config.recording.margin_after.value * 60
47 return (begin, end, name, description, eit)
55 def findSafeRecordPath(dirname):
58 from Components import Harddisk
59 dirname = os.path.realpath(dirname)
60 mountpoint = Harddisk.findMountPoint(dirname)
61 if mountpoint in ('/', '/media'):
62 print '[RecordTimer] media is not mounted:', dirname
64 if not os.path.isdir(dirname):
68 print '[RecordTimer] Failed to create dir "%s":' % dirname, ex
72 def checkForRecordings():
73 if NavigationInstance.instance.getRecordings():
75 rec_time = NavigationInstance.instance.RecordTimer.getNextTimerTime(isWakeup=True)
76 return rec_time > 0 and (rec_time - time()) < 360
78 # please do not translate log messages
79 class RecordTimerEntry(timer.TimerEntry, object):
80 ######### the following static methods and members are only in use when the box is in (soft) standby
82 wasInDeepStandby = False
83 receiveRecordEvents = False
86 def keypress(key=None, flag=1):
87 if flag and (RecordTimerEntry.wasInStandby or RecordTimerEntry.wasInDeepStandby):
88 RecordTimerEntry.wasInStandby = False
89 RecordTimerEntry.wasInDeepStandby = False
90 eActionMap.getInstance().unbindAction('', RecordTimerEntry.keypress)
93 def setWasInDeepStandby():
94 RecordTimerEntry.wasInDeepStandby = True
95 eActionMap.getInstance().bindAction('', -maxint - 1, RecordTimerEntry.keypress)
98 def setWasInStandby():
99 if not RecordTimerEntry.wasInStandby:
100 if not RecordTimerEntry.wasInDeepStandby:
101 eActionMap.getInstance().bindAction('', -maxint - 1, RecordTimerEntry.keypress)
102 RecordTimerEntry.wasInDeepStandby = False
103 RecordTimerEntry.wasInStandby = True
110 def staticGotRecordEvent(recservice, event):
111 if event == iRecordableService.evEnd:
112 print "RecordTimer.staticGotRecordEvent(iRecordableService.evEnd)"
113 if not checkForRecordings():
114 print "No recordings busy of sceduled within 6 minutes so shutdown"
115 RecordTimerEntry.shutdown() # immediate shutdown
116 elif event == iRecordableService.evStart:
117 print "RecordTimer.staticGotRecordEvent(iRecordableService.evStart)"
120 def stopTryQuitMainloop():
121 print "RecordTimer.stopTryQuitMainloop"
122 NavigationInstance.instance.record_event.remove(RecordTimerEntry.staticGotRecordEvent)
123 RecordTimerEntry.receiveRecordEvents = False
126 def TryQuitMainloop():
127 if not RecordTimerEntry.receiveRecordEvents and Screens.Standby.inStandby:
128 print "RecordTimer.TryQuitMainloop"
129 NavigationInstance.instance.record_event.append(RecordTimerEntry.staticGotRecordEvent)
130 RecordTimerEntry.receiveRecordEvents = True
131 # send fake event.. to check if another recordings are running or
132 # other timers start in a few seconds
133 RecordTimerEntry.staticGotRecordEvent(None, iRecordableService.evEnd)
134 #################################################################
136 def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.AUTO, checkOldTimers = False, dirname = None, tags = None, descramble = True, record_ecm = False, always_zap = False, zap_wakeup = "always", rename_repeat = True):
137 timer.TimerEntry.__init__(self, int(begin), int(end))
139 if checkOldTimers == True:
140 if self.begin < time() - 1209600:
141 self.begin = int(time())
143 if self.end < self.begin:
144 self.end = self.begin
146 assert isinstance(serviceref, ServiceReference)
148 if serviceref and serviceref.isRecordable():
149 self.service_ref = serviceref
151 self.service_ref = ServiceReference(None)
153 self.dontSave = False
155 self.description = description
156 self.disabled = disabled
158 self.__record_service = None
159 self.start_prepare = 0
160 self.justplay = justplay
161 self.always_zap = always_zap
162 self.zap_wakeup = zap_wakeup
163 self.afterEvent = afterEvent
164 self.dirname = dirname
165 self.dirnameHadToFallback = False
166 self.autoincrease = False
167 self.autoincreasetime = 3600 * 24 # 1 day
168 self.tags = tags or []
169 self.descramble = descramble
170 self.record_ecm = record_ecm
171 self.rename_repeat = rename_repeat
172 self.needChangePriorityFrontend = config.usage.recording_frontend_priority.value != "-2" and config.usage.recording_frontend_priority.value != config.usage.frontend_priority.value
173 self.change_frontend = False
174 self.InfoBarInstance = Screens.InfoBar.InfoBar.instance
175 self.ts_dialog = None
176 self.log_entries = []
180 return "RecordTimerEntry(name=%s, begin=%s, serviceref=%s, justplay=%s)" % (self.name, ctime(self.begin), self.service_ref, self.justplay)
182 def log(self, code, msg):
183 self.log_entries.append((int(time()), code, msg))
186 def calculateFilename(self, name=None):
187 service_name = self.service_ref.getServiceName()
188 begin_date = strftime("%Y%m%d %H%M", localtime(self.begin))
189 name = name or self.name
190 filename = begin_date + " - " + service_name
192 if config.recording.filename_composition.value == "short":
193 filename = strftime("%Y%m%d", localtime(self.begin)) + " - " + name
194 elif config.recording.filename_composition.value == "long":
195 filename += " - " + name + " - " + self.description
197 filename += " - " + name # standard
199 if config.recording.ascii_filenames.value:
200 filename = ASCIItranslit.legacyEncode(filename)
202 dirname = findSafeRecordPath(defaultMoviePath())
204 dirname = findSafeRecordPath(self.dirname)
206 dirname = findSafeRecordPath(defaultMoviePath())
207 self.dirnameHadToFallback = True
210 self.Filename = Directories.getRecordingFilename(filename, dirname)
211 self.log(0, "Filename calculated as: '%s'" % self.Filename)
214 def tryPrepare(self):
218 if not self.calculateFilename():
220 self.start_prepare = time() + self.backoff
222 rec_ref = self.service_ref and self.service_ref.ref
223 if rec_ref and rec_ref.flags & eServiceReference.isGroup:
224 rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
226 self.log(1, "'get best playable service for group... record' failed")
228 self.setRecordingPreferredTuner()
229 self.record_service = rec_ref and NavigationInstance.instance.recordService(rec_ref)
231 if not self.record_service:
232 self.log(1, "'record service' failed")
233 self.setRecordingPreferredTuner(setdefault=True)
237 description = self.description
239 epgcache = eEPGCache.getInstance()
240 queryTime=self.begin+(self.end-self.begin)/2
241 evt = epgcache.lookupEventTime(rec_ref, queryTime)
243 if self.rename_repeat:
244 event_description = evt.getShortDescription()
245 if not event_description:
246 event_description = evt.getExtendedDescription()
247 if event_description and event_description != description:
248 description = event_description
249 event_name = evt.getEventName()
250 if event_name and event_name != name:
252 if not self.calculateFilename(event_name):
254 self.start_prepare = time() + self.backoff
256 event_id = evt.getEventId()
264 prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, name.replace("\n", ""), description.replace("\n", ""), ' '.join(self.tags), bool(self.descramble), bool(self.record_ecm))
267 self.log(4, "failed to write meta information")
269 self.log(2, "'prepare' failed: error %d" % prep_res)
271 # we must calc nur start time before stopRecordService call because in Screens/Standby.py TryQuitMainloop tries to get
272 # the next start time in evEnd event handler...
274 self.start_prepare = time() + self.backoff
276 NavigationInstance.instance.stopRecordService(self.record_service)
277 self.record_service = None
278 self.setRecordingPreferredTuner(setdefault=True)
282 def do_backoff(self):
283 if self.backoff == 0:
287 if self.backoff > 100:
289 self.log(10, "backoff: retry in %d seconds" % self.backoff)
292 next_state = self.state + 1
293 self.log(5, "activating state %d" % next_state)
297 if Screens.Standby.inStandby:
298 self.log(5, "wakeup and zap to recording service")
299 RecordTimerEntry.setWasInStandby()
300 #set service to zap after standby
301 Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
302 Screens.Standby.inStandby.paused_service = None
304 Screens.Standby.inStandby.Power()
306 if RecordTimerEntry.wasInDeepStandby:
307 RecordTimerEntry.setWasInStandby()
308 cur_zap_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
309 if cur_zap_ref and not cur_zap_ref.getPath():# we do not zap away if it is no live service
310 if self.checkingTimeshiftRunning():
311 if self.ts_dialog is None:
312 self.openChoiceActionBeforeZap()
314 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
315 self.setRecordingPreferredTuner()
317 self.log(5, "zap to recording service")
319 if next_state == self.StatePrepared:
320 if self.tryPrepare():
321 self.log(6, "prepare ok, waiting for begin")
322 # create file to "reserve" the filename
323 # because another recording at the same time on another service can try to record the same event
324 # i.e. cable / sat.. then the second recording needs an own extension... when we create the file
325 # here than calculateFilename is happy
326 if not self.justplay:
327 open(self.Filename + ".ts", "w").close()
328 # Give the Trashcan a chance to clean up
330 Trashcan.instance.cleanIfIdle(self.Filename)
332 print "[TIMER] Failed to call Trashcan.instance.cleanIfIdle()"
333 print "[TIMER] Error:", e
334 # fine. it worked, resources are allocated.
335 self.next_activation = self.begin
339 self.log(7, "prepare failed")
340 if self.first_try_prepare or (self.ts_dialog is not None and not self.checkingTimeshiftRunning()):
341 self.first_try_prepare = False
342 cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
343 if cur_ref and not cur_ref.getPath():
346 if Screens.Standby.inStandby:
347 self.setRecordingPreferredTuner()
349 elif self.checkingTimeshiftRunning():
350 if self.ts_dialog is None:
351 self.openChoiceActionBeforeZap()
352 elif not config.recording.asktozap.value:
353 self.log(8, "asking user to zap away")
354 Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20, default=True)
355 else: # zap without asking
356 self.log(9, "zap without asking")
357 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
358 self.setRecordingPreferredTuner()
361 self.log(8, "currently running service is not a live service.. so stop it makes no sense")
363 self.log(8, "currently no service running... so we dont need to stop it")
366 elif next_state == self.StateRunning:
367 # if this timer has been cancelled, just go to "end" state.
371 if Screens.Standby.inStandby:
372 if RecordTimerEntry.wasInDeepStandby and self.zap_wakeup in ("always", "from_deep_standby") or self.zap_wakeup in ("always", "from_standby"):
373 self.log(11, "wakeup and zap")
374 RecordTimerEntry.setWasInStandby()
375 #set service to zap after standby
376 Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
377 Screens.Standby.inStandby.paused_service = None
379 Screens.Standby.inStandby.Power()
381 if RecordTimerEntry.wasInDeepStandby:
382 RecordTimerEntry.setWasInStandby()
383 if self.checkingTimeshiftRunning():
384 if self.ts_dialog is None:
385 self.openChoiceActionBeforeZap()
387 self.log(11, "zapping")
388 NavigationInstance.instance.playService(self.service_ref.ref)
391 self.log(11, "start recording")
393 if RecordTimerEntry.wasInDeepStandby:
394 RecordTimerEntry.keypress()
395 if Screens.Standby.inStandby: #In case some plugin did put the receiver already in standby
396 config.misc.standbyCounter.value = 0
398 Notifications.AddNotification(Screens.Standby.Standby, StandbyCounterIncrease=False)
399 record_res = self.record_service.start()
400 self.setRecordingPreferredTuner(setdefault=True)
402 self.log(13, "start record returned %d" % record_res)
405 self.begin = time() + self.backoff
408 # Tell the trashcan we started recording. The trashcan gets events,
409 # but cannot tell what the associated path is.
410 Trashcan.instance.markDirty(self.Filename)
414 elif next_state == self.StateEnded:
416 self.ts_dialog = None
417 if self.setAutoincreaseEnd():
418 self.log(12, "autoincrase recording %d minute(s)" % int((self.end - old_end)/60))
421 self.log(12, "stop recording")
422 if not self.justplay:
423 NavigationInstance.instance.stopRecordService(self.record_service)
424 self.record_service = None
425 if not checkForRecordings():
426 if self.afterEvent == AFTEREVENT.DEEPSTANDBY or self.afterEvent == AFTEREVENT.AUTO and (Screens.Standby.inStandby or RecordTimerEntry.wasInStandby) and not config.misc.standbyCounter.value:
427 if not Screens.Standby.inTryQuitMainloop:
428 if Screens.Standby.inStandby:
429 RecordTimerEntry.TryQuitMainloop()
431 Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour receiver. Shutdown now?"), timeout=20, default=True)
432 elif self.afterEvent == AFTEREVENT.STANDBY or self.afterEvent == AFTEREVENT.AUTO and RecordTimerEntry.wasInStandby:
433 if not Screens.Standby.inStandby:
434 Notifications.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nreceiver to standby. Do that now?"), timeout=20, default=True)
436 RecordTimerEntry.keypress()
439 def setAutoincreaseEnd(self, entry = None):
440 if not self.autoincrease:
443 new_end = int(time()) + self.autoincreasetime
445 new_end = entry.begin - 30
447 dummyentry = RecordTimerEntry(self.service_ref, self.begin, new_end, self.name, self.description, self.eit, disabled=True, justplay = self.justplay, afterEvent = self.afterEvent, dirname = self.dirname, tags = self.tags)
448 dummyentry.disabled = self.disabled
449 timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, dummyentry)
450 if not timersanitycheck.check():
451 simulTimerList = timersanitycheck.getSimulTimerList()
452 if simulTimerList is not None and len(simulTimerList) > 1:
453 new_end = simulTimerList[1].begin
454 new_end -= 30 # 30 Sekunden Prepare-Zeit lassen
455 if new_end <= time():
460 def setRecordingPreferredTuner(self, setdefault=False):
461 if self.needChangePriorityFrontend:
463 if not self.change_frontend and not setdefault:
464 elem = config.usage.recording_frontend_priority.value
465 self.change_frontend = True
466 elif self.change_frontend and setdefault:
467 elem = config.usage.frontend_priority.value
468 self.change_frontend = False
470 setPreferredTuner(int(elem))
472 def checkingTimeshiftRunning(self):
473 return config.usage.check_timeshift.value and self.InfoBarInstance and self.InfoBarInstance.timeshiftEnabled() and self.InfoBarInstance.timeshift_was_activated
475 def openChoiceActionBeforeZap(self):
476 if self.ts_dialog is None:
480 elif self.always_zap:
481 type = _("zap and record")
482 message = _("You must switch to the service %s (%s - '%s')!\n") % (type, self.service_ref.getServiceName(), self.name)
484 message += _("Attention, this is repeated timer!\n")
485 message += _("Timeshift is running. Select an action.\n")
486 choice = [(_("Zap"), "zap"), (_("Don't zap and disable timer"), "disable"), (_("Don't zap and remove timer"), "remove")]
487 if not self.InfoBarInstance.save_timeshift_file:
488 choice.insert(1, (_("Save timeshift in movie dir and zap"), "save_movie"))
489 if self.InfoBarInstance.timeshiftActivated():
490 choice.insert(0, (_("Save timeshift and zap"), "save"))
492 choice.insert(1, (_("Save timeshift and zap"), "save"))
494 message += _("Reminder, you have chosen to save timeshift file.")
495 #if self.justplay or self.always_zap:
496 # choice.insert(2, (_("Don't zap"), "continue"))
497 choice.insert(2, (_("Don't zap"), "continue"))
498 def zapAction(choice):
501 if choice in ("zap", "save", "save_movie"):
502 self.log(8, "zap to recording service")
503 if choice in ("save", "save_movie"):
504 ts = self.InfoBarInstance.getTimeshift()
505 if ts and ts.isTimeshiftEnabled():
506 if choice =="save_movie":
507 self.InfoBarInstance.save_timeshift_in_movie_dir = True
508 self.InfoBarInstance.save_timeshift_file = True
509 ts.saveTimeshiftFile()
511 self.InfoBarInstance.saveTimeshiftFiles()
512 elif choice == "disable":
514 NavigationInstance.instance.RecordTimer.timeChanged(self)
516 self.log(8, "zap canceled by the user, timer disabled")
517 elif choice == "remove":
519 self.afterEvent = AFTEREVENT.NONE
520 NavigationInstance.instance.RecordTimer.removeEntry(self)
521 self.log(8, "zap canceled by the user, timer removed")
522 elif choice == "continue":
524 self.end = self.begin
526 self.log(8, "zap canceled by the user")
528 if not self.justplay:
529 self.setRecordingPreferredTuner()
532 self.log(8, "zapping")
533 NavigationInstance.instance.playService(self.service_ref.ref)
534 self.ts_dialog = self.InfoBarInstance.session.openWithCallback(zapAction, MessageBox, message, simple=True, list=choice, timeout=20)
536 def sendStandbyNotification(self, answer):
537 RecordTimerEntry.keypress()
539 Notifications.AddNotification(Screens.Standby.Standby)
541 def sendTryQuitMainloopNotification(self, answer):
542 RecordTimerEntry.keypress()
544 Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
546 def getNextActivation(self):
547 if self.state == self.StateEnded:
550 next_state = self.state + 1
552 return {self.StatePrepared: self.start_prepare,
553 self.StateRunning: self.begin,
554 self.StateEnded: self.end }[next_state]
556 def failureCB(self, answer):
557 self.ts_dialog = None
559 self.log(13, "ok, zapped away")
560 #NavigationInstance.instance.stopUserServices()
561 NavigationInstance.instance.playService(self.service_ref.ref)
563 self.log(14, "user didn't want to zap away, record will probably fail")
565 def timeChanged(self):
566 old_prepare = self.start_prepare
567 self.start_prepare = self.begin - self.prepare_time
570 if int(old_prepare) != int(self.start_prepare):
571 self.log(15, "record time changed, start prepare is now: %s" % ctime(self.start_prepare))
573 def gotRecordEvent(self, record, event):
574 # TODO: this is not working (never true), please fix. (comparing two swig wrapped ePtrs)
575 if self.__record_service.__deref__() != record.__deref__():
577 self.log(16, "record event %d" % event)
578 if event == iRecordableService.evRecordWriteError:
579 print "WRITE ERROR on recording, disk full?"
580 # show notification. the 'id' will make sure that it will be
581 # displayed only once, even if more timers are failing at the
582 # same time. (which is very likely in case of disk fullness)
583 Notifications.AddPopup(text = _("Write error while recording. Disk full?\n"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "DiskFullMessage")
584 # ok, the recording has been stopped. we need to properly note
585 # that in our state, with also keeping the possibility to re-try.
586 # TODO: this has to be done.
587 elif event == iRecordableService.evStart:
588 text = _("A record has been started:\n%s") % self.name
589 notify = config.usage.show_message_when_recording_starts.value and not Screens.Standby.inStandby and self.InfoBarInstance and self.InfoBarInstance.execing
590 if self.dirnameHadToFallback:
591 text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead.")))
594 Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
595 elif event == iRecordableService.evRecordAborted:
596 NavigationInstance.instance.RecordTimer.removeEntry(self)
598 # we have record_service as property to automatically subscribe to record service events
599 def setRecordService(self, service):
600 if self.__record_service is not None:
601 print "[remove callback]"
602 NavigationInstance.instance.record_event.remove(self.gotRecordEvent)
604 self.__record_service = service
606 if self.__record_service is not None:
607 print "[add callback]"
608 NavigationInstance.instance.record_event.append(self.gotRecordEvent)
610 record_service = property(lambda self: self.__record_service, setRecordService)
612 def createTimer(xml):
613 begin = int(xml.get("begin"))
614 end = int(xml.get("end"))
615 serviceref = ServiceReference(xml.get("serviceref").encode("utf-8"))
616 description = xml.get("description").encode("utf-8")
617 repeated = xml.get("repeated").encode("utf-8")
618 rename_repeat = long(xml.get("rename_repeat") or "1")
619 disabled = long(xml.get("disabled") or "0")
620 justplay = long(xml.get("justplay") or "0")
621 always_zap = long(xml.get("always_zap") or "0")
622 zap_wakeup = str(xml.get("zap_wakeup") or "always")
623 afterevent = str(xml.get("afterevent") or "nothing")
625 "nothing": AFTEREVENT.NONE,
626 "standby": AFTEREVENT.STANDBY,
627 "deepstandby": AFTEREVENT.DEEPSTANDBY,
628 "auto": AFTEREVENT.AUTO
631 if eit and eit != "None":
635 location = xml.get("location")
636 if location and location != "None":
637 location = location.encode("utf-8")
640 tags = xml.get("tags")
641 if tags and tags != "None":
642 tags = tags.encode("utf-8").split(' ')
645 descramble = int(xml.get("descramble") or "1")
646 record_ecm = int(xml.get("record_ecm") or "0")
648 name = xml.get("name").encode("utf-8")
649 #filename = xml.get("filename").encode("utf-8")
650 entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags, descramble = descramble, record_ecm = record_ecm, always_zap = always_zap, zap_wakeup = zap_wakeup, rename_repeat = rename_repeat)
651 entry.repeated = int(repeated)
653 for l in xml.findall("log"):
654 time = int(l.get("time"))
655 code = int(l.get("code"))
656 msg = l.text.strip().encode("utf-8")
657 entry.log_entries.append((time, code, msg))
661 class RecordTimer(timer.Timer):
663 timer.Timer.__init__(self)
665 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
670 print "unable to load timers from file!"
672 def doActivate(self, w):
673 # when activating a timer which has already passed,
674 # simply abort the timer. don't run trough all the stages.
676 w.state = RecordTimerEntry.StateEnded
678 # when active returns true, this means "accepted".
679 # otherwise, the current state is kept.
680 # the timer entry itself will fix up the delay then.
684 self.timer_list.remove(w)
686 # did this timer reached the last state?
687 if w.state < RecordTimerEntry.StateEnded:
688 # no, sort it into active list
689 insort(self.timer_list, w)
691 # yes. Process repeated, and re-add.
694 w.state = RecordTimerEntry.StateWaiting
695 w.first_try_prepare = True
696 self.addTimerEntry(w)
698 # Remove old timers as set in config
699 self.cleanupDaily(config.recording.keep_timers.value)
700 insort(self.processed_timers, w)
703 def isRecording(self):
704 for timer in self.timer_list:
705 if timer.isRunning() and not timer.justplay:
711 if not Directories.fileExists(self.Filename):
714 doc = xml.etree.cElementTree.parse(self.Filename)
716 from Tools.Notifications import AddPopup
717 from Screens.MessageBox import MessageBox
719 AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
721 print "timers.xml failed to load!"
724 os.rename(self.Filename, self.Filename + "_old")
725 except (IOError, OSError):
726 print "renaming broken timer failed"
729 print "timers.xml not found!"
734 # put out a message when at least one timer overlaps
736 for timer in root.findall("timer"):
737 newTimer = createTimer(timer)
738 if (self.record(newTimer, True, dosave=False) is not None) and (checkit == True):
739 from Tools.Notifications import AddPopup
740 from Screens.MessageBox import MessageBox
741 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
742 checkit = False # at moment it is enough when the message is displayed one time
745 #root_element = xml.etree.cElementTree.Element('timers')
746 #root_element.text = "\n"
748 #for timer in self.timer_list + self.processed_timers:
749 # some timers (instant records) don't want to be saved.
753 #t = xml.etree.cElementTree.SubElement(root_element, 'timers')
754 #t.set("begin", str(int(timer.begin)))
755 #t.set("end", str(int(timer.end)))
756 #t.set("serviceref", str(timer.service_ref))
757 #t.set("repeated", str(timer.repeated))
758 #t.set("name", timer.name)
759 #t.set("description", timer.description)
760 #t.set("afterevent", str({
761 # AFTEREVENT.NONE: "nothing",
762 # AFTEREVENT.STANDBY: "standby",
763 # AFTEREVENT.DEEPSTANDBY: "deepstandby",
764 # AFTEREVENT.AUTO: "auto"}))
765 #if timer.eit is not None:
766 # t.set("eit", str(timer.eit))
767 #if timer.dirname is not None:
768 # t.set("location", str(timer.dirname))
769 #t.set("disabled", str(int(timer.disabled)))
770 #t.set("justplay", str(int(timer.justplay)))
774 #for time, code, msg in timer.log_entries:
775 #l = xml.etree.cElementTree.SubElement(t, 'log')
776 #l.set("time", str(time))
777 #l.set("code", str(code))
781 #doc = xml.etree.cElementTree.ElementTree(root_element)
782 #doc.write(self.Filename)
786 list.append('<?xml version="1.0" ?>\n')
787 list.append('<timers>\n')
789 for timer in self.timer_list + self.processed_timers:
793 list.append('<timer')
794 list.append(' begin="' + str(int(timer.begin)) + '"')
795 list.append(' end="' + str(int(timer.end)) + '"')
796 list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
797 list.append(' repeated="' + str(int(timer.repeated)) + '"')
798 list.append(' name="' + str(stringToXML(timer.name)) + '"')
799 list.append(' description="' + str(stringToXML(timer.description)) + '"')
800 list.append(' afterevent="' + str(stringToXML({
801 AFTEREVENT.NONE: "nothing",
802 AFTEREVENT.STANDBY: "standby",
803 AFTEREVENT.DEEPSTANDBY: "deepstandby",
804 AFTEREVENT.AUTO: "auto"
805 }[timer.afterEvent])) + '"')
806 if timer.eit is not None:
807 list.append(' eit="' + str(timer.eit) + '"')
808 if timer.dirname is not None:
809 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
810 if timer.tags is not None:
811 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
812 list.append(' disabled="' + str(int(timer.disabled)) + '"')
813 list.append(' justplay="' + str(int(timer.justplay)) + '"')
814 list.append(' always_zap="' + str(int(timer.always_zap)) + '"')
815 list.append(' zap_wakeup="' + str(timer.zap_wakeup) + '"')
816 list.append(' rename_repeat="' + str(int(timer.rename_repeat)) + '"')
817 list.append(' descramble="' + str(int(timer.descramble)) + '"')
818 list.append(' record_ecm="' + str(int(timer.record_ecm)) + '"')
821 if config.recording.debug.value:
822 for time, code, msg in timer.log_entries:
824 list.append(' code="' + str(code) + '"')
825 list.append(' time="' + str(time) + '"')
827 list.append(str(stringToXML(msg)))
828 list.append('</log>\n')
830 list.append('</timer>\n')
832 list.append('</timers>\n')
834 file = open(self.Filename + ".writing", "w")
840 os.fsync(file.fileno())
842 os.rename(self.Filename + ".writing", self.Filename)
844 def getNextZapTime(self, isWakeup=False):
846 for timer in self.timer_list:
847 if not timer.justplay or timer.begin < now or isWakeup and timer.zap_wakeup in ("from_standby", "never"):
852 def getNextRecordingTime(self):
854 for timer in self.timer_list:
855 next_act = timer.getNextActivation()
856 if timer.justplay or next_act < now:
861 def getNextTimerTime(self, isWakeup=False):
863 for timer in self.timer_list:
864 next_act = timer.getNextActivation()
865 if next_act < now or isWakeup and timer.justplay and timer.zap_wakeup in ("from_standby", "never"):
870 def isNextRecordAfterEventActionAuto(self):
873 for timer in self.timer_list:
874 if timer.justplay or timer.begin < now:
876 if t is None or t.begin == timer.begin:
878 if t.afterEvent == AFTEREVENT.AUTO:
882 def record(self, entry, ignoreTSC=False, dosave=True): # wird von loadTimer mit dosave=False aufgerufen
883 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
884 if not timersanitycheck.check():
885 if ignoreTSC != True:
886 print "timer conflict detected!"
887 print timersanitycheck.getSimulTimerList()
888 return timersanitycheck.getSimulTimerList()
890 print "ignore timer conflict"
891 elif timersanitycheck.doubleCheck():
892 print "ignore double timer"
895 print "[Timer] Record " + str(entry)
897 self.addTimerEntry(entry)
902 def isInRepeatTimer(self, timer, event):
905 begin = event.getBeginTime()
906 duration = event.getDuration()
907 end = begin + duration
908 timer_end = timer.end
909 if timer.disabled and timer.isRunning():
910 if begin < timer.begin <= end or timer.begin <= begin <= timer_end:
914 if timer.justplay and (timer_end - timer.begin) <= 1:
916 bt = localtime(begin)
918 begin2 = 1440 + bt.tm_hour * 60 + bt.tm_min
919 end2 = begin2 + duration / 60
920 xbt = localtime(timer.begin)
921 xet = localtime(timer_end)
923 checking_time = timer.begin < begin or begin <= timer.begin <= end
924 if xbt.tm_yday != xet.tm_yday:
926 if oday == -1: oday = 6
927 offset_day = timer.repeated & (1 << oday)
928 xbegin = 1440 + xbt.tm_hour * 60 + xbt.tm_min
929 xend = xbegin + ((timer_end - timer.begin) / 60)
932 if timer.repeated & (1 << bday) and checking_time:
933 if begin2 < xbegin <= end2:
935 # recording within event
936 time_match = (xend - xbegin) * 60
939 # recording last part of event
940 time_match = (end2 - xbegin) * 60
941 summary_end = (xend - end2) * 60
942 is_editable = not summary_end and True or time_match >= summary_end
943 elif xbegin <= begin2 <= xend:
945 # recording first part of event
946 time_match = (xend - begin2) * 60
947 summary_end = (begin2 - xbegin) * 60
948 is_editable = not summary_end and True or time_match >= summary_end
950 # recording whole event
951 time_match = (end2 - begin2) * 60
956 if begin2 < xbegin <= end2:
958 # recording within event
959 time_match = (xend - xbegin) * 60
962 # recording last part of event
963 time_match = (end2 - xbegin) * 60
964 summary_end = (xend - end2) * 60
965 is_editable = not summary_end and True or time_match >= summary_end
966 elif xbegin <= begin2 <= xend:
968 # recording first part of event
969 time_match = (xend - begin2) * 60
970 summary_end = (begin2 - xbegin) * 60
971 is_editable = not summary_end and True or time_match >= summary_end
973 # recording whole event
974 time_match = (end2 - begin2) * 60
976 elif offset_day and checking_time:
979 if begin2 < xbegin <= end2:
981 # recording within event
982 time_match = (xend - xbegin) * 60
985 # recording last part of event
986 time_match = (end2 - xbegin) * 60
987 summary_end = (xend - end2) * 60
988 is_editable = not summary_end and True or time_match >= summary_end
989 elif xbegin <= begin2 <= xend:
991 # recording first part of event
992 time_match = (xend - begin2) * 60
993 summary_end = (begin2 - xbegin) * 60
994 is_editable = not summary_end and True or time_match >= summary_end
996 # recording whole event
997 time_match = (end2 - begin2) * 60
999 return time_match and is_editable
1001 def isInTimer(self, eventid, begin, duration, service):
1006 check_offset_time = not config.recording.margin_before.value and not config.recording.margin_after.value
1007 end = begin + duration
1008 refstr = ':'.join(service.split(':')[:11])
1009 for x in self.timer_list:
1010 check = ':'.join(x.service_ref.ref.toString().split(':')[:11]) == refstr
1012 sref = x.service_ref.ref
1013 parent_sid = sref.getUnsignedData(5)
1014 parent_tsid = sref.getUnsignedData(6)
1015 if parent_sid and parent_tsid:
1016 # check for subservice
1017 sid = sref.getUnsignedData(1)
1018 tsid = sref.getUnsignedData(2)
1019 sref.setUnsignedData(1, parent_sid)
1020 sref.setUnsignedData(2, parent_tsid)
1021 sref.setUnsignedData(5, 0)
1022 sref.setUnsignedData(6, 0)
1023 check = sref.toCompareString() == refstr
1027 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
1028 num = event and event.getNumOfLinkageServices() or 0
1029 sref.setUnsignedData(1, sid)
1030 sref.setUnsignedData(2, tsid)
1031 sref.setUnsignedData(5, parent_sid)
1032 sref.setUnsignedData(6, parent_tsid)
1033 for cnt in range(num):
1034 subservice = event.getLinkageService(sref, cnt)
1035 if sref.toCompareString() == subservice.toCompareString():
1040 timer_begin = x.begin
1042 if not x.repeated and check_offset_time:
1043 if 0 < end - timer_end <= 59:
1045 elif 0 < timer_begin - begin <= 59:
1049 if (timer_end - x.begin) <= 1:
1054 timer_repeat = x.repeated
1055 # if set 'don't stop current event but disable coming events' for repeat timer
1056 running_only_curevent = x.disabled and x.isRunning() and timer_repeat
1057 if running_only_curevent:
1061 if timer_repeat != 0:
1064 bt = localtime(begin)
1066 begin2 = 1440 + bt.tm_hour * 60 + bt.tm_min
1067 end2 = begin2 + duration / 60
1068 xbt = localtime(x.begin)
1069 xet = localtime(timer_end)
1071 checking_time = x.begin < begin or begin <= x.begin <= end
1072 if xbt.tm_yday != xet.tm_yday:
1074 if oday == -1: oday = 6
1075 offset_day = x.repeated & (1 << oday)
1076 xbegin = 1440 + xbt.tm_hour * 60 + xbt.tm_min
1077 xend = xbegin + ((timer_end - x.begin) / 60)
1080 if x.repeated & (1 << bday) and checking_time:
1081 if begin2 < xbegin <= end2:
1083 # recording within event
1084 time_match = (xend - xbegin) * 60
1085 type = type_offset + 3
1087 # recording last part of event
1088 time_match = (end2 - xbegin) * 60
1089 type = type_offset + 1
1090 elif xbegin <= begin2 <= xend:
1092 # recording first part of event
1093 time_match = (xend - begin2) * 60
1094 type = type_offset + 4
1096 # recording whole event
1097 time_match = (end2 - begin2) * 60
1098 type = type_offset + 2
1102 if begin2 < xbegin <= end2:
1104 # recording within event
1105 time_match = (xend - xbegin) * 60
1106 type = type_offset + 3
1108 # recording last part of event
1109 time_match = (end2 - xbegin) * 60
1110 type = type_offset + 1
1111 elif xbegin <= begin2 <= xend:
1113 # recording first part of event
1114 time_match = (xend - begin2) * 60
1115 type = type_offset + 4
1117 # recording whole event
1118 time_match = (end2 - begin2) * 60
1119 type = type_offset + 2
1120 elif offset_day and checking_time:
1123 if begin2 < xbegin <= end2:
1125 # recording within event
1126 time_match = (xend - xbegin) * 60
1127 type = type_offset + 3
1129 # recording last part of event
1130 time_match = (end2 - xbegin) * 60
1131 type = type_offset + 1
1132 elif xbegin <= begin2 <= xend:
1134 # recording first part of event
1135 time_match = (xend - begin2) * 60
1136 type = type_offset + 4
1138 # recording whole event
1139 time_match = (end2 - begin2) * 60
1140 type = type_offset + 2
1142 if begin < timer_begin <= end:
1144 # recording within event
1145 time_match = timer_end - timer_begin
1146 type = type_offset + 3
1148 # recording last part of event
1149 time_match = end - timer_begin
1150 type = type_offset + 1
1151 elif timer_begin <= begin <= timer_end:
1153 # recording first part of event
1154 time_match = timer_end - begin
1155 type = type_offset + 4
1157 # recording whole event
1158 time_match = end - begin
1159 type = type_offset + 2
1161 if type in (2,7,12,17,22,27):
1162 # When full recording do not look further
1163 returnValue = (time_match, [type])
1166 if type not in returnValue[1]:
1167 returnValue[1].append(type)
1169 returnValue = (time_match, [type])
1173 def removeEntry(self, entry):
1174 print "[Timer] Remove " + str(entry)
1176 # avoid re-enqueuing
1177 entry.repeated = False
1180 # this sets the end time to current time, so timer will be stopped.
1181 entry.autoincrease = False
1184 if entry.state != entry.StateEnded:
1185 self.timeChanged(entry)
1187 print "state: ", entry.state
1188 print "in processed: ", entry in self.processed_timers
1189 print "in running: ", entry in self.timer_list
1190 # autoincrease instanttimer if possible
1191 if not entry.dontSave:
1192 for x in self.timer_list:
1193 if x.setAutoincreaseEnd():
1195 # now the timer should be in the processed_timers list. remove it from there.
1196 self.processed_timers.remove(entry)