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