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