[PermanentTimeshift] Improved BackToLive Method (not enabled yet, for OE 2.0)
[enigma2-plugins.git] / permanenttimeshift / src / plugin.py
1 #####################################################
2 # Permanent Timeshift Plugin for Enigma2 Dreamboxes
3 # Coded by Homey (c) 2012
4 #
5 # Version: 1.2
6 # Support: www.dreambox-plugins.de
7 #####################################################
8 from Components.ActionMap import ActionMap
9 from Components.ConfigList import ConfigList, ConfigListScreen
10 from Components.config import config, configfile, getConfigListEntry, ConfigSubsection, ConfigYesNo, ConfigInteger, ConfigSelection, NoSave
11 from Components.Label import Label
12 from Components.Language import language
13 from Components.Pixmap import Pixmap
14 from Components.ServiceEventTracker import ServiceEventTracker
15 from Components.Sources.StaticText import StaticText
16 from Components.SystemInfo import SystemInfo
17 from Components.Task import Task, Job, job_manager as JobManager
18 from Components.UsageConfig import preferredInstantRecordPath, defaultMoviePath
19 from Screens.ChoiceBox import ChoiceBox
20 from Screens.ChannelSelection import ChannelSelection
21 from Screens.InfoBar import InfoBar as InfoBarOrg
22 from Screens.InfoBarGenerics import NumberZap, InfoBarSeek, InfoBarNumberZap, InfoBarTimeshiftState, InfoBarInstantRecord, InfoBarChannelSelection
23 from Screens.MessageBox import MessageBox
24 from Screens.Screen import Screen
25 from Screens.Setup import SetupSummary
26 from Screens.Standby import Standby, TryQuitMainloop
27 from Screens.PVRState import TimeshiftState
28 from ServiceReference import ServiceReference
29 from Tools import Directories, ASCIItranslit, Notifications
30 from Tools.Directories import fileExists, copyfile, resolveFilename, SCOPE_LANGUAGE, SCOPE_PLUGINS
31 from Plugins.Plugin import PluginDescriptor
32 from RecordTimer import RecordTimer, RecordTimerEntry, parseEvent
33
34 from random import randint
35 from enigma import eTimer, eServiceCenter, eBackgroundFileEraser, iPlayableService, iRecordableService, iServiceInformation
36 from os import environ, stat as os_stat, listdir as os_listdir, link as os_link, path as os_path, system as os_system, statvfs
37 from time import localtime, time, gmtime, strftime
38 from timer import TimerEntry
39
40 import gettext
41 import Screens.InfoBar
42 import Screens.Standby
43
44 ##############################
45 ###   Multilanguage Init   ###
46 ##############################
47
48 def localeInit():
49         lang = language.getLanguage()
50         environ["LANGUAGE"] = lang[:2]
51         gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE))
52         gettext.textdomain("enigma2")
53         gettext.bindtextdomain("PermanentTimeshift", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/PermanentTimeshift/locale/"))
54
55 def _(txt):
56         t = gettext.dgettext("PermanentTimeshift", txt)
57         if t == txt:
58                 t = gettext.gettext(txt)
59         return t
60
61 localeInit()
62 language.addCallback(localeInit)
63
64 ##############################
65 #####  CONFIG SETTINGS   #####
66 ##############################
67
68 config.plugins.pts = ConfigSubsection()
69 config.plugins.pts.enabled = ConfigYesNo(default = True)
70 config.plugins.pts.maxevents = ConfigInteger(default=5, limits=(1, 99))
71 config.plugins.pts.maxlength = ConfigInteger(default=180, limits=(5, 999))
72 config.plugins.pts.startdelay = ConfigInteger(default=5, limits=(5, 999))
73 config.plugins.pts.showinfobar = ConfigYesNo(default = False)
74 config.plugins.pts.stopwhilerecording = ConfigYesNo(default = False)
75 config.plugins.pts.favoriteSaveAction = ConfigSelection([("askuser", _("Ask user")),("savetimeshift", _("Save and stop")),("savetimeshiftandrecord", _("Save and record")),("noSave", _("Don't save"))], "askuser")
76 config.plugins.pts.permanentrecording = ConfigYesNo(default = False)
77 config.plugins.pts.isRecording = NoSave(ConfigYesNo(default = False))
78
79 ###################################
80 ###  PTS TimeshiftState Screen  ###
81 ###################################
82
83 class PTSTimeshiftState(Screen):
84         skin = """
85                 <screen position="center,40" zPosition="2" size="420,70" backgroundColor="transpBlack" flags="wfNoBorder">
86                         <widget name="state" position="10,3" size="80,27" font="Regular;20" halign="center" backgroundColor="transpBlack" />
87                         <widget source="session.CurrentService" render="Label" position="95,5" size="120,27" font="Regular;20" halign="left" foregroundColor="white" backgroundColor="transpBlack">
88                                 <convert type="ServicePosition">Position</convert>
89                         </widget>
90                         <widget source="session.CurrentService" render="Label" position="335,5" size="70,27" font="Regular;20" halign="left" foregroundColor="white" backgroundColor="transpBlack">
91                                 <convert type="ServicePosition">Length</convert>
92                         </widget>
93                         <widget name="PTSSeekPointer" position="8,30" zPosition="3" size="19,50" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/timeline-now.png" alphatest="on" />
94                         <ePixmap position="10,33" size="840,15" zPosition="1" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/slider_back.png" alphatest="on"/>
95                                 <widget source="session.CurrentService" render="Progress" position="10,33" size="390,15" zPosition="2" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/images/slider.png" transparent="1">
96                                 <convert type="ServicePosition">Position</convert>
97                         </widget>
98                         <widget name="eventname" position="10,49" zPosition="4" size="420,20" font="Regular;18" halign="center" backgroundColor="transpBlack" />
99                 </screen>"""
100
101         def __init__(self, session):
102                 Screen.__init__(self, session)
103                 self["state"] = Label(text="")
104                 self["PTSSeekPointer"] = Pixmap()
105                 self["eventname"] = Label(text="")
106
107 ###################################
108 ###   PTS CopyTimeshift Task    ###
109 ###################################
110
111 class CopyTimeshiftJob(Job):
112         def __init__(self, toolbox, cmdline, srcfile, destfile, eventname):
113                 Job.__init__(self, _("Saving Timeshift files"))
114                 self.toolbox = toolbox
115                 AddCopyTimeshiftTask(self, cmdline, srcfile, destfile, eventname)
116
117 class AddCopyTimeshiftTask(Task):
118         def __init__(self, job, cmdline, srcfile, destfile, eventname):
119                 Task.__init__(self, job, eventname)
120                 self.toolbox = job.toolbox
121                 self.setCmdline(cmdline)
122                 self.srcfile = config.usage.timeshift_path.value + "/" + srcfile + ".copy"
123                 self.destfile = destfile + ".ts"
124
125                 self.ProgressTimer = eTimer()
126                 self.ProgressTimer.callback.append(self.ProgressUpdate)
127
128         def ProgressUpdate(self):
129                 if self.srcsize <= 0 or not fileExists(self.destfile, 'r'):
130                         return
131
132                 self.setProgress(int((os_path.getsize(self.destfile)/float(self.srcsize))*100))
133                 self.ProgressTimer.start(15000, True)
134
135         def prepare(self):
136                 if fileExists(self.srcfile, 'r'):
137                         self.srcsize = os_path.getsize(self.srcfile)
138                         self.ProgressTimer.start(15000, True)
139
140                 self.toolbox.ptsFrontpanelActions("start")
141                 config.plugins.pts.isRecording.value = True
142
143         def afterRun(self):
144                 self.setProgress(100)
145                 self.ProgressTimer.stop()
146                 self.toolbox.ptsCopyFilefinished(self.srcfile, self.destfile)
147
148 ###################################
149 ###   PTS MergeTimeshift Task   ###
150 ###################################
151
152 class MergeTimeshiftJob(Job):
153         def __init__(self, toolbox, cmdline, srcfile, destfile, eventname):
154                 Job.__init__(self, _("Merging Timeshift files"))
155                 self.toolbox = toolbox
156                 AddMergeTimeshiftTask(self, cmdline, srcfile, destfile, eventname)
157
158 class AddMergeTimeshiftTask(Task):
159         def __init__(self, job, cmdline, srcfile, destfile, eventname):
160                 Task.__init__(self, job, eventname)
161                 self.toolbox = job.toolbox
162                 self.setCmdline(cmdline)
163                 self.srcfile = config.usage.default_path.value + "/" + srcfile
164                 self.destfile = config.usage.default_path.value + "/" + destfile
165
166                 self.ProgressTimer = eTimer()
167                 self.ProgressTimer.callback.append(self.ProgressUpdate)
168
169         def ProgressUpdate(self):
170                 if self.srcsize <= 0 or not fileExists(self.destfile, 'r'):
171                         return
172
173                 self.setProgress(int((os_path.getsize(self.destfile)/float(self.srcsize))*100))
174                 self.ProgressTimer.start(7500, True)
175
176         def prepare(self):
177                 if fileExists(self.srcfile, 'r') and fileExists(self.destfile, 'r'):
178                         fsize1 = os_path.getsize(self.srcfile)
179                         fsize2 = os_path.getsize(self.destfile)
180                         self.srcsize = fsize1 + fsize2
181                         self.ProgressTimer.start(7500, True)
182
183                 self.toolbox.ptsFrontpanelActions("start")
184                 config.plugins.pts.isRecording.value = True
185
186         def afterRun(self):
187                 self.setProgress(100)
188                 self.ProgressTimer.stop()
189                 self.toolbox.ptsMergeFilefinished(self.srcfile, self.destfile)
190
191 ##################################
192 ###   Create APSC Files Task   ###
193 ##################################
194
195 class CreateAPSCFilesJob(Job):
196         def __init__(self, toolbox, cmdline, eventname):
197                 Job.__init__(self, _("Creating AP and SC Files"))
198                 self.toolbox = toolbox
199                 CreateAPSCFilesTask(self, cmdline, eventname)
200
201 class CreateAPSCFilesTask(Task):
202         def __init__(self, job, cmdline, eventname):
203                 Task.__init__(self, job, eventname)
204                 self.toolbox = job.toolbox
205                 self.setCmdline(cmdline)
206
207         def prepare(self):
208                 self.toolbox.ptsFrontpanelActions("start")
209                 config.plugins.pts.isRecording.value = True
210
211         def afterRun(self):
212                 self.setProgress(100)
213                 self.toolbox.ptsSaveTimeshiftFinished()
214
215 ###########################
216 #####  Class InfoBar  #####
217 ###########################
218 class InfoBar(InfoBarOrg):
219         def __init__(self, session):
220                 InfoBarOrg.__init__(self, session)
221                 InfoBarOrg.instance = self
222
223                 self.__event_tracker = ServiceEventTracker(screen = self, eventmap =
224                         {
225                                 iPlayableService.evStart: self.__evStart,
226                                 iPlayableService.evEnd: self.__evEnd,
227                                 iPlayableService.evSOF: self.__evSOF,
228                                 iPlayableService.evUpdatedInfo: self.__evInfoChanged,
229                                 iPlayableService.evUpdatedEventInfo: self.__evEventInfoChanged,
230                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
231                                 iPlayableService.evUser+1: self.ptsTimeshiftFileChanged
232                         })
233
234                 self["PTSactions"] = ActionMap(["PTS_GlobalActions"],{"instantRecord": self.instantRecord, "restartTimeshift": self.restartTimeshift},-2)
235                 self["PTSSeekPointerActions"] = ActionMap(["PTS_SeekPointerActions"],{"SeekPointerOK": self.ptsSeekPointerOK, "SeekPointerLeft": self.ptsSeekPointerLeft, "SeekPointerRight": self.ptsSeekPointerRight},-2)
236                 self["PTSSeekPointerActions"].setEnabled(False)
237
238                 self.pts_begintime = 0
239                 self.pts_pathchecked = False
240                 self.pts_pvrStateDialog = "TimeshiftState"
241                 self.pts_seektoprevfile = False
242                 self.pts_switchtolive = False
243                 self.pts_currplaying = 1
244                 self.pts_lastseekspeed = 0
245                 self.pts_service_changed = False
246                 self.pts_record_running = self.session.nav.RecordTimer.isRecording()
247                 self.save_current_timeshift = False
248                 self.save_timeshift_postaction = None
249                 self.save_timeshift_filename = None
250                 self.service_changed = 0
251
252                 # Init Global Variables
253                 self.session.ptsmainloopvalue = 0
254                 config.plugins.pts.isRecording.value = False
255
256                 # Init eBackgroundFileEraser
257                 self.BgFileEraser = eBackgroundFileEraser.getInstance()
258
259                 # Init PTS Delay-Timer
260                 self.pts_delay_timer = eTimer()
261                 self.pts_delay_timer.callback.append(self.activatePermanentTimeshift)
262
263                 # Init PTS LengthCheck-Timer
264                 self.pts_LengthCheck_timer = eTimer()
265                 self.pts_LengthCheck_timer.callback.append(self.ptsLengthCheck)
266
267                 # Init PTS MergeRecords-Timer
268                 self.pts_mergeRecords_timer = eTimer()
269                 self.pts_mergeRecords_timer.callback.append(self.ptsMergeRecords)
270
271                 # Init PTS Merge Cleanup-Timer
272                 self.pts_mergeCleanUp_timer = eTimer()
273                 self.pts_mergeCleanUp_timer.callback.append(self.ptsMergePostCleanUp)
274
275                 # Init PTS QuitMainloop-Timer
276                 self.pts_QuitMainloop_timer = eTimer()
277                 self.pts_QuitMainloop_timer.callback.append(self.ptsTryQuitMainloop)
278
279                 # Init PTS CleanUp-Timer
280                 self.pts_cleanUp_timer = eTimer()
281                 self.pts_cleanUp_timer.callback.append(self.ptsCleanTimeshiftFolder)
282                 self.pts_cleanUp_timer.start(30000, True)
283
284                 # Init PTS SeekBack-Timer
285                 self.pts_SeekBack_timer = eTimer()
286                 self.pts_SeekBack_timer.callback.append(self.ptsSeekBackTimer)
287
288                 # Init Block-Zap Timer
289                 self.pts_blockZap_timer = eTimer()
290
291                 # Record Event Tracker
292                 self.session.nav.RecordTimer.on_state_change.append(self.ptsTimerEntryStateChange)
293
294                 # Keep Current Event Info for recordings
295                 self.pts_eventcount = 1
296                 self.pts_curevent_begin = int(time())
297                 self.pts_curevent_end = 0
298                 self.pts_curevent_name = _("Timeshift")
299                 self.pts_curevent_description = ""
300                 self.pts_curevent_servicerefname = ""
301                 self.pts_curevent_station = ""
302                 self.pts_curevent_eventid = None
303
304                 # Init PTS Infobar
305                 self.pts_seekpointer_MinX = 8
306                 self.pts_seekpointer_MaxX = 396 # make sure you can divide this through 2
307
308         def __evStart(self):
309                 self.service_changed = 1
310                 self.pts_delay_timer.stop()
311                 self.pts_service_changed = True
312
313         def __evEnd(self):
314                 self.service_changed = 0
315
316         def __evSOF(self):
317                 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
318                         return
319
320                 if self.pts_currplaying == 1:
321                         preptsfile = config.plugins.pts.maxevents.value
322                 else:
323                         preptsfile = self.pts_currplaying-1
324
325                 # Switch to previous TS file by seeking forward to next one
326                 if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value, preptsfile), 'r') and preptsfile != self.pts_eventcount:
327                         self.pts_seektoprevfile = True
328                         self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (preptsfile))
329
330                         self.setSeekState(self.SEEK_STATE_PAUSE)
331                         if self.seekstate != self.SEEK_STATE_PLAY:
332                                 self.setSeekState(self.SEEK_STATE_PLAY)
333                         self.doSeek(-1)
334                         self.seekFwd()
335
336         def __evInfoChanged(self):
337                 if self.service_changed:
338                         self.service_changed = 0
339
340                         # We zapped away before saving the file, save it now!
341                         if self.save_current_timeshift:
342                                 self.SaveTimeshift("pts_livebuffer.%s" % (self.pts_eventcount))
343
344                         # Delete Timeshift Records on zap
345                         self.pts_eventcount = 0
346                         self.pts_cleanUp_timer.start(3000, True)
347
348         def __evEventInfoChanged(self):
349                 if not config.plugins.pts.enabled.value:
350                         return
351
352                 # Get Current Event Info
353                 service = self.session.nav.getCurrentService()
354                 old_begin_time = self.pts_begintime
355                 info = service and service.info()
356                 ptr = info and info.getEvent(0)
357                 self.pts_begintime = ptr and ptr.getBeginTime() or 0
358
359                 # Save current TimeShift permanently now ...
360                 if info.getInfo(iServiceInformation.sVideoPID) != -1:
361
362                         # Take care of Record Margin Time ...
363                         if self.save_current_timeshift and self.timeshift_enabled:
364                                 if config.recording.margin_after.value > 0 and len(self.recording) == 0:
365                                         self.SaveTimeshift(mergelater=True)
366                                         recording = RecordTimerEntry(ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()), time(), time()+(config.recording.margin_after.value*60), self.pts_curevent_name, self.pts_curevent_description, self.pts_curevent_eventid, dirname = config.usage.default_path.value)
367                                         recording.dontSave = True
368                                         self.session.nav.RecordTimer.record(recording)
369                                         self.recording.append(recording)
370                                 else:
371                                         self.SaveTimeshift()
372
373                         # Restarting active timers after zap ...
374                         if self.pts_delay_timer.isActive() and not self.timeshift_enabled:
375                                 self.pts_delay_timer.start(config.plugins.pts.startdelay.value*1000, True)
376                         if self.pts_cleanUp_timer.isActive() and not self.timeshift_enabled:
377                                 self.pts_cleanUp_timer.start(3000, True)
378
379                         # (Re)Start TimeShift
380                         if not self.pts_delay_timer.isActive():
381                                 if not self.timeshift_enabled or old_begin_time != self.pts_begintime or old_begin_time == 0:
382                                         if self.pts_service_changed:
383                                                 self.pts_service_changed = False
384                                                 self.pts_delay_timer.start(config.plugins.pts.startdelay.value*1000, True)
385                                         else:
386                                                 self.pts_delay_timer.start(1000, True)
387
388         def __seekableStatusChanged(self):
389                 enabled = False
390                 if not self.isSeekable() and self.timeshift_enabled:
391                         enabled = True
392                 self["TimeshiftActivateActions"].setEnabled(enabled)
393
394                 enabled = False
395                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.timeshift_enabled and self.isSeekable():
396                         enabled = True
397
398                 self["PTSSeekPointerActions"].setEnabled(enabled)
399
400                 # Reset Seek Pointer And Eventname in InfoBar
401                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.timeshift_enabled and not self.isSeekable():
402                         if self.pts_pvrStateDialog == "PTSTimeshiftState":
403                                 self.pvrStateDialog["eventname"].setText("")
404                         self.ptsSeekPointerReset()
405
406                 # setNextPlaybackFile() when switching back to live tv
407                 if config.plugins.pts.enabled.value and self.timeshift_enabled and not self.isSeekable():
408                         if self.pts_starttime <= (time()-5):
409                                 self.pts_blockZap_timer.start(3000, True)
410                         self.pts_currplaying = self.pts_eventcount
411                         self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (self.pts_eventcount))
412
413         def activatePermanentTimeshift(self):
414                 if self.ptsCheckTimeshiftPath() is False or self.session.screen["Standby"].boolean is True or self.ptsLiveTVStatus() is False or (config.plugins.pts.stopwhilerecording.value and self.pts_record_running):
415                         return
416
417                 # Replace PVR Timeshift State Icon
418                 if config.plugins.pts.showinfobar.value:
419                         if self.pts_pvrStateDialog != "PTSTimeshiftState":
420                                 self.pts_pvrStateDialog = "PTSTimeshiftState"
421                                 self.pvrStateDialog = self.session.instantiateDialog(PTSTimeshiftState)
422                 elif not config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog != "TimeshiftState":
423                         self.pts_pvrStateDialog = "TimeshiftState"
424                         self.pvrStateDialog = self.session.instantiateDialog(TimeshiftState)
425
426                 # Set next-file on event change only when watching latest timeshift ...
427                 if self.isSeekable() and self.pts_eventcount == self.pts_currplaying:
428                         pts_setnextfile = True
429                 else:
430                         pts_setnextfile = False
431
432                 # Update internal Event Counter
433                 if self.pts_eventcount >= config.plugins.pts.maxevents.value:
434                         self.pts_eventcount = 0
435
436                 self.pts_eventcount += 1
437
438                 # Do not switch back to LiveTV while timeshifting
439                 if self.isSeekable():
440                         switchToLive = False
441                 else:
442                         switchToLive = True
443
444                 # setNextPlaybackFile() on event change while timeshifting
445                 if self.pts_eventcount > 1 and self.isSeekable() and pts_setnextfile:
446                         self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (self.pts_eventcount))
447
448                 # (Re)start Timeshift now
449                 self.stopTimeshiftConfirmed(True, switchToLive)
450                 ts = self.getTimeshift()
451                 if ts and not ts.startTimeshift():
452                         self.pts_starttime = time()
453                         self.pts_LengthCheck_timer.start(120000)
454                         self.timeshift_enabled = 1
455                         self.save_timeshift_postaction = None
456                         self.ptsGetEventInfo()
457                         self.ptsCreateHardlink()
458                         self.__seekableStatusChanged()
459                 else:
460                         self.pts_eventcount = 0
461
462         def startTimeshift(self):
463                 if config.plugins.pts.enabled.value:
464                         self.pts_delay_timer.stop()
465                         self.activatePermanentTimeshift()
466                         self.activateTimeshiftEndAndPause()
467                 else:
468                         InfoBarOrg.startTimeshift(self)
469
470         def stopTimeshift(self):
471                 if not self.timeshift_enabled:
472                         return 0
473
474                 # Jump Back to Live TV
475                 if config.plugins.pts.enabled.value and self.timeshift_enabled:
476                         if self.isSeekable():
477                                 self.pts_switchtolive = True
478                                 self.ptsSetNextPlaybackFile("")
479                                 #IF NEW OE 2.0:
480                                 #self.doSeekRelative(3600 * 24 * 60 * 90000)
481                                 #ELSE:
482                                 self.setSeekState(self.SEEK_STATE_PAUSE)
483                                 if self.seekstate != self.SEEK_STATE_PLAY:
484                                         self.setSeekState(self.SEEK_STATE_PLAY)
485                                 self.doSeek(-1) # seek 1 gop before end
486                                 self.seekFwd() # seekFwd to switch to live TV
487                                 return 1
488                         return 0
489                 InfoBarOrg.stopTimeshift(self)
490
491         def stopTimeshiftConfirmed(self, confirmed, switchToLive=True):
492                 was_enabled = self.timeshift_enabled
493
494                 if not confirmed:
495                         return
496                 ts = self.getTimeshift()
497                 if ts is None:
498                         return
499
500                 # Stop Timeshift now
501                 try:
502                         ts.stopTimeshift(switchToLive)
503                 except:
504                         ts.stopTimeshift()
505
506                 self.timeshift_enabled = 0
507                 self.__seekableStatusChanged()
508
509                 if was_enabled and not self.timeshift_enabled:
510                         self.timeshift_enabled = 0
511                         self.pts_LengthCheck_timer.stop()
512
513         def restartTimeshift(self):
514                 self.activatePermanentTimeshift()
515                 Notifications.AddNotification(MessageBox, _("PTS-Plugin: Restarting Timeshift!"), MessageBox.TYPE_INFO, timeout=5)
516
517         def saveTimeshiftPopup(self):
518                 self.session.openWithCallback(self.saveTimeshiftPopupCallback, ChoiceBox, \
519                         title=_("The Timeshift record was not saved yet!\nWhat do you want to do now with the timeshift file?"), \
520                         list=((_("Save Timeshift as Movie and stop recording"), "savetimeshift"), \
521                         (_("Save Timeshift as Movie and continue recording"), "savetimeshiftandrecord"), \
522                         (_("Don't save Timeshift as Movie"), "noSave")))
523
524         def saveTimeshiftPopupCallback(self, answer):
525                 if answer is None:
526                         return
527
528                 if answer[1] == "savetimeshift":
529                         self.saveTimeshiftActions("savetimeshift", self.save_timeshift_postaction)
530                 elif answer[1] == "savetimeshiftandrecord":
531                         self.saveTimeshiftActions("savetimeshiftandrecord", self.save_timeshift_postaction)
532                 elif answer[1] == "noSave":
533                         self.save_current_timeshift = False
534                         self.saveTimeshiftActions("noSave", self.save_timeshift_postaction)
535
536         def saveTimeshiftEventPopup(self):
537                 filecount = 0
538                 entrylist = []
539                 entrylist.append((_("Current Event:")+" %s" % (self.pts_curevent_name), "savetimeshift"))
540
541                 filelist = os_listdir(config.usage.timeshift_path.value)
542
543                 if filelist is not None:
544                         filelist.sort()
545
546                 for filename in filelist:
547                         if (filename.startswith("pts_livebuffer.") is True) and (filename.endswith(".del") is False and filename.endswith(".meta") is False and filename.endswith(".eit") is False and filename.endswith(".copy") is False):
548                                 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
549                                 if statinfo.st_mtime < (time()-5.0):
550                                         # Get Event Info from meta file
551                                         readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,filename), "r")
552                                         servicerefname = readmetafile.readline()[0:-1]
553                                         eventname = readmetafile.readline()[0:-1]
554                                         description = readmetafile.readline()[0:-1]
555                                         begintime = readmetafile.readline()[0:-1]
556                                         readmetafile.close()
557
558                                         # Add Event to list
559                                         filecount += 1
560                                         entrylist.append((_("Record") + " #%s (%s): %s" % (filecount,strftime("%H:%M",localtime(int(begintime))),eventname), "%s" % filename))
561
562                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, title=_("Which event do you want to save permanently?"), list=entrylist)
563
564         def saveTimeshiftActions(self, action=None, postaction=None):
565                 self.save_timeshift_postaction = postaction
566
567                 if action is None:
568                         if config.plugins.pts.favoriteSaveAction.value == "askuser":
569                                 self.saveTimeshiftPopup()
570                                 return
571                         elif config.plugins.pts.favoriteSaveAction.value == "savetimeshift":
572                                 self.SaveTimeshift()
573                         elif config.plugins.pts.favoriteSaveAction.value == "savetimeshiftandrecord":
574                                 if self.pts_curevent_end > time():
575                                         self.SaveTimeshift(mergelater=True)
576                                         self.ptsRecordCurrentEvent()
577                                 else:
578                                         self.SaveTimeshift()
579                         elif config.plugins.pts.favoriteSaveAction.value == "noSave":
580                                 config.plugins.pts.isRecording.value = False
581                                 self.save_current_timeshift = False
582                 elif action == "savetimeshift":
583                         self.SaveTimeshift()
584                 elif action == "savetimeshiftandrecord":
585                         if self.pts_curevent_end > time():
586                                 self.SaveTimeshift(mergelater=True)
587                                 self.ptsRecordCurrentEvent()
588                         else:
589                                 self.SaveTimeshift()
590                 elif action == "noSave":
591                         config.plugins.pts.isRecording.value = False
592                         self.save_current_timeshift = False
593
594                 # Workaround: Show Dummy Popup for a second to prevent StandBy Bug
595                 if action is None and postaction == "standby" and (config.plugins.pts.favoriteSaveAction.value == "savetimeshift" or config.plugins.pts.favoriteSaveAction.value == "savetimeshiftandrecord"):
596                         self.session.open(MessageBox, _("Saving timeshift as movie now. This might take a while!"), MessageBox.TYPE_INFO, timeout=1)
597                         
598                 # Post PTS Actions like ZAP or whatever the user requested
599                 if self.save_timeshift_postaction == "zapUp":
600                         InfoBarChannelSelection.zapUp(self)
601                 elif self.save_timeshift_postaction == "zapDown":
602                         InfoBarChannelSelection.zapDown(self)
603                 elif self.save_timeshift_postaction == "historyBack":
604                         InfoBarChannelSelection.historyBack(self)
605                 elif self.save_timeshift_postaction == "historyNext":
606                         InfoBarChannelSelection.historyNext(self)
607                 elif self.save_timeshift_postaction == "switchChannelUp":
608                         InfoBarChannelSelection.switchChannelUp(self)
609                 elif self.save_timeshift_postaction == "switchChannelDown":
610                         InfoBarChannelSelection.switchChannelDown(self)
611                 elif self.save_timeshift_postaction == "openServiceList":
612                         InfoBarChannelSelection.openServiceList(self)
613                 elif self.save_timeshift_postaction == "showRadioChannelList":
614                         InfoBarChannelSelection.showRadioChannelList(self, zap=True)
615                 elif self.save_timeshift_postaction == "standby":
616                         Notifications.AddNotification(Screens_Standby_Standby)
617
618         def SaveTimeshift(self, timeshiftfile=None, mergelater=False):
619                 self.save_current_timeshift = False
620                 savefilename = None
621
622                 if timeshiftfile is not None:
623                         savefilename = timeshiftfile
624
625                 if savefilename is None:
626                         for filename in os_listdir(config.usage.timeshift_path.value):
627                                 if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy") and not filename.endswith(".sc"):
628                                         try:
629                                                 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
630                                                 if statinfo.st_mtime > (time()-5.0):
631                                                         savefilename=filename
632                                         except Exception, errormsg:
633                                                 Notifications.AddNotification(MessageBox, _("PTS Plugin Error: %s" % (errormsg)), MessageBox.TYPE_ERROR)
634
635                 if savefilename is None:
636                         Notifications.AddNotification(MessageBox, _("No Timeshift found to save as recording!"), MessageBox.TYPE_ERROR)
637                 else:
638                         timeshift_saved = True
639                         timeshift_saveerror1 = ""
640                         timeshift_saveerror2 = ""
641                         metamergestring = ""
642
643                         config.plugins.pts.isRecording.value = True
644
645                         if mergelater:
646                                 self.pts_mergeRecords_timer.start(120000, True)
647                                 metamergestring = "pts_merge\n"
648
649                         try:
650                                 if timeshiftfile is None:
651                                         # Save Current Event by creating hardlink to ts file
652                                         if self.pts_starttime >= (time()-60):
653                                                 self.pts_starttime -= 60
654
655                                         ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name)
656                                         try:
657                                                 if config.usage.setup_level.index >= 2:
658                                                         if config.recording.filename_composition.value == "long" and self.pts_curevent_name != pts_curevent_description:
659                                                                 ptsfilename = "%s - %s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name,self.pts_curevent_description)
660                                                         elif config.recording.filename_composition.value == "short":
661                                                                 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(self.pts_starttime)),self.pts_curevent_name)
662                                         except Exception, errormsg:
663                                                 print "PTS-Plugin: Using default filename"
664
665                                         if config.recording.ascii_filenames.value:
666                                                 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
667
668                                         fullname = Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
669                                         os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s.ts" % (fullname))
670                                         metafile = open("%s.ts.meta" % (fullname), "w")
671                                         metafile.write("%s\n%s\n%s\n%i\n%s" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime),metamergestring))
672                                         metafile.close()
673                                         self.ptsCreateEITFile(fullname)
674                                 elif timeshiftfile.startswith("pts_livebuffer"):
675                                         # Save stored timeshift by creating hardlink to ts file
676                                         readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile), "r")
677                                         servicerefname = readmetafile.readline()[0:-1]
678                                         eventname = readmetafile.readline()[0:-1]
679                                         description = readmetafile.readline()[0:-1]
680                                         begintime = readmetafile.readline()[0:-1]
681                                         readmetafile.close()
682
683                                         ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname)
684                                         try:
685                                                 if config.usage.setup_level.index >= 2:
686                                                         if config.recording.filename_composition.value == "long" and eventname != description:
687                                                                 ptsfilename = "%s - %s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname,description)
688                                                         elif config.recording.filename_composition.value == "short":
689                                                                 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(int(begintime))),eventname)
690                                         except Exception, errormsg:
691                                                 print "PTS-Plugin: Using default filename"
692
693                                         if config.recording.ascii_filenames.value:
694                                                 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
695
696                                         fullname=Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
697                                         os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts" % (fullname))
698                                         os_link("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
699                                         if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
700                                                 os_link("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
701
702                                         # Add merge-tag to metafile
703                                         if mergelater:
704                                                 metafile = open("%s.ts.meta" % (fullname), "a")
705                                                 metafile.write("%s\n" % (metamergestring))
706                                                 metafile.close()
707
708                                 # Create AP and SC Files when not merging
709                                 if not mergelater:
710                                         self.ptsCreateAPSCFiles(fullname+".ts")
711
712                         except Exception, errormsg:
713                                 timeshift_saved = False
714                                 timeshift_saveerror1 = errormsg
715
716                         # Hmpppf! Saving Timeshift via Hardlink-Method failed. Probably other device?
717                         # Let's try to copy the file in background now! This might take a while ...
718                         if not timeshift_saved:
719                                 try:
720                                         stat = statvfs(config.usage.default_path.value)
721                                         freespace = stat.f_bfree / 1000 * stat.f_bsize / 1000
722                                         randomint = randint(1, 999)
723
724                                         if timeshiftfile is None:
725                                                 # Get Filesize for Free Space Check
726                                                 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value,savefilename)) / (1024*1024))
727
728                                                 # Save Current Event by copying it to the other device
729                                                 if filesize <= freespace:
730                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,savefilename,randomint))
731                                                         copy_file = savefilename
732                                                         metafile = open("%s.ts.meta" % (fullname), "w")
733                                                         metafile.write("%s\n%s\n%s\n%i\n%s" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime),metamergestring))
734                                                         metafile.close()
735                                                         self.ptsCreateEITFile(fullname)
736                                         elif timeshiftfile.startswith("pts_livebuffer"):
737                                                 # Get Filesize for Free Space Check
738                                                 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value, timeshiftfile)) / (1024*1024))
739
740                                                 # Save stored timeshift by copying it to the other device
741                                                 if filesize <= freespace:
742                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,timeshiftfile,randomint))
743                                                         copyfile("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
744                                                         if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
745                                                                 copyfile("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
746                                                         copy_file = timeshiftfile
747
748                                                 # Add merge-tag to metafile
749                                                 if mergelater:
750                                                         metafile = open("%s.ts.meta" % (fullname), "a")
751                                                         metafile.write("%s\n" % (metamergestring))
752                                                         metafile.close()
753
754                                         # Only copy file when enough disk-space available!
755                                         if filesize <= freespace:
756                                                 timeshift_saved = True
757                                                 copy_file = copy_file+"."+str(randomint)
758
759                                                 # Get Event Info from meta file
760                                                 if fileExists("%s.ts.meta" % (fullname)):
761                                                         readmetafile = open("%s.ts.meta" % (fullname), "r")
762                                                         servicerefname = readmetafile.readline()[0:-1]
763                                                         eventname = readmetafile.readline()[0:-1]
764                                                 else:
765                                                         eventname = "";
766
767                                                 JobManager.AddJob(CopyTimeshiftJob(self, "cp \"%s/%s.copy\" \"%s.ts\"" % (config.usage.timeshift_path.value,copy_file,fullname), copy_file, fullname, eventname))
768                                                 if not Screens.Standby.inTryQuitMainloop and not Screens.Standby.inStandby and not mergelater and self.save_timeshift_postaction != "standby":
769                                                         Notifications.AddNotification(MessageBox, _("Saving timeshift as movie now. This might take a while!"), MessageBox.TYPE_INFO, timeout=5)
770                                         else:
771                                                 timeshift_saved = False
772                                                 timeshift_saveerror1 = ""
773                                                 timeshift_saveerror2 = _("Not enough free Diskspace!\n\nFilesize: %sMB\nFree Space: %sMB\nPath: %s" % (filesize,freespace,config.usage.default_path.value))
774
775                                 except Exception, errormsg:
776                                         timeshift_saved = False
777                                         timeshift_saveerror2 = errormsg
778
779                         if not timeshift_saved:
780                                 config.plugins.pts.isRecording.value = False
781                                 self.save_timeshift_postaction = None
782                                 errormessage = str(timeshift_saveerror1) + "\n" + str(timeshift_saveerror2)
783                                 Notifications.AddNotification(MessageBox, _("Timeshift save failed!")+"\n\n%s" % errormessage, MessageBox.TYPE_ERROR)
784
785         def ptsCleanTimeshiftFolder(self):
786                 if not config.plugins.pts.enabled.value or self.ptsCheckTimeshiftPath() is False or self.session.screen["Standby"].boolean is True:
787                         return
788
789                 try:
790                         for filename in os_listdir(config.usage.timeshift_path.value):
791                                 if (filename.startswith("timeshift.") or filename.startswith("pts_livebuffer.")) and (filename.endswith(".del") is False and filename.endswith(".copy") is False and filename.endswith(".meta") is False and filename.endswith(".eit") is False):
792
793                                         statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
794                                         # if no write for 5 sec = stranded timeshift
795                                         if statinfo.st_mtime < (time()-5.0):
796                                                 print "PTS-Plugin: Erasing stranded timeshift %s" % filename
797                                                 self.BgFileEraser.erase("%s/%s" % (config.usage.timeshift_path.value,filename))
798
799                                                 # Delete Meta and EIT File too
800                                                 if filename.startswith("pts_livebuffer.") is True:
801                                                         self.BgFileEraser.erase("%s/%s.meta" % (config.usage.timeshift_path.value,filename))
802                                                         self.BgFileEraser.erase("%s/%s.eit" % (config.usage.timeshift_path.value,filename))
803                 except:
804                         print "PTS: IO-Error while cleaning Timeshift Folder ..."
805
806         def ptsGetEventInfo(self):
807                 event = None
808                 try:
809                         serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
810                         serviceHandler = eServiceCenter.getInstance()
811                         info = serviceHandler.info(serviceref)
812
813                         self.pts_curevent_servicerefname = serviceref.toString()
814                         self.pts_curevent_station = info.getName(serviceref)
815
816                         service = self.session.nav.getCurrentService()
817                         info = service and service.info()
818                         event = info and info.getEvent(0)
819                 except Exception, errormsg:
820                         Notifications.AddNotification(MessageBox, _("Getting Event Info failed!")+"\n\n%s" % errormsg, MessageBox.TYPE_ERROR, timeout=10)
821
822                 if event is not None:
823                         curEvent = parseEvent(event)
824                         self.pts_curevent_begin = int(curEvent[0])
825                         self.pts_curevent_end = int(curEvent[1])
826                         self.pts_curevent_name = curEvent[2]
827                         self.pts_curevent_description = curEvent[3]
828                         self.pts_curevent_eventid = curEvent[4]
829
830         def ptsFrontpanelActions(self, action=None):
831                 if self.session.nav.RecordTimer.isRecording() or SystemInfo.get("NumFrontpanelLEDs", 0) == 0:
832                         return
833
834                 try:
835                         if action == "start":
836                                 if fileExists("/proc/stb/fp/led_set_pattern"):
837                                         open("/proc/stb/fp/led_set_pattern", "w").write("0xa7fccf7a")
838                                 elif fileExists("/proc/stb/fp/led0_pattern"):
839                                         open("/proc/stb/fp/led0_pattern", "w").write("0x55555555")
840                                 if fileExists("/proc/stb/fp/led_pattern_speed"):
841                                         open("/proc/stb/fp/led_pattern_speed", "w").write("20")
842                                 elif fileExists("/proc/stb/fp/led_set_speed"):
843                                         open("/proc/stb/fp/led_set_speed", "w").write("20")
844                         elif action == "stop":
845                                 if fileExists("/proc/stb/fp/led_set_pattern"):
846                                         open("/proc/stb/fp/led_set_pattern", "w").write("0")
847                                 elif fileExists("/proc/stb/fp/led0_pattern"):
848                                         open("/proc/stb/fp/led0_pattern", "w").write("0")
849                 except Exception, errormsg:
850                         print "PTS Plugin: %s" % (errormsg)
851
852         def ptsCreateHardlink(self):
853                 for filename in os_listdir(config.usage.timeshift_path.value):
854                         if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy") and not filename.endswith(".sc"):
855                                 try:
856                                         statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
857                                         if statinfo.st_mtime > (time()-5.0):
858                                                 try:
859                                                         self.BgFileEraser.erase("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
860                                                         self.BgFileEraser.erase("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount))
861                                                 except Exception, errormsg:
862                                                         print "PTS Plugin: %s" % (errormsg)
863
864                                                 try:
865                                                         # Create link to pts_livebuffer file
866                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
867
868                                                         # Create a Meta File
869                                                         metafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount), "w")
870                                                         metafile.write("%s\n%s\n%s\n%i\n" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime)))
871                                                         metafile.close()
872                                                 except Exception, errormsg:
873                                                         Notifications.AddNotification(MessageBox, _("Creating Hardlink to Timeshift file failed!")+"\n"+_("The Filesystem on your Timeshift-Device does not support hardlinks.\nMake sure it is formated in EXT2 or EXT3!")+"\n\n%s" % errormsg, MessageBox.TYPE_ERROR)
874
875                                                 # Create EIT File
876                                                 self.ptsCreateEITFile("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
877
878                                                 # Permanent Recording Hack
879                                                 if config.plugins.pts.permanentrecording.value:
880                                                         try:
881                                                                 fullname = Directories.getRecordingFilename("%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name),config.usage.default_path.value)
882                                                                 os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s.ts" % (fullname))
883                                                                 # Create a Meta File
884                                                                 metafile = open("%s.ts.meta" % (fullname), "w")
885                                                                 metafile.write("%s\n%s\n%s\n%i\nautosaved\n" % (self.pts_curevent_servicerefname,self.pts_curevent_name.replace("\n", ""),self.pts_curevent_description.replace("\n", ""),int(self.pts_starttime)))
886                                                                 metafile.close()
887                                                         except Exception, errormsg:
888                                                                 print "PTS Plugin: %s" % (errormsg)
889                                 except Exception, errormsg:
890                                         errormsg = str(errormsg)
891                                         if errormsg.find('Input/output error') != -1:
892                                                 errormsg += _("\nAn Input/output error usually indicates a corrupted filesystem! Please check the filesystem of your timeshift-device!")
893                                         Notifications.AddNotification(MessageBox, _("Creating Hardlink to Timeshift file failed!")+"\n%s" % (errormsg), MessageBox.TYPE_ERROR)
894
895         def ptsRecordCurrentEvent(self):
896                         recording = RecordTimerEntry(ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()), time(), self.pts_curevent_end, self.pts_curevent_name, self.pts_curevent_description, self.pts_curevent_eventid, dirname = config.usage.default_path.value)
897                         recording.dontSave = True
898                         self.session.nav.RecordTimer.record(recording)
899                         self.recording.append(recording)
900
901         def ptsMergeRecords(self):
902                 if self.session.nav.RecordTimer.isRecording():
903                         self.pts_mergeRecords_timer.start(120000, True)
904                         return
905
906                 ptsmergeSRC = ""
907                 ptsmergeDEST = ""
908                 ptsmergeeventname = ""
909                 ptsgetnextfile = False
910                 ptsfilemerged = False
911
912                 filelist = os_listdir(config.usage.default_path.value)
913
914                 if filelist is not None:
915                         filelist.sort()
916
917                 for filename in filelist:
918                         if filename.endswith(".meta"):
919                                 # Get Event Info from meta file
920                                 readmetafile = open("%s/%s" % (config.usage.default_path.value,filename), "r")
921                                 servicerefname = readmetafile.readline()[0:-1]
922                                 eventname = readmetafile.readline()[0:-1]
923                                 eventtitle = readmetafile.readline()[0:-1]
924                                 eventtime = readmetafile.readline()[0:-1]
925                                 eventtag = readmetafile.readline()[0:-1]
926                                 readmetafile.close()
927
928                                 if ptsgetnextfile:
929                                         ptsgetnextfile = False
930                                         ptsmergeSRC = filename[0:-5]
931
932                                         if ASCIItranslit.legacyEncode(eventname) == ASCIItranslit.legacyEncode(ptsmergeeventname):
933                                                 # Copy EIT File
934                                                 if fileExists("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3])):
935                                                         copyfile("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3]),"%s/%s.eit" % (config.usage.default_path.value, ptsmergeDEST[0:-3]))
936
937                                                 # Delete AP and SC Files
938                                                 self.BgFileEraser.erase("%s/%s.ap" % (config.usage.default_path.value, ptsmergeDEST))
939                                                 self.BgFileEraser.erase("%s/%s.sc" % (config.usage.default_path.value, ptsmergeDEST))
940
941                                                 # Add Merge Job to JobManager
942                                                 JobManager.AddJob(MergeTimeshiftJob(self, "cat \"%s/%s\" >> \"%s/%s\"" % (config.usage.default_path.value,ptsmergeSRC,config.usage.default_path.value,ptsmergeDEST), ptsmergeSRC, ptsmergeDEST, eventname))
943                                                 config.plugins.pts.isRecording.value = True
944                                                 ptsfilemerged = True
945                                         else:
946                                                 ptsgetnextfile = True
947
948                                 if eventtag == "pts_merge" and not ptsgetnextfile:
949                                         ptsgetnextfile = True
950                                         ptsmergeDEST = filename[0:-5]
951                                         ptsmergeeventname = eventname
952                                         ptsfilemerged = False
953
954                                         # If still recording or transfering, try again later ...
955                                         if fileExists("%s/%s" % (config.usage.default_path.value,ptsmergeDEST)):
956                                                 statinfo = os_stat("%s/%s" % (config.usage.default_path.value,ptsmergeDEST))
957                                                 if statinfo.st_mtime > (time()-10.0):
958                                                         self.pts_mergeRecords_timer.start(120000, True)
959                                                         return
960
961                                         # Rewrite Meta File to get rid of pts_merge tag
962                                         metafile = open("%s/%s.meta" % (config.usage.default_path.value,ptsmergeDEST), "w")
963                                         metafile.write("%s\n%s\n%s\n%i\n" % (servicerefname,eventname.replace("\n", ""),eventtitle.replace("\n", ""),int(eventtime)))
964                                         metafile.close()
965
966                 # Merging failed :(
967                 if not ptsfilemerged and ptsgetnextfile:
968                         Notifications.AddNotification(MessageBox,_("PTS-Plugin: Merging records failed!"), MessageBox.TYPE_ERROR)
969
970         def ptsCreateAPSCFiles(self, filename):
971                 if fileExists(filename, 'r'):
972                         if fileExists(filename+".meta", 'r'):
973                                 # Get Event Info from meta file
974                                 readmetafile = open(filename+".meta", "r")
975                                 servicerefname = readmetafile.readline()[0:-1]
976                                 eventname = readmetafile.readline()[0:-1]
977                         else:
978                                 eventname = ""
979                         JobManager.AddJob(CreateAPSCFilesJob(self, "/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/createapscfiles \"%s\"" % (filename), eventname))
980                 else:
981                         self.ptsSaveTimeshiftFinished()
982
983         def ptsCreateEITFile(self, filename):
984                 if self.pts_curevent_eventid is not None:
985                         try:
986                                 import eitsave
987                                 serviceref = ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()).ref.toString()
988                                 eitsave.SaveEIT(serviceref, filename+".eit", self.pts_curevent_eventid, -1, -1)
989                         except Exception, errormsg:
990                                 print "PTS Plugin: %s" % (errormsg)
991
992         def ptsCopyFilefinished(self, srcfile, destfile):
993                 # Erase Source File
994                 if fileExists(srcfile):
995                         self.BgFileEraser.erase(srcfile)
996
997                 # Restart Merge Timer
998                 if self.pts_mergeRecords_timer.isActive():
999                         self.pts_mergeRecords_timer.stop()
1000                         self.pts_mergeRecords_timer.start(15000, True)
1001                 else:
1002                         # Create AP and SC Files
1003                         self.ptsCreateAPSCFiles(destfile)
1004
1005         def ptsMergeFilefinished(self, srcfile, destfile):
1006                 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
1007                         # Rename files and delete them later ...
1008                         self.pts_mergeCleanUp_timer.start(120000, True)
1009                         os_system("echo \"\" > \"%s.pts.del\"" % (srcfile[0:-3]))
1010                 else:
1011                         # Delete Instant Record permanently now ... R.I.P.
1012                         self.BgFileEraser.erase("%s" % (srcfile))
1013                         self.BgFileEraser.erase("%s.ap" % (srcfile))
1014                         self.BgFileEraser.erase("%s.sc" % (srcfile))
1015                         self.BgFileEraser.erase("%s.meta" % (srcfile))
1016                         self.BgFileEraser.erase("%s.cuts" % (srcfile))
1017                         self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1018
1019                 # Create AP and SC Files
1020                 self.ptsCreateAPSCFiles(destfile)
1021
1022                 # Run Merge-Process one more time to check if there are more records to merge
1023                 self.pts_mergeRecords_timer.start(10000, True)
1024
1025         def ptsSaveTimeshiftFinished(self):
1026                 if not self.pts_mergeCleanUp_timer.isActive():
1027                         self.ptsFrontpanelActions("stop")
1028                         config.plugins.pts.isRecording.value = False
1029
1030                 if Screens.Standby.inTryQuitMainloop:
1031                         self.pts_QuitMainloop_timer.start(30000, True)
1032                 else:
1033                         Notifications.AddNotification(MessageBox, _("Timeshift saved to your harddisk!"), MessageBox.TYPE_INFO, timeout = 5)
1034
1035         def ptsMergePostCleanUp(self):
1036                 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
1037                         config.plugins.pts.isRecording.value = True
1038                         self.pts_mergeCleanUp_timer.start(120000, True)
1039                         return
1040
1041                 self.ptsFrontpanelActions("stop")
1042                 config.plugins.pts.isRecording.value = False
1043
1044                 filelist = os_listdir(config.usage.default_path.value)
1045                 for filename in filelist:
1046                         if filename.endswith(".pts.del"):
1047                                 srcfile = config.usage.default_path.value + "/" + filename[0:-8] + ".ts"
1048                                 self.BgFileEraser.erase("%s" % (srcfile))
1049                                 self.BgFileEraser.erase("%s.ap" % (srcfile))
1050                                 self.BgFileEraser.erase("%s.sc" % (srcfile))
1051                                 self.BgFileEraser.erase("%s.meta" % (srcfile))
1052                                 self.BgFileEraser.erase("%s.cuts" % (srcfile))
1053                                 self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1054                                 self.BgFileEraser.erase("%s.pts.del" % (srcfile[0:-3]))
1055
1056                                 # Restart QuitMainloop Timer to give BgFileEraser enough time
1057                                 if Screens.Standby.inTryQuitMainloop and self.pts_QuitMainloop_timer.isActive():
1058                                         self.pts_QuitMainloop_timer.start(60000, True)
1059
1060         def ptsTryQuitMainloop(self):
1061                 if Screens.Standby.inTryQuitMainloop and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeCleanUp_timer.isActive()):
1062                         self.pts_QuitMainloop_timer.start(60000, True)
1063                         return
1064
1065                 if Screens.Standby.inTryQuitMainloop and self.session.ptsmainloopvalue:
1066                         self.session.dialog_stack = []
1067                         self.session.summary_stack = [None]
1068                         self.session.open(TryQuitMainloop, self.session.ptsmainloopvalue)
1069
1070         def ptsGetSeekInfo(self):
1071                 s = self.session.nav.getCurrentService()
1072                 return s and s.seek()
1073
1074         def ptsGetPosition(self):
1075                 seek = self.ptsGetSeekInfo()
1076                 if seek is None:
1077                         return None
1078                 pos = seek.getPlayPosition()
1079                 if pos[0]:
1080                         return 0
1081                 return pos[1]
1082
1083         def ptsGetLength(self):
1084                 seek = self.ptsGetSeekInfo()
1085                 if seek is None:
1086                         return None
1087                 length = seek.getLength()
1088                 if length[0]:
1089                         return 0
1090                 return length[1]
1091
1092         def ptsGetSaveTimeshiftStatus(self):
1093                 return self.save_current_timeshift
1094
1095         def ptsSeekPointerOK(self):
1096                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1097                         if not self.pvrstate_hide_timer.isActive():
1098                                 if self.seekstate != self.SEEK_STATE_PLAY:
1099                                         self.setSeekState(self.SEEK_STATE_PLAY)
1100                                 self.doShow()
1101                                 return
1102
1103                         length = self.ptsGetLength()
1104                         position = self.ptsGetPosition()
1105
1106                         if length is None or position is None:
1107                                 return
1108
1109                         cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1110                         jumptox = int(cur_pos[0]) - int(self.pts_seekpointer_MinX)
1111                         jumptoperc = round((jumptox / 400.0) * 100, 0)
1112                         jumptotime = int((length / 100) * jumptoperc)
1113                         jumptodiff = position - jumptotime
1114
1115                         self.doSeekRelative(-jumptodiff)
1116                 else:
1117                         return
1118
1119         def ptsSeekPointerLeft(self):
1120                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1121                         self.ptsMoveSeekPointer(direction="left")
1122                 else:
1123                         return
1124
1125         def ptsSeekPointerRight(self):
1126                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1127                         self.ptsMoveSeekPointer(direction="right")
1128                 else:
1129                         return
1130
1131         def ptsSeekPointerReset(self):
1132                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled:
1133                         self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX,self.pvrStateDialog["PTSSeekPointer"].position[1])
1134
1135         def ptsSeekPointerSetCurrentPos(self):
1136                 if not self.pts_pvrStateDialog == "PTSTimeshiftState" or not self.timeshift_enabled or not self.isSeekable():
1137                         return
1138
1139                 position = self.ptsGetPosition()
1140                 length = self.ptsGetLength()
1141
1142                 if length >= 1:
1143                         tpixels = int((float(int((position*100)/length))/100)*400)
1144                         self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX+tpixels, self.pvrStateDialog["PTSSeekPointer"].position[1])
1145
1146         def ptsMoveSeekPointer(self, direction=None):
1147                 if direction is None or self.pts_pvrStateDialog != "PTSTimeshiftState":
1148                         return
1149
1150                 isvalidjump = False
1151                 cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1152                 InfoBarTimeshiftState._mayShow(self)
1153
1154                 if direction == "left":
1155                         minmaxval = self.pts_seekpointer_MinX
1156                         movepixels = -15
1157                         if cur_pos[0]+movepixels > minmaxval:
1158                                 isvalidjump = True
1159                 elif direction == "right":
1160                         minmaxval = self.pts_seekpointer_MaxX
1161                         movepixels = 15
1162                         if cur_pos[0]+movepixels < minmaxval:
1163                                 isvalidjump = True
1164                 else:
1165                         return 0
1166
1167                 if isvalidjump:
1168                         self.pvrStateDialog["PTSSeekPointer"].setPosition(cur_pos[0]+movepixels,cur_pos[1])
1169                 else:
1170                         self.pvrStateDialog["PTSSeekPointer"].setPosition(minmaxval,cur_pos[1])
1171
1172         def ptsTimeshiftFileChanged(self):
1173                 # Reset Seek Pointer
1174                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value:
1175                         self.ptsSeekPointerReset()
1176
1177                 if self.pts_switchtolive:
1178                         self.pts_switchtolive = False
1179                         return
1180
1181                 if self.pts_seektoprevfile:
1182                         if self.pts_currplaying == 1:
1183                                 self.pts_currplaying = config.plugins.pts.maxevents.value
1184                         else:
1185                                 self.pts_currplaying -= 1
1186                 else:
1187                         if self.pts_currplaying == config.plugins.pts.maxevents.value:
1188                                 self.pts_currplaying = 1
1189                         else:
1190                                 self.pts_currplaying += 1
1191
1192                 if not fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_currplaying), 'r'):
1193                         self.pts_currplaying = self.pts_eventcount
1194
1195                 # Set Eventname in PTS InfoBar
1196                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog == "PTSTimeshiftState":
1197                         try:
1198                                 if self.pts_eventcount != self.pts_currplaying:
1199                                         readmetafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_currplaying), "r")
1200                                         servicerefname = readmetafile.readline()[0:-1]
1201                                         eventname = readmetafile.readline()[0:-1]
1202                                         readmetafile.close()
1203                                         self.pvrStateDialog["eventname"].setText(eventname)
1204                                 else:
1205                                         self.pvrStateDialog["eventname"].setText("")
1206                         except Exception, errormsg:
1207                                 self.pvrStateDialog["eventname"].setText("")
1208
1209                 # Get next pts file ...
1210                 if self.pts_currplaying+1 > config.plugins.pts.maxevents.value:
1211                         nextptsfile = 1
1212                 else:
1213                         nextptsfile = self.pts_currplaying+1
1214
1215                 # Seek to previous file
1216                 if self.pts_seektoprevfile:
1217                         self.pts_seektoprevfile = False
1218
1219                         if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r'):
1220                                 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1221
1222                         self.ptsSeekBackHack()
1223                 else:
1224                         if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r') and nextptsfile <= self.pts_eventcount:
1225                                 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1226                         if nextptsfile == self.pts_currplaying:
1227                                 self.pts_switchtolive = True
1228                                 self.ptsSetNextPlaybackFile("")
1229
1230         def ptsSetNextPlaybackFile(self, nexttsfile):
1231                 ts = self.getTimeshift()
1232                 if ts is None:
1233                         return
1234
1235                 try:
1236                         ts.setNextPlaybackFile("%s/%s" % (config.usage.timeshift_path.value,nexttsfile))
1237                 except:
1238                         print "PTS-Plugin: setNextPlaybackFile() not supported by OE. Enigma2 too old !?"
1239
1240         def ptsSeekBackHack(self):
1241                 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1242                         return
1243
1244                 self.setSeekState(self.SEEK_STATE_PAUSE)
1245                 self.doSeek(-90000*4) # seek ~4s before end
1246                 self.pts_SeekBack_timer.start(1000, True)
1247
1248         def ptsSeekBackTimer(self):
1249                 if self.pts_lastseekspeed == 0:
1250                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1251                 else:
1252                         self.setSeekState(self.makeStateBackward(int(-self.pts_lastseekspeed)))
1253
1254         def ptsCheckTimeshiftPath(self):
1255                 if self.pts_pathchecked:
1256                         return True
1257                 else:
1258                         if fileExists(config.usage.timeshift_path.value, 'w'):
1259                                 self.pts_pathchecked = True
1260                                 return True
1261                         else:
1262                                 Notifications.AddNotification(MessageBox, _("Could not activate Permanent-Timeshift!\nTimeshift-Path does not exist"), MessageBox.TYPE_ERROR, timeout=15)
1263                                 if self.pts_delay_timer.isActive():
1264                                         self.pts_delay_timer.stop()
1265                                 if self.pts_cleanUp_timer.isActive():
1266                                         self.pts_cleanUp_timer.stop()
1267                                 return False
1268
1269         def ptsTimerEntryStateChange(self, timer):
1270                 if not config.plugins.pts.enabled.value or not config.plugins.pts.stopwhilerecording.value:
1271                         return
1272
1273                 self.pts_record_running = self.session.nav.RecordTimer.isRecording()
1274
1275                 # Abort here when box is in standby mode
1276                 if self.session.screen["Standby"].boolean is True:
1277                         return
1278
1279                 # Stop Timeshift when Record started ...
1280                 if timer.state == TimerEntry.StateRunning and self.timeshift_enabled and self.pts_record_running:
1281                         if self.ptsLiveTVStatus() is False:
1282                                 self.timeshift_enabled = 0
1283                                 self.pts_LengthCheck_timer.stop()
1284                                 return
1285
1286                         if self.seekstate != self.SEEK_STATE_PLAY:
1287                                 self.setSeekState(self.SEEK_STATE_PLAY)
1288
1289                         if self.isSeekable():
1290                                 Notifications.AddNotification(MessageBox,_("Record started! Stopping timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1291
1292                         self.stopTimeshiftConfirmed(True, False)
1293
1294                 # Restart Timeshift when all records stopped
1295                 if timer.state == TimerEntry.StateEnded and not self.timeshift_enabled and not self.pts_record_running:
1296                         self.activatePermanentTimeshift()
1297
1298                 # Restart Merge-Timer when all records stopped
1299                 if timer.state == TimerEntry.StateEnded and self.pts_mergeRecords_timer.isActive():
1300                         self.pts_mergeRecords_timer.stop()
1301                         self.pts_mergeRecords_timer.start(15000, True)
1302
1303                 # Restart FrontPanel LED when still copying or merging files
1304                 # ToDo: Only do this on PTS Events and not events from other jobs
1305                 if timer.state == TimerEntry.StateEnded and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeRecords_timer.isActive()):
1306                         self.ptsFrontpanelActions("start")
1307                         config.plugins.pts.isRecording.value = True
1308
1309         def ptsLiveTVStatus(self):
1310                 service = self.session.nav.getCurrentService()
1311                 info = service and service.info()
1312                 sTSID = info and info.getInfo(iServiceInformation.sTSID) or -1
1313
1314                 if sTSID is None or sTSID == -1:
1315                         return False
1316                 else:
1317                         return True
1318
1319         def ptsLengthCheck(self):
1320                 # Check if we are in TV Mode ...
1321                 if self.ptsLiveTVStatus() is False:
1322                         self.timeshift_enabled = 0
1323                         self.pts_LengthCheck_timer.stop()
1324                         return
1325
1326                 if config.plugins.pts.stopwhilerecording.value and self.pts_record_running:
1327                         return
1328
1329                 # Length Check
1330                 if config.plugins.pts.enabled.value and self.session.screen["Standby"].boolean is not True and self.timeshift_enabled and (time() - self.pts_starttime) >= (config.plugins.pts.maxlength.value * 60):
1331                         if self.save_current_timeshift:
1332                                 self.saveTimeshiftActions("savetimeshift")
1333                                 self.activatePermanentTimeshift()
1334                                 self.save_current_timeshift = True
1335                         else:
1336                                 self.activatePermanentTimeshift()
1337                         Notifications.AddNotification(MessageBox,_("Maximum Timeshift length per Event reached!\nRestarting Timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1338
1339 #Replace the InfoBar with our version ;)
1340 Screens.InfoBar.InfoBar = InfoBar
1341
1342 ################################
1343 ##### Class Standby Hack 1 #####
1344 ################################
1345 TryQuitMainloop_getRecordEvent = Screens.Standby.TryQuitMainloop.getRecordEvent
1346
1347 class TryQuitMainloopPTS(TryQuitMainloop):
1348         def __init__(self, session, retvalue=1, timeout=-1, default_yes = True):
1349                 TryQuitMainloop.__init__(self, session, retvalue, timeout, default_yes)
1350
1351                 self.session.ptsmainloopvalue = retvalue
1352
1353         def getRecordEvent(self, recservice, event):
1354                 if event == iRecordableService.evEnd and (config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1):
1355                         return
1356                 else:
1357                         TryQuitMainloop_getRecordEvent(self, recservice, event)
1358
1359 Screens.Standby.TryQuitMainloop = TryQuitMainloopPTS
1360
1361 ################################
1362 ##### Class Standby Hack 2 #####
1363 ################################
1364
1365 Screens_Standby_Standby = Screens.Standby.Standby
1366
1367 class StandbyPTS(Standby):
1368         def __init__(self, session):
1369                 if InfoBar and InfoBar.instance and InfoBar.ptsGetSaveTimeshiftStatus(InfoBar.instance):
1370                         self.skin = """<screen position="0,0" size="0,0"/>"""
1371                         Screen.__init__(self, session)
1372                         self.onFirstExecBegin.append(self.showMessageBox)
1373                         self.onHide.append(self.close)
1374                 else:
1375                         Standby.__init__(self, session)
1376                         self.skinName = "Standby"
1377
1378         def showMessageBox(self):
1379                 if InfoBar and InfoBar.instance:
1380                         InfoBar.saveTimeshiftActions(InfoBar.instance, postaction="standby")
1381
1382 Screens.Standby.Standby = StandbyPTS
1383
1384 ############
1385 #zapUp Hack#
1386 ############
1387 InfoBarChannelSelection_zapUp = InfoBarChannelSelection.zapUp
1388
1389 def zapUp(self):
1390         if self.pts_blockZap_timer.isActive():
1391                 return
1392
1393         if self.save_current_timeshift and self.timeshift_enabled:
1394                 InfoBar.saveTimeshiftActions(self, postaction="zapUp")
1395         else:
1396                 InfoBarChannelSelection_zapUp(self)
1397
1398 InfoBarChannelSelection.zapUp = zapUp
1399
1400 ##############
1401 #zapDown Hack#
1402 ##############
1403 InfoBarChannelSelection_zapDown = InfoBarChannelSelection.zapDown
1404
1405 def zapDown(self):
1406         if self.pts_blockZap_timer.isActive():
1407                 return
1408
1409         if self.save_current_timeshift and self.timeshift_enabled:
1410                 InfoBar.saveTimeshiftActions(self, postaction="zapDown")
1411         else:
1412                 InfoBarChannelSelection_zapDown(self)
1413
1414 InfoBarChannelSelection.zapDown = zapDown
1415
1416 ##################
1417 #historyBack Hack#
1418 ##################
1419 InfoBarChannelSelection_historyBack = InfoBarChannelSelection.historyBack
1420
1421 def historyBack(self):
1422         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1423                 InfoBarTimeshiftState._mayShow(self)
1424                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1425                 if self.seekstate != self.SEEK_STATE_PLAY:
1426                         self.setSeekState(self.SEEK_STATE_PLAY)
1427                 self.ptsSeekPointerOK()
1428         elif self.save_current_timeshift and self.timeshift_enabled:
1429                 InfoBar.saveTimeshiftActions(self, postaction="historyBack")
1430         else:
1431                 InfoBarChannelSelection_historyBack(self)
1432
1433 InfoBarChannelSelection.historyBack = historyBack
1434
1435 ##################
1436 #historyNext Hack#
1437 ##################
1438 InfoBarChannelSelection_historyNext = InfoBarChannelSelection.historyNext
1439
1440 def historyNext(self):
1441         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1442                 InfoBarTimeshiftState._mayShow(self)
1443                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1444                 if self.seekstate != self.SEEK_STATE_PLAY:
1445                         self.setSeekState(self.SEEK_STATE_PLAY)
1446                 self.ptsSeekPointerOK()
1447         elif self.save_current_timeshift and self.timeshift_enabled:
1448                 InfoBar.saveTimeshiftActions(self, postaction="historyNext")
1449         else:
1450                 InfoBarChannelSelection_historyNext(self)
1451
1452 InfoBarChannelSelection.historyNext = historyNext
1453
1454 ######################
1455 #switchChannelUp Hack#
1456 ######################
1457 InfoBarChannelSelection_switchChannelUp = InfoBarChannelSelection.switchChannelUp
1458
1459 def switchChannelUp(self):
1460         if self.save_current_timeshift and self.timeshift_enabled:
1461                 InfoBar.saveTimeshiftActions(self, postaction="switchChannelUp")
1462         else:
1463                 InfoBarChannelSelection_switchChannelUp(self)
1464
1465 InfoBarChannelSelection.switchChannelUp = switchChannelUp
1466
1467 ########################
1468 #switchChannelDown Hack#
1469 ########################
1470 InfoBarChannelSelection_switchChannelDown = InfoBarChannelSelection.switchChannelDown
1471
1472 def switchChannelDown(self):
1473         if self.save_current_timeshift and self.timeshift_enabled:
1474                 InfoBar.saveTimeshiftActions(self, postaction="switchChannelDown")
1475         else:
1476                 InfoBarChannelSelection_switchChannelDown(self)
1477
1478 InfoBarChannelSelection.switchChannelDown = switchChannelDown
1479
1480 ######################
1481 #openServiceList Hack#
1482 ######################
1483 InfoBarChannelSelection_openServiceList = InfoBarChannelSelection.openServiceList
1484
1485 def openServiceList(self):
1486         if self.save_current_timeshift and self.timeshift_enabled:
1487                 InfoBar.saveTimeshiftActions(self, postaction="openServiceList")
1488         else:
1489                 InfoBarChannelSelection_openServiceList(self)
1490
1491 InfoBarChannelSelection.openServiceList = openServiceList
1492
1493 ###########################
1494 #showRadioChannelList Hack#
1495 ###########################
1496 InfoBarChannelSelection_showRadioChannelList = InfoBarChannelSelection.showRadioChannelList
1497
1498 def showRadioChannelList(self, zap=False):
1499         if self.save_current_timeshift and self.timeshift_enabled:
1500                 InfoBar.saveTimeshiftActions(self, postaction="showRadioChannelList")
1501         else:
1502                 InfoBarChannelSelection_showRadioChannelList(self, zap)
1503
1504 InfoBarChannelSelection.showRadioChannelList = showRadioChannelList
1505
1506 #######################
1507 #InfoBarNumberZap Hack#
1508 #######################
1509 InfoBarNumberZap_keyNumberGlobal = InfoBarNumberZap.keyNumberGlobal
1510
1511 def keyNumberGlobal(self, number):
1512         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable() and number == 0:
1513                 InfoBarTimeshiftState._mayShow(self)
1514                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX/2, self.pvrStateDialog["PTSSeekPointer"].position[1])
1515                 if self.seekstate != self.SEEK_STATE_PLAY:
1516                         self.setSeekState(self.SEEK_STATE_PLAY)
1517                 self.ptsSeekPointerOK()
1518                 return
1519
1520         if self.pts_blockZap_timer.isActive():
1521                 return
1522
1523         if self.save_current_timeshift and self.timeshift_enabled:
1524                 InfoBar.saveTimeshiftActions(self)
1525                 return
1526
1527         InfoBarNumberZap_keyNumberGlobal(self, number)
1528         if number and config.plugins.pts.enabled.value and self.timeshift_enabled and not self.isSeekable():
1529                 self.session.openWithCallback(self.numberEntered, NumberZap, number)
1530
1531 InfoBarNumberZap.keyNumberGlobal = keyNumberGlobal
1532
1533 #############################
1534 # getNextRecordingTime Hack #
1535 #############################
1536 RecordTimer_getNextRecordingTime = RecordTimer.getNextRecordingTime
1537
1538 def getNextRecordingTime(self):
1539         nextrectime = RecordTimer_getNextRecordingTime(self)
1540         faketime = time()+300
1541
1542         if config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1:
1543                 if nextrectime > 0 and nextrectime < faketime:
1544                         return nextrectime
1545                 else:
1546                         return faketime
1547         else:
1548                 return nextrectime
1549
1550 RecordTimer.getNextRecordingTime = getNextRecordingTime
1551
1552 ############################
1553 #InfoBarTimeshiftState Hack#
1554 ############################
1555 def _mayShow(self):
1556         if InfoBar and InfoBar.instance and self.execing and self.timeshift_enabled and self.isSeekable():
1557                 InfoBar.ptsSeekPointerSetCurrentPos(self)
1558                 self.pvrStateDialog.show()
1559
1560                 self.pvrstate_hide_timer = eTimer()
1561                 self.pvrstate_hide_timer.callback.append(self.pvrStateDialog.hide)
1562
1563                 if self.seekstate == self.SEEK_STATE_PLAY:
1564                         idx = config.usage.infobar_timeout.index
1565                         if not idx:
1566                                 idx = 5
1567                         self.pvrstate_hide_timer.start(idx*1000, True)
1568                 else:
1569                         self.pvrstate_hide_timer.stop()
1570         elif self.execing and self.timeshift_enabled and not self.isSeekable():
1571                 self.pvrStateDialog.hide()
1572
1573 InfoBarTimeshiftState._mayShow = _mayShow
1574
1575 ##################
1576 # seekBack Hack  #
1577 ##################
1578 InfoBarSeek_seekBack = InfoBarSeek.seekBack
1579
1580 def seekBack(self):
1581         InfoBarSeek_seekBack(self)
1582         self.pts_lastseekspeed = self.seekstate[1]
1583
1584 InfoBarSeek.seekBack = seekBack
1585
1586 ####################
1587 #instantRecord Hack#
1588 ####################
1589 InfoBarInstantRecord_instantRecord = InfoBarInstantRecord.instantRecord
1590
1591 def instantRecord(self):
1592         if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1593                 InfoBarInstantRecord_instantRecord(self)
1594                 return
1595
1596         dir = preferredInstantRecordPath()
1597         if not dir or not fileExists(dir, 'w'):
1598                 dir = defaultMoviePath()
1599                 
1600         if not harddiskmanager.inside_mountpoint(dir):
1601                 if harddiskmanager.HDDCount() and not harddiskmanager.HDDEnabledCount():
1602                         self.session.open(MessageBox, _("Unconfigured storage devices found!") + "\n" \
1603                                 + _("Please make sure to set up your storage devices with the storage management in menu -> setup -> system -> storage devices."), MessageBox.TYPE_ERROR)
1604                         return
1605                 elif harddiskmanager.HDDEnabledCount() and defaultStorageDevice() == "<undefined>":
1606                         self.session.open(MessageBox, _("No default storage device found!") + "\n" \
1607                                 + _("Please make sure to set up your default storage device in menu -> setup -> system -> recording paths."), MessageBox.TYPE_ERROR)
1608                         return
1609                 elif harddiskmanager.HDDEnabledCount() and defaultStorageDevice() != "<undefined>":
1610                         part = harddiskmanager.getDefaultStorageDevicebyUUID(defaultStorageDevice())
1611                         if part is None:
1612                                 self.session.open(MessageBox, _("Default storage device is not available!") + "\n" \
1613                                         + _("Please verify if your default storage device is attached or set up your default storage device in menu -> setup -> system -> recording paths."), MessageBox.TYPE_ERROR)
1614                                 return
1615                 else:
1616                         # XXX: this message is a little odd as we might be recording to a remote device
1617                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1618                         return
1619
1620         if self.isInstantRecordRunning():
1621                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1622                         title=_("A recording is currently running.\nWhat do you want to do?"), \
1623                         list=((_("stop recording"), "stop"), \
1624                         (_("add recording (stop after current event)"), "event"), \
1625                         (_("add recording (indefinitely)"), "indefinitely"), \
1626                         (_("add recording (enter recording duration)"), "manualduration"), \
1627                         (_("add recording (enter recording endtime)"), "manualendtime"), \
1628                         (_("change recording (duration)"), "changeduration"), \
1629                         (_("change recording (endtime)"), "changeendtime"), \
1630                         (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1631                         (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1632                         (_("do nothing"), "no")))
1633         else:
1634                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1635                         title=_("Start recording?"), \
1636                         list=((_("add recording (stop after current event)"), "event"), \
1637                         (_("add recording (indefinitely)"), "indefinitely"), \
1638                         (_("add recording (enter recording duration)"), "manualduration"), \
1639                         (_("add recording (enter recording endtime)"), "manualendtime"), \
1640                         (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1641                         (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1642                         (_("don't record"), "no")))
1643
1644 InfoBarInstantRecord.instantRecord = instantRecord
1645
1646 #############################
1647 #recordQuestionCallback Hack#
1648 #############################
1649 InfoBarInstantRecord_recordQuestionCallback = InfoBarInstantRecord.recordQuestionCallback
1650
1651 def recordQuestionCallback(self, answer):
1652         InfoBarInstantRecord_recordQuestionCallback(self, answer)
1653
1654         if config.plugins.pts.enabled.value:
1655                 if answer is not None and answer[1] == "savetimeshift":
1656                         if InfoBarSeek.isSeekable(self) and self.pts_eventcount != self.pts_currplaying:
1657                                 InfoBar.SaveTimeshift(self, timeshiftfile="pts_livebuffer.%s" % self.pts_currplaying)
1658                         else:
1659                                 Notifications.AddNotification(MessageBox,_("Timeshift will get saved at end of event!"), MessageBox.TYPE_INFO, timeout=5)
1660                                 self.save_current_timeshift = True
1661                                 config.plugins.pts.isRecording.value = True
1662                 if answer is not None and answer[1] == "savetimeshiftEvent":
1663                         InfoBar.saveTimeshiftEventPopup(self)
1664
1665                 if answer is not None and answer[1].startswith("pts_livebuffer") is True:
1666                         InfoBar.SaveTimeshift(self, timeshiftfile=answer[1])
1667
1668 InfoBarInstantRecord.recordQuestionCallback = recordQuestionCallback
1669
1670 ############################
1671 #####  SETTINGS SCREEN #####
1672 ############################
1673 class PermanentTimeShiftSetup(Screen, ConfigListScreen):
1674         def __init__(self, session):
1675                 Screen.__init__(self, session)
1676                 self.skinName = [ "PTSSetup", "Setup" ]
1677                 self.setup_title = _("Permanent Timeshift Settings")
1678
1679                 self.onChangedEntry = [ ]
1680                 self.list = [ ]
1681                 ConfigListScreen.__init__(self, self.list, session = session, on_change = self.changedEntry)
1682
1683                 self["actions"] = ActionMap(["SetupActions", "ColorActions"],
1684                 {
1685                         "ok": self.SaveSettings,
1686                         "green": self.SaveSettings,
1687                         "red": self.Exit,
1688                         "cancel": self.Exit
1689                 }, -2)
1690
1691                 self["key_green"] = StaticText(_("OK"))
1692                 self["key_red"] = StaticText(_("Cancel"))
1693
1694                 self.createSetup()
1695                 self.onLayoutFinish.append(self.layoutFinished)
1696
1697         def layoutFinished(self):
1698                 self.setTitle(self.setup_title)
1699
1700         def createSetup(self):
1701                 self.list = [ getConfigListEntry(_("Permanent Timeshift Enable"), config.plugins.pts.enabled) ]
1702                 if config.plugins.pts.enabled.value:
1703                         self.list.extend((
1704                                 getConfigListEntry(_("Permanent Timeshift Max Events"), config.plugins.pts.maxevents),
1705                                 getConfigListEntry(_("Permanent Timeshift Max Length"), config.plugins.pts.maxlength),
1706                                 getConfigListEntry(_("Permanent Timeshift Start Delay"), config.plugins.pts.startdelay),
1707                                 getConfigListEntry(_("Timeshift-Save Action on zap"), config.plugins.pts.favoriteSaveAction),
1708                                 getConfigListEntry(_("Stop timeshift while recording?"), config.plugins.pts.stopwhilerecording),
1709                                 getConfigListEntry(_("Show PTS Infobar while timeshifting?"), config.plugins.pts.showinfobar)
1710                         ))
1711
1712                 # Permanent Recording Hack
1713                 if fileExists("/usr/lib/enigma2/python/Plugins/Extensions/HouseKeeping/plugin.py"):
1714                         self.list.append(getConfigListEntry(_("Beta: Enable Permanent Recording?"), config.plugins.pts.permanentrecording))
1715
1716                 self["config"].list = self.list
1717                 self["config"].setList(self.list)
1718
1719         def keyLeft(self):
1720                 ConfigListScreen.keyLeft(self)
1721                 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1722                         self.createSetup()
1723
1724         def keyRight(self):
1725                 ConfigListScreen.keyRight(self)
1726                 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1727                         self.createSetup()
1728
1729         def changedEntry(self):
1730                 for x in self.onChangedEntry:
1731                         x()
1732
1733         def getCurrentEntry(self):
1734                 return self["config"].getCurrent()[0]
1735
1736         def getCurrentValue(self):
1737                 return str(self["config"].getCurrent()[1].getText())
1738
1739         def createSummary(self):
1740                 return SetupSummary
1741
1742         def SaveSettings(self):
1743                 config.plugins.pts.save()
1744                 configfile.save()
1745                 self.close()
1746
1747         def Exit(self):
1748                 self.close()
1749
1750 #################################################
1751
1752 def startSetup(menuid):
1753         if menuid != "system":
1754                 return [ ]
1755         return [(_("Timeshift Settings"), PTSSetupMenu, "pts_setup", 50)]
1756
1757 def PTSSetupMenu(session, **kwargs):
1758         session.open(PermanentTimeShiftSetup)
1759
1760 def Plugins(path, **kwargs):
1761         return [ PluginDescriptor(name=_("Permanent Timeshift Settings"), description=_("Permanent Timeshift Settings"), where=PluginDescriptor.WHERE_MENU, fnc=startSetup) ]