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