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