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