add new after record timer event behavior "auto".. and use it as default
[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.dom.minidom
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 elementsWithTag, mergeText, 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.getAttribute("begin"))
358         end = int(xml.getAttribute("end"))
359         serviceref = ServiceReference(xml.getAttribute("serviceref").encode("utf-8"))
360         description = xml.getAttribute("description").encode("utf-8")
361         repeated = xml.getAttribute("repeated").encode("utf-8")
362         disabled = long(xml.getAttribute("disabled") or "0")
363         justplay = long(xml.getAttribute("justplay") or "0")
364         afterevent = str(xml.getAttribute("afterevent") or "nothing")
365         afterevent = {
366                 "nothing": AFTEREVENT.NONE,
367                 "standby": AFTEREVENT.STANDBY,
368                 "deepstandby": AFTEREVENT.DEEPSTANDBY,
369                 "auto": AFTEREVENT.AUTO
370                 }[afterevent]
371         if xml.hasAttribute("eit") and xml.getAttribute("eit") != "None":
372                 eit = long(xml.getAttribute("eit"))
373         else:
374                 eit = None
375         if xml.hasAttribute("location") and xml.getAttribute("location") != "None":
376                 location = xml.getAttribute("location").encode("utf-8")
377         else:
378                 location = None
379         if xml.hasAttribute("tags") and xml.getAttribute("tags"):
380                 tags = xml.getAttribute("tags").encode("utf-8").split(' ')
381         else:
382                 tags = None
383
384         name = xml.getAttribute("name").encode("utf-8")
385         #filename = xml.getAttribute("filename").encode("utf-8")
386         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags)
387         entry.repeated = int(repeated)
388         
389         for l in elementsWithTag(xml.childNodes, "log"):
390                 time = int(l.getAttribute("time"))
391                 code = int(l.getAttribute("code"))
392                 msg = mergeText(l.childNodes).strip().encode("utf-8")
393                 entry.log_entries.append((time, code, msg))
394         
395         return entry
396
397 class RecordTimer(timer.Timer):
398         def __init__(self):
399                 timer.Timer.__init__(self)
400                 
401                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
402                 
403                 try:
404                         self.loadTimer()
405                 except IOError:
406                         print "unable to load timers from file!"
407                         
408         def isRecording(self):
409                 isRunning = False
410                 for timer in self.timer_list:
411                         if timer.isRunning() and not timer.justplay:
412                                 isRunning = True
413                 return isRunning
414         
415         def loadTimer(self):
416                 # TODO: PATH!
417                 try:
418                         doc = xml.dom.minidom.parse(self.Filename)
419                 except xml.parsers.expat.ExpatError:
420                         from Tools.Notifications import AddPopup
421                         from Screens.MessageBox import MessageBox
422
423                         AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
424
425                         print "timers.xml failed to load!"
426                         try:
427                                 import os
428                                 os.rename(self.Filename, self.Filename + "_old")
429                         except IOError:
430                                 print "renaming broken timer failed"
431                         return
432
433                 root = doc.childNodes[0]
434
435                 # put out a message when at least one timer overlaps
436                 checkit = True
437                 for timer in elementsWithTag(root.childNodes, "timer"):
438                         newTimer = createTimer(timer)
439                         if (self.record(newTimer, True, True) is not None) and (checkit == True):
440                                 from Tools.Notifications import AddPopup
441                                 from Screens.MessageBox import MessageBox
442                                 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
443                                 checkit = False # at moment it is enough when the message is displayed one time
444
445         def saveTimer(self):
446                 #doc = xml.dom.minidom.Document()
447                 #root_element = doc.createElement('timers')
448                 #doc.appendChild(root_element)
449                 #root_element.appendChild(doc.createTextNode("\n"))
450                 
451                 #for timer in self.timer_list + self.processed_timers:
452                         # some timers (instant records) don't want to be saved.
453                         # skip them
454                         #if timer.dontSave:
455                                 #continue
456                         #t = doc.createTextNode("\t")
457                         #root_element.appendChild(t)
458                         #t = doc.createElement('timer')
459                         #t.setAttribute("begin", str(int(timer.begin)))
460                         #t.setAttribute("end", str(int(timer.end)))
461                         #t.setAttribute("serviceref", str(timer.service_ref))
462                         #t.setAttribute("repeated", str(timer.repeated))                        
463                         #t.setAttribute("name", timer.name)
464                         #t.setAttribute("description", timer.description)
465                         #t.setAttribute("eit", str(timer.eit))
466                         
467                         #for time, code, msg in timer.log_entries:
468                                 #t.appendChild(doc.createTextNode("\t\t"))
469                                 #l = doc.createElement('log')
470                                 #l.setAttribute("time", str(time))
471                                 #l.setAttribute("code", str(code))
472                                 #l.appendChild(doc.createTextNode(msg))
473                                 #t.appendChild(l)
474                                 #t.appendChild(doc.createTextNode("\n"))
475
476                         #root_element.appendChild(t)
477                         #t = doc.createTextNode("\n")
478                         #root_element.appendChild(t)
479
480
481                 #file = open(self.Filename, "w")
482                 #doc.writexml(file)
483                 #file.write("\n")
484                 #file.close()
485
486                 list = []
487
488                 list.append('<?xml version="1.0" ?>\n')
489                 list.append('<timers>\n')
490                 
491                 for timer in self.timer_list + self.processed_timers:
492                         if timer.dontSave:
493                                 continue
494
495                         list.append('<timer')
496                         list.append(' begin="' + str(int(timer.begin)) + '"')
497                         list.append(' end="' + str(int(timer.end)) + '"')
498                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
499                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
500                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
501                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
502                         list.append(' afterevent="' + str(stringToXML({
503                                 AFTEREVENT.NONE: "nothing",
504                                 AFTEREVENT.STANDBY: "standby",
505                                 AFTEREVENT.DEEPSTANDBY: "deepstandby",
506                                 AFTEREVENT.AUTO: "auto"
507                                 }[timer.afterEvent])) + '"')
508                         if timer.eit is not None:
509                                 list.append(' eit="' + str(timer.eit) + '"')
510                         if timer.dirname is not None:
511                                 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
512                         if timer.tags is not None:
513                                 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
514                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
515                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
516                         list.append('>\n')
517                         
518                         if config.recording.debug.value:
519                                 for time, code, msg in timer.log_entries:
520                                         list.append('<log')
521                                         list.append(' code="' + str(code) + '"')
522                                         list.append(' time="' + str(time) + '"')
523                                         list.append('>')
524                                         list.append(str(stringToXML(msg)))
525                                         list.append('</log>\n')
526                         
527                         list.append('</timer>\n')
528
529                 list.append('</timers>\n')
530
531                 file = open(self.Filename, "w")
532                 for x in list:
533                         file.write(x)
534                 file.close()
535
536         def getNextZapTime(self):
537                 now = time.time()
538                 for timer in self.timer_list:
539                         if not timer.justplay or timer.begin < now:
540                                 continue
541                         return timer.begin
542                 return -1
543
544         def getNextRecordingTime(self):
545                 now = time.time()
546                 for timer in self.timer_list:
547                         if timer.justplay or timer.begin < now:
548                                 continue
549                         return timer.begin
550                 return -1
551
552         def isNextRecordAfterEventActionAuto(self):
553                 now = time.time()
554                 t = None
555                 for timer in self.timer_list:
556                         if timer.justplay or timer.begin < now:
557                                 continue
558                         if t is None or t.begin == timer.begin:
559                                 t = timer
560                                 if t.afterEvent == AFTEREVENT.AUTO:
561                                         return True
562                 return False
563
564         def record(self, entry, ignoreTSC=False, dosave=True):          #wird von loadTimer mit dosave=False aufgerufen
565                 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
566                 if not timersanitycheck.check():
567                         if ignoreTSC != True:
568                                 print "timer conflict detected!"
569                                 print timersanitycheck.getSimulTimerList()
570                                 return timersanitycheck.getSimulTimerList()
571                         else:
572                                 print "ignore timer conflict"
573                 elif timersanitycheck.doubleCheck():
574                         print "ignore double timer"
575                 entry.timeChanged()
576                 print "[Timer] Record " + str(entry)
577                 entry.Timer = self
578                 self.addTimerEntry(entry)
579                 if dosave:
580                         self.saveTimer()
581                 return None
582                 
583         def isInTimer(self, eventid, begin, duration, service):
584                 time_match = 0
585                 chktime = None
586                 chktimecmp = None
587                 chktimecmp_end = None
588                 end = begin + duration
589                 for x in self.timer_list:
590                         check = x.service_ref.ref.toCompareString() == str(service)
591                         if not check:
592                                 sref = x.service_ref.ref
593                                 parent_sid = sref.getUnsignedData(5)
594                                 parent_tsid = sref.getUnsignedData(6)
595                                 if parent_sid and parent_tsid: # check for subservice
596                                         sid = sref.getUnsignedData(1)
597                                         tsid = sref.getUnsignedData(2)
598                                         sref.setUnsignedData(1, parent_sid)
599                                         sref.setUnsignedData(2, parent_tsid)
600                                         sref.setUnsignedData(5, 0)
601                                         sref.setUnsignedData(6, 0)
602                                         check = x.service_ref.ref.toCompareString() == str(service)
603                                         num = 0
604                                         if check:
605                                                 check = False
606                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
607                                                 num = event and event.getNumOfLinkageServices() or 0
608                                         sref.setUnsignedData(1, sid)
609                                         sref.setUnsignedData(2, tsid)
610                                         sref.setUnsignedData(5, parent_sid)
611                                         sref.setUnsignedData(6, parent_tsid)
612                                         for cnt in range(num):
613                                                 subservice = event.getLinkageService(sref, cnt)
614                                                 if sref.toCompareString() == subservice.toCompareString():
615                                                         check = True
616                                                         break
617                         if check:
618                                 #if x.eit is not None and x.repeated == 0:
619                                 #       if x.eit == eventid:
620                                 #               return duration
621                                 if x.repeated != 0:
622                                         if chktime is None:
623                                                 chktime = localtime(begin)
624                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
625                                                 chktimecmp_end = chktimecmp + (duration / 60)
626                                         time = localtime(x.begin)
627                                         for y in range(7):
628                                                 if x.repeated & (2 ** y):
629                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
630                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
631                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
632                                                         elif chktimecmp <= timecmp < chktimecmp_end:
633                                                                 time_match = (chktimecmp_end - timecmp) * 60
634                                 else: #if x.eit is None:
635                                         if begin <= x.begin <= end:
636                                                 diff = end - x.begin
637                                                 if time_match < diff:
638                                                         time_match = diff
639                                         elif x.begin <= begin <= x.end:
640                                                 diff = x.end - begin
641                                                 if time_match < diff:
642                                                         time_match = diff
643                 return time_match
644
645         def removeEntry(self, entry):
646                 print "[Timer] Remove " + str(entry)
647                 
648                 # avoid re-enqueuing
649                 entry.repeated = False
650
651                 # abort timer.
652                 # this sets the end time to current time, so timer will be stopped.
653                 entry.abort()
654                 
655                 if entry.state != entry.StateEnded:
656                         self.timeChanged(entry)
657                 
658                 print "state: ", entry.state
659                 print "in processed: ", entry in self.processed_timers
660                 print "in running: ", entry in self.timer_list
661                 # now the timer should be in the processed_timers list. remove it from there.
662                 self.processed_timers.remove(entry)
663                 self.saveTimer()
664
665         def shutdown(self):
666                 self.saveTimer()