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