fix more (no more allowed) global imports of parentalControl instance
[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\nDreambox 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 Dreambox. 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                         new_end = simulTimerList[1].begin
323                         del simulTimerList
324                         new_end -= 30                           # 30 Sekunden Prepare-Zeit lassen
325                 del dummyentry
326                 if new_end <= time():
327                         return False
328                 self.end = new_end
329                 return True
330         
331         
332         def sendStandbyNotification(self, answer):
333                 if answer:
334                         Notifications.AddNotification(Screens.Standby.Standby)
335
336         def sendTryQuitMainloopNotification(self, answer):
337                 if answer:
338                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
339
340         def getNextActivation(self):
341                 if self.state == self.StateEnded:
342                         return self.end
343                 
344                 next_state = self.state + 1
345                 
346                 return {self.StatePrepared: self.start_prepare, 
347                                 self.StateRunning: self.begin, 
348                                 self.StateEnded: self.end }[next_state]
349
350         def failureCB(self, answer):
351                 if answer == True:
352                         self.log(13, "ok, zapped away")
353                         #NavigationInstance.instance.stopUserServices()
354                         NavigationInstance.instance.playService(self.service_ref.ref)
355                 else:
356                         self.log(14, "user didn't want to zap away, record will probably fail")
357
358         def timeChanged(self):
359                 old_prepare = self.start_prepare
360                 self.start_prepare = self.begin - self.prepare_time
361                 self.backoff = 0
362                 
363                 if int(old_prepare) != int(self.start_prepare):
364                         self.log(15, "record time changed, start prepare is now: %s" % ctime(self.start_prepare))
365
366         def gotRecordEvent(self, record, event):
367                 # TODO: this is not working (never true), please fix. (comparing two swig wrapped ePtrs)
368                 if self.__record_service.__deref__() != record.__deref__():
369                         return
370                 self.log(16, "record event %d" % event)
371                 if event == iRecordableService.evRecordWriteError:
372                         print "WRITE ERROR on recording, disk full?"
373                         # show notification. the 'id' will make sure that it will be
374                         # displayed only once, even if more timers are failing at the
375                         # same time. (which is very likely in case of disk fullness)
376                         Notifications.AddPopup(text = _("Write error while recording. Disk full?\n"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "DiskFullMessage")
377                         # ok, the recording has been stopped. we need to properly note 
378                         # that in our state, with also keeping the possibility to re-try.
379                         # TODO: this has to be done.
380                 elif event == iRecordableService.evStart:
381                         text = _("A record has been started:\n%s") % self.name
382                         if self.dirnameHadToFallback:
383                                 text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead.")))
384
385                         if config.usage.show_message_when_recording_starts.value:
386                                 Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
387
388         # we have record_service as property to automatically subscribe to record service events
389         def setRecordService(self, service):
390                 if self.__record_service is not None:
391                         print "[remove callback]"
392                         NavigationInstance.instance.record_event.remove(self.gotRecordEvent)
393
394                 self.__record_service = service
395
396                 if self.__record_service is not None:
397                         print "[add callback]"
398                         NavigationInstance.instance.record_event.append(self.gotRecordEvent)
399
400         record_service = property(lambda self: self.__record_service, setRecordService)
401
402 def createTimer(xml):
403         begin = int(xml.get("begin"))
404         end = int(xml.get("end"))
405         serviceref = ServiceReference(xml.get("serviceref").encode("utf-8"))
406         description = xml.get("description").encode("utf-8")
407         repeated = xml.get("repeated").encode("utf-8")
408         disabled = long(xml.get("disabled") or "0")
409         justplay = long(xml.get("justplay") or "0")
410         afterevent = str(xml.get("afterevent") or "nothing")
411         afterevent = {
412                 "nothing": AFTEREVENT.NONE,
413                 "standby": AFTEREVENT.STANDBY,
414                 "deepstandby": AFTEREVENT.DEEPSTANDBY,
415                 "auto": AFTEREVENT.AUTO
416                 }[afterevent]
417         eit = xml.get("eit")
418         if eit and eit != "None":
419                 eit = long(eit);
420         else:
421                 eit = None
422         location = xml.get("location")
423         if location and location != "None":
424                 location = location.encode("utf-8")
425         else:
426                 location = None
427         tags = xml.get("tags")
428         if tags and tags != "None":
429                 tags = tags.encode("utf-8").split(' ')
430         else:
431                 tags = None
432
433         name = xml.get("name").encode("utf-8")
434         #filename = xml.get("filename").encode("utf-8")
435         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags)
436         entry.repeated = int(repeated)
437         
438         for l in xml.findall("log"):
439                 time = int(l.get("time"))
440                 code = int(l.get("code"))
441                 msg = l.text.strip().encode("utf-8")
442                 entry.log_entries.append((time, code, msg))
443         
444         return entry
445
446 class RecordTimer(timer.Timer):
447         def __init__(self):
448                 timer.Timer.__init__(self)
449                 
450                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
451                 
452                 try:
453                         self.loadTimer()
454                 except IOError:
455                         print "unable to load timers from file!"
456
457         def doActivate(self, w):
458                 # when activating a timer which has already passed,
459                 # simply abort the timer. don't run trough all the stages.
460                 if w.shouldSkip():
461                         w.state = RecordTimerEntry.StateEnded
462                 else:
463                         # when active returns true, this means "accepted".
464                         # otherwise, the current state is kept.
465                         # the timer entry itself will fix up the delay then.
466                         if w.activate():
467                                 w.state += 1
468
469                 self.timer_list.remove(w)
470
471                 # did this timer reached the last state?
472                 if w.state < RecordTimerEntry.StateEnded:
473                         # no, sort it into active list
474                         insort(self.timer_list, w)
475                 else:
476                         # yes. Process repeated, and re-add.
477                         if w.repeated:
478                                 w.processRepeated()
479                                 w.state = RecordTimerEntry.StateWaiting
480                                 self.addTimerEntry(w)
481                         else:
482                                 insort(self.processed_timers, w)
483                 
484                 self.stateChanged(w)
485
486         def isRecording(self):
487                 isRunning = False
488                 for timer in self.timer_list:
489                         if timer.isRunning() and not timer.justplay:
490                                 isRunning = True
491                 return isRunning
492         
493         def loadTimer(self):
494                 # TODO: PATH!
495                 try:
496                         doc = xml.etree.cElementTree.parse(self.Filename)
497                 except SyntaxError:
498                         from Tools.Notifications import AddPopup
499                         from Screens.MessageBox import MessageBox
500
501                         AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
502
503                         print "timers.xml failed to load!"
504                         try:
505                                 import os
506                                 os.rename(self.Filename, self.Filename + "_old")
507                         except (IOError, OSError):
508                                 print "renaming broken timer failed"
509                         return
510                 except IOError:
511                         print "timers.xml not found!"
512                         return
513
514                 root = doc.getroot()
515
516                 # put out a message when at least one timer overlaps
517                 checkit = True
518                 for timer in root.findall("timer"):
519                         newTimer = createTimer(timer)
520                         if (self.record(newTimer, True, True) is not None) and (checkit == True):
521                                 from Tools.Notifications import AddPopup
522                                 from Screens.MessageBox import MessageBox
523                                 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
524                                 checkit = False # at moment it is enough when the message is displayed one time
525
526         def saveTimer(self):
527                 #root_element = xml.etree.cElementTree.Element('timers')
528                 #root_element.text = "\n"
529
530                 #for timer in self.timer_list + self.processed_timers:
531                         # some timers (instant records) don't want to be saved.
532                         # skip them
533                         #if timer.dontSave:
534                                 #continue
535                         #t = xml.etree.cElementTree.SubElement(root_element, 'timers')
536                         #t.set("begin", str(int(timer.begin)))
537                         #t.set("end", str(int(timer.end)))
538                         #t.set("serviceref", str(timer.service_ref))
539                         #t.set("repeated", str(timer.repeated))                 
540                         #t.set("name", timer.name)
541                         #t.set("description", timer.description)
542                         #t.set("afterevent", str({
543                         #       AFTEREVENT.NONE: "nothing",
544                         #       AFTEREVENT.STANDBY: "standby",
545                         #       AFTEREVENT.DEEPSTANDBY: "deepstandby",
546                         #       AFTEREVENT.AUTO: "auto"}))
547                         #if timer.eit is not None:
548                         #       t.set("eit", str(timer.eit))
549                         #if timer.dirname is not None:
550                         #       t.set("location", str(timer.dirname))
551                         #t.set("disabled", str(int(timer.disabled)))
552                         #t.set("justplay", str(int(timer.justplay)))
553                         #t.text = "\n"
554                         #t.tail = "\n"
555
556                         #for time, code, msg in timer.log_entries:
557                                 #l = xml.etree.cElementTree.SubElement(t, 'log')
558                                 #l.set("time", str(time))
559                                 #l.set("code", str(code))
560                                 #l.text = str(msg)
561                                 #l.tail = "\n"
562
563                 #doc = xml.etree.cElementTree.ElementTree(root_element)
564                 #doc.write(self.Filename)
565
566                 list = []
567
568                 list.append('<?xml version="1.0" ?>\n')
569                 list.append('<timers>\n')
570                 
571                 for timer in self.timer_list + self.processed_timers:
572                         if timer.dontSave:
573                                 continue
574
575                         list.append('<timer')
576                         list.append(' begin="' + str(int(timer.begin)) + '"')
577                         list.append(' end="' + str(int(timer.end)) + '"')
578                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
579                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
580                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
581                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
582                         list.append(' afterevent="' + str(stringToXML({
583                                 AFTEREVENT.NONE: "nothing",
584                                 AFTEREVENT.STANDBY: "standby",
585                                 AFTEREVENT.DEEPSTANDBY: "deepstandby",
586                                 AFTEREVENT.AUTO: "auto"
587                                 }[timer.afterEvent])) + '"')
588                         if timer.eit is not None:
589                                 list.append(' eit="' + str(timer.eit) + '"')
590                         if timer.dirname is not None:
591                                 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
592                         if timer.tags is not None:
593                                 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
594                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
595                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
596                         list.append('>\n')
597                         
598                         if config.recording.debug.value:
599                                 for time, code, msg in timer.log_entries:
600                                         list.append('<log')
601                                         list.append(' code="' + str(code) + '"')
602                                         list.append(' time="' + str(time) + '"')
603                                         list.append('>')
604                                         list.append(str(stringToXML(msg)))
605                                         list.append('</log>\n')
606                         
607                         list.append('</timer>\n')
608
609                 list.append('</timers>\n')
610
611                 file = open(self.Filename, "w")
612                 for x in list:
613                         file.write(x)
614                 file.close()
615
616         def getNextZapTime(self):
617                 now = time()
618                 for timer in self.timer_list:
619                         if not timer.justplay or timer.begin < now:
620                                 continue
621                         return timer.begin
622                 return -1
623
624         def getNextRecordingTime(self):
625                 now = time()
626                 for timer in self.timer_list:
627                         next_act = timer.getNextActivation()
628                         if timer.justplay or next_act < now:
629                                 continue
630                         return next_act
631                 return -1
632
633         def isNextRecordAfterEventActionAuto(self):
634                 now = time()
635                 t = None
636                 for timer in self.timer_list:
637                         if timer.justplay or timer.begin < now:
638                                 continue
639                         if t is None or t.begin == timer.begin:
640                                 t = timer
641                                 if t.afterEvent == AFTEREVENT.AUTO:
642                                         return True
643                 return False
644
645         def record(self, entry, ignoreTSC=False, dosave=True):          #wird von loadTimer mit dosave=False aufgerufen
646                 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
647                 if not timersanitycheck.check():
648                         if ignoreTSC != True:
649                                 print "timer conflict detected!"
650                                 print timersanitycheck.getSimulTimerList()
651                                 return timersanitycheck.getSimulTimerList()
652                         else:
653                                 print "ignore timer conflict"
654                 elif timersanitycheck.doubleCheck():
655                         print "ignore double timer"
656                         return None
657                 entry.timeChanged()
658                 print "[Timer] Record " + str(entry)
659                 entry.Timer = self
660                 self.addTimerEntry(entry)
661                 if dosave:
662                         self.saveTimer()
663                 return None
664
665         def isInTimer(self, eventid, begin, duration, service):
666                 time_match = 0
667                 chktime = None
668                 chktimecmp = None
669                 chktimecmp_end = None
670                 end = begin + duration
671                 refstr = str(service)
672                 for x in self.timer_list:
673                         check = x.service_ref.ref.toString() == refstr
674                         if not check:
675                                 sref = x.service_ref.ref
676                                 parent_sid = sref.getUnsignedData(5)
677                                 parent_tsid = sref.getUnsignedData(6)
678                                 if parent_sid and parent_tsid: # check for subservice
679                                         sid = sref.getUnsignedData(1)
680                                         tsid = sref.getUnsignedData(2)
681                                         sref.setUnsignedData(1, parent_sid)
682                                         sref.setUnsignedData(2, parent_tsid)
683                                         sref.setUnsignedData(5, 0)
684                                         sref.setUnsignedData(6, 0)
685                                         check = sref.toCompareString() == refstr
686                                         num = 0
687                                         if check:
688                                                 check = False
689                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
690                                                 num = event and event.getNumOfLinkageServices() or 0
691                                         sref.setUnsignedData(1, sid)
692                                         sref.setUnsignedData(2, tsid)
693                                         sref.setUnsignedData(5, parent_sid)
694                                         sref.setUnsignedData(6, parent_tsid)
695                                         for cnt in range(num):
696                                                 subservice = event.getLinkageService(sref, cnt)
697                                                 if sref.toCompareString() == subservice.toCompareString():
698                                                         check = True
699                                                         break
700                         if check:
701                                 if x.repeated != 0:
702                                         if chktime is None:
703                                                 chktime = localtime(begin)
704                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
705                                                 chktimecmp_end = chktimecmp + (duration / 60)
706                                         time = localtime(x.begin)
707                                         for y in (0, 1, 2, 3, 4, 5, 6):
708                                                 if x.repeated & (1 << y) and (x.begin <= begin or begin <= x.begin <= end):
709                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
710                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
711                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
712                                                         elif chktimecmp <= timecmp < chktimecmp_end:
713                                                                 time_match = (chktimecmp_end - timecmp) * 60
714                                 else: #if x.eit is None:
715                                         if begin <= x.begin <= end:
716                                                 diff = end - x.begin
717                                                 if time_match < diff:
718                                                         time_match = diff
719                                         elif x.begin <= begin <= x.end:
720                                                 diff = x.end - begin
721                                                 if time_match < diff:
722                                                         time_match = diff
723                                 if time_match:
724                                         break
725                 return time_match
726
727         def removeEntry(self, entry):
728                 print "[Timer] Remove " + str(entry)
729                 
730                 # avoid re-enqueuing
731                 entry.repeated = False
732
733                 # abort timer.
734                 # this sets the end time to current time, so timer will be stopped.
735                 entry.autoincrease = False
736                 entry.abort()
737                 
738                 if entry.state != entry.StateEnded:
739                         self.timeChanged(entry)
740                 
741                 print "state: ", entry.state
742                 print "in processed: ", entry in self.processed_timers
743                 print "in running: ", entry in self.timer_list
744                 # autoincrease instanttimer if possible
745                 if not entry.dontSave:
746                         for x in self.timer_list:
747                                 if x.setAutoincreaseEnd():
748                                         self.timeChanged(x)
749                 # now the timer should be in the processed_timers list. remove it from there.
750                 self.processed_timers.remove(entry)
751                 self.saveTimer()
752
753         def shutdown(self):
754                 self.saveTimer()