Revert "Advanced functions(edit/delete/disable) repeated timers in EPG list"
[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 isInTimer(self, eventid, begin, duration, service):
825                 returnValue = None
826                 type = 0
827                 time_match = 0
828                 bt = None
829                 check_offset_time = not config.recording.margin_before.value and not config.recording.margin_after.value
830                 end = begin + duration
831                 refstr = ':'.join(service.split(':')[:11])
832                 for x in self.timer_list:
833                         check = ':'.join(x.service_ref.ref.toString().split(':')[:11]) == refstr
834                         if not check:
835                                 sref = x.service_ref.ref
836                                 parent_sid = sref.getUnsignedData(5)
837                                 parent_tsid = sref.getUnsignedData(6)
838                                 if parent_sid and parent_tsid:
839                                         # check for subservice
840                                         sid = sref.getUnsignedData(1)
841                                         tsid = sref.getUnsignedData(2)
842                                         sref.setUnsignedData(1, parent_sid)
843                                         sref.setUnsignedData(2, parent_tsid)
844                                         sref.setUnsignedData(5, 0)
845                                         sref.setUnsignedData(6, 0)
846                                         check = sref.toCompareString() == refstr
847                                         num = 0
848                                         if check:
849                                                 check = False
850                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
851                                                 num = event and event.getNumOfLinkageServices() or 0
852                                         sref.setUnsignedData(1, sid)
853                                         sref.setUnsignedData(2, tsid)
854                                         sref.setUnsignedData(5, parent_sid)
855                                         sref.setUnsignedData(6, parent_tsid)
856                                         for cnt in range(num):
857                                                 subservice = event.getLinkageService(sref, cnt)
858                                                 if sref.toCompareString() == subservice.toCompareString():
859                                                         check = True
860                                                         break
861                         if check:
862                                 timer_end = x.end
863                                 timer_begin = x.begin
864                                 type_offset = 0
865                                 if not x.repeated and check_offset_time:
866                                         if 0 < end - timer_end <= 59:
867                                                 timer_end = end
868                                         elif 0 < timer_begin - begin <= 59:
869                                                 timer_begin = begin
870                                 if x.justplay:
871                                         type_offset = 5
872                                         if (timer_end - x.begin) <= 1:
873                                                 timer_end += 60
874                                 if x.always_zap:
875                                         type_offset = 10
876
877                                 if x.repeated != 0:
878                                         type_offset += 15
879                                         if bt is None:
880                                                 bt = localtime(begin)
881                                                 bday = bt.tm_wday
882                                                 begin2 = 1440 + bt.tm_hour * 60 + bt.tm_min
883                                                 end2 = begin2 + duration / 60
884                                         xbt = localtime(x.begin)
885                                         xet = localtime(timer_end)
886                                         offset_day = False
887                                         checking_time = x.begin < begin or begin <= x.begin <= end
888                                         if xbt.tm_yday != xet.tm_yday:
889                                                 oday = bday - 1
890                                                 if oday == -1: oday = 6
891                                                 offset_day = x.repeated & (1 << oday)
892                                         xbegin = 1440 + xbt.tm_hour * 60 + xbt.tm_min
893                                         xend = xbegin + ((timer_end - x.begin) / 60)
894                                         if xend < xbegin:
895                                                 xend += 1440
896                                         if x.repeated & (1 << bday) and checking_time:
897                                                 if begin2 < xbegin <= end2:
898                                                         if xend < end2:
899                                                                 # recording within event
900                                                                 time_match = (xend - xbegin) * 60
901                                                                 type = type_offset + 3
902                                                         else:
903                                                                 # recording last part of event
904                                                                 time_match = (end2 - xbegin) * 60
905                                                                 type = type_offset + 1
906                                                 elif xbegin <= begin2 <= xend:
907                                                         if xend < end2:
908                                                                 # recording first part of event
909                                                                 time_match = (xend - begin2) * 60
910                                                                 type = type_offset + 4
911                                                         else:
912                                                                 # recording whole event
913                                                                 time_match = (end2 - begin2) * 60
914                                                                 type = type_offset + 2
915                                                 elif offset_day:
916                                                         xbegin -= 1440
917                                                         xend -= 1440
918                                                         if begin2 < xbegin <= end2:
919                                                                 if xend < end2:
920                                                                         # recording within event
921                                                                         time_match = (xend - xbegin) * 60
922                                                                         type = type_offset + 3
923                                                                 else:
924                                                                         # recording last part of event
925                                                                         time_match = (end2 - xbegin) * 60
926                                                                         type = type_offset + 1
927                                                         elif xbegin <= begin2 <= xend:
928                                                                 if xend < end2:
929                                                                         # recording first part of event
930                                                                         time_match = (xend - begin2) * 60
931                                                                         type = type_offset + 4
932                                                                 else:
933                                                                         # recording whole event
934                                                                         time_match = (end2 - begin2) * 60
935                                                                         type = type_offset + 2
936                                         elif offset_day and checking_time:
937                                                 xbegin -= 1440
938                                                 xend -= 1440
939                                                 if begin2 < xbegin <= end2:
940                                                         if xend < end2:
941                                                                 # recording within event
942                                                                 time_match = (xend - xbegin) * 60
943                                                                 type = type_offset + 3
944                                                         else:
945                                                                 # recording last part of event
946                                                                 time_match = (end2 - xbegin) * 60
947                                                                 type = type_offset + 1
948                                                 elif xbegin <= begin2 <= xend:
949                                                         if xend < end2:
950                                                                 # recording first part of event
951                                                                 time_match = (xend - begin2) * 60
952                                                                 type = type_offset + 4
953                                                         else:
954                                                                 # recording whole event
955                                                                 time_match = (end2 - begin2) * 60
956                                                                 type = type_offset + 2
957                                 else:
958                                         if begin < timer_begin <= end:
959                                                 if timer_end < end:
960                                                         # recording within event
961                                                         time_match = timer_end - timer_begin
962                                                         type = type_offset + 3
963                                                 else:
964                                                         # recording last part of event
965                                                         time_match = end - timer_begin
966                                                         type = type_offset + 1
967                                         elif timer_begin <= begin <= timer_end:
968                                                 if timer_end < end:
969                                                         # recording first part of event
970                                                         time_match = timer_end - begin
971                                                         type = type_offset + 4
972                                                 else:
973                                                         # recording whole event
974                                                         time_match = end - begin
975                                                         type = type_offset + 2
976                                 if time_match:
977                                         if type in (2,7,12,17,22,27):
978                                                 # When full recording do not look further
979                                                 returnValue = (time_match, [type])
980                                                 break
981                                         elif returnValue:
982                                                 if type not in returnValue[1]:
983                                                         returnValue[1].append(type)
984                                         else:
985                                                 returnValue = (time_match, [type])
986
987                 return returnValue
988
989         def removeEntry(self, entry):
990                 print "[Timer] Remove " + str(entry)
991
992                 # avoid re-enqueuing
993                 entry.repeated = False
994
995                 # abort timer.
996                 # this sets the end time to current time, so timer will be stopped.
997                 entry.autoincrease = False
998                 entry.abort()
999
1000                 if entry.state != entry.StateEnded:
1001                         self.timeChanged(entry)
1002
1003                 print "state: ", entry.state
1004                 print "in processed: ", entry in self.processed_timers
1005                 print "in running: ", entry in self.timer_list
1006                 # autoincrease instanttimer if possible
1007                 if not entry.dontSave:
1008                         for x in self.timer_list:
1009                                 if x.setAutoincreaseEnd():
1010                                         self.timeChanged(x)
1011                 # now the timer should be in the processed_timers list. remove it from there.
1012                 self.processed_timers.remove(entry)
1013                 self.saveTimer()
1014
1015         def shutdown(self):
1016                 self.saveTimer()