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