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