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