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