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