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