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