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