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