RecordTimer.py: import needed insort
[vuplus_dvbapp] / RecordTimer.py
1 import time
2 #from time import datetime
3 from Tools import Directories, Notifications, ASCIItranslit
4
5 from Components.config import config
6 import timer
7 import xml.etree.cElementTree
8
9 from enigma import eEPGCache, getBestPlayableServiceReference, \
10         eServiceReference, iRecordableService, quitMainloop
11
12 from Screens.MessageBox import MessageBox
13 from Components.TimerSanityCheck import TimerSanityCheck
14 import NavigationInstance
15
16 import Screens.Standby
17
18 from time import localtime
19
20 from Tools.XMLTools import stringToXML
21 from ServiceReference import ServiceReference
22
23 from bisect import insort
24
25 # ok, for descriptions etc we have:
26 # service reference  (to get the service name)
27 # name               (title)
28 # description        (description)
29 # event data         (ONLY for time adjustments etc.)
30
31
32 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
33 # begin and end will be corrected
34 def parseEvent(ev, description = True):
35         if description:
36                 name = ev.getEventName()
37                 description = ev.getShortDescription()
38         else:
39                 name = ""
40                 description = ""
41         begin = ev.getBeginTime()
42         end = begin + ev.getDuration()
43         eit = ev.getEventId()
44         begin -= config.recording.margin_before.value * 60
45         end += config.recording.margin_after.value * 60
46         return (begin, end, name, description, eit)
47
48 class AFTEREVENT:
49         NONE = 0
50         STANDBY = 1
51         DEEPSTANDBY = 2
52         AUTO = 3
53
54 # please do not translate log messages
55 class RecordTimerEntry(timer.TimerEntry, object):
56 ######### the following static methods and members are only in use when the box is in (soft) standby
57         receiveRecordEvents = False
58
59         @staticmethod
60         def shutdown():
61                 quitMainloop(1)
62
63         @staticmethod
64         def staticGotRecordEvent(recservice, event):
65                 if event == iRecordableService.evEnd:
66                         print "RecordTimer.staticGotRecordEvent(iRecordableService.evEnd)"
67                         recordings = NavigationInstance.instance.getRecordings()
68                         if not recordings: # no more recordings exist
69                                 rec_time = NavigationInstance.instance.RecordTimer.getNextRecordingTime()
70                                 if rec_time > 0 and (rec_time - time.time()) < 360:
71                                         print "another recording starts in", rec_time - time.time(), "seconds... do not shutdown yet"
72                                 else:
73                                         print "no starting records in the next 360 seconds... immediate shutdown"
74                                         RecordTimerEntry.shutdown() # immediate shutdown
75                 elif event == iRecordableService.evStart:
76                         print "RecordTimer.staticGotRecordEvent(iRecordableService.evStart)"
77
78         @staticmethod
79         def stopTryQuitMainloop():
80                 print "RecordTimer.stopTryQuitMainloop"
81                 NavigationInstance.instance.record_event.remove(RecordTimerEntry.staticGotRecordEvent)
82                 RecordTimerEntry.receiveRecordEvents = False
83
84         @staticmethod
85         def TryQuitMainloop(default_yes = True):
86                 if not RecordTimerEntry.receiveRecordEvents:
87                         print "RecordTimer.TryQuitMainloop"
88                         NavigationInstance.instance.record_event.append(RecordTimerEntry.staticGotRecordEvent)
89                         RecordTimerEntry.receiveRecordEvents = True
90                         # send fake event.. to check if another recordings are running or
91                         # other timers start in a few seconds
92                         RecordTimerEntry.staticGotRecordEvent(None, iRecordableService.evEnd)
93                         # send normal notification for the case the user leave the standby now..
94                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1, onSessionOpenCallback=RecordTimerEntry.stopTryQuitMainloop, default_yes = default_yes)
95 #################################################################
96
97         def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.AUTO, checkOldTimers = False, dirname = None, tags = None):
98                 timer.TimerEntry.__init__(self, int(begin), int(end))
99
100                 if checkOldTimers == True:
101                         if self.begin < time.time() - 1209600:
102                                 self.begin = int(time.time())
103                 
104                 if self.end < self.begin:
105                         self.end = self.begin
106                 
107                 assert isinstance(serviceref, ServiceReference)
108                 
109                 self.service_ref = serviceref
110                 self.eit = eit
111                 self.dontSave = False
112                 self.name = name
113                 self.description = description
114                 self.disabled = disabled
115                 self.timer = None
116                 self.__record_service = None
117                 self.start_prepare = 0
118                 self.justplay = justplay
119                 self.afterEvent = afterEvent
120                 self.dirname = dirname
121                 self.dirnameHadToFallback = False
122                 self.autoincrease = False
123                 self.autoincreasetime = 3600 * 24 # 1 day
124                 self.tags = tags or []
125
126                 self.log_entries = []
127                 self.resetState()
128         
129         def log(self, code, msg):
130                 self.log_entries.append((int(time.time()), code, msg))
131                 print "[TIMER]", msg
132
133         def calculateFilename(self):
134                 service_name = self.service_ref.getServiceName()
135                 begin_date = time.strftime("%Y%m%d %H%M", time.localtime(self.begin))
136                 
137                 print "begin_date: ", begin_date
138                 print "service_name: ", service_name
139                 print "name:", self.name
140                 print "description: ", self.description
141                 
142                 filename = begin_date + " - " + service_name
143                 if self.name:
144                         filename += " - " + self.name
145
146                 if config.recording.ascii_filenames.value:
147                         filename = ASCIItranslit.legacyEncode(filename)
148
149                 if self.dirname and not Directories.fileExists(self.dirname, 'w'):
150                         self.dirnameHadToFallback = True
151                         self.Filename = Directories.getRecordingFilename(filename, None)
152                 else:
153                         self.Filename = Directories.getRecordingFilename(filename, self.dirname)
154                 self.log(0, "Filename calculated as: '%s'" % self.Filename)
155                 #begin_date + " - " + service_name + description)
156
157         def tryPrepare(self):
158                 if self.justplay:
159                         return True
160                 else:
161                         self.calculateFilename()
162                         rec_ref = self.service_ref and self.service_ref.ref
163                         if rec_ref and rec_ref.flags & eServiceReference.isGroup:
164                                 rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
165                                 if not rec_ref:
166                                         self.log(1, "'get best playable service for group... record' failed")
167                                         return False
168                                 
169                         self.record_service = rec_ref and NavigationInstance.instance.recordService(rec_ref)
170
171                         if not self.record_service:
172                                 self.log(1, "'record service' failed")
173                                 return False
174
175                         if self.repeated:
176                                 epgcache = eEPGCache.getInstance()
177                                 queryTime=self.begin+(self.end-self.begin)/2
178                                 evt = epgcache.lookupEventTime(rec_ref, queryTime)
179                                 if evt:
180                                         self.description = evt.getShortDescription()
181                                         event_id = evt.getEventId()
182                                 else:
183                                         event_id = -1
184                         else:
185                                 event_id = self.eit
186                                 if event_id is None:
187                                         event_id = -1
188
189                         prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, self.name.replace("\n", ""), self.description.replace("\n", ""), ' '.join(self.tags))
190                         if prep_res:
191                                 if prep_res == -255:
192                                         self.log(4, "failed to write meta information")
193                                 else:
194                                         self.log(2, "'prepare' failed: error %d" % prep_res)
195
196                                 # we must calc nur start time before stopRecordService call because in Screens/Standby.py TryQuitMainloop tries to get
197                                 # the next start time in evEnd event handler...
198                                 self.do_backoff()
199                                 self.start_prepare = time.time() + self.backoff
200
201                                 NavigationInstance.instance.stopRecordService(self.record_service)
202                                 self.record_service = None
203                                 return False
204                         return True
205
206         def do_backoff(self):
207                 if self.backoff == 0:
208                         self.backoff = 5
209                 else:
210                         self.backoff *= 2
211                         if self.backoff > 100:
212                                 self.backoff = 100
213                 self.log(10, "backoff: retry in %d seconds" % self.backoff)
214
215         def activate(self):
216                 next_state = self.state + 1
217                 self.log(5, "activating state %d" % next_state)
218
219                 if next_state == self.StatePrepared:
220                         if self.tryPrepare():
221                                 self.log(6, "prepare ok, waiting for begin")
222                                 # create file to "reserve" the filename
223                                 # because another recording at the same time on another service can try to record the same event
224                                 # i.e. cable / sat.. then the second recording needs an own extension... when we create the file
225                                 # here than calculateFilename is happy
226                                 open(self.Filename + ".ts", "w").close() 
227                                 # fine. it worked, resources are allocated.
228                                 self.next_activation = self.begin
229                                 self.backoff = 0
230                                 return True
231
232                         self.log(7, "prepare failed")
233                         if self.first_try_prepare:
234                                 self.first_try_prepare = False
235                                 cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
236                                 if cur_ref and not cur_ref.getPath():
237                                         if not config.recording.asktozap.value:
238                                                 self.log(8, "asking user to zap away")
239                                                 Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20)
240                                         else: # zap without asking
241                                                 self.log(9, "zap without asking")
242                                                 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
243                                                 self.failureCB(True)
244                                 elif cur_ref:
245                                         self.log(8, "currently running service is not a live service.. so stop it makes no sense")
246                                 else:
247                                         self.log(8, "currently no service running... so we dont need to stop it")
248                         return False
249                 elif next_state == self.StateRunning:
250                         # if this timer has been cancelled, just go to "end" state.
251                         if self.cancelled:
252                                 return True
253
254                         if self.justplay:
255                                 if Screens.Standby.inStandby:
256                                         self.log(11, "wakeup and zap")
257                                         #set service to zap after standby
258                                         Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
259                                         #wakeup standby
260                                         Screens.Standby.inStandby.Power()
261                                 else:
262                                         self.log(11, "zapping")
263                                         NavigationInstance.instance.playService(self.service_ref.ref)
264                                 return True
265                         else:
266                                 self.log(11, "start recording")
267                                 record_res = self.record_service.start()
268                                 
269                                 if record_res:
270                                         self.log(13, "start record returned %d" % record_res)
271                                         self.do_backoff()
272                                         # retry
273                                         self.begin = time.time() + self.backoff
274                                         return False
275
276                                 return True
277                 elif next_state == self.StateEnded:
278                         old_end = self.end
279                         if self.setAutoincreaseEnd():
280                                 self.log(12, "autoincrase recording %d minute(s)" % int((self.end - old_end)/60))
281                                 self.state -= 1
282                                 return True
283                         self.log(12, "stop recording")
284                         if not self.justplay:
285                                 NavigationInstance.instance.stopRecordService(self.record_service)
286                                 self.record_service = None
287                         if self.afterEvent == AFTEREVENT.STANDBY:
288                                 if not Screens.Standby.inStandby: # not already in standby
289                                         Notifications.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nDreambox to standby. Do that now?"), timeout = 20)
290                         elif self.afterEvent == AFTEREVENT.DEEPSTANDBY:
291                                 if not Screens.Standby.inTryQuitMainloop: # not a shutdown messagebox is open
292                                         if Screens.Standby.inStandby: # in standby
293                                                 RecordTimerEntry.TryQuitMainloop() # start shutdown handling without screen
294                                         else:
295                                                 Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour Dreambox. Shutdown now?"), timeout = 20)
296                         return True
297
298         def setAutoincreaseEnd(self, entry = None):
299                 if not self.autoincrease:
300                         return False
301                 if entry is None:
302                         new_end =  int(time.time()) + self.autoincreasetime
303                 else:
304                         new_end = entry.begin -30
305
306                 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)
307                 dummyentry.disabled = self.disabled
308                 timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, dummyentry)
309                 if not timersanitycheck.check():
310                         simulTimerList = timersanitycheck.getSimulTimerList()
311                         new_end = simulTimerList[1].begin
312                         del simulTimerList
313                         new_end -= 30                           # 30 Sekunden Prepare-Zeit lassen
314                 del dummyentry
315                 if new_end <= time.time():
316                         return False
317                 self.end = new_end
318                 return True
319         
320         
321         def sendStandbyNotification(self, answer):
322                 if answer:
323                         Notifications.AddNotification(Screens.Standby.Standby)
324
325         def sendTryQuitMainloopNotification(self, answer):
326                 if answer:
327                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
328
329         def getNextActivation(self):
330                 if self.state == self.StateEnded:
331                         return self.end
332                 
333                 next_state = self.state + 1
334                 
335                 return {self.StatePrepared: self.start_prepare, 
336                                 self.StateRunning: self.begin, 
337                                 self.StateEnded: self.end }[next_state]
338
339         def failureCB(self, answer):
340                 if answer == True:
341                         self.log(13, "ok, zapped away")
342                         #NavigationInstance.instance.stopUserServices()
343                         NavigationInstance.instance.playService(self.service_ref.ref)
344                 else:
345                         self.log(14, "user didn't want to zap away, record will probably fail")
346
347         def timeChanged(self):
348                 old_prepare = self.start_prepare
349                 self.start_prepare = self.begin - self.prepare_time
350                 self.backoff = 0
351                 
352                 if int(old_prepare) != int(self.start_prepare):
353                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
354
355         def gotRecordEvent(self, record, event):
356                 # TODO: this is not working (never true), please fix. (comparing two swig wrapped ePtrs)
357                 if self.__record_service.__deref__() != record.__deref__():
358                         return
359                 self.log(16, "record event %d" % event)
360                 if event == iRecordableService.evRecordWriteError:
361                         print "WRITE ERROR on recording, disk full?"
362                         # show notification. the 'id' will make sure that it will be
363                         # displayed only once, even if more timers are failing at the
364                         # same time. (which is very likely in case of disk fullness)
365                         Notifications.AddPopup(text = _("Write error while recording. Disk full?\n"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "DiskFullMessage")
366                         # ok, the recording has been stopped. we need to properly note 
367                         # that in our state, with also keeping the possibility to re-try.
368                         # TODO: this has to be done.
369                 elif event == iRecordableService.evStart:
370                         text = _("A record has been started:\n%s") % self.name
371                         if self.dirnameHadToFallback:
372                                 text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead.")))
373
374                         if config.usage.show_message_when_recording_starts.value:
375                                 Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
376
377         # we have record_service as property to automatically subscribe to record service events
378         def setRecordService(self, service):
379                 if self.__record_service is not None:
380                         print "[remove callback]"
381                         NavigationInstance.instance.record_event.remove(self.gotRecordEvent)
382
383                 self.__record_service = service
384
385                 if self.__record_service is not None:
386                         print "[add callback]"
387                         NavigationInstance.instance.record_event.append(self.gotRecordEvent)
388
389         record_service = property(lambda self: self.__record_service, setRecordService)
390
391 def createTimer(xml):
392         begin = int(xml.get("begin"))
393         end = int(xml.get("end"))
394         serviceref = ServiceReference(xml.get("serviceref").encode("utf-8"))
395         description = xml.get("description").encode("utf-8")
396         repeated = xml.get("repeated").encode("utf-8")
397         disabled = long(xml.get("disabled") or "0")
398         justplay = long(xml.get("justplay") or "0")
399         afterevent = str(xml.get("afterevent") or "nothing")
400         afterevent = {
401                 "nothing": AFTEREVENT.NONE,
402                 "standby": AFTEREVENT.STANDBY,
403                 "deepstandby": AFTEREVENT.DEEPSTANDBY,
404                 "auto": AFTEREVENT.AUTO
405                 }[afterevent]
406         eit = xml.get("eit")
407         if eit and eit != "None":
408                 eit = long(eit);
409         else:
410                 eit = None
411         location = xml.get("location")
412         if location and location != "None":
413                 location = location.encode("utf-8")
414         else:
415                 location = None
416         tags = xml.get("tags")
417         if tags and tags != "None":
418                 tags = tags.encode("utf-8").split(' ')
419         else:
420                 tags = None
421
422         name = xml.get("name").encode("utf-8")
423         #filename = xml.get("filename").encode("utf-8")
424         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags)
425         entry.repeated = int(repeated)
426         
427         for l in xml.findall("log"):
428                 time = int(l.get("time"))
429                 code = int(l.get("code"))
430                 msg = l.text.strip().encode("utf-8")
431                 entry.log_entries.append((time, code, msg))
432         
433         return entry
434
435 class RecordTimer(timer.Timer):
436         def __init__(self):
437                 timer.Timer.__init__(self)
438                 
439                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
440                 
441                 try:
442                         self.loadTimer()
443                 except IOError:
444                         print "unable to load timers from file!"
445
446         def doActivate(self, w):
447                 # when activating a timer which has already passed,
448                 # simply abort the timer. don't run trough all the stages.
449                 if w.shouldSkip():
450                         w.state = RecordTimerEntry.StateEnded
451                 else:
452                         # when active returns true, this means "accepted".
453                         # otherwise, the current state is kept.
454                         # the timer entry itself will fix up the delay then.
455                         if w.activate():
456                                 w.state += 1
457
458                 self.timer_list.remove(w)
459
460                 # did this timer reached the last state?
461                 if w.state < RecordTimerEntry.StateEnded:
462                         # no, sort it into active list
463                         insort(self.timer_list, w)
464                 else:
465                         # yes. Process repeated, and re-add.
466                         if w.repeated:
467                                 w.processRepeated()
468                                 w.state = RecordTimerEntry.StateWaiting
469                                 self.addTimerEntry(w)
470                         else:
471                                 insort(self.processed_timers, w)
472                 
473                 self.stateChanged(w)
474
475         def isRecording(self):
476                 isRunning = False
477                 for timer in self.timer_list:
478                         if timer.isRunning() and not timer.justplay:
479                                 isRunning = True
480                 return isRunning
481         
482         def loadTimer(self):
483                 # TODO: PATH!
484                 try:
485                         doc = xml.etree.cElementTree.parse(self.Filename)
486                 except SyntaxError:
487                         from Tools.Notifications import AddPopup
488                         from Screens.MessageBox import MessageBox
489
490                         AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
491
492                         print "timers.xml failed to load!"
493                         try:
494                                 import os
495                                 os.rename(self.Filename, self.Filename + "_old")
496                         except (IOError, OSError):
497                                 print "renaming broken timer failed"
498                         return
499                 except IOError:
500                         print "timers.xml not found!"
501                         return
502
503                 root = doc.getroot()
504
505                 # put out a message when at least one timer overlaps
506                 checkit = True
507                 for timer in root.findall("timer"):
508                         newTimer = createTimer(timer)
509                         if (self.record(newTimer, True, True) is not None) and (checkit == True):
510                                 from Tools.Notifications import AddPopup
511                                 from Screens.MessageBox import MessageBox
512                                 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
513                                 checkit = False # at moment it is enough when the message is displayed one time
514
515         def saveTimer(self):
516                 #root_element = xml.etree.cElementTree.Element('timers')
517                 #root_element.text = "\n"
518
519                 #for timer in self.timer_list + self.processed_timers:
520                         # some timers (instant records) don't want to be saved.
521                         # skip them
522                         #if timer.dontSave:
523                                 #continue
524                         #t = xml.etree.cElementTree.SubElement(root_element, 'timers')
525                         #t.set("begin", str(int(timer.begin)))
526                         #t.set("end", str(int(timer.end)))
527                         #t.set("serviceref", str(timer.service_ref))
528                         #t.set("repeated", str(timer.repeated))                 
529                         #t.set("name", timer.name)
530                         #t.set("description", timer.description)
531                         #t.set("afterevent", str({
532                         #       AFTEREVENT.NONE: "nothing",
533                         #       AFTEREVENT.STANDBY: "standby",
534                         #       AFTEREVENT.DEEPSTANDBY: "deepstandby",
535                         #       AFTEREVENT.AUTO: "auto"}))
536                         #if timer.eit is not None:
537                         #       t.set("eit", str(timer.eit))
538                         #if timer.dirname is not None:
539                         #       t.set("location", str(timer.dirname))
540                         #t.set("disabled", str(int(timer.disabled)))
541                         #t.set("justplay", str(int(timer.justplay)))
542                         #t.text = "\n"
543                         #t.tail = "\n"
544
545                         #for time, code, msg in timer.log_entries:
546                                 #l = xml.etree.cElementTree.SubElement(t, 'log')
547                                 #l.set("time", str(time))
548                                 #l.set("code", str(code))
549                                 #l.text = str(msg)
550                                 #l.tail = "\n"
551
552                 #doc = xml.etree.cElementTree.ElementTree(root_element)
553                 #doc.write(self.Filename)
554
555                 list = []
556
557                 list.append('<?xml version="1.0" ?>\n')
558                 list.append('<timers>\n')
559                 
560                 for timer in self.timer_list + self.processed_timers:
561                         if timer.dontSave:
562                                 continue
563
564                         list.append('<timer')
565                         list.append(' begin="' + str(int(timer.begin)) + '"')
566                         list.append(' end="' + str(int(timer.end)) + '"')
567                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
568                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
569                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
570                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
571                         list.append(' afterevent="' + str(stringToXML({
572                                 AFTEREVENT.NONE: "nothing",
573                                 AFTEREVENT.STANDBY: "standby",
574                                 AFTEREVENT.DEEPSTANDBY: "deepstandby",
575                                 AFTEREVENT.AUTO: "auto"
576                                 }[timer.afterEvent])) + '"')
577                         if timer.eit is not None:
578                                 list.append(' eit="' + str(timer.eit) + '"')
579                         if timer.dirname is not None:
580                                 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
581                         if timer.tags is not None:
582                                 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
583                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
584                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
585                         list.append('>\n')
586                         
587                         if config.recording.debug.value:
588                                 for time, code, msg in timer.log_entries:
589                                         list.append('<log')
590                                         list.append(' code="' + str(code) + '"')
591                                         list.append(' time="' + str(time) + '"')
592                                         list.append('>')
593                                         list.append(str(stringToXML(msg)))
594                                         list.append('</log>\n')
595                         
596                         list.append('</timer>\n')
597
598                 list.append('</timers>\n')
599
600                 file = open(self.Filename, "w")
601                 for x in list:
602                         file.write(x)
603                 file.close()
604
605         def getNextZapTime(self):
606                 now = time.time()
607                 for timer in self.timer_list:
608                         if not timer.justplay or timer.begin < now:
609                                 continue
610                         return timer.begin
611                 return -1
612
613         def getNextRecordingTime(self):
614                 now = time.time()
615                 for timer in self.timer_list:
616                         next_act = timer.getNextActivation()
617                         if timer.justplay or next_act < now:
618                                 continue
619                         return next_act
620                 return -1
621
622         def isNextRecordAfterEventActionAuto(self):
623                 now = time.time()
624                 t = None
625                 for timer in self.timer_list:
626                         if timer.justplay or timer.begin < now:
627                                 continue
628                         if t is None or t.begin == timer.begin:
629                                 t = timer
630                                 if t.afterEvent == AFTEREVENT.AUTO:
631                                         return True
632                 return False
633
634         def record(self, entry, ignoreTSC=False, dosave=True):          #wird von loadTimer mit dosave=False aufgerufen
635                 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
636                 if not timersanitycheck.check():
637                         if ignoreTSC != True:
638                                 print "timer conflict detected!"
639                                 print timersanitycheck.getSimulTimerList()
640                                 return timersanitycheck.getSimulTimerList()
641                         else:
642                                 print "ignore timer conflict"
643                 elif timersanitycheck.doubleCheck():
644                         print "ignore double timer"
645                         return None
646                 entry.timeChanged()
647                 print "[Timer] Record " + str(entry)
648                 entry.Timer = self
649                 self.addTimerEntry(entry)
650                 if dosave:
651                         self.saveTimer()
652                 return None
653
654         def isInTimer(self, eventid, begin, duration, service):
655                 time_match = 0
656                 chktime = None
657                 chktimecmp = None
658                 chktimecmp_end = None
659                 end = begin + duration
660                 refstr = str(service)
661                 for x in self.timer_list:
662                         check = x.service_ref.ref.toString() == refstr
663                         if not check:
664                                 sref = x.service_ref.ref
665                                 parent_sid = sref.getUnsignedData(5)
666                                 parent_tsid = sref.getUnsignedData(6)
667                                 if parent_sid and parent_tsid: # check for subservice
668                                         sid = sref.getUnsignedData(1)
669                                         tsid = sref.getUnsignedData(2)
670                                         sref.setUnsignedData(1, parent_sid)
671                                         sref.setUnsignedData(2, parent_tsid)
672                                         sref.setUnsignedData(5, 0)
673                                         sref.setUnsignedData(6, 0)
674                                         check = sref.toCompareString() == refstr
675                                         num = 0
676                                         if check:
677                                                 check = False
678                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
679                                                 num = event and event.getNumOfLinkageServices() or 0
680                                         sref.setUnsignedData(1, sid)
681                                         sref.setUnsignedData(2, tsid)
682                                         sref.setUnsignedData(5, parent_sid)
683                                         sref.setUnsignedData(6, parent_tsid)
684                                         for cnt in range(num):
685                                                 subservice = event.getLinkageService(sref, cnt)
686                                                 if sref.toCompareString() == subservice.toCompareString():
687                                                         check = True
688                                                         break
689                         if check:
690                                 if x.repeated != 0:
691                                         if chktime is None:
692                                                 chktime = localtime(begin)
693                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
694                                                 chktimecmp_end = chktimecmp + (duration / 60)
695                                         time = localtime(x.begin)
696                                         for y in (0, 1, 2, 3, 4, 5, 6):
697                                                 if x.repeated & (2 ** y) and (x.begin <= begin or begin <= x.begin <= end):
698                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
699                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
700                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
701                                                         elif chktimecmp <= timecmp < chktimecmp_end:
702                                                                 time_match = (chktimecmp_end - timecmp) * 60
703                                 else: #if x.eit is None:
704                                         if begin <= x.begin <= end:
705                                                 diff = end - x.begin
706                                                 if time_match < diff:
707                                                         time_match = diff
708                                         elif x.begin <= begin <= x.end:
709                                                 diff = x.end - begin
710                                                 if time_match < diff:
711                                                         time_match = diff
712                                 if time_match:
713                                         break
714                 return time_match
715
716         def removeEntry(self, entry):
717                 print "[Timer] Remove " + str(entry)
718                 
719                 # avoid re-enqueuing
720                 entry.repeated = False
721
722                 # abort timer.
723                 # this sets the end time to current time, so timer will be stopped.
724                 entry.autoincrease = False
725                 entry.abort()
726                 
727                 if entry.state != entry.StateEnded:
728                         self.timeChanged(entry)
729                 
730                 print "state: ", entry.state
731                 print "in processed: ", entry in self.processed_timers
732                 print "in running: ", entry in self.timer_list
733                 # autoincrease instanttimer if possible
734                 if not entry.dontSave:
735                         for x in self.timer_list:
736                                 if x.setAutoincreaseEnd():
737                                         self.timeChanged(x)
738                 # now the timer should be in the processed_timers list. remove it from there.
739                 self.processed_timers.remove(entry)
740                 self.saveTimer()
741
742         def shutdown(self):
743                 self.saveTimer()