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