Merge branch 'master' of git.opendreambox.org:/git/enigma2
[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, self.name.replace("\n", ""), self.description.replace("\n", ""), ' '.join(self.tags))
188                         if prep_res:
189                                 if prep_res == -255:
190                                         self.log(4, "failed to write meta information")
191                                 else:
192                                         self.log(2, "'prepare' failed: error %d" % prep_res)
193
194                                 # we must calc nur start time before stopRecordService call because in Screens/Standby.py TryQuitMainloop tries to get
195                                 # the next start time in evEnd event handler...
196                                 self.do_backoff()
197                                 self.start_prepare = time.time() + self.backoff
198
199                                 NavigationInstance.instance.stopRecordService(self.record_service)
200                                 self.record_service = None
201                                 return False
202                         return True
203
204         def do_backoff(self):
205                 if self.backoff == 0:
206                         self.backoff = 5
207                 else:
208                         self.backoff *= 2
209                         if self.backoff > 100:
210                                 self.backoff = 100
211                 self.log(10, "backoff: retry in %d seconds" % self.backoff)
212
213         def activate(self):
214                 next_state = self.state + 1
215                 self.log(5, "activating state %d" % next_state)
216
217                 if next_state == self.StatePrepared:
218                         if self.tryPrepare():
219                                 self.log(6, "prepare ok, waiting for begin")
220                                 # fine. it worked, resources are allocated.
221                                 self.next_activation = self.begin
222                                 self.backoff = 0
223                                 return True
224
225                         self.log(7, "prepare failed")
226                         if self.first_try_prepare:
227                                 self.first_try_prepare = False
228                                 cur_ref = NavigationInstance.instance.getCurrentlyPlayingServiceReference()
229                                 if cur_ref and not cur_ref.getPath():
230                                         if not config.recording.asktozap.value:
231                                                 self.log(8, "asking user to zap away")
232                                                 Notifications.AddNotificationWithCallback(self.failureCB, MessageBox, _("A timer failed to record!\nDisable TV and try again?\n"), timeout=20)
233                                         else: # zap without asking
234                                                 self.log(9, "zap without asking")
235                                                 Notifications.AddNotification(MessageBox, _("In order to record a timer, the TV was switched to the recording service!\n"), type=MessageBox.TYPE_INFO, timeout=20)
236                                                 self.failureCB(True)
237                                 elif cur_ref:
238                                         self.log(8, "currently running service is not a live service.. so stop it makes no sense")
239                                 else:
240                                         self.log(8, "currently no service running... so we dont need to stop it")
241                         return False
242                 elif next_state == self.StateRunning:
243                         # if this timer has been cancelled, just go to "end" state.
244                         if self.cancelled:
245                                 return True
246
247                         if self.justplay:
248                                 if Screens.Standby.inStandby:
249                                         self.log(11, "wakeup and zap")
250                                         #set service to zap after standby
251                                         Screens.Standby.inStandby.prev_running_service = self.service_ref.ref
252                                         #wakeup standby
253                                         Screens.Standby.inStandby.Power()
254                                 else:
255                                         self.log(11, "zapping")
256                                         NavigationInstance.instance.playService(self.service_ref.ref)
257                                 return True
258                         else:
259                                 self.log(11, "start recording")
260                                 record_res = self.record_service.start()
261                                 
262                                 if record_res:
263                                         self.log(13, "start record returned %d" % record_res)
264                                         self.do_backoff()
265                                         # retry
266                                         self.begin = time.time() + self.backoff
267                                         return False
268
269                                 return True
270                 elif next_state == self.StateEnded:
271                         old_end = self.end
272                         if self.setAutoincreaseEnd():
273                                 self.log(12, "autoincrase recording %d minute(s)" % int((self.end - old_end)/60))
274                                 self.state -= 1
275                                 return True
276                         self.log(12, "stop recording")
277                         if not self.justplay:
278                                 NavigationInstance.instance.stopRecordService(self.record_service)
279                                 self.record_service = None
280                         if self.afterEvent == AFTEREVENT.STANDBY:
281                                 if not Screens.Standby.inStandby: # not already in standby
282                                         Notifications.AddNotificationWithCallback(self.sendStandbyNotification, MessageBox, _("A finished record timer wants to set your\nDreambox to standby. Do that now?"), timeout = 20)
283                         elif self.afterEvent == AFTEREVENT.DEEPSTANDBY:
284                                 if not Screens.Standby.inTryQuitMainloop: # not a shutdown messagebox is open
285                                         if Screens.Standby.inStandby: # in standby
286                                                 RecordTimerEntry.TryQuitMainloop() # start shutdown handling without screen
287                                         else:
288                                                 Notifications.AddNotificationWithCallback(self.sendTryQuitMainloopNotification, MessageBox, _("A finished record timer wants to shut down\nyour Dreambox. Shutdown now?"), timeout = 20)
289                         return True
290
291         def setAutoincreaseEnd(self, entry = None):
292                 if not self.autoincrease:
293                         return False
294                 if entry is None:
295                         new_end =  int(time.time()) + self.autoincreasetime
296                 else:
297                         new_end = entry.begin -30
298
299                 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)
300                 dummyentry.disabled = self.disabled
301                 timersanitycheck = TimerSanityCheck(NavigationInstance.instance.RecordTimer.timer_list, dummyentry)
302                 if not timersanitycheck.check():
303                         simulTimerList = timersanitycheck.getSimulTimerList()
304                         new_end = simulTimerList[1].begin
305                         del simulTimerList
306                         new_end -= 30                           # 30 Sekunden Prepare-Zeit lassen
307                 del dummyentry
308                 if new_end <= time.time():
309                         return False
310                 self.end = new_end
311                 return True
312         
313         
314         def sendStandbyNotification(self, answer):
315                 if answer:
316                         Notifications.AddNotification(Screens.Standby.Standby)
317
318         def sendTryQuitMainloopNotification(self, answer):
319                 if answer:
320                         Notifications.AddNotification(Screens.Standby.TryQuitMainloop, 1)
321
322         def getNextActivation(self):
323                 if self.state == self.StateEnded:
324                         return self.end
325                 
326                 next_state = self.state + 1
327                 
328                 return {self.StatePrepared: self.start_prepare, 
329                                 self.StateRunning: self.begin, 
330                                 self.StateEnded: self.end }[next_state]
331
332         def failureCB(self, answer):
333                 if answer == True:
334                         self.log(13, "ok, zapped away")
335                         #NavigationInstance.instance.stopUserServices()
336                         NavigationInstance.instance.playService(self.service_ref.ref)
337                 else:
338                         self.log(14, "user didn't want to zap away, record will probably fail")
339
340         def timeChanged(self):
341                 old_prepare = self.start_prepare
342                 self.start_prepare = self.begin - self.prepare_time
343                 self.backoff = 0
344                 
345                 if int(old_prepare) != int(self.start_prepare):
346                         self.log(15, "record time changed, start prepare is now: %s" % time.ctime(self.start_prepare))
347
348         def gotRecordEvent(self, record, event):
349                 # TODO: this is not working (never true), please fix. (comparing two swig wrapped ePtrs)
350                 if self.__record_service.__deref__() != record.__deref__():
351                         return
352                 self.log(16, "record event %d" % event)
353                 if event == iRecordableService.evRecordWriteError:
354                         print "WRITE ERROR on recording, disk full?"
355                         # show notification. the 'id' will make sure that it will be
356                         # displayed only once, even if more timers are failing at the
357                         # same time. (which is very likely in case of disk fullness)
358                         Notifications.AddPopup(text = _("Write error while recording. Disk full?\n"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "DiskFullMessage")
359                         # ok, the recording has been stopped. we need to properly note 
360                         # that in our state, with also keeping the possibility to re-try.
361                         # TODO: this has to be done.
362                 elif event == iRecordableService.evStart:
363                         text = _("A record has been started:\n%s") % self.name
364                         if self.dirnameHadToFallback:
365                                 text = '\n'.join((text, _("Please note that the previously selected media could not be accessed and therefore the default directory is being used instead.")))
366
367                         if config.usage.show_message_when_recording_starts.value:
368                                 Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
369
370         # we have record_service as property to automatically subscribe to record service events
371         def setRecordService(self, service):
372                 if self.__record_service is not None:
373                         print "[remove callback]"
374                         NavigationInstance.instance.record_event.remove(self.gotRecordEvent)
375
376                 self.__record_service = service
377
378                 if self.__record_service is not None:
379                         print "[add callback]"
380                         NavigationInstance.instance.record_event.append(self.gotRecordEvent)
381
382         record_service = property(lambda self: self.__record_service, setRecordService)
383
384 def createTimer(xml):
385         begin = int(xml.get("begin"))
386         end = int(xml.get("end"))
387         serviceref = ServiceReference(xml.get("serviceref").encode("utf-8"))
388         description = xml.get("description").encode("utf-8")
389         repeated = xml.get("repeated").encode("utf-8")
390         disabled = long(xml.get("disabled") or "0")
391         justplay = long(xml.get("justplay") or "0")
392         afterevent = str(xml.get("afterevent") or "nothing")
393         afterevent = {
394                 "nothing": AFTEREVENT.NONE,
395                 "standby": AFTEREVENT.STANDBY,
396                 "deepstandby": AFTEREVENT.DEEPSTANDBY,
397                 "auto": AFTEREVENT.AUTO
398                 }[afterevent]
399         eit = xml.get("eit")
400         if eit and eit != "None":
401                 eit = long(eit);
402         else:
403                 eit = None
404         location = xml.get("location")
405         if location and location != "None":
406                 location = location.encode("utf-8")
407         else:
408                 location = None
409         tags = xml.get("tags")
410         if tags and tags != "None":
411                 tags = tags.encode("utf-8").split(' ')
412         else:
413                 tags = None
414
415         name = xml.get("name").encode("utf-8")
416         #filename = xml.get("filename").encode("utf-8")
417         entry = RecordTimerEntry(serviceref, begin, end, name, description, eit, disabled, justplay, afterevent, dirname = location, tags = tags)
418         entry.repeated = int(repeated)
419         
420         for l in xml.findall("log"):
421                 time = int(l.get("time"))
422                 code = int(l.get("code"))
423                 msg = l.text.strip().encode("utf-8")
424                 entry.log_entries.append((time, code, msg))
425         
426         return entry
427
428 class RecordTimer(timer.Timer):
429         def __init__(self):
430                 timer.Timer.__init__(self)
431                 
432                 self.Filename = Directories.resolveFilename(Directories.SCOPE_CONFIG, "timers.xml")
433                 
434                 try:
435                         self.loadTimer()
436                 except IOError:
437                         print "unable to load timers from file!"
438                         
439         def isRecording(self):
440                 isRunning = False
441                 for timer in self.timer_list:
442                         if timer.isRunning() and not timer.justplay:
443                                 isRunning = True
444                 return isRunning
445         
446         def loadTimer(self):
447                 # TODO: PATH!
448                 try:
449                         doc = xml.etree.cElementTree.parse(self.Filename)
450                 except SyntaxError:
451                         from Tools.Notifications import AddPopup
452                         from Screens.MessageBox import MessageBox
453
454                         AddPopup(_("The timer file (timers.xml) is corrupt and could not be loaded."), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
455
456                         print "timers.xml failed to load!"
457                         try:
458                                 import os
459                                 os.rename(self.Filename, self.Filename + "_old")
460                         except (IOError, OSError):
461                                 print "renaming broken timer failed"
462                         return
463                 except IOError:
464                         print "timers.xml not found!"
465                         return
466
467                 root = doc.getroot()
468
469                 # put out a message when at least one timer overlaps
470                 checkit = True
471                 for timer in root.findall("timer"):
472                         newTimer = createTimer(timer)
473                         if (self.record(newTimer, True, True) is not None) and (checkit == True):
474                                 from Tools.Notifications import AddPopup
475                                 from Screens.MessageBox import MessageBox
476                                 AddPopup(_("Timer overlap in timers.xml detected!\nPlease recheck it!"), type = MessageBox.TYPE_ERROR, timeout = 0, id = "TimerLoadFailed")
477                                 checkit = False # at moment it is enough when the message is displayed one time
478
479         def saveTimer(self):
480                 #root_element = xml.etree.cElementTree.Element('timers')
481                 #root_element.text = "\n"
482
483                 #for timer in self.timer_list + self.processed_timers:
484                         # some timers (instant records) don't want to be saved.
485                         # skip them
486                         #if timer.dontSave:
487                                 #continue
488                         #t = xml.etree.cElementTree.SubElement(root_element, 'timers')
489                         #t.set("begin", str(int(timer.begin)))
490                         #t.set("end", str(int(timer.end)))
491                         #t.set("serviceref", str(timer.service_ref))
492                         #t.set("repeated", str(timer.repeated))                 
493                         #t.set("name", timer.name)
494                         #t.set("description", timer.description)
495                         #t.set("afterevent", str({
496                         #       AFTEREVENT.NONE: "nothing",
497                         #       AFTEREVENT.STANDBY: "standby",
498                         #       AFTEREVENT.DEEPSTANDBY: "deepstandby",
499                         #       AFTEREVENT.AUTO: "auto"}))
500                         #if timer.eit is not None:
501                         #       t.set("eit", str(timer.eit))
502                         #if timer.dirname is not None:
503                         #       t.set("location", str(timer.dirname))
504                         #t.set("disabled", str(int(timer.disabled)))
505                         #t.set("justplay", str(int(timer.justplay)))
506                         #t.text = "\n"
507                         #t.tail = "\n"
508
509                         #for time, code, msg in timer.log_entries:
510                                 #l = xml.etree.cElementTree.SubElement(t, 'log')
511                                 #l.set("time", str(time))
512                                 #l.set("code", str(code))
513                                 #l.text = str(msg)
514                                 #l.tail = "\n"
515
516                 #doc = xml.etree.cElementTree.ElementTree(root_element)
517                 #doc.write(self.Filename)
518
519                 list = []
520
521                 list.append('<?xml version="1.0" ?>\n')
522                 list.append('<timers>\n')
523                 
524                 for timer in self.timer_list + self.processed_timers:
525                         if timer.dontSave:
526                                 continue
527
528                         list.append('<timer')
529                         list.append(' begin="' + str(int(timer.begin)) + '"')
530                         list.append(' end="' + str(int(timer.end)) + '"')
531                         list.append(' serviceref="' + stringToXML(str(timer.service_ref)) + '"')
532                         list.append(' repeated="' + str(int(timer.repeated)) + '"')
533                         list.append(' name="' + str(stringToXML(timer.name)) + '"')
534                         list.append(' description="' + str(stringToXML(timer.description)) + '"')
535                         list.append(' afterevent="' + str(stringToXML({
536                                 AFTEREVENT.NONE: "nothing",
537                                 AFTEREVENT.STANDBY: "standby",
538                                 AFTEREVENT.DEEPSTANDBY: "deepstandby",
539                                 AFTEREVENT.AUTO: "auto"
540                                 }[timer.afterEvent])) + '"')
541                         if timer.eit is not None:
542                                 list.append(' eit="' + str(timer.eit) + '"')
543                         if timer.dirname is not None:
544                                 list.append(' location="' + str(stringToXML(timer.dirname)) + '"')
545                         if timer.tags is not None:
546                                 list.append(' tags="' + str(stringToXML(' '.join(timer.tags))) + '"')
547                         list.append(' disabled="' + str(int(timer.disabled)) + '"')
548                         list.append(' justplay="' + str(int(timer.justplay)) + '"')
549                         list.append('>\n')
550                         
551                         if config.recording.debug.value:
552                                 for time, code, msg in timer.log_entries:
553                                         list.append('<log')
554                                         list.append(' code="' + str(code) + '"')
555                                         list.append(' time="' + str(time) + '"')
556                                         list.append('>')
557                                         list.append(str(stringToXML(msg)))
558                                         list.append('</log>\n')
559                         
560                         list.append('</timer>\n')
561
562                 list.append('</timers>\n')
563
564                 file = open(self.Filename, "w")
565                 for x in list:
566                         file.write(x)
567                 file.close()
568
569         def getNextZapTime(self):
570                 now = time.time()
571                 for timer in self.timer_list:
572                         if not timer.justplay or timer.begin < now:
573                                 continue
574                         return timer.begin
575                 return -1
576
577         def getNextRecordingTime(self):
578                 now = time.time()
579                 for timer in self.timer_list:
580                         next_act = timer.getNextActivation()
581                         if timer.justplay or next_act < now:
582                                 continue
583                         return next_act
584                 return -1
585
586         def isNextRecordAfterEventActionAuto(self):
587                 now = time.time()
588                 t = None
589                 for timer in self.timer_list:
590                         if timer.justplay or timer.begin < now:
591                                 continue
592                         if t is None or t.begin == timer.begin:
593                                 t = timer
594                                 if t.afterEvent == AFTEREVENT.AUTO:
595                                         return True
596                 return False
597
598         def record(self, entry, ignoreTSC=False, dosave=True):          #wird von loadTimer mit dosave=False aufgerufen
599                 timersanitycheck = TimerSanityCheck(self.timer_list,entry)
600                 if not timersanitycheck.check():
601                         if ignoreTSC != True:
602                                 print "timer conflict detected!"
603                                 print timersanitycheck.getSimulTimerList()
604                                 return timersanitycheck.getSimulTimerList()
605                         else:
606                                 print "ignore timer conflict"
607                 elif timersanitycheck.doubleCheck():
608                         print "ignore double timer"
609                         return None
610                 entry.timeChanged()
611                 print "[Timer] Record " + str(entry)
612                 entry.Timer = self
613                 self.addTimerEntry(entry)
614                 if dosave:
615                         self.saveTimer()
616                 return None
617
618         def isInTimer(self, eventid, begin, duration, service):
619                 time_match = 0
620                 chktime = None
621                 chktimecmp = None
622                 chktimecmp_end = None
623                 end = begin + duration
624                 refstr = str(service)
625                 for x in self.timer_list:
626                         check = x.service_ref.ref.toString() == refstr
627                         if not check:
628                                 sref = x.service_ref.ref
629                                 parent_sid = sref.getUnsignedData(5)
630                                 parent_tsid = sref.getUnsignedData(6)
631                                 if parent_sid and parent_tsid: # check for subservice
632                                         sid = sref.getUnsignedData(1)
633                                         tsid = sref.getUnsignedData(2)
634                                         sref.setUnsignedData(1, parent_sid)
635                                         sref.setUnsignedData(2, parent_tsid)
636                                         sref.setUnsignedData(5, 0)
637                                         sref.setUnsignedData(6, 0)
638                                         check = sref.toCompareString() == refstr
639                                         num = 0
640                                         if check:
641                                                 check = False
642                                                 event = eEPGCache.getInstance().lookupEventId(sref, eventid)
643                                                 num = event and event.getNumOfLinkageServices() or 0
644                                         sref.setUnsignedData(1, sid)
645                                         sref.setUnsignedData(2, tsid)
646                                         sref.setUnsignedData(5, parent_sid)
647                                         sref.setUnsignedData(6, parent_tsid)
648                                         for cnt in range(num):
649                                                 subservice = event.getLinkageService(sref, cnt)
650                                                 if sref.toCompareString() == subservice.toCompareString():
651                                                         check = True
652                                                         break
653                         if check:
654                                 if x.repeated != 0:
655                                         if chktime is None:
656                                                 chktime = localtime(begin)
657                                                 chktimecmp = chktime.tm_wday * 1440 + chktime.tm_hour * 60 + chktime.tm_min
658                                                 chktimecmp_end = chktimecmp + (duration / 60)
659                                         time = localtime(x.begin)
660                                         for y in (0, 1, 2, 3, 4, 5, 6):
661                                                 if x.repeated & (2 ** y) and (x.begin <= begin or begin <= x.begin <= end):
662                                                         timecmp = y * 1440 + time.tm_hour * 60 + time.tm_min
663                                                         if timecmp <= chktimecmp < (timecmp + ((x.end - x.begin) / 60)):
664                                                                 time_match = ((timecmp + ((x.end - x.begin) / 60)) - chktimecmp) * 60
665                                                         elif chktimecmp <= timecmp < chktimecmp_end:
666                                                                 time_match = (chktimecmp_end - timecmp) * 60
667                                 else: #if x.eit is None:
668                                         if begin <= x.begin <= end:
669                                                 diff = end - x.begin
670                                                 if time_match < diff:
671                                                         time_match = diff
672                                         elif x.begin <= begin <= x.end:
673                                                 diff = x.end - begin
674                                                 if time_match < diff:
675                                                         time_match = diff
676                                 if time_match:
677                                         break
678                 return time_match
679
680         def removeEntry(self, entry):
681                 print "[Timer] Remove " + str(entry)
682                 
683                 # avoid re-enqueuing
684                 entry.repeated = False
685
686                 # abort timer.
687                 # this sets the end time to current time, so timer will be stopped.
688                 entry.autoincrease = False
689                 entry.abort()
690                 
691                 if entry.state != entry.StateEnded:
692                         self.timeChanged(entry)
693                 
694                 print "state: ", entry.state
695                 print "in processed: ", entry in self.processed_timers
696                 print "in running: ", entry in self.timer_list
697                 # autoincrease instanttimer if possible
698                 if not entry.dontSave:
699                         for x in self.timer_list:
700                                 if x.setAutoincreaseEnd():
701                                         self.timeChanged(x)
702                 # now the timer should be in the processed_timers list. remove it from there.
703                 self.processed_timers.remove(entry)
704                 self.saveTimer()
705
706         def shutdown(self):
707                 self.saveTimer()