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