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