3 #from time import datetime
4 from Tools import Directories, Notifications
6 from Components.config import config, ConfigYesNo
10 from enigma import eEPGCache, getBestPlayableServiceReference, eServiceReference
12 from Screens.MessageBox import MessageBox
13 from Screens.Standby import Standby, TryQuitMainloop, inStandby, inTryQuitMainloop
15 import NavigationInstance
16 from time import localtime
18 from Tools.XMLTools import elementsWithTag, mergeText, stringToXML
19 from ServiceReference import ServiceReference
21 # ok, for descriptions etc we have:
22 # service reference (to get the service name)
24 # description (description)
25 # event data (ONLY for time adjustments etc.)
28 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
29 # begin and end will be corrected
30 def parseEvent(ev, description = True):
32 name = ev.getEventName()
33 description = ev.getShortDescription()
37 begin = ev.getBeginTime()
38 end = begin + ev.getDuration()
40 begin -= config.recording.margin_before.value * 60
41 end += config.recording.margin_after.value * 60
42 return (begin, end, name, description, eit)
49 # please do not translate log messages
50 class RecordTimerEntry(timer.TimerEntry):
51 def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.NONE, checkOldTimers = False):
52 timer.TimerEntry.__init__(self, int(begin), int(end))
54 if checkOldTimers == True:
55 if self.begin < time.time() - 1209600:
56 self.begin = int(time.time())
58 if self.end < self.begin:
61 assert isinstance(serviceref, ServiceReference)
63 self.service_ref = serviceref
67 self.description = description
68 self.disabled = disabled
70 self.record_service = None
71 self.start_prepare = 0
72 self.justplay = justplay
73 self.afterEvent = afterEvent
78 def log(self, code, msg):
79 self.log_entries.append((int(time.time()), code, msg))
83 self.state = self.StateWaiting
84 self.cancelled = False
85 self.first_try_prepare = True
88 def calculateFilename(self):
89 service_name = self.service_ref.getServiceName()
90 begin_date = time.strftime("%Y%m%d %H%M", time.localtime(self.begin))
92 print "begin_date: ", begin_date
93 print "service_name: ", service_name
94 print "name:", self.name
95 print "description: ", self.description
97 filename = begin_date + " - " + service_name
99 filename += " - " + self.name
101 self.Filename = Directories.getRecordingFilename(filename)
102 self.log(0, "Filename calculated as: '%s'" % self.Filename)
103 #begin_date + " - " + service_name + description)
105 def tryPrepare(self):
109 self.calculateFilename()
110 rec_ref = self.service_ref and self.service_ref.ref
111 if rec_ref and rec_ref.flags & eServiceReference.isGroup:
112 rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
114 self.log(1, "'get best playable service for group... record' failed")
117 self.record_service = rec_ref and NavigationInstance.instance.recordService(rec_ref)
118 if not self.record_service:
119 self.log(1, "'record service' failed")
126 prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id)
128 self.log(2, "'prepare' failed: error %d" % prep_res)
129 self.record_service = None
133 epgcache = eEPGCache.getInstance()
134 queryTime=self.begin+(self.end-self.begin)/2
135 evt = epgcache.lookupEventTime(rec_ref, queryTime)
137 self.description = evt.getShortDescription()
139 self.log(3, "prepare ok, writing meta information to %s" % self.Filename)
141 f = open(self.Filename + ".ts.meta", "w")
142 f.write(rec_ref.toString() + "\n")
143 f.write(self.name + "\n")
144 f.write(self.description + "\n")
145 f.write(str(self.begin) + "\n")
148 self.log(4, "failed to write meta information")
149 self.record_service = None
153 def do_backoff(self):
154 if self.backoff == 0:
158 if self.backoff > 100:
160 self.log(10, "backoff: retry in %d seconds" % self.backoff)
163 next_state = self.state + 1
164 self.log(5, "activating state %d" % next_state)
166 if next_state == self.StatePrepared:
167 if self.tryPrepare():
168 self.log(6, "prepare ok, waiting for begin")
169 # fine. it worked, resources are allocated.
170 self.next_activation = self.begin
174 self.log(7, "prepare failed")
175 if self.first_try_prepare:
176 self.first_try_prepare = False
177 if not config.recording.asktozap.value:
178 self.log(8, "asking user to zap away")
179 Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20)
180 else: # zap without asking
181 self.log(9, "zap without asking")
182 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
187 self.start_prepare = time.time() + self.backoff
189 elif next_state == self.StateRunning:
190 # if this timer has been cancelled, just go to "end" state.
195 self.log(11, "zapping")
196 NavigationInstance.instance.playService(self.service_ref.ref)
199 self.log(11, "start recording")
200 record_res = self.record_service.start()
203 self.log(13, "start record returned %d" % record_res)
206 self.begin = time.time() + self.backoff
210 elif next_state == self.StateEnded:
211 self.log(12, "stop recording")
212 if not self.justplay:
213 NavigationInstance.instance.stopRecordService(self.record_service)
214 self.record_service = None
215 if self.afterEvent == AFTEREVENT.STANDBY:
218 Notifications.AddNotification(Standby)
219 if self.afterEvent == AFTEREVENT.DEEPSTANDBY:
220 global inTryQuitMainloop
221 if not inTryQuitMainloop:
222 Notifications.AddNotification(TryQuitMainloop, 1)
225 def getNextActivation(self):
226 if self.state == self.StateEnded:
229 next_state = self.state + 1
231 return {self.StatePrepared: self.start_prepare,
232 self.StateRunning: self.begin,
233 self.StateEnded: self.end }[next_state]
235 def failureCB(self, answer):
237 self.log(13, "ok, zapped away")
238 #NavigationInstance.instance.stopUserServices()
239 NavigationInstance.instance.playService(self.service_ref.ref)
241 self.log(14, "user didn't want to zap away, record will probably fail")
243 def timeChanged(self):
244 old_prepare = self.start_prepare
245 self.start_prepare = self.begin - self.prepare_time
248 if int(old_prepare) != int(self.start_prepare):
249 self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
251 def createTimer(xml):
252 begin = int(xml.getAttribute("begin"))
253 end = int(xml.getAttribute("end"))
254 serviceref = ServiceReference(xml.getAttribute("serviceref").encode("utf-8"))
255 description = xml.getAttribute("description").encode("utf-8")
256 repeated = xml.getAttribute("repeated").encode("utf-8")
257 disabled = long(xml.getAttribute("disabled") or "0")
258 justplay = long(xml.getAttribute("justplay") or "0")
259 afterevent = str(xml.getAttribute("afterevent") or "nothing")
260 afterevent = { "nothing": AFTEREVENT.NONE, "standby": AFTEREVENT.STANDBY, "deepstandby": AFTEREVENT.DEEPSTANDBY }[afterevent]
261 if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
262 eit = long(xml.getAttribute("eit"))
266 name = xml.getAttribute("name").encode("utf-8")
267 #filename = xml.getAttribute("filename").encode("utf-8")
268 entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent)
269 entry.repeated = int(repeated)
271 for l in elementsWithTag(xml.childNodes, "log"):
272 time = int(l.getAttribute("time"))
273 code = int(l.getAttribute("code"))
274 msg = mergeText(l.childNodes).strip().encode("utf-8")
275 entry.log_entries.append((time, code, msg))
279 class RecordTimer(timer.Timer):
281 timer.Timer.__init__(self)
283 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
288 print "unable to load timers from file!"
290 def isRecording(self):
292 for timer in self.timer_list:
293 if timer.isRunning() and not timer.justplay:
299 doc = xml.dom.minidom.parse(self.Filename)
301 root = doc.childNodes[0]
302 for timer in elementsWithTag(root.childNodes, "timer"):
303 self.record(createTimer(timer))
306 #doc = xml.dom.minidom.Document()
307 #root_element = doc.createElement('timers')
308 #doc.appendChild(root_element)
309 #root_element.appendChild(doc.createTextNode("\n"))
311 #for timer in self.timer_list + self.processed_timers:
312 # some timers (instant records) don't want to be saved.
316 #t = doc.createTextNode("\t")
317 #root_element.appendChild(t)
318 #t = doc.createElement('timer')
319 #t.setAttribute("begin", str(int(timer.begin)))
320 #t.setAttribute("end", str(int(timer.end)))
321 #t.setAttribute("serviceref", str(timer.service_ref))
322 #t.setAttribute("repeated", str(timer.repeated))
323 #t.setAttribute("name", timer.name)
324 #t.setAttribute("description", timer.description)
325 #t.setAttribute("eit", str(timer.eit))
327 #for time, code, msg in timer.log_entries:
328 #t.appendChild(doc.createTextNode("\t\t"))
329 #l = doc.createElement('log')
330 #l.setAttribute("time", str(time))
331 #l.setAttribute("code", str(code))
332 #l.appendChild(doc.createTextNode(msg))
334 #t.appendChild(doc.createTextNode("\n"))
336 #root_element.appendChild(t)
337 #t = doc.createTextNode("\n")
338 #root_element.appendChild(t)
341 #file = open(self.Filename, "w")
348 list.append('<?xml version="1.0" ?>\n')
349 list.append('<timers>\n')
351 for timer in self.timer_list + self.processed_timers:
355 list.append('<timer')
356 list.append(' begin="' + str(int(timer.begin)) + '"')
357 list.append(' end="' + str(int(timer.end)) + '"')
358 list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
359 list.append(' repeated="' + str(int(timer.repeated)) + '"')
360 list.append(' name="' + str(stringToXML(timer.name)) + '"')
361 list.append(' description="' + str(stringToXML(timer.description)) + '"')
362 list.append(' afterevent="' + str(stringToXML({ AFTEREVENT.NONE: "nothing", AFTEREVENT.STANDBY: "standby", AFTEREVENT.DEEPSTANDBY: "deepstandby" }[timer.afterEvent])) + '"')
363 if timer.eit is not None:
364 list.append(' eit="' + str(timer.eit) + '"')
365 list.append(' disabled="' + str(int(timer.disabled)) + '"')
366 list.append(' justplay="' + str(int(timer.justplay)) + '"')
369 if config.recording.debug.value:
370 for time, code, msg in timer.log_entries:
372 list.append(' code="' + str(code) + '"')
373 list.append(' time="' + str(time) + '"')
375 list.append(str(stringToXML(msg)))
376 list.append('</log>\n')
378 list.append('</timer>\n')
380 list.append('</timers>\n')
382 file = open(self.Filename, "w")
387 def record(self, entry):
389 print "[Timer] Record " + str(entry)
391 self.addTimerEntry(entry)
393 def isInTimer(self, eventid, begin, duration, service):
397 chktimecmp_end = None
398 end = begin + duration
399 for x in self.timer_list:
400 if str(x.service_ref) == str(service):
401 #if x.eit is not None and x.repeated == 0:
402 # if x.eit == eventid:
406 chktime = localtime(begin)
407 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
408 chktimecmp_end = chktimecmp + (duration / 60)
409 time = localtime(x.begin)
411 if x.repeated & (2 ** y):
412 timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
413 if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
414 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
415 elif chktimecmp <= timecmp < chktimecmp_end:
416 time_match = (chktimecmp_end - timecmp) * 60
417 else: #if x.eit is None:
418 if begin <= x.begin <= end:
420 if time_match < diff:
422 elif x.begin <= begin <= x.end:
424 if time_match < diff:
428 def removeEntry(self, entry):
429 print "[Timer] Remove " + str(entry)
432 entry.repeated = False
435 # this sets the end time to current time, so timer will be stopped.
438 if entry.state != entry.StateEnded:
439 self.timeChanged(entry)
441 print "state: ", entry.state
442 print "in processed: ", entry in self.processed_timers
443 print "in running: ", entry in self.timer_list
444 # now the timer should be in the processed_timers list. remove it from there.
445 self.processed_timers.remove(entry)