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