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