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