add confirmation dialog to harddisk init
[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 enigma import quitMainloop
11
12 from Screens.MessageBox import MessageBox
13 import NavigationInstance
14 from time import localtime
15
16 from Tools.XMLTools import elementsWithTag, mergeText, stringToXML
17 from ServiceReference import ServiceReference
18
19 # ok, for descriptions etc we have:
20 # service reference  (to get the service name)
21 # name               (title)
22 # description        (description)
23 # event data         (ONLY for time adjustments etc.)
24
25
26 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
27 # begin and end will be corrected
28 def parseEvent(ev):
29         name = ev.getEventName()
30         description = ev.getShortDescription()
31         begin = ev.getBeginTime()
32         end = begin + ev.getDuration()
33         eit = ev.getEventId()
34         begin -= config.recording.margin_before.value[0] * 60
35         end += config.recording.margin_after.value[0] * 60
36         return (begin, end, name, description, eit)
37
38 class AFTEREVENT:
39         NONE = 0
40         STANDBY = 1
41         DEEPSTANDBY = 2
42
43 # please do not translate log messages
44 class RecordTimerEntry(timer.TimerEntry):
45         def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.NONE, checkOldTimers = False):
46                 timer.TimerEntry.__init__(self, int(begin), int(end))
47                 
48                 if checkOldTimers == True:
49                         if self.begin < time.time() - 1209600:
50                                 self.begin = int(time.time())
51                 
52                 if self.end < self.begin:
53                         self.end = self.begin
54                 
55                 assert isinstance(serviceref, ServiceReference)
56                 
57                 self.service_ref = serviceref
58                 self.eit = eit
59                 self.dontSave = False
60                 self.name = name
61                 self.description = description
62                 self.disabled = disabled
63                 self.timer = None
64                 self.record_service = None
65                 self.start_prepare = 0
66                 self.justplay = justplay
67                 self.afterEvent = afterEvent
68                 self.session = None
69                 
70                 self.log_entries = []
71                 self.resetState()
72         
73         def log(self, code, msg):
74                 self.log_entries.append((int(time.time()), code, msg))
75                 print "[TIMER]", msg
76         
77         def resetState(self):
78                 self.state = self.StateWaiting
79                 self.cancelled = False
80                 self.first_try_prepare = True
81                 self.timeChanged()
82         
83         def calculateFilename(self):
84                 service_name = self.service_ref.getServiceName()
85                 begin_date = time.strftime("%Y%m%d %H%M", time.localtime(self.begin))
86                 
87                 print "begin_date: ", begin_date
88                 print "service_name: ", service_name
89                 print "name:", self.name
90                 print "description: ", self.description
91                 
92                 filename = begin_date + " - " + service_name
93                 if self.name:
94                         filename += " - " + self.name
95
96                 self.Filename = Directories.getRecordingFilename(filename)
97                 self.log(0, "Filename calculated as: '%s'" % self.Filename)
98                 #begin_date + " - " + service_name + description)
99         
100         def tryPrepare(self):
101                 if self.justplay:
102                         return True
103                 else:
104                         self.calculateFilename()
105                         self.record_service = NavigationInstance.instance.recordService(self.service_ref)
106                         if self.record_service == None:
107                                 self.log(1, "'record service' failed")
108                                 return False
109                         else:
110                                 event_id = self.eit
111                                 if event_id is None:
112                                         event_id = -1
113                                 prep_res = self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id )
114                                 if prep_res:
115                                         self.log(2, "'prepare' failed: error %d" % prep_res)
116                                         self.record_service = None
117                                         return False
118         
119                                 self.log(3, "prepare ok, writing meta information to %s" % self.Filename)
120                                 try:
121                                         f = open(self.Filename + ".ts.meta", "w")
122                                         f.write(str(self.service_ref) + "\n")
123                                         f.write(self.name + "\n")
124                                         f.write(self.description + "\n")
125                                         f.write(str(self.begin) + "\n")
126                                         f.close()
127                                 except IOError:
128                                         self.log(4, "failed to write meta information")
129                                 return True
130
131         def do_backoff(self):
132                 if self.backoff == 0:
133                         self.backoff = 5
134                 else:
135                         self.backoff *= 2
136                         if self.backoff > 100:
137                                 self.backoff = 100
138                 self.log(10, "backoff: retry in %d seconds" % self.backoff)
139
140         def activate(self):
141                 next_state = self.state + 1
142                 self.log(5, "activating state %d" % next_state)
143                 
144                 if next_state == self.StatePrepared:
145                         if self.tryPrepare():
146                                 self.log(6, "prepare ok, waiting for begin")
147                                 # fine. it worked, resources are allocated.
148                                 self.next_activation = self.begin
149                                 self.backoff = 0
150                                 return True
151                         
152                         self.log(7, "prepare failed")
153                         if self.first_try_prepare:
154                                 self.first_try_prepare = False
155                                 if config.recording.asktozap.value == 0:
156                                         self.log(8, "asking user to zap away")
157                                         Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"))
158                                 else: # zap without asking
159                                         self.log(9, "zap without asking")
160                                         Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_WARNING)
161                                         self.failureCB(True)
162
163                         self.do_backoff()
164                         # retry
165                         self.start_prepare = time.time() + self.backoff
166                         return False
167                 elif next_state == self.StateRunning:
168                         # if this timer has been cancelled, just go to "end" state.
169                         if self.cancelled:
170                                 return True
171
172                         if self.justplay:
173                                 self.log(11, "zapping")
174                                 NavigationInstance.instance.playService(self.service_ref.ref)
175                                 return True
176                         else:
177                                 self.log(11, "start recording")
178                                 record_res = self.record_service.start()
179                                 
180                                 if record_res:
181                                         self.log(13, "start record returned %d" % record_res)
182                                         self.do_backoff()
183                                         # retry
184                                         self.begin = time.time() + self.backoff
185                                         return False
186                                 
187                                 return True
188                 elif next_state == self.StateEnded:
189                         self.log(12, "stop recording")
190                         if not self.justplay:
191                                 self.record_service.stop()
192                                 self.record_service = None
193                         if self.afterEvent == AFTEREVENT.STANDBY:
194                                 if self.session is not None:
195                                         self.session.open(Standby, self)
196                         elif self.afterEvent == AFTEREVENT.DEEPSTANDBY:
197                                 quitMainloop(1)
198                         return True
199
200         def getNextActivation(self):
201                 if self.state == self.StateEnded:
202                         return self.end
203                 
204                 next_state = self.state + 1
205                 
206                 return {self.StatePrepared: self.start_prepare, 
207                                 self.StateRunning: self.begin, 
208                                 self.StateEnded: self.end }[next_state]
209
210         def failureCB(self, answer):
211                 if answer == True:
212                         self.log(13, "ok, zapped away")
213                         #NavigationInstance.instance.stopUserServices()
214                         NavigationInstance.instance.playService(self.service_ref.ref)
215                 else:
216                         self.log(14, "user didn't want to zap away, record will probably fail")
217
218         def timeChanged(self):
219                 old_prepare = self.start_prepare
220                 self.start_prepare = self.begin - self.prepare_time
221                 self.backoff = 0
222                 
223                 if old_prepare != self.start_prepare:
224                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
225
226 def createTimer(xml):
227         begin = int(xml.getAttribute("begin"))
228         end = int(xml.getAttribute("end"))
229         serviceref = ServiceReference(str(xml.getAttribute("serviceref")))
230         description = xml.getAttribute("description").encode("utf-8")
231         repeated = xml.getAttribute("repeated").encode("utf-8")
232         disabled = long(xml.getAttribute("disabled") or "0")
233         justplay = long(xml.getAttribute("justplay") or "0")
234         afterevent = str(xml.getAttribute("afterevent") or "nothing")
235         afterevent = { "nothing": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY }[afterevent]
236         if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
237                 eit = long(xml.getAttribute("eit"))
238         else:
239                 eit = None
240         
241         name = xml.getAttribute("name").encode("utf-8")
242         #filename = xml.getAttribute("filename").encode("utf-8")
243         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent)
244         entry.repeated = int(repeated)
245         
246         for l in elementsWithTag(xml.childNodes, "log"):
247                 time = int(l.getAttribute("time"))
248                 code = int(l.getAttribute("code"))
249                 msg = mergeText(l.childNodes).strip().encode("utf-8")
250                 entry.log_entries.append((time, code, msg))
251         
252         return entry
253
254 class RecordTimer(timer.Timer):
255         def __init__(self):
256                 timer.Timer.__init__(self)
257                 
258                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
259                 
260                 try:
261                         self.loadTimer()
262                 except IOError:
263                         print "unable to load timers from file!"
264                         
265         def isRecording(self):
266                 isRunning = False
267                 for timer in self.timer_list:
268                         if timer.isRunning() and not timer.justplay:
269                                 isRunning = True
270                 return isRunning
271         
272         def loadTimer(self):
273                 # TODO: PATH!
274                 doc = xml.dom.minidom.parse(self.Filename)
275                 
276                 root = doc.childNodes[0]
277                 for timer in elementsWithTag(root.childNodes, "timer"):
278                         self.record(createTimer(timer))
279
280         def saveTimer(self):
281                 #doc = xml.dom.minidom.Document()
282                 #root_element = doc.createElement('timers')
283                 #doc.appendChild(root_element)
284                 #root_element.appendChild(doc.createTextNode("\n"))
285                 
286                 #for timer in self.timer_list + self.processed_timers:
287                         # some timers (instant records) don't want to be saved.
288                         # skip them
289                         #if timer.dontSave:
290                                 #continue
291                         #t = doc.createTextNode("\t")
292                         #root_element.appendChild(t)
293                         #t = doc.createElement('timer')
294                         #t.setAttribute("begin", str(int(timer.begin)))
295                         #t.setAttribute("end", str(int(timer.end)))
296                         #t.setAttribute("serviceref", str(timer.service_ref))
297                         #t.setAttribute("repeated", str(timer.repeated))                        
298                         #t.setAttribute("name", timer.name)
299                         #t.setAttribute("description", timer.description)
300                         #t.setAttribute("eit", str(timer.eit))
301                         
302                         #for time, code, msg in timer.log_entries:
303                                 #t.appendChild(doc.createTextNode("\t\t"))
304                                 #l = doc.createElement('log')
305                                 #l.setAttribute("time", str(time))
306                                 #l.setAttribute("code", str(code))
307                                 #l.appendChild(doc.createTextNode(msg))
308                                 #t.appendChild(l)
309                                 #t.appendChild(doc.createTextNode("\n"))
310
311                         #root_element.appendChild(t)
312                         #t = doc.createTextNode("\n")
313                         #root_element.appendChild(t)
314
315
316                 #file = open(self.Filename, "w")
317                 #doc.writexml(file)
318                 #file.write("\n")
319                 #file.close()
320
321                 list = []
322
323                 list.append('<?xml version="1.0" ?>\n')
324                 list.append('<timers>\n')
325                 
326                 for timer in self.timer_list + self.processed_timers:
327                         if timer.dontSave:
328                                 continue
329
330                         list.append('<timer')
331                         list.append(' begin="' + str(int(timer.begin)) + '"')
332                         list.append(' end="' + str(int(timer.end)) + '"')
333                         list.append(' serviceref="' + str(timer.service_ref) + '"')
334                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
335                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
336                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
337                         list.append(' afterevent="' + str(stringToXML({ AFTEREVENT.NONE: "nothing", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "deepstandby" }[timer.afterEvent])) + '"')
338                         if timer.eit is not None:
339                                 list.append(' eit="' + str(timer.eit) + '"')
340                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
341                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
342                         list.append('>\n')
343                         
344                         #for time, code, msg in timer.log_entries:
345                                 #list.append('<log')
346                                 #list.append(' code="' + str(code) + '"')
347                                 #list.append(' time="' + str(time) + '"')
348                                 #list.append('>')
349                                 #list.append(str(msg))
350                                 #list.append('</log>\n')
351
352                         
353                         list.append('</timer>\n')
354
355                 list.append('</timers>\n')
356
357                 file = open(self.Filename, "w")
358                 for x in list:
359                         file.write(x)
360                 file.close()
361
362         def record(self, entry):
363                 entry.timeChanged()
364                 print "[Timer] Record " + str(entry)
365                 entry.Timer = self
366                 self.addTimerEntry(entry)
367                 
368         def isInTimer(self, eventid, begin, duration, service):
369                 time_match = 0
370                 chktime = None
371                 chktimecmp = None
372                 chktimecmp_end = None
373                 end = begin + duration
374                 for x in self.timer_list:
375                         if str(x.service_ref) == str(service):
376                                 #if x.eit is not None and x.repeated == 0:
377                                 #       if x.eit == eventid:
378                                 #               return duration
379                                 if x.repeated != 0:
380                                         if chktime is None:
381                                                 chktime = localtime(begin)
382                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
383                                                 chktimecmp_end = chktimecmp + (duration / 60)
384                                         time = localtime(x.begin)
385                                         for y in range(7):
386                                                 if x.repeated & (2 ** y):
387                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
388                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
389                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
390                                                         elif chktimecmp <= timecmp < chktimecmp_end:
391                                                                 time_match = (chktimecmp_end - timecmp) * 60
392                                 else: #if x.eit is None:
393                                         if begin <= x.begin <= end:
394                                                 diff = end - x.begin
395                                                 if time_match < diff:
396                                                         time_match = diff
397                                         elif x.begin <= begin <= x.end:
398                                                 diff = x.end - begin
399                                                 if time_match < diff:
400                                                         time_match = diff
401                 return time_match
402
403         def removeEntry(self, entry):
404                 print "[Timer] Remove " + str(entry)
405                 
406                 # avoid re-enqueuing
407                 entry.repeated = False
408
409                 # abort timer.
410                 # this sets the end time to current time, so timer will be stopped.
411                 entry.abort()
412                 
413                 if entry.state != entry.StateEnded:
414                         self.timeChanged(entry)
415                 
416                 print "state: ", entry.state
417                 print "in processed: ", entry in self.processed_timers
418                 print "in running: ", entry in self.timer_list
419                 # now the timer should be in the processed_timers list. remove it from there.
420                 self.processed_timers.remove(entry)
421
422         def shutdown(self):
423                 self.saveTimer()