move SYNC_PAINT define to right place
[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.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                         self.record_service.stop()
170                         self.record_service = None
171                         return True
172
173         def getNextActivation(self):
174                 if self.state == self.StateEnded:
175                         return self.end
176                 
177                 next_state = self.state + 1
178                 
179                 return {self.StatePrepared: self.start_prepare, 
180                                 self.StateRunning: self.begin, 
181                                 self.StateEnded: self.end }[next_state]
182
183         def failureCB(self, answer):
184                 if answer == True:
185                         self.log(13, "ok, zapped away")
186                         #NavigationInstance.instance.stopUserServices()
187                         NavigationInstance.instance.playService(self.service_ref.ref)
188                 else:
189                         self.log(14, "user didn't want to zap away, record will probably fail")
190
191         def timeChanged(self):
192                 old_prepare = self.start_prepare
193                 self.start_prepare = self.begin - self.prepare_time
194                 self.backoff = 0
195                 
196                 if old_prepare != self.start_prepare:
197                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
198
199 def createTimer(xml):
200         begin = int(xml.getAttribute("begin"))
201         end = int(xml.getAttribute("end"))
202         serviceref = ServiceReference(str(xml.getAttribute("serviceref")))
203         description = xml.getAttribute("description").encode("utf-8")
204         repeated = xml.getAttribute("repeated").encode("utf-8")
205         disabled = long(xml.getAttribute("disabled") or "0")
206         justplay = long(xml.getAttribute("justplay") or "0")
207         if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
208                 eit = long(xml.getAttribute("eit"))
209         else:
210                 eit = None
211         
212         name = xml.getAttribute("name").encode("utf-8")
213         #filename = xml.getAttribute("filename").encode("utf-8")
214         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay)
215         entry.repeated = int(repeated)
216         
217         for l in elementsWithTag(xml.childNodes, "log"):
218                 time = int(l.getAttribute("time"))
219                 code = int(l.getAttribute("code"))
220                 msg = mergeText(l.childNodes).strip().encode("utf-8")
221                 entry.log_entries.append((time, code, msg))
222         
223         return entry
224
225 class RecordTimer(timer.Timer):
226         def __init__(self):
227                 timer.Timer.__init__(self)
228                 
229                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
230                 
231                 try:
232                         self.loadTimer()
233                 except IOError:
234                         print "unable to load timers from file!"
235                         
236         def isRecording(self):
237                 isRunning = False
238                 for timer in self.timer_list:
239                         if timer.isRunning():
240                                 isRunning = True
241                 return isRunning
242         
243         def loadTimer(self):
244                 # TODO: PATH!
245                 doc = xml.dom.minidom.parse(self.Filename)
246                 
247                 root = doc.childNodes[0]
248                 for timer in elementsWithTag(root.childNodes, "timer"):
249                         self.record(createTimer(timer))
250
251         def saveTimer(self):
252                 #doc = xml.dom.minidom.Document()
253                 #root_element = doc.createElement('timers')
254                 #doc.appendChild(root_element)
255                 #root_element.appendChild(doc.createTextNode("\n"))
256                 
257                 #for timer in self.timer_list + self.processed_timers:
258                         # some timers (instant records) don't want to be saved.
259                         # skip them
260                         #if timer.dontSave:
261                                 #continue
262                         #t = doc.createTextNode("\t")
263                         #root_element.appendChild(t)
264                         #t = doc.createElement('timer')
265                         #t.setAttribute("begin", str(int(timer.begin)))
266                         #t.setAttribute("end", str(int(timer.end)))
267                         #t.setAttribute("serviceref", str(timer.service_ref))
268                         #t.setAttribute("repeated", str(timer.repeated))                        
269                         #t.setAttribute("name", timer.name)
270                         #t.setAttribute("description", timer.description)
271                         #t.setAttribute("eit", str(timer.eit))
272                         
273                         #for time, code, msg in timer.log_entries:
274                                 #t.appendChild(doc.createTextNode("\t\t"))
275                                 #l = doc.createElement('log')
276                                 #l.setAttribute("time", str(time))
277                                 #l.setAttribute("code", str(code))
278                                 #l.appendChild(doc.createTextNode(msg))
279                                 #t.appendChild(l)
280                                 #t.appendChild(doc.createTextNode("\n"))
281
282                         #root_element.appendChild(t)
283                         #t = doc.createTextNode("\n")
284                         #root_element.appendChild(t)
285
286
287                 #file = open(self.Filename, "w")
288                 #doc.writexml(file)
289                 #file.write("\n")
290                 #file.close()
291
292                 list = []
293
294                 list.append('<?xml version="1.0" ?>\n')
295                 list.append('<timers>\n')
296                 
297                 for timer in self.timer_list + self.processed_timers:
298                         if timer.dontSave:
299                                 continue
300
301                         list.append('<timer')
302                         list.append(' begin="' + str(int(timer.begin)) + '"')
303                         list.append(' end="' + str(int(timer.end)) + '"')
304                         list.append(' serviceref="' + str(timer.service_ref) + '"')
305                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
306                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
307                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
308                         if timer.eit is not None:
309                                 list.append(' eit="' + str(timer.eit) + '"')
310                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
311                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
312                         list.append('>\n')
313                         
314                         #for time, code, msg in timer.log_entries:
315                                 #list.append('<log')
316                                 #list.append(' code="' + str(code) + '"')
317                                 #list.append(' time="' + str(time) + '"')
318                                 #list.append('>')
319                                 #list.append(str(msg))
320                                 #list.append('</log>\n')
321
322                         
323                         list.append('</timer>\n')
324
325                 list.append('</timers>\n')
326
327                 file = open(self.Filename, "w")
328                 for x in list:
329                         file.write(x)
330                 file.close()
331
332         def record(self, entry):
333                 entry.timeChanged()
334                 print "[Timer] Record " + str(entry)
335                 entry.Timer = self
336                 self.addTimerEntry(entry)
337                 
338         def isInTimer(self, eventid, begin, duration, service):
339                 time_match = 0
340                 for x in self.timer_list:
341                         if str(x.service_ref) == str(service):
342                                 #if x.eit is not None and x.repeated == 0:
343                                 #       if x.eit == eventid:
344                                 #               return duration
345                                 if x.repeated != 0:
346                                         chktime = localtime(begin)
347                                         time = localtime(x.begin)
348                                         chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
349                                         for y in range(7):
350                                                 if x.repeated & (2 ** y):
351                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
352                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
353                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
354                                                         elif chktimecmp <= timecmp < (chktimecmp + (duration / 60)):
355                                                                 time_match = ((chktimecmp + (duration / 60)) - timecmp) * 60
356                                 else: #if x.eit is None:
357                                         end = begin + duration
358                                         if begin <= x.begin <= end:
359                                                 diff = end - x.begin
360                                                 if time_match < diff:
361                                                         time_match = diff
362                                         elif x.begin <= begin <= x.end:
363                                                 diff = x.end - begin
364                                                 if time_match < diff:
365                                                         time_match = diff
366                 return time_match
367                                                         
368                                                 
369                                                 
370                         
371
372         def removeEntry(self, entry):
373                 print "[Timer] Remove " + str(entry)
374                 
375                 # avoid re-enqueuing
376                 entry.repeated = False
377
378                 # abort timer.
379                 # this sets the end time to current time, so timer will be stopped.
380                 entry.abort()
381                 
382                 if entry.state != entry.StateEnded:
383                         self.timeChanged(entry)
384                 
385                 print "state: ", entry.state
386                 print "in processed: ", entry in self.processed_timers
387                 print "in running: ", entry in self.timer_list
388                 # now the timer should be in the processed_timers list. remove it from there.
389                 self.processed_timers.remove(entry)
390
391         def shutdown(self):
392                 self.saveTimer()