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