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