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