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