don't show REC symbol in infobar when timer is a 'justplay' timer
[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:
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                 for x in self.timer_list:
347                         if str(x.service_ref) == str(service):
348                                 #if x.eit is not None and x.repeated == 0:
349                                 #       if x.eit == eventid:
350                                 #               return duration
351                                 if x.repeated != 0:
352                                         chktime = localtime(begin)
353                                         time = localtime(x.begin)
354                                         chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
355                                         for y in range(7):
356                                                 if x.repeated & (2 ** y):
357                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
358                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
359                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
360                                                         elif chktimecmp <= timecmp < (chktimecmp + (duration / 60)):
361                                                                 time_match = ((chktimecmp + (duration / 60)) - timecmp) * 60
362                                 else: #if x.eit is None:
363                                         end = begin + duration
364                                         if begin <= x.begin <= end:
365                                                 diff = end - x.begin
366                                                 if time_match < diff:
367                                                         time_match = diff
368                                         elif x.begin <= begin <= x.end:
369                                                 diff = x.end - begin
370                                                 if time_match < diff:
371                                                         time_match = diff
372                 return time_match
373
374         def removeEntry(self, entry):
375                 print "[Timer] Remove " + str(entry)
376                 
377                 # avoid re-enqueuing
378                 entry.repeated = False
379
380                 # abort timer.
381                 # this sets the end time to current time, so timer will be stopped.
382                 entry.abort()
383                 
384                 if entry.state != entry.StateEnded:
385                         self.timeChanged(entry)
386                 
387                 print "state: ", entry.state
388                 print "in processed: ", entry in self.processed_timers
389                 print "in running: ", entry in self.timer_list
390                 # now the timer should be in the processed_timers list. remove it from there.
391                 self.processed_timers.remove(entry)
392
393         def shutdown(self):
394                 self.saveTimer()