zaptimers now do wakeup the box from standby and deepstandby
[vuplus_dvbapp] / RecordTimer.py
1 import time
2 import codecs
3 #from time import datetime
4 from Tools import Directories, Notifications
5
6 from Components.config import config, ConfigYesNo
7 import timer
8 import xml.dom.minidom
9
10 from enigma import eEPGCache, getBestPlayableServiceReference, \
11         eServiceReference, iRecordableService, quitMainloop
12
13 from Screens.MessageBox import MessageBox
14
15 import NavigationInstance
16
17 import Screens.Standby
18
19 from time import localtime
20
21 from Tools.XMLTools import elementsWithTag, mergeText, stringToXML
22 from ServiceReference import ServiceReference
23
24 # ok, for descriptions etc we have:
25 # service reference  (to get the service name)
26 # name               (title)
27 # description        (description)
28 # event data         (ONLY for time adjustments etc.)
29
30
31 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
32 # begin and end will be corrected
33 def parseEvent(ev, description = True):
34         if description:
35                 name = ev.getEventName()
36                 description = ev.getShortDescription()
37         else:
38                 name = ""
39                 description = ""
40         begin = ev.getBeginTime()
41         end = begin + ev.getDuration()
42         eit = ev.getEventId()
43         begin -= config.recording.margin_before.value * 60
44         end += config.recording.margin_after.value * 60
45         return (begin, end, name, description, eit)
46
47 class AFTEREVENT:
48         NONE = 0
49         STANDBY = 1
50         DEEPSTANDBY = 2
51
52 # please do not translate log messages
53 class RecordTimerEntry(timer.TimerEntry):
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 gotRecordEvent(recservice, event):
63                 if event == iRecordableService.evEnd:
64                         print "RecordTimer.gotRecordEvent(iRecordableService.evEnd)"
65                         recordings = NavigationInstance.instance.getRecordings()
66                         if not len(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.gotRecordEvent(iRecordableService.evStart)"
75
76         @staticmethod
77         def stopTryQuitMainloop():
78                 print "RecordTimer.stopTryQuitMainloop"
79                 NavigationInstance.instance.record_event.remove(RecordTimerEntry.gotRecordEvent)
80                 RecordTimerEntry.receiveRecordEvents = False
81
82         @staticmethod
83         def TryQuitMainloop():
84                 if not RecordTimerEntry.receiveRecordEvents:
85                         print "RecordTimer.TryQuitMainloop"
86                         NavigationInstance.instance.record_event.append(RecordTimerEntry.gotRecordEvent)
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.gotRecordEvent(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)
93 #################################################################
94
95         def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.NONE, checkOldTimers = False):
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                 
119                 self.log_entries = []
120                 self.resetState()
121         
122         def log(self, code, msg):
123                 self.log_entries.append((int(time.time()), code, msg))
124                 print "[TIMER]", msg
125         
126         def resetState(self):
127                 self.state = self.StateWaiting
128                 self.cancelled = False
129                 self.first_try_prepare = True
130                 self.timeChanged()
131         
132         def calculateFilename(self):
133                 service_name = self.service_ref.getServiceName()
134                 begin_date = time.strftime("%Y%m%d %H%M", time.localtime(self.begin))
135                 
136                 print "begin_date: ", begin_date
137                 print "service_name: ", service_name
138                 print "name:", self.name
139                 print "description: ", self.description
140                 
141                 filename = begin_date + " - " + service_name
142                 if self.name:
143                         filename += " - " + self.name
144
145                 self.Filename = Directories.getRecordingFilename(filename)
146                 self.log(0, "Filename calculated as: '%s'" % self.Filename)
147                 #begin_date + " - " + service_name + description)
148         
149         def tryPrepare(self):
150                 if self.justplay:
151                         return True
152                 else:
153                         self.calculateFilename()
154                         rec_ref = self.service_ref and self.service_ref.ref
155                         if rec_ref and rec_ref.flags & eServiceReference.isGroup:
156                                 rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
157                                 if not rec_ref:
158                                         self.log(1, "'get best playable service for group... record' failed")
159                                         return False
160                                 
161                         self.record_service = rec_ref and NavigationInstance.instance.recordService(rec_ref)
162                         if not self.record_service:
163                                 self.log(1, "'record service' failed")
164                                 return False
165                                 
166                         event_id = self.eit
167                         if event_id is None:
168                                 event_id = -1
169                                 
170                         prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id)
171                         if prep_res:
172                                 self.log(2, "'prepare' failed: error %d" % prep_res)
173                                 self.record_service = None
174                                 return False
175                                 
176                         if self.repeated:
177                                 epgcache = eEPGCache.getInstance()
178                                 queryTime=self.begin+(self.end-self.begin)/2
179                                 evt = epgcache.lookupEventTime(rec_ref, queryTime)
180                                 if evt:
181                                         self.description = evt.getShortDescription()
182                                 
183                         self.log(3, "prepare ok, writing meta information to %s" % self.Filename)
184                         try:
185                                 f = open(self.Filename + ".ts.meta", "w")
186                                 f.write(rec_ref.toString() + "\n")
187                                 f.write(self.name + "\n")
188                                 f.write(self.description + "\n")
189                                 f.write(str(self.begin) + "\n")
190                                 f.close()
191                         except IOError:
192                                 self.log(4, "failed to write meta information")
193                                 self.record_service = None
194                                 return False
195                         return True
196
197         def do_backoff(self):
198                 if self.backoff == 0:
199                         self.backoff = 5
200                 else:
201                         self.backoff *= 2
202                         if self.backoff > 100:
203                                 self.backoff = 100
204                 self.log(10, "backoff: retry in %d seconds" % self.backoff)
205
206         def activate(self):
207                 next_state = self.state + 1
208                 self.log(5, "activating state %d" % next_state)
209                 
210                 if next_state == self.StatePrepared:
211                         if self.tryPrepare():
212                                 self.log(6, "prepare ok, waiting for begin")
213                                 # fine. it worked, resources are allocated.
214                                 self.next_activation = self.begin
215                                 self.backoff = 0
216                                 return True
217                         
218                         self.log(7, "prepare failed")
219                         if self.first_try_prepare:
220                                 self.first_try_prepare = False
221                                 if not config.recording.asktozap.value:
222                                         self.log(8, "asking user to zap away")
223                                         Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20)
224                                 else: # zap without asking
225                                         self.log(9, "zap without asking")
226                                         Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
227                                         self.failureCB(True)
228
229                         self.do_backoff()
230                         # retry
231                         self.start_prepare = time.time() + self.backoff
232                         return False
233                 elif next_state == self.StateRunning:
234                         # if this timer has been cancelled, just go to "end" state.
235                         if self.cancelled:
236                                 return True
237
238                         if self.justplay:
239                                 if Screens.Standby.inStandby:
240                                         self.log(11, "wakeup and zap")
241                                         #set service to zap after standby
242                                         Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
243                                         #wakeup standby
244                                         Screens.Standby.inStandby.Power()
245                                 else:
246                                         self.log(11, "zapping")
247                                         NavigationInstance.instance.playService(self.service_ref.ref)
248                                 return True
249                         else:
250                                 self.log(11, "start recording")
251                                 record_res = self.record_service.start()
252                                 
253                                 if record_res:
254                                         self.log(13, "start record returned %d" % record_res)
255                                         self.do_backoff()
256                                         # retry
257                                         self.begin = time.time() + self.backoff
258                                         return False
259                                 
260                                 return True
261                 elif next_state == self.StateEnded:
262                         self.log(12, "stop recording")
263                         if not self.justplay:
264                                 NavigationInstance.instance.stopRecordService(self.record_service)
265                                 self.record_service = None
266                         if self.afterEvent == AFTEREVENT.STANDBY:
267                                 if not Screens.Standby.inStandby: # not already in standby
268                                         Notifications.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nDreambox to standby. Do that now?"), timeout = 20)
269                         if self.afterEvent == AFTEREVENT.DEEPSTANDBY:
270                                 if not Screens.Standby.inTryQuitMainloop: # not a shutdown messagebox is open
271                                         if Screens.Standby.inStandby: # not in standby
272                                                 RecordTimerEntry.TryQuitMainloop() # start shutdown handling without screen
273                                         else:
274                                                 Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour Dreambox. Shutdown now?"), timeout = 20)
275                         return True
276
277         def sendStandbyNotification(self, answer):
278                 if answer:
279                         Notifications.AddNotification(Screens.Standby.Standby)
280
281         def sendTryQuitMainloopNotification(self, answer):
282                 if answer:
283                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
284
285         def getNextActivation(self):
286                 if self.state == self.StateEnded:
287                         return self.end
288                 
289                 next_state = self.state + 1
290                 
291                 return {self.StatePrepared: self.start_prepare, 
292                                 self.StateRunning: self.begin, 
293                                 self.StateEnded: self.end }[next_state]
294
295         def failureCB(self, answer):
296                 if answer == True:
297                         self.log(13, "ok, zapped away")
298                         #NavigationInstance.instance.stopUserServices()
299                         NavigationInstance.instance.playService(self.service_ref.ref)
300                 else:
301                         self.log(14, "user didn't want to zap away, record will probably fail")
302
303         def timeChanged(self):
304                 old_prepare = self.start_prepare
305                 self.start_prepare = self.begin - self.prepare_time
306                 self.backoff = 0
307                 
308                 if int(old_prepare) != int(self.start_prepare):
309                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
310
311 def createTimer(xml):
312         begin = int(xml.getAttribute("begin"))
313         end = int(xml.getAttribute("end"))
314         serviceref = ServiceReference(xml.getAttribute("serviceref").encode("utf-8"))
315         description = xml.getAttribute("description").encode("utf-8")
316         repeated = xml.getAttribute("repeated").encode("utf-8")
317         disabled = long(xml.getAttribute("disabled") or "0")
318         justplay = long(xml.getAttribute("justplay") or "0")
319         afterevent = str(xml.getAttribute("afterevent") or "nothing")
320         afterevent = { "nothing": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY }[afterevent]
321         if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
322                 eit = long(xml.getAttribute("eit"))
323         else:
324                 eit = None
325         
326         name = xml.getAttribute("name").encode("utf-8")
327         #filename = xml.getAttribute("filename").encode("utf-8")
328         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent)
329         entry.repeated = int(repeated)
330         
331         for l in elementsWithTag(xml.childNodes, "log"):
332                 time = int(l.getAttribute("time"))
333                 code = int(l.getAttribute("code"))
334                 msg = mergeText(l.childNodes).strip().encode("utf-8")
335                 entry.log_entries.append((time, code, msg))
336         
337         return entry
338
339 class RecordTimer(timer.Timer):
340         def __init__(self):
341                 timer.Timer.__init__(self)
342                 
343                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
344                 
345                 try:
346                         self.loadTimer()
347                 except IOError:
348                         print "unable to load timers from file!"
349                         
350         def isRecording(self):
351                 isRunning = False
352                 for timer in self.timer_list:
353                         if timer.isRunning() and not timer.justplay:
354                                 isRunning = True
355                 return isRunning
356         
357         def loadTimer(self):
358                 # TODO: PATH!
359                 doc = xml.dom.minidom.parse(self.Filename)
360                 
361                 root = doc.childNodes[0]
362                 for timer in elementsWithTag(root.childNodes, "timer"):
363                         self.record(createTimer(timer))
364
365         def saveTimer(self):
366                 #doc = xml.dom.minidom.Document()
367                 #root_element = doc.createElement('timers')
368                 #doc.appendChild(root_element)
369                 #root_element.appendChild(doc.createTextNode("\n"))
370                 
371                 #for timer in self.timer_list + self.processed_timers:
372                         # some timers (instant records) don't want to be saved.
373                         # skip them
374                         #if timer.dontSave:
375                                 #continue
376                         #t = doc.createTextNode("\t")
377                         #root_element.appendChild(t)
378                         #t = doc.createElement('timer')
379                         #t.setAttribute("begin", str(int(timer.begin)))
380                         #t.setAttribute("end", str(int(timer.end)))
381                         #t.setAttribute("serviceref", str(timer.service_ref))
382                         #t.setAttribute("repeated", str(timer.repeated))                        
383                         #t.setAttribute("name", timer.name)
384                         #t.setAttribute("description", timer.description)
385                         #t.setAttribute("eit", str(timer.eit))
386                         
387                         #for time, code, msg in timer.log_entries:
388                                 #t.appendChild(doc.createTextNode("\t\t"))
389                                 #l = doc.createElement('log')
390                                 #l.setAttribute("time", str(time))
391                                 #l.setAttribute("code", str(code))
392                                 #l.appendChild(doc.createTextNode(msg))
393                                 #t.appendChild(l)
394                                 #t.appendChild(doc.createTextNode("\n"))
395
396                         #root_element.appendChild(t)
397                         #t = doc.createTextNode("\n")
398                         #root_element.appendChild(t)
399
400
401                 #file = open(self.Filename, "w")
402                 #doc.writexml(file)
403                 #file.write("\n")
404                 #file.close()
405
406                 list = []
407
408                 list.append('<?xml version="1.0" ?>\n')
409                 list.append('<timers>\n')
410                 
411                 for timer in self.timer_list + self.processed_timers:
412                         if timer.dontSave:
413                                 continue
414
415                         list.append('<timer')
416                         list.append(' begin="' + str(int(timer.begin)) + '"')
417                         list.append(' end="' + str(int(timer.end)) + '"')
418                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
419                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
420                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
421                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
422                         list.append(' afterevent="' + str(stringToXML({ AFTEREVENT.NONE: "nothing", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "deepstandby" }[timer.afterEvent])) + '"')
423                         if timer.eit is not None:
424                                 list.append(' eit="' + str(timer.eit) + '"')
425                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
426                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
427                         list.append('>\n')
428                         
429                         if config.recording.debug.value:
430                                 for time, code, msg in timer.log_entries:
431                                         list.append('<log')
432                                         list.append(' code="' + str(code) + '"')
433                                         list.append(' time="' + str(time) + '"')
434                                         list.append('>')
435                                         list.append(str(stringToXML(msg)))
436                                         list.append('</log>\n')
437                         
438                         list.append('</timer>\n')
439
440                 list.append('</timers>\n')
441
442                 file = open(self.Filename, "w")
443                 for x in list:
444                         file.write(x)
445                 file.close()
446
447         def getNextZapTime(self):
448                 llen = len(self.timer_list)
449                 idx = 0
450                 now = time.time()
451                 while idx < llen:
452                         timer = self.timer_list[idx]
453                         if not timer.justplay or timer.begin < now:
454                                 idx += 1
455                         else:
456                                 return timer.begin
457                 return -1
458
459         def getNextRecordingTime(self):
460                 llen = len(self.timer_list)
461                 idx = 0
462                 now = time.time()
463                 while idx < llen:
464                         timer = self.timer_list[idx]
465                         if timer.justplay or timer.begin < now:
466                                 idx += 1
467                         else:
468                                 return timer.begin
469                 return -1
470
471         def record(self, entry):
472                 entry.timeChanged()
473                 print "[Timer] Record " + str(entry)
474                 entry.Timer = self
475                 self.addTimerEntry(entry)
476                 
477         def isInTimer(self, eventid, begin, duration, service):
478                 time_match = 0
479                 chktime = None
480                 chktimecmp = None
481                 chktimecmp_end = None
482                 end = begin + duration
483                 for x in self.timer_list:
484                         if str(x.service_ref) == str(service):
485                                 #if x.eit is not None and x.repeated == 0:
486                                 #       if x.eit == eventid:
487                                 #               return duration
488                                 if x.repeated != 0:
489                                         if chktime is None:
490                                                 chktime = localtime(begin)
491                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
492                                                 chktimecmp_end = chktimecmp + (duration / 60)
493                                         time = localtime(x.begin)
494                                         for y in range(7):
495                                                 if x.repeated & (2 ** y):
496                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
497                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
498                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
499                                                         elif chktimecmp <= timecmp < chktimecmp_end:
500                                                                 time_match = (chktimecmp_end - timecmp) * 60
501                                 else: #if x.eit is None:
502                                         if begin <= x.begin <= end:
503                                                 diff = end - x.begin
504                                                 if time_match < diff:
505                                                         time_match = diff
506                                         elif x.begin <= begin <= x.end:
507                                                 diff = x.end - begin
508                                                 if time_match < diff:
509                                                         time_match = diff
510                 return time_match
511
512         def removeEntry(self, entry):
513                 print "[Timer] Remove " + str(entry)
514                 
515                 # avoid re-enqueuing
516                 entry.repeated = False
517
518                 # abort timer.
519                 # this sets the end time to current time, so timer will be stopped.
520                 entry.abort()
521                 
522                 if entry.state != entry.StateEnded:
523                         self.timeChanged(entry)
524                 
525                 print "state: ", entry.state
526                 print "in processed: ", entry in self.processed_timers
527                 print "in running: ", entry in self.timer_list
528                 # now the timer should be in the processed_timers list. remove it from there.
529                 self.processed_timers.remove(entry)
530
531         def shutdown(self):
532                 self.saveTimer()