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