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