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