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