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