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