fix typo
[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 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.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nDreambox to standby. Do that now?"), timeout = 20)
219                         if self.afterEvent == AFTEREVENT.DEEPSTANDBY:
220                                 global inTryQuitMainloop
221                                 if not inTryQuitMainloop:
222                                         Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour Dreambox. Shutdown now?"), timeout = 20)
223                         return True
224
225         def sendStandbyNotification(self, answer):
226                 if answer:
227                         Notifications.AddNotification(Standby)
228
229         def sendTryQuitMainloopNotification(self, answer):
230                 if answer:
231                         Notifications.AddNotification(TryQuitMainloop, 1)
232
233         def getNextActivation(self):
234                 if self.state == self.StateEnded:
235                         return self.end
236                 
237                 next_state = self.state + 1
238                 
239                 return {self.StatePrepared: self.start_prepare, 
240                                 self.StateRunning: self.begin, 
241                                 self.StateEnded: self.end }[next_state]
242
243         def failureCB(self, answer):
244                 if answer == True:
245                         self.log(13, "ok, zapped away")
246                         #NavigationInstance.instance.stopUserServices()
247                         NavigationInstance.instance.playService(self.service_ref.ref)
248                 else:
249                         self.log(14, "user didn't want to zap away, record will probably fail")
250
251         def timeChanged(self):
252                 old_prepare = self.start_prepare
253                 self.start_prepare = self.begin - self.prepare_time
254                 self.backoff = 0
255                 
256                 if int(old_prepare) != int(self.start_prepare):
257                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
258
259 def createTimer(xml):
260         begin = int(xml.getAttribute("begin"))
261         end = int(xml.getAttribute("end"))
262         serviceref = ServiceReference(xml.getAttribute("serviceref").encode("utf-8"))
263         description = xml.getAttribute("description").encode("utf-8")
264         repeated = xml.getAttribute("repeated").encode("utf-8")
265         disabled = long(xml.getAttribute("disabled") or "0")
266         justplay = long(xml.getAttribute("justplay") or "0")
267         afterevent = str(xml.getAttribute("afterevent") or "nothing")
268         afterevent = { "nothing": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY }[afterevent]
269         if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
270                 eit = long(xml.getAttribute("eit"))
271         else:
272                 eit = None
273         
274         name = xml.getAttribute("name").encode("utf-8")
275         #filename = xml.getAttribute("filename").encode("utf-8")
276         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent)
277         entry.repeated = int(repeated)
278         
279         for l in elementsWithTag(xml.childNodes, "log"):
280                 time = int(l.getAttribute("time"))
281                 code = int(l.getAttribute("code"))
282                 msg = mergeText(l.childNodes).strip().encode("utf-8")
283                 entry.log_entries.append((time, code, msg))
284         
285         return entry
286
287 class RecordTimer(timer.Timer):
288         def __init__(self):
289                 timer.Timer.__init__(self)
290                 
291                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
292                 
293                 try:
294                         self.loadTimer()
295                 except IOError:
296                         print "unable to load timers from file!"
297                         
298         def isRecording(self):
299                 isRunning = False
300                 for timer in self.timer_list:
301                         if timer.isRunning() and not timer.justplay:
302                                 isRunning = True
303                 return isRunning
304         
305         def loadTimer(self):
306                 # TODO: PATH!
307                 doc = xml.dom.minidom.parse(self.Filename)
308                 
309                 root = doc.childNodes[0]
310                 for timer in elementsWithTag(root.childNodes, "timer"):
311                         self.record(createTimer(timer))
312
313         def saveTimer(self):
314                 #doc = xml.dom.minidom.Document()
315                 #root_element = doc.createElement('timers')
316                 #doc.appendChild(root_element)
317                 #root_element.appendChild(doc.createTextNode("\n"))
318                 
319                 #for timer in self.timer_list + self.processed_timers:
320                         # some timers (instant records) don't want to be saved.
321                         # skip them
322                         #if timer.dontSave:
323                                 #continue
324                         #t = doc.createTextNode("\t")
325                         #root_element.appendChild(t)
326                         #t = doc.createElement('timer')
327                         #t.setAttribute("begin", str(int(timer.begin)))
328                         #t.setAttribute("end", str(int(timer.end)))
329                         #t.setAttribute("serviceref", str(timer.service_ref))
330                         #t.setAttribute("repeated", str(timer.repeated))                        
331                         #t.setAttribute("name", timer.name)
332                         #t.setAttribute("description", timer.description)
333                         #t.setAttribute("eit", str(timer.eit))
334                         
335                         #for time, code, msg in timer.log_entries:
336                                 #t.appendChild(doc.createTextNode("\t\t"))
337                                 #l = doc.createElement('log')
338                                 #l.setAttribute("time", str(time))
339                                 #l.setAttribute("code", str(code))
340                                 #l.appendChild(doc.createTextNode(msg))
341                                 #t.appendChild(l)
342                                 #t.appendChild(doc.createTextNode("\n"))
343
344                         #root_element.appendChild(t)
345                         #t = doc.createTextNode("\n")
346                         #root_element.appendChild(t)
347
348
349                 #file = open(self.Filename, "w")
350                 #doc.writexml(file)
351                 #file.write("\n")
352                 #file.close()
353
354                 list = []
355
356                 list.append('<?xml version="1.0" ?>\n')
357                 list.append('<timers>\n')
358                 
359                 for timer in self.timer_list + self.processed_timers:
360                         if timer.dontSave:
361                                 continue
362
363                         list.append('<timer')
364                         list.append(' begin="' + str(int(timer.begin)) + '"')
365                         list.append(' end="' + str(int(timer.end)) + '"')
366                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
367                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
368                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
369                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
370                         list.append(' afterevent="' + str(stringToXML({ AFTEREVENT.NONE: "nothing", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "deepstandby" }[timer.afterEvent])) + '"')
371                         if timer.eit is not None:
372                                 list.append(' eit="' + str(timer.eit) + '"')
373                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
374                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
375                         list.append('>\n')
376                         
377                         if config.recording.debug.value:
378                                 for time, code, msg in timer.log_entries:
379                                         list.append('<log')
380                                         list.append(' code="' + str(code) + '"')
381                                         list.append(' time="' + str(time) + '"')
382                                         list.append('>')
383                                         list.append(str(stringToXML(msg)))
384                                         list.append('</log>\n')
385                         
386                         list.append('</timer>\n')
387
388                 list.append('</timers>\n')
389
390                 file = open(self.Filename, "w")
391                 for x in list:
392                         file.write(x)
393                 file.close()
394
395         def record(self, entry):
396                 entry.timeChanged()
397                 print "[Timer] Record " + str(entry)
398                 entry.Timer = self
399                 self.addTimerEntry(entry)
400                 
401         def isInTimer(self, eventid, begin, duration, service):
402                 time_match = 0
403                 chktime = None
404                 chktimecmp = None
405                 chktimecmp_end = None
406                 end = begin + duration
407                 for x in self.timer_list:
408                         if str(x.service_ref) == str(service):
409                                 #if x.eit is not None and x.repeated == 0:
410                                 #       if x.eit == eventid:
411                                 #               return duration
412                                 if x.repeated != 0:
413                                         if chktime is None:
414                                                 chktime = localtime(begin)
415                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
416                                                 chktimecmp_end = chktimecmp + (duration / 60)
417                                         time = localtime(x.begin)
418                                         for y in range(7):
419                                                 if x.repeated & (2 ** y):
420                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
421                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
422                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
423                                                         elif chktimecmp <= timecmp < chktimecmp_end:
424                                                                 time_match = (chktimecmp_end - timecmp) * 60
425                                 else: #if x.eit is None:
426                                         if begin <= x.begin <= end:
427                                                 diff = end - x.begin
428                                                 if time_match < diff:
429                                                         time_match = diff
430                                         elif x.begin <= begin <= x.end:
431                                                 diff = x.end - begin
432                                                 if time_match < diff:
433                                                         time_match = diff
434                 return time_match
435
436         def removeEntry(self, entry):
437                 print "[Timer] Remove " + str(entry)
438                 
439                 # avoid re-enqueuing
440                 entry.repeated = False
441
442                 # abort timer.
443                 # this sets the end time to current time, so timer will be stopped.
444                 entry.abort()
445                 
446                 if entry.state != entry.StateEnded:
447                         self.timeChanged(entry)
448                 
449                 print "state: ", entry.state
450                 print "in processed: ", entry in self.processed_timers
451                 print "in running: ", entry in self.timer_list
452                 # now the timer should be in the processed_timers list. remove it from there.
453                 self.processed_timers.remove(entry)
454
455         def shutdown(self):
456                 self.saveTimer()