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