don't move the choice box
[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):
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 saveTimer(self):
242                 #doc = xml.dom.minidom.Document()
243                 #root_element = doc.createElement('timers')
244                 #doc.appendChild(root_element)
245                 #root_element.appendChild(doc.createTextNode("\n"))
246                 
247                 #for timer in self.timer_list + self.processed_timers:
248                         # some timers (instant records) don't want to be saved.
249                         # skip them
250                         #if timer.dontSave:
251                                 #continue
252                         #t = doc.createTextNode("\t")
253                         #root_element.appendChild(t)
254                         #t = doc.createElement('timer')
255                         #t.setAttribute("begin", str(int(timer.begin)))
256                         #t.setAttribute("end", str(int(timer.end)))
257                         #t.setAttribute("serviceref", str(timer.service_ref))
258                         #t.setAttribute("repeated", str(timer.repeated))                        
259                         #t.setAttribute("name", timer.name)
260                         #t.setAttribute("description", timer.description)
261                         #t.setAttribute("eit", str(timer.eit))
262                         
263                         #for time, code, msg in timer.log_entries:
264                                 #t.appendChild(doc.createTextNode("\t\t"))
265                                 #l = doc.createElement('log')
266                                 #l.setAttribute("time", str(time))
267                                 #l.setAttribute("code", str(code))
268                                 #l.appendChild(doc.createTextNode(msg))
269                                 #t.appendChild(l)
270                                 #t.appendChild(doc.createTextNode("\n"))
271
272                         #root_element.appendChild(t)
273                         #t = doc.createTextNode("\n")
274                         #root_element.appendChild(t)
275
276
277                 #file = open(self.Filename, "w")
278                 #doc.writexml(file)
279                 #file.write("\n")
280                 #file.close()
281
282                 list = []
283
284                 list.append('<?xml version="1.0" ?>\n')
285                 list.append('<timers>\n')
286                 
287                 for timer in self.timer_list + self.processed_timers:
288                         if timer.dontSave:
289                                 continue
290
291                         list.append('<timer')
292                         list.append(' begin="' + str(int(timer.begin)) + '"')
293                         list.append(' end="' + str(int(timer.end)) + '"')
294                         list.append(' serviceref="' + str(timer.service_ref) + '"')
295                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
296                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
297                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
298                         if timer.eit is not None:
299                                 list.append(' eit="' + str(timer.eit) + '"')
300                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
301                         list.append('>\n')
302                         
303                         #for time, code, msg in timer.log_entries:
304                                 #list.append('<log')
305                                 #list.append(' code="' + str(code) + '"')
306                                 #list.append(' time="' + str(time) + '"')
307                                 #list.append('>')
308                                 #list.append(str(msg))
309                                 #list.append('</log>\n')
310
311                         
312                         list.append('</timer>\n')
313
314                 list.append('</timers>\n')
315
316                 file = open(self.Filename, "w")
317                 for x in list:
318                         file.write(x)
319                 file.close()
320
321         def record(self, entry):
322                 entry.timeChanged()
323                 print "[Timer] Record " + str(entry)
324                 entry.Timer = self
325                 self.addTimerEntry(entry)
326                 
327         def isInTimer(self, eventid, begin, duration, service):
328                 time_match = 0
329                 for x in self.timer_list:
330                         if str(x.service_ref) == str(service):
331                                 #if x.eit is not None and x.repeated == 0:
332                                 #       if x.eit == eventid:
333                                 #               return duration
334                                 if x.repeated != 0:
335                                         chktime = localtime(begin)
336                                         time = localtime(x.begin)
337                                         chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
338                                         for y in range(7):
339                                                 if x.repeated & (2 ** y):
340                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
341                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
342                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
343                                                         elif chktimecmp <= timecmp < (chktimecmp + (duration / 60)):
344                                                                 time_match = ((chktimecmp + (duration / 60)) - timecmp) * 60
345                                 else: #if x.eit is None:
346                                         end = begin + duration
347                                         if begin <= x.begin <= end:
348                                                 diff = end - x.begin
349                                                 if time_match < diff:
350                                                         time_match = diff
351                                         elif x.begin <= begin <= x.end:
352                                                 diff = x.end - begin
353                                                 if time_match < diff:
354                                                         time_match = diff
355                 return time_match
356                                                         
357                                                 
358                                                 
359                         
360
361         def removeEntry(self, entry):
362                 print "[Timer] Remove " + str(entry)
363                 
364                 # avoid re-enqueuing
365                 entry.repeated = False
366
367                 # abort timer.
368                 # this sets the end time to current time, so timer will be stopped.
369                 entry.abort()
370                 
371                 if entry.state != entry.StateEnded:
372                         self.timeChanged(entry)
373                 
374                 print "state: ", entry.state
375                 print "in processed: ", entry in self.processed_timers
376                 print "in running: ", entry in self.timer_list
377                 # now the timer should be in the processed_timers list. remove it from there.
378                 self.processed_timers.remove(entry)
379
380         def shutdown(self):
381                 self.saveTimer()