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