python_service.i: naming change getAITApplication -> getAITApplications
[openblackhole/openblackhole-enigma2.git] / RecordTimer.py
1 import os
2 from enigma import eEPGCache, getBestPlayableServiceReference, \
3         eServiceReference, iRecordableService, quitMainloop, eActionMap
4
5 from Components.config import config
6 from Components.UsageConfig import defaultMoviePath
7 from Components.TimerSanityCheck import TimerSanityCheck
8
9 from Screens.MessageBox import MessageBox
10 import Screens.Standby
11 from Tools import Directories, Notifications, ASCIItranslit, Trashcan
12 from Tools.XMLTools import stringToXML
13
14 import timer
15 import xml.etree.cElementTree
16 import NavigationInstance
17 from ServiceReference import ServiceReference
18
19 from time import localtime, strftime, ctime, time
20 from bisect import insort
21 from sys import maxint
22
23 # ok, for descriptions etc we have:
24 # service reference  (to get the service name)
25 # name               (title)
26 # description        (description)
27 # event data         (ONLY for time adjustments etc.)
28
29
30 # parses an event, and gives out a (begin, end, name, duration, eit)-tuple.
31 # begin and end will be corrected
32 def parseEvent(ev, description = True):
33         if description:
34                 name = ev.getEventName()
35                 description = ev.getShortDescription()
36                 if description == "":
37                         description = ev.getExtendedDescription()
38         else:
39                 name = ""
40                 description = ""
41         begin = ev.getBeginTime()
42         end = begin + ev.getDuration()
43         eit = ev.getEventId()
44         begin -= config.recording.margin_before.value * 60
45         end += config.recording.margin_after.value * 60
46         return (begin, end, name, description, eit)
47
48 class AFTEREVENT:
49         NONE = 0
50         STANDBY = 1
51         DEEPSTANDBY = 2
52         AUTO = 3
53
54 def findSafeRecordPath(dirname):
55         if not dirname:
56                 return None
57         from Components import Harddisk
58         dirname = os.path.realpath(dirname)
59         mountpoint = Harddisk.findMountPoint(dirname)
60         if mountpoint in ('/', '/media'):
61                 print '[RecordTimer] media is not mounted:', dirname
62                 return None
63         if not os.path.isdir(dirname):
64                 try:
65                         os.makedirs(dirname)
66                 except Exception, ex:
67                         print '[RecordTimer] Failed to create dir "%s":' % dirname, ex
68                         return None
69         return dirname
70
71 def chechForRecordings():
72         if NavigationInstance.instance.getRecordings():
73                 return True
74         rec_time = NavigationInstance.instance.RecordTimer.getNextRecordingTime()
75         return rec_time > 0 and (rec_time - time()) < 360
76
77 # please do not translate log messages
78 class RecordTimerEntry(timer.TimerEntry, object):
79 ######### the following static methods and members are only in use when the box is in (soft) standby
80         wasInStandby = False
81         receiveRecordEvents = False
82
83         @staticmethod
84         def keypress(key=None, flag=1):
85                 if flag and RecordTimerEntry.wasInStandby:
86                         RecordTimerEntry.wasInStandby = False
87                         eActionMap.getInstance().unbindAction('', RecordTimerEntry.keypress)
88
89         @staticmethod
90         def setWasInStandby():
91                 if not RecordTimerEntry.wasInStandby:
92                         RecordTimerEntry.wasInStandby = True
93                         eActionMap.getInstance().bindAction('', -maxint - 1, RecordTimerEntry.keypress)
94
95         @staticmethod
96         def shutdown():
97                 quitMainloop(1)
98
99         @staticmethod
100         def staticGotRecordEvent(recservice, event):
101                 if event == iRecordableService.evEnd:
102                         print "RecordTimer.staticGotRecordEvent(iRecordableService.evEnd)"
103                         if not chechForRecordings():
104                                 print "No recordings busy of sceduled within 6 minutes so shutdown"
105                                 RecordTimerEntry.shutdown() # immediate shutdown
106                 elif event == iRecordableService.evStart:
107                         print "RecordTimer.staticGotRecordEvent(iRecordableService.evStart)"
108
109         @staticmethod
110         def stopTryQuitMainloop():
111                 print "RecordTimer.stopTryQuitMainloop"
112                 NavigationInstance.instance.record_event.remove(RecordTimerEntry.staticGotRecordEvent)
113                 RecordTimerEntry.receiveRecordEvents = False
114
115         @staticmethod
116         def TryQuitMainloop():
117                 if not RecordTimerEntry.receiveRecordEvents and Screens.Standby.inStandby:
118                         print "RecordTimer.TryQuitMainloop"
119                         NavigationInstance.instance.record_event.append(RecordTimerEntry.staticGotRecordEvent)
120                         RecordTimerEntry.receiveRecordEvents = True
121                         # send fake event.. to check if another recordings are running or
122                         # other timers start in a few seconds
123                         RecordTimerEntry.staticGotRecordEvent(None, iRecordableService.evEnd)
124 #################################################################
125
126         def __init__(self, serviceref, begin, end, name, description, eit, disabled = False, justplay = False, afterEvent = AFTEREVENT.AUTO, checkOldTimers = False, dirname = None, tags = None, descramble = True, record_ecm = False, always_zap = False):
127                 timer.TimerEntry.__init__(self, int(begin), int(end))
128
129                 if checkOldTimers == True:
130                         if self.begin < time() - 1209600:
131                                 self.begin = int(time())
132                 
133                 if self.end < self.begin:
134                         self.end = self.begin
135                 
136                 assert isinstance(serviceref, ServiceReference)
137                 
138                 if serviceref.isRecordable():
139                         self.service_ref = serviceref
140                 else:
141                         self.service_ref = ServiceReference(None)
142                 self.eit = eit
143                 self.dontSave = False
144                 self.name = name
145                 self.description = description
146                 self.disabled = disabled
147                 self.timer = None
148                 self.__record_service = None
149                 self.start_prepare = 0
150                 self.justplay = justplay
151                 self.always_zap = always_zap
152                 self.afterEvent = afterEvent
153                 self.dirname = dirname
154                 self.dirnameHadToFallback = False
155                 self.autoincrease = False
156                 self.autoincreasetime = 3600 * 24 # 1 day
157                 self.tags = tags or []
158                 self.descramble = descramble
159                 self.record_ecm = record_ecm
160
161                 self.log_entries = []
162                 self.resetState()
163
164         def __repr__(self):
165                 return "RecordTimerEntry(name=%s, begin=%s, serviceref=%s, justplay=%s)" % (self.name, ctime(self.begin), self.service_ref, self.justplay)
166
167         def log(self, code, msg):
168                 self.log_entries.append((int(time()), code, msg))
169                 print "[TIMER]", msg
170
171         def calculateFilename(self):
172                 service_name = self.service_ref.getServiceName()
173                 begin_date = strftime("%Y%m%d %H%M", localtime(self.begin))
174
175                 print "begin_date: ", begin_date
176                 print "service_name: ", service_name
177                 print "name:", self.name
178                 print "description: ", self.description
179
180                 filename = begin_date + " - " + service_name
181                 if self.name:
182                         if config.recording.filename_composition.value == "short":
183                                 filename = strftime("%Y%m%d", localtime(self.begin)) + " - " + self.name
184                         elif config.recording.filename_composition.value == "long":
185                                 filename += " - " + self.name + " - " + self.description
186                         else:
187                                 filename += " - " + self.name # standard
188
189                 if config.recording.ascii_filenames.value:
190                         filename = ASCIItranslit.legacyEncode(filename)
191
192
193                 if not self.dirname:
194                         dirname = findSafeRecordPath(defaultMoviePath())
195                 else:
196                         dirname = findSafeRecordPath(self.dirname)
197                         if dirname is None:
198                                 dirname = findSafeRecordPath(defaultMoviePath())
199                                 self.dirnameHadToFallback = True
200                 if not dirname:
201                         return None
202                 self.Filename = Directories.getRecordingFilename(filename, dirname)
203                 self.log(0, "Filename calculated as: '%s'" % self.Filename)
204                 return self.Filename
205
206         def tryPrepare(self):
207                 if self.justplay:
208                         return True
209                 else:
210                         if not self.calculateFilename():
211                                 self.do_backoff()
212                                 self.start_prepare = time() + self.backoff
213                                 return False
214                         rec_ref = self.service_ref and self.service_ref.ref
215                         if rec_ref and rec_ref.flags & eServiceReference.isGroup:
216                                 rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
217                                 if not rec_ref:
218                                         self.log(1, "'get best playable service for group... record' failed")
219                                         return False
220                                 
221                         self.record_service = rec_ref and NavigationInstance.instance.recordService(rec_ref)
222
223                         if not self.record_service:
224                                 self.log(1, "'record service' failed")
225                                 return False
226
227                         if self.repeated:
228                                 epgcache = eEPGCache.getInstance()
229                                 queryTime=self.begin+(self.end-self.begin)/2
230                                 evt = epgcache.lookupEventTime(rec_ref, queryTime)
231                                 if evt:
232                                         self.description = evt.getShortDescription()
233                                         if self.description == "":
234                                                 self.description = evt.getExtendedDescription()
235                                         event_id = evt.getEventId()
236                                 else:
237                                         event_id = -1
238                         else:
239                                 event_id = self.eit
240                                 if event_id is None:
241                                         event_id = -1
242
243                         prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, self.name.replace("\n", ""), self.description.replace("\n", ""), ' '.join(self.tags), self.descramble, self.record_ecm)
244                         if prep_res:
245                                 if prep_res == -255:
246                                         self.log(4, "failed to write meta information")
247                                 else:
248                                         self.log(2, "'prepare' failed: error %d" % prep_res)
249
250                                 # we must calc nur start time before stopRecordService call because in Screens/Standby.py TryQuitMainloop tries to get
251                                 # the next start time in evEnd event handler...
252                                 self.do_backoff()
253                                 self.start_prepare = time() + self.backoff
254
255                                 NavigationInstance.instance.stopRecordService(self.record_service)
256                                 self.record_service = None
257                                 return False
258                         return True
259
260         def do_backoff(self):
261                 if self.backoff == 0:
262                         self.backoff = 5
263                 else:
264                         self.backoff *= 2
265                         if self.backoff > 100:
266                                 self.backoff = 100
267                 self.log(10, "backoff: retry in %d seconds" % self.backoff)
268
269         def activate(self):
270                 next_state = self.state + 1
271                 self.log(5, "activating state %d" % next_state)
272
273                 if next_state == 1:
274                         if self.always_zap:
275                                 if Screens.Standby.inStandby:
276                                         self.log(5, "wakeup and zap to recording service")
277                                         RecordTimerEntry.setWasInStandby()
278                                         #set service to zap after standby
279                                         Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
280                                         Screens.Standby.inStandby.paused_service = None
281                                         #wakeup standby
282                                         Screens.Standby.inStandby.Power()
283                                 else:
284                                         cur_zap_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
285                                         if cur_zap_ref and not cur_zap_ref.getPath():# we do not zap away if it is no live service
286                                                 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
287                                                 self.failureCB(True)
288                                                 self.log(5, "zap to recording service")
289
290                 if next_state == self.StatePrepared:
291                         if self.tryPrepare():
292                                 self.log(6, "prepare ok, waiting for begin")
293                                 # create file to "reserve" the filename
294                                 # because another recording at the same time on another service can try to record the same event
295                                 # i.e. cable / sat.. then the second recording needs an own extension... when we create the file
296                                 # here than calculateFilename is happy
297                                 if not self.justplay:
298                                         open(self.Filename + ".ts", "w").close()
299                                         # Give the Trashcan a chance to clean up
300                                         try:
301                                                 Trashcan.instance.cleanIfIdle(self.Filename)
302                                         except Exception, e:
303                                                  print "[TIMER] Failed to call Trashcan.instance.cleanIfIdle()"
304                                                  print "[TIMER] Error:", e
305                                 # fine. it worked, resources are allocated.
306                                 self.next_activation = self.begin
307                                 self.backoff = 0
308                                 return True
309
310                         self.log(7, "prepare failed")
311                         if self.first_try_prepare:
312                                 self.first_try_prepare = False
313                                 cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
314                                 if cur_ref and not cur_ref.getPath():
315                                         if Screens.Standby.inStandby:
316                                                 self.failureCB(True)
317                                         elif not config.recording.asktozap.value:
318                                                 self.log(8, "asking user to zap away")
319                                                 Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20, default=True)
320                                         else: # zap without asking
321                                                 self.log(9, "zap without asking")
322                                                 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
323                                                 self.failureCB(True)
324                                 elif cur_ref:
325                                         self.log(8, "currently running service is not a live service.. so stop it makes no sense")
326                                 else:
327                                         self.log(8, "currently no service running... so we dont need to stop it")
328                         return False
329
330                 elif next_state == self.StateRunning:
331                         # if this timer has been cancelled, just go to "end" state.
332                         if self.cancelled:
333                                 return True
334                         if self.justplay:
335                                 if Screens.Standby.inStandby:
336                                         self.log(11, "wakeup and zap")
337                                         RecordTimerEntry.setWasInStandby()
338                                         #set service to zap after standby
339                                         Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
340                                         Screens.Standby.inStandby.paused_service = None
341                                         #wakeup standby
342                                         Screens.Standby.inStandby.Power()
343                                 else:
344                                         self.log(11, "zapping")
345                                         NavigationInstance.instance.playService(self.service_ref.ref)
346                                 return True
347                         else:
348                                 self.log(11, "start recording")
349                                 record_res = self.record_service.start()
350                                 
351                                 if record_res:
352                                         self.log(13, "start record returned %d" % record_res)
353                                         self.do_backoff()
354                                         # retry
355                                         self.begin = time() + self.backoff
356                                         return False
357
358                                 # Tell the trashcan we started recording. The trashcan gets events,
359                                 # but cannot tell what the associated path is.
360                                 Trashcan.instance.markDirty(self.Filename)
361
362                                 return True
363
364                 elif next_state == self.StateEnded:
365                         old_end = self.end
366                         if self.setAutoincreaseEnd():
367                                 self.log(12, "autoincrase recording %d minute(s)" % int((self.end - old_end)/60))
368                                 self.state -= 1
369                                 return True
370                         self.log(12, "stop recording")
371                         if not self.justplay:
372                                 NavigationInstance.instance.stopRecordService(self.record_service)
373                                 self.record_service = None
374                         if not chechForRecordings():
375                                 if self.afterEvent == AFTEREVENT.DEEPSTANDBY or self.afterEvent == AFTEREVENT.AUTO and (Screens.Standby.inStandby or RecordTimerEntry.wasInStandby) and not config.misc.standbyCounter.value:
376                                         if not Screens.Standby.inTryQuitMainloop:
377                                                 if Screens.Standby.inStandby:
378                                                         RecordTimerEntry.TryQuitMainloop()
379                                                 else:
380                                                         Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour receiver. Shutdown now?"), timeout=20, default=True)
381                                 elif self.afterEvent == AFTEREVENT.STANDBY or self.afterEvent == AFTEREVENT.AUTO and RecordTimerEntry.wasInStandby:
382                                         if not Screens.Standby.inStandby:
383                                                 Notifications.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nreceiver to standby. Do that now?"), timeout=20, default=True)
384                                 else:
385                                         RecordTimerEntry.keypress()
386                         return True
387
388         def setAutoincreaseEnd(self, entry = None):
389                 if not self.autoincrease:
390                         return False
391                 if entry is None:
392                         new_end =  int(time()) + self.autoincreasetime
393                 else:
394                         new_end = entry.begin - 30
395
396                 dummyentry = RecordTimerEntry(self.service_ref, self.begin, new_end, self.name, self.description, self.eit, disabled=True, justplay = self.justplay, afterEvent = self.afterEvent, dirname = self.dirname, tags = self.tags)
397                 dummyentry.disabled = self.disabled
398                 timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, dummyentry)
399                 if not timersanitycheck.check():
400                         simulTimerList = timersanitycheck.getSimulTimerList()
401                         if simulTimerList is not None and len(simulTimerList) > 1:
402                                 new_end = simulTimerList[1].begin
403                                 new_end -= 30                           # 30 Sekunden Prepare-Zeit lassen
404                 if new_end <= time():
405                         return False
406                 self.end = new_end
407                 return True
408
409         def sendStandbyNotification(self, answer):
410                 RecordTimerEntry.keypress()
411                 if answer:
412                         Notifications.AddNotification(Screens.Standby.Standby)
413
414         def sendTryQuitMainloopNotification(self, answer):
415                 RecordTimerEntry.keypress()
416                 if answer:
417                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
418
419         def getNextActivation(self):
420                 if self.state == self.StateEnded:
421                         return self.end
422                 
423                 next_state = self.state + 1
424                 
425                 return {self.StatePrepared: self.start_prepare, 
426                                 self.StateRunning: self.begin, 
427                                 self.StateEnded: self.end }[next_state]
428
429         def failureCB(self, answer):
430                 if answer == True:
431                         self.log(13, "ok, zapped away")
432                         #NavigationInstance.instance.stopUserServices()
433                         NavigationInstance.instance.playService(self.service_ref.ref)
434                 else:
435                         self.log(14, "user didn't want to zap away, record will probably fail")
436
437         def timeChanged(self):
438                 old_prepare = self.start_prepare
439                 self.start_prepare = self.begin - self.prepare_time
440                 self.backoff = 0
441                 
442                 if int(old_prepare) != int(self.start_prepare):
443                         self.log(15, "record time changed, start prepare is now: %s" % ctime(self.start_prepare))
444
445         def gotRecordEvent(self, record, event):
446                 # TODO: this is not working (never true), please fix. (comparing two swig wrapped ePtrs)
447                 if self.__record_service.__deref__() != record.__deref__():
448                         return
449                 self.log(16, "record event %d" % event)
450                 if event == iRecordableService.evRecordWriteError:
451                         print "WRITE ERROR on recording, disk full?"
452                         # show notification. the 'id' will make sure that it will be
453                         # displayed only once, even if more timers are failing at the
454                         # same time. (which is very likely in case of disk fullness)
455                         Notifications.AddPopup(text = _("Write error while recording. Disk full?\n"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "DiskFullMessage")
456                         # ok, the recording has been stopped. we need to properly note 
457                         # that in our state, with also keeping the possibility to re-try.
458                         # TODO: this has to be done.
459                 elif event == iRecordableService.evStart:
460                         text = _("A record has been started:\n%s") % self.name
461                         notify = config.usage.show_message_when_recording_starts.value and not Screens.Standby.inStandby
462                         if self.dirnameHadToFallback:
463                                 text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead.")))
464                                 notify = True
465                         if notify:
466                                 Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
467                 elif event == iRecordableService.evRecordAborted:
468                         NavigationInstance.instance.RecordTimer.removeEntry(self)
469
470         # we have record_service as property to automatically subscribe to record service events
471         def setRecordService(self, service):
472                 if self.__record_service is not None:
473                         print "[remove callback]"
474                         NavigationInstance.instance.record_event.remove(self.gotRecordEvent)
475
476                 self.__record_service = service
477
478                 if self.__record_service is not None:
479                         print "[add callback]"
480                         NavigationInstance.instance.record_event.append(self.gotRecordEvent)
481
482         record_service = property(lambda self: self.__record_service, setRecordService)
483
484 def createTimer(xml):
485         begin = int(xml.get("begin"))
486         end = int(xml.get("end"))
487         serviceref = ServiceReference(xml.get("serviceref").encode("utf-8"))
488         description = xml.get("description").encode("utf-8")
489         repeated = xml.get("repeated").encode("utf-8")
490         disabled = long(xml.get("disabled") or "0")
491         justplay = long(xml.get("justplay") or "0")
492         always_zap = long(xml.get("always_zap") or "0")
493         afterevent = str(xml.get("afterevent") or "nothing")
494         afterevent = {
495                 "nothing": AFTEREVENT.NONE,
496                 "standby": AFTEREVENT.STANDBY,
497                 "deepstandby": AFTEREVENT.DEEPSTANDBY,
498                 "auto": AFTEREVENT.AUTO
499                 }[afterevent]
500         eit = xml.get("eit")
501         if eit and eit != "None":
502                 eit = long(eit);
503         else:
504                 eit = None
505         location = xml.get("location")
506         if location and location != "None":
507                 location = location.encode("utf-8")
508         else:
509                 location = None
510         tags = xml.get("tags")
511         if tags and tags != "None":
512                 tags = tags.encode("utf-8").split(' ')
513         else:
514                 tags = None
515         descramble = int(xml.get("descramble") or "1")
516         record_ecm = int(xml.get("record_ecm") or "0")
517
518         name = xml.get("name").encode("utf-8")
519         #filename = xml.get("filename").encode("utf-8")
520         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags, descramble = descramble, record_ecm = record_ecm, always_zap = always_zap)
521         entry.repeated = int(repeated)
522         
523         for l in xml.findall("log"):
524                 time = int(l.get("time"))
525                 code = int(l.get("code"))
526                 msg = l.text.strip().encode("utf-8")
527                 entry.log_entries.append((time, code, msg))
528         
529         return entry
530
531 class RecordTimer(timer.Timer):
532         def __init__(self):
533                 timer.Timer.__init__(self)
534                 
535                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
536                 
537                 try:
538                         self.loadTimer()
539                 except IOError:
540                         print "unable to load timers from file!"
541
542         def doActivate(self, w):
543                 # when activating a timer which has already passed,
544                 # simply abort the timer. don't run trough all the stages.
545                 if w.shouldSkip():
546                         w.state = RecordTimerEntry.StateEnded
547                 else:
548                         # when active returns true, this means "accepted".
549                         # otherwise, the current state is kept.
550                         # the timer entry itself will fix up the delay then.
551                         if w.activate():
552                                 w.state += 1
553
554                 self.timer_list.remove(w)
555
556                 # did this timer reached the last state?
557                 if w.state < RecordTimerEntry.StateEnded:
558                         # no, sort it into active list
559                         insort(self.timer_list, w)
560                 else:
561                         # yes. Process repeated, and re-add.
562                         if w.repeated:
563                                 w.processRepeated()
564                                 w.state = RecordTimerEntry.StateWaiting
565                                 self.addTimerEntry(w)
566                         else:
567                                 # Remove old timers as set in config
568                                 self.cleanupDaily(config.recording.keep_timers.value)
569                                 insort(self.processed_timers, w)
570                 self.stateChanged(w)
571
572         def isRecording(self):
573                 for timer in self.timer_list:
574                         if timer.isRunning() and not timer.justplay:
575                                 return True
576                 return False
577         
578         def loadTimer(self):
579                 # TODO: PATH!
580                 if not Directories.fileExists(self.Filename):
581                         return
582                 try:
583                         doc = xml.etree.cElementTree.parse(self.Filename)
584                 except SyntaxError:
585                         from Tools.Notifications import AddPopup
586                         from Screens.MessageBox import MessageBox
587
588                         AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
589
590                         print "timers.xml failed to load!"
591                         try:
592                                 import os
593                                 os.rename(self.Filename, self.Filename + "_old")
594                         except (IOError, OSError):
595                                 print "renaming broken timer failed"
596                         return
597                 except IOError:
598                         print "timers.xml not found!"
599                         return
600
601                 root = doc.getroot()
602
603                 # put out a message when at least one timer overlaps
604                 checkit = True
605                 for timer in root.findall("timer"):
606                         newTimer = createTimer(timer)
607                         if (self.record(newTimer, True, dosave=False) is not None) and (checkit == True):
608                                 from Tools.Notifications import AddPopup
609                                 from Screens.MessageBox import MessageBox
610                                 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
611                                 checkit = False # at moment it is enough when the message is displayed one time
612
613         def saveTimer(self):
614                 #root_element = xml.etree.cElementTree.Element('timers')
615                 #root_element.text = "\n"
616
617                 #for timer in self.timer_list + self.processed_timers:
618                         # some timers (instant records) don't want to be saved.
619                         # skip them
620                         #if timer.dontSave:
621                                 #continue
622                         #t = xml.etree.cElementTree.SubElement(root_element, 'timers')
623                         #t.set("begin", str(int(timer.begin)))
624                         #t.set("end", str(int(timer.end)))
625                         #t.set("serviceref", str(timer.service_ref))
626                         #t.set("repeated", str(timer.repeated))                 
627                         #t.set("name", timer.name)
628                         #t.set("description", timer.description)
629                         #t.set("afterevent", str({
630                         #       AFTEREVENT.NONE: "nothing",
631                         #       AFTEREVENT.STANDBY: "standby",
632                         #       AFTEREVENT.DEEPSTANDBY: "deepstandby",
633                         #       AFTEREVENT.AUTO: "auto"}))
634                         #if timer.eit is not None:
635                         #       t.set("eit", str(timer.eit))
636                         #if timer.dirname is not None:
637                         #       t.set("location", str(timer.dirname))
638                         #t.set("disabled", str(int(timer.disabled)))
639                         #t.set("justplay", str(int(timer.justplay)))
640                         #t.text = "\n"
641                         #t.tail = "\n"
642
643                         #for time, code, msg in timer.log_entries:
644                                 #l = xml.etree.cElementTree.SubElement(t, 'log')
645                                 #l.set("time", str(time))
646                                 #l.set("code", str(code))
647                                 #l.text = str(msg)
648                                 #l.tail = "\n"
649
650                 #doc = xml.etree.cElementTree.ElementTree(root_element)
651                 #doc.write(self.Filename)
652
653                 list = []
654
655                 list.append('<?xml version="1.0" ?>\n')
656                 list.append('<timers>\n')
657                 
658                 for timer in self.timer_list + self.processed_timers:
659                         if timer.dontSave:
660                                 continue
661
662                         list.append('<timer')
663                         list.append(' begin="' + str(int(timer.begin)) + '"')
664                         list.append(' end="' + str(int(timer.end)) + '"')
665                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
666                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
667                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
668                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
669                         list.append(' afterevent="' + str(stringToXML({
670                                 AFTEREVENT.NONE: "nothing",
671                                 AFTEREVENT.STANDBY: "standby",
672                                 AFTEREVENT.DEEPSTANDBY: "deepstandby",
673                                 AFTEREVENT.AUTO: "auto"
674                                 }[timer.afterEvent])) + '"')
675                         if timer.eit is not None:
676                                 list.append(' eit="' + str(timer.eit) + '"')
677                         if timer.dirname is not None:
678                                 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
679                         if timer.tags is not None:
680                                 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
681                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
682                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
683                         list.append(' always_zap="' + str(int(timer.always_zap)) + '"')
684                         list.append(' descramble="' + str(int(timer.descramble)) + '"')
685                         list.append(' record_ecm="' + str(int(timer.record_ecm)) + '"')
686                         list.append('>\n')
687                         
688                         if config.recording.debug.value:
689                                 for time, code, msg in timer.log_entries:
690                                         list.append('<log')
691                                         list.append(' code="' + str(code) + '"')
692                                         list.append(' time="' + str(time) + '"')
693                                         list.append('>')
694                                         list.append(str(stringToXML(msg)))
695                                         list.append('</log>\n')
696                         
697                         list.append('</timer>\n')
698
699                 list.append('</timers>\n')
700
701                 file = open(self.Filename + ".writing", "w")
702                 for x in list:
703                         file.write(x)
704                 file.flush()
705
706                 import os
707                 os.fsync(file.fileno())
708                 file.close()
709                 os.rename(self.Filename + ".writing", self.Filename)
710
711         def getNextZapTime(self):
712                 now = time()
713                 for timer in self.timer_list:
714                         if not timer.justplay or timer.begin < now:
715                                 continue
716                         return timer.begin
717                 return -1
718
719         def getNextRecordingTime(self):
720                 now = time()
721                 for timer in self.timer_list:
722                         next_act = timer.getNextActivation()
723                         if timer.justplay or next_act < now:
724                                 continue
725                         return next_act
726                 return -1
727
728         def getNextTimerTime(self):
729                 now = time()
730                 for timer in self.timer_list:
731                         next_act = timer.getNextActivation()
732                         if next_act < now:
733                                 continue
734                         return next_act
735                 return -1
736
737         def isNextRecordAfterEventActionAuto(self):
738                 now = time()
739                 t = None
740                 for timer in self.timer_list:
741                         if timer.justplay or timer.begin < now:
742                                 continue
743                         if t is None or t.begin == timer.begin:
744                                 t = timer
745                                 if t.afterEvent == AFTEREVENT.AUTO:
746                                         return True
747                 return False
748
749         def record(self, entry, ignoreTSC=False, dosave=True):          #wird von loadTimer mit dosave=False aufgerufen
750                 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
751                 if not timersanitycheck.check():
752                         if ignoreTSC != True:
753                                 print "timer conflict detected!"
754                                 print timersanitycheck.getSimulTimerList()
755                                 return timersanitycheck.getSimulTimerList()
756                         else:
757                                 print "ignore timer conflict"
758                 elif timersanitycheck.doubleCheck():
759                         print "ignore double timer"
760                         return None
761                 entry.timeChanged()
762                 print "[Timer] Record " + str(entry)
763                 entry.Timer = self
764                 self.addTimerEntry(entry)
765                 if dosave:
766                         self.saveTimer()
767                 return None
768
769         def isInTimer(self, eventid, begin, duration, service):
770                 returnValue = None
771                 type = 0
772                 time_match = 0
773                 bt = None
774                 end = begin + duration
775                 refstr = str(service)
776                 for x in self.timer_list:
777                         check = x.service_ref.ref.toString() == refstr
778                         if not check:
779                                 sref = x.service_ref.ref
780                                 parent_sid = sref.getUnsignedData(5)
781                                 parent_tsid = sref.getUnsignedData(6)
782                                 if parent_sid and parent_tsid: # check for subservice
783                                         sid = sref.getUnsignedData(1)
784                                         tsid = sref.getUnsignedData(2)
785                                         sref.setUnsignedData(1, parent_sid)
786                                         sref.setUnsignedData(2, parent_tsid)
787                                         sref.setUnsignedData(5, 0)
788                                         sref.setUnsignedData(6, 0)
789                                         check = sref.toCompareString() == refstr
790                                         num = 0
791                                         if check:
792                                                 check = False
793                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
794                                                 num = event and event.getNumOfLinkageServices() or 0
795                                         sref.setUnsignedData(1, sid)
796                                         sref.setUnsignedData(2, tsid)
797                                         sref.setUnsignedData(5, parent_sid)
798                                         sref.setUnsignedData(6, parent_tsid)
799                                         for cnt in range(num):
800                                                 subservice = event.getLinkageService(sref, cnt)
801                                                 if sref.toCompareString() == subservice.toCompareString():
802                                                         check = True
803                                                         break
804                         if check:
805                                 timer_end = x.end
806                                 type_offset = 0
807                                 if x.justplay:
808                                         type_offset = 5
809                                         if (timer_end - x.begin) <= 1:
810                                                 timer_end += 60
811                                 if x.always_zap:
812                                         type_offset = 10
813
814                                 if x.repeated != 0:
815                                         if bt is None:
816                                                 bt = localtime(begin)
817                                                 et = localtime(end)
818                                                 bday = bt.tm_wday;
819                                                 begin2 = bday * 1440 + bt.tm_hour * 60 + bt.tm_min
820                                                 end2   = et.tm_wday * 1440 + et.tm_hour * 60 + et.tm_min
821                                         if x.repeated & (1 << bday):
822                                                 xbt = localtime(x.begin)
823                                                 xet = localtime(timer_end)
824                                                 xbegin = bday * 1440 + xbt.tm_hour * 60 + xbt.tm_min
825                                                 xend   = bday * 1440 + xet.tm_hour * 60 + xet.tm_min
826                                                 if xend < xbegin:
827                                                         xend += 1440
828                                                 if begin2 < xbegin <= end2:
829                                                         if xend < end2: # recording within event
830                                                                 time_match = (xend - xbegin) * 60
831                                                                 type = type_offset + 3
832                                                         else:           # recording last part of event
833                                                                 time_match = (end2 - xbegin) * 60
834                                                                 type = type_offset + 1
835                                                 elif xbegin <= begin2 <= xend:
836                                                         if xend < end2: # recording first part of event
837                                                                 time_match = (xend - begin2) * 60
838                                                                 type = type_offset + 4
839                                                         else:           # recording whole event
840                                                                 time_match = (end2 - begin2) * 60
841                                                                 type = type_offset + 2
842                                 else:
843                                         if begin < x.begin <= end:
844                                                 if timer_end < end: # recording within event
845                                                         time_match = timer_end - x.begin
846                                                         type = type_offset + 3
847                                                 else:           # recording last part of event
848                                                         time_match = end - x.begin
849                                                         type = type_offset + 1
850                                         elif x.begin <= begin <= timer_end:
851                                                 if timer_end < end: # recording first part of event
852                                                         time_match = timer_end - begin
853                                                         type = type_offset + 4
854                                                 else:           # recording whole event
855                                                         time_match = end - begin
856                                                         type = type_offset + 2
857                                 if time_match:
858                                         if type in (2,7,12): # When full recording do not look further
859                                                 returnValue = (time_match, [type])
860                                                 break
861                                         elif returnValue:
862                                                 if type not in returnValue[1]:
863                                                         returnValue[1].append(type)
864                                         else:
865                                                 returnValue = (time_match, [type])
866
867                 return returnValue
868
869         def removeEntry(self, entry):
870                 print "[Timer] Remove " + str(entry)
871                 
872                 # avoid re-enqueuing
873                 entry.repeated = False
874
875                 # abort timer.
876                 # this sets the end time to current time, so timer will be stopped.
877                 entry.autoincrease = False
878                 entry.abort()
879                 
880                 if entry.state != entry.StateEnded:
881                         self.timeChanged(entry)
882                 
883                 print "state: ", entry.state
884                 print "in processed: ", entry in self.processed_timers
885                 print "in running: ", entry in self.timer_list
886                 # autoincrease instanttimer if possible
887                 if not entry.dontSave:
888                         for x in self.timer_list:
889                                 if x.setAutoincreaseEnd():
890                                         self.timeChanged(x)
891                 # now the timer should be in the processed_timers list. remove it from there.
892                 self.processed_timers.remove(entry)
893                 self.saveTimer()
894
895         def shutdown(self):
896                 self.saveTimer()