Implemented Workaround for early Enigma2-3.20 bug in
[enigma2-plugins.git] / permanenttimeshift / src / plugin.py
1 #####################################################
2 # Permanent Timeshift Plugin for Enigma2 Dreamboxes
3 # Coded by Homey (c) 2011
4 #
5 # Version: 1.1
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, remove as os_remove, 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("PTSPlugin", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/PermanentTimeshift/locale/"))
54
55 def _(txt):
56         t = gettext.dgettext("PTSPlugin", 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="340,5" size="65,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 eraseFile(self, filePath):
414                 # We only want to use E2 Background Eraser if file has no other links on it.
415                 # Early Enigma2 3.20 Releases had a bug that also affected the hardlinked files.
416                 if fileExists(filePath):
417                         if os_stat(filePath).st_nlink != 1:
418                                 os_remove(filePath)
419                         else:
420                                 self.BgFileEraser.erase(filePath)
421
422         def eraseTimeshiftFile(self):
423                 for filename in os_listdir(config.usage.timeshift_path.value):
424                         if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy"):
425                                 self.eraseFile("%s/%s" % (config.usage.timeshift_path.value,filename))
426
427         def activatePermanentTimeshift(self):
428                 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):
429                         return
430
431                 # Replace PVR Timeshift State Icon
432                 if config.plugins.pts.showinfobar.value:
433                         if self.pts_pvrStateDialog != "PTSTimeshiftState":
434                                 self.pts_pvrStateDialog = "PTSTimeshiftState"
435                                 self.pvrStateDialog = self.session.instantiateDialog(PTSTimeshiftState)
436                 elif not config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog != "TimeshiftState":
437                         self.pts_pvrStateDialog = "TimeshiftState"
438                         self.pvrStateDialog = self.session.instantiateDialog(TimeshiftState)
439
440                 # Set next-file on event change only when watching latest timeshift ...
441                 if self.isSeekable() and self.pts_eventcount == self.pts_currplaying:
442                         pts_setnextfile = True
443                 else:
444                         pts_setnextfile = False
445
446                 # Update internal Event Counter
447                 if self.pts_eventcount >= config.plugins.pts.maxevents.value:
448                         self.pts_eventcount = 0
449
450                 self.pts_eventcount += 1
451
452                 # Do not switch back to LiveTV while timeshifting
453                 if self.isSeekable():
454                         switchToLive = False
455                 else:
456                         switchToLive = True
457
458                 # setNextPlaybackFile() on event change while timeshifting
459                 if self.pts_eventcount > 1 and self.isSeekable() and pts_setnextfile:
460                         self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (self.pts_eventcount))
461
462                 # (Re)start Timeshift now
463                 self.stopTimeshiftConfirmed(True, switchToLive)
464                 ts = self.getTimeshift()
465                 if ts and not ts.startTimeshift():
466                         self.pts_starttime = time()
467                         self.pts_LengthCheck_timer.start(120000)
468                         self.timeshift_enabled = 1
469                         self.save_timeshift_postaction = None
470                         self.ptsGetEventInfo()
471                         self.ptsCreateHardlink()
472                         self.__seekableStatusChanged()
473                 else:
474                         self.pts_eventcount = 0
475
476         def startTimeshift(self):
477                 if config.plugins.pts.enabled.value:
478                         self.pts_delay_timer.stop()
479                         self.activatePermanentTimeshift()
480                         self.activateTimeshiftEndAndPause()
481                 else:
482                         InfoBarOrg.startTimeshift(self)
483
484         def stopTimeshift(self):
485                 if not self.timeshift_enabled:
486                         return 0
487
488                 # Jump Back to Live TV
489                 if config.plugins.pts.enabled.value and self.timeshift_enabled:
490                         if self.isSeekable():
491                                 self.pts_switchtolive = True
492                                 self.ptsSetNextPlaybackFile("")
493                                 self.setSeekState(self.SEEK_STATE_PAUSE)
494                                 if self.seekstate != self.SEEK_STATE_PLAY:
495                                         self.setSeekState(self.SEEK_STATE_PLAY)
496                                 self.doSeek(-1) # seek 1 gop before end
497                                 self.seekFwd() # seekFwd to switch to live TV
498                                 return 1
499                         return 0
500                 InfoBarOrg.stopTimeshift(self)
501
502         def stopTimeshiftConfirmed(self, confirmed, switchToLive=True):
503                 was_enabled = self.timeshift_enabled
504
505                 if not confirmed:
506                         return
507                 ts = self.getTimeshift()
508                 if ts is None:
509                         return
510
511                 # Get rid of old timeshift file before E2 truncates its filesize
512                 self.eraseTimeshiftFile()
513
514                 # Stop Timeshift now
515                 try:
516                         ts.stopTimeshift(switchToLive)
517                 except:
518                         ts.stopTimeshift()
519
520                 self.timeshift_enabled = 0
521                 self.__seekableStatusChanged()
522
523                 if was_enabled and not self.timeshift_enabled:
524                         self.timeshift_enabled = 0
525                         self.pts_LengthCheck_timer.stop()
526
527         def restartTimeshift(self):
528                 self.activatePermanentTimeshift()
529                 Notifications.AddNotification(MessageBox, _("PTS-Plugin: Restarting Timeshift!"), MessageBox.TYPE_INFO, timeout=5)
530
531         def saveTimeshiftPopup(self):
532                 self.session.openWithCallback(self.saveTimeshiftPopupCallback, ChoiceBox, \
533                         title=_("The Timeshift record was not saved yet!\nWhat do you want to do now with the timeshift file?"), \
534                         list=((_("Save Timeshift as Movie and stop recording"), "savetimeshift"), \
535                         (_("Save Timeshift as Movie and continue recording"), "savetimeshiftandrecord"), \
536                         (_("Don't save Timeshift as Movie"), "noSave")))
537
538         def saveTimeshiftPopupCallback(self, answer):
539                 if answer is None:
540                         return
541
542                 if answer[1] == "savetimeshift":
543                         self.saveTimeshiftActions("savetimeshift", self.save_timeshift_postaction)
544                 elif answer[1] == "savetimeshiftandrecord":
545                         self.saveTimeshiftActions("savetimeshiftandrecord", self.save_timeshift_postaction)
546                 elif answer[1] == "noSave":
547                         self.save_current_timeshift = False
548                         self.saveTimeshiftActions("noSave", self.save_timeshift_postaction)
549
550         def saveTimeshiftEventPopup(self):
551                 filecount = 0
552                 entrylist = []
553                 entrylist.append((_("Current Event:")+" %s" % (self.pts_curevent_name), "savetimeshift"))
554
555                 filelist = os_listdir(config.usage.timeshift_path.value)
556
557                 if filelist is not None:
558                         filelist.sort()
559
560                 for filename in filelist:
561                         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):
562                                 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
563                                 if statinfo.st_mtime < (time()-5.0):
564                                         # Get Event Info from meta file
565                                         readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,filename), "r")
566                                         servicerefname = readmetafile.readline()[0:-1]
567                                         eventname = readmetafile.readline()[0:-1]
568                                         description = readmetafile.readline()[0:-1]
569                                         begintime = readmetafile.readline()[0:-1]
570                                         readmetafile.close()
571
572                                         # Add Event to list
573                                         filecount += 1
574                                         entrylist.append((_("Record") + " #%s (%s): %s" % (filecount,strftime("%H:%M",localtime(int(begintime))),eventname), "%s" % filename))
575
576                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, title=_("Which event do you want to save permanently?"), list=entrylist)
577
578         def saveTimeshiftActions(self, action=None, postaction=None):
579                 self.save_timeshift_postaction = postaction
580
581                 if action is None:
582                         if config.plugins.pts.favoriteSaveAction.value == "askuser":
583                                 self.saveTimeshiftPopup()
584                                 return
585                         elif config.plugins.pts.favoriteSaveAction.value == "savetimeshift":
586                                 self.SaveTimeshift()
587                         elif config.plugins.pts.favoriteSaveAction.value == "savetimeshiftandrecord":
588                                 if self.pts_curevent_end > time():
589                                         self.SaveTimeshift(mergelater=True)
590                                         self.ptsRecordCurrentEvent()
591                                 else:
592                                         self.SaveTimeshift()
593                         elif config.plugins.pts.favoriteSaveAction.value == "noSave":
594                                 config.plugins.pts.isRecording.value = False
595                                 self.save_current_timeshift = False
596                 elif action == "savetimeshift":
597                         self.SaveTimeshift()
598                 elif action == "savetimeshiftandrecord":
599                         if self.pts_curevent_end > time():
600                                 self.SaveTimeshift(mergelater=True)
601                                 self.ptsRecordCurrentEvent()
602                         else:
603                                 self.SaveTimeshift()
604                 elif action == "noSave":
605                         config.plugins.pts.isRecording.value = False
606                         self.save_current_timeshift = False
607
608                 # Get rid of old timeshift file before E2 truncates its filesize
609                 if self.save_timeshift_postaction is not None:
610                         self.eraseTimeshiftFile()
611
612                 # Post PTS Actions like ZAP or whatever the user requested
613                 if self.save_timeshift_postaction == "zapUp":
614                         InfoBarChannelSelection.zapUp(self)
615                 elif self.save_timeshift_postaction == "zapDown":
616                         InfoBarChannelSelection.zapDown(self)
617                 elif self.save_timeshift_postaction == "historyBack":
618                         InfoBarChannelSelection.historyBack(self)
619                 elif self.save_timeshift_postaction == "historyNext":
620                         InfoBarChannelSelection.historyNext(self)
621                 elif self.save_timeshift_postaction == "switchChannelUp":
622                         InfoBarChannelSelection.switchChannelUp(self)
623                 elif self.save_timeshift_postaction == "switchChannelDown":
624                         InfoBarChannelSelection.switchChannelDown(self)
625                 elif self.save_timeshift_postaction == "openServiceList":
626                         InfoBarChannelSelection.openServiceList(self)
627                 elif self.save_timeshift_postaction == "showRadioChannelList":
628                         InfoBarChannelSelection.showRadioChannelList(self, zap=True)
629                 elif self.save_timeshift_postaction == "standby":
630                         Notifications.AddNotification(Screens_Standby_Standby)
631
632         def SaveTimeshift(self, timeshiftfile=None, mergelater=False):
633                 self.save_current_timeshift = False
634                 savefilename = None
635
636                 if timeshiftfile is not None:
637                         savefilename = timeshiftfile
638
639                 if savefilename is None:
640                         for filename in os_listdir(config.usage.timeshift_path.value):
641                                 if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy"):
642                                         try:
643                                                 statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
644                                                 if statinfo.st_mtime > (time()-5.0):
645                                                         savefilename=filename
646                                         except Exception, errormsg:
647                                                 Notifications.AddNotification(MessageBox, _("PTS Plugin Error: %s" % (errormsg)), MessageBox.TYPE_ERROR)
648
649                 if savefilename is None:
650                         Notifications.AddNotification(MessageBox, _("No Timeshift found to save as recording!"), MessageBox.TYPE_ERROR)
651                 else:
652                         timeshift_saved = True
653                         timeshift_saveerror1 = ""
654                         timeshift_saveerror2 = ""
655                         metamergestring = ""
656
657                         config.plugins.pts.isRecording.value = True
658
659                         if mergelater:
660                                 self.pts_mergeRecords_timer.start(120000, True)
661                                 metamergestring = "pts_merge\n"
662
663                         try:
664                                 if timeshiftfile is None:
665                                         # Save Current Event by creating hardlink to ts file
666                                         if self.pts_starttime >= (time()-60):
667                                                 self.pts_starttime -= 60
668
669                                         ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(self.pts_starttime)),self.pts_curevent_station,self.pts_curevent_name)
670                                         try:
671                                                 if config.usage.setup_level.index >= 2:
672                                                         if config.recording.filename_composition.value == "long" and self.pts_curevent_name != pts_curevent_description:
673                                                                 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)
674                                                         elif config.recording.filename_composition.value == "short":
675                                                                 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(self.pts_starttime)),self.pts_curevent_name)
676                                         except Exception, errormsg:
677                                                 print "PTS-Plugin: Using default filename"
678
679                                         if config.recording.ascii_filenames.value:
680                                                 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
681
682                                         fullname = Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
683                                         os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s.ts" % (fullname))
684                                         metafile = open("%s.ts.meta" % (fullname), "w")
685                                         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))
686                                         metafile.close()
687                                         self.ptsCreateEITFile(fullname)
688                                 elif timeshiftfile.startswith("pts_livebuffer"):
689                                         # Save stored timeshift by creating hardlink to ts file
690                                         readmetafile = open("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile), "r")
691                                         servicerefname = readmetafile.readline()[0:-1]
692                                         eventname = readmetafile.readline()[0:-1]
693                                         description = readmetafile.readline()[0:-1]
694                                         begintime = readmetafile.readline()[0:-1]
695                                         readmetafile.close()
696
697                                         ptsfilename = "%s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname)
698                                         try:
699                                                 if config.usage.setup_level.index >= 2:
700                                                         if config.recording.filename_composition.value == "long" and eventname != description:
701                                                                 ptsfilename = "%s - %s - %s - %s" % (strftime("%Y%m%d %H%M",localtime(int(begintime))),self.pts_curevent_station,eventname,description)
702                                                         elif config.recording.filename_composition.value == "short":
703                                                                 ptsfilename = "%s - %s" % (strftime("%Y%m%d",localtime(int(begintime))),eventname)
704                                         except Exception, errormsg:
705                                                 print "PTS-Plugin: Using default filename"
706
707                                         if config.recording.ascii_filenames.value:
708                                                 ptsfilename = ASCIItranslit.legacyEncode(ptsfilename)
709
710                                         fullname=Directories.getRecordingFilename(ptsfilename,config.usage.default_path.value)
711                                         os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts" % (fullname))
712                                         os_link("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
713                                         if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
714                                                 os_link("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
715
716                                         # Add merge-tag to metafile
717                                         if mergelater:
718                                                 metafile = open("%s.ts.meta" % (fullname), "a")
719                                                 metafile.write("%s\n" % (metamergestring))
720                                                 metafile.close()
721
722                                 # Create AP and SC Files when not merging
723                                 if not mergelater:
724                                         self.ptsCreateAPSCFiles(fullname+".ts")
725
726                         except Exception, errormsg:
727                                 timeshift_saved = False
728                                 timeshift_saveerror1 = errormsg
729
730                         # Hmpppf! Saving Timeshift via Hardlink-Method failed. Probably other device?
731                         # Let's try to copy the file in background now! This might take a while ...
732                         if not timeshift_saved:
733                                 try:
734                                         stat = statvfs(config.usage.default_path.value)
735                                         freespace = stat.f_bfree / 1000 * stat.f_bsize / 1000
736                                         randomint = randint(1, 999)
737
738                                         if timeshiftfile is None:
739                                                 # Get Filesize for Free Space Check
740                                                 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value,savefilename)) / (1024*1024))
741
742                                                 # Save Current Event by copying it to the other device
743                                                 if filesize <= freespace:
744                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,savefilename), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,savefilename,randomint))
745                                                         copy_file = savefilename
746                                                         metafile = open("%s.ts.meta" % (fullname), "w")
747                                                         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))
748                                                         metafile.close()
749                                                         self.ptsCreateEITFile(fullname)
750                                         elif timeshiftfile.startswith("pts_livebuffer"):
751                                                 # Get Filesize for Free Space Check
752                                                 filesize = int(os_path.getsize("%s/%s" % (config.usage.timeshift_path.value, timeshiftfile)) / (1024*1024))
753
754                                                 # Save stored timeshift by copying it to the other device
755                                                 if filesize <= freespace:
756                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,timeshiftfile), "%s/%s.%s.copy" % (config.usage.timeshift_path.value,timeshiftfile,randomint))
757                                                         copyfile("%s/%s.meta" % (config.usage.timeshift_path.value,timeshiftfile),"%s.ts.meta" % (fullname))
758                                                         if fileExists("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile)):
759                                                                 copyfile("%s/%s.eit" % (config.usage.timeshift_path.value,timeshiftfile),"%s.eit" % (fullname))
760                                                         copy_file = timeshiftfile
761
762                                                 # Add merge-tag to metafile
763                                                 if mergelater:
764                                                         metafile = open("%s.ts.meta" % (fullname), "a")
765                                                         metafile.write("%s\n" % (metamergestring))
766                                                         metafile.close()
767
768                                         # Only copy file when enough disk-space available!
769                                         if filesize <= freespace:
770                                                 timeshift_saved = True
771                                                 copy_file = copy_file+"."+str(randomint)
772
773                                                 # Get Event Info from meta file
774                                                 if fileExists("%s.ts.meta" % (fullname)):
775                                                         readmetafile = open("%s.ts.meta" % (fullname), "r")
776                                                         servicerefname = readmetafile.readline()[0:-1]
777                                                         eventname = readmetafile.readline()[0:-1]
778                                                 else:
779                                                         eventname = "";
780
781                                                 JobManager.AddJob(CopyTimeshiftJob(self, "cp \"%s/%s.copy\" \"%s.ts\"" % (config.usage.timeshift_path.value,copy_file,fullname), copy_file, fullname, eventname))
782                                                 if not Screens.Standby.inTryQuitMainloop and not Screens.Standby.inStandby and not mergelater and self.save_timeshift_postaction != "standby":
783                                                         Notifications.AddNotification(MessageBox, _("Saving timeshift as movie now. This might take a while!"), MessageBox.TYPE_INFO, timeout=5)
784                                         else:
785                                                 timeshift_saved = False
786                                                 timeshift_saveerror1 = ""
787                                                 timeshift_saveerror2 = _("Not enough free Diskspace!\n\nFilesize: %sMB\nFree Space: %sMB\nPath: %s" % (filesize,freespace,config.usage.default_path.value))
788
789                                 except Exception, errormsg:
790                                         timeshift_saved = False
791                                         timeshift_saveerror2 = errormsg
792
793                         if not timeshift_saved:
794                                 config.plugins.pts.isRecording.value = False
795                                 self.save_timeshift_postaction = None
796                                 errormessage = str(timeshift_saveerror1) + "\n" + str(timeshift_saveerror2)
797                                 Notifications.AddNotification(MessageBox, _("Timeshift save failed!")+"\n\n%s" % errormessage, MessageBox.TYPE_ERROR)
798
799         def ptsCleanTimeshiftFolder(self):
800                 if not config.plugins.pts.enabled.value or self.ptsCheckTimeshiftPath() is False or self.session.screen["Standby"].boolean is True:
801                         return
802
803                 try:
804                         for filename in os_listdir(config.usage.timeshift_path.value):
805                                 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):
806
807                                         statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
808                                         # if no write for 5 sec = stranded timeshift
809                                         if statinfo.st_mtime < (time()-5.0):
810                                                 print "PTS-Plugin: Erasing stranded timeshift %s" % filename
811                                                 self.eraseFile("%s/%s" % (config.usage.timeshift_path.value,filename))
812
813                                                 # Delete Meta and EIT File too
814                                                 if filename.startswith("pts_livebuffer.") is True:
815                                                         self.BgFileEraser.erase("%s/%s.meta" % (config.usage.timeshift_path.value,filename))
816                                                         self.BgFileEraser.erase("%s/%s.eit" % (config.usage.timeshift_path.value,filename))
817                 except:
818                         print "PTS: IO-Error while cleaning Timeshift Folder ..."
819
820         def ptsGetEventInfo(self):
821                 event = None
822                 try:
823                         serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
824                         serviceHandler = eServiceCenter.getInstance()
825                         info = serviceHandler.info(serviceref)
826
827                         self.pts_curevent_servicerefname = serviceref.toString()
828                         self.pts_curevent_station = info.getName(serviceref)
829
830                         service = self.session.nav.getCurrentService()
831                         info = service and service.info()
832                         event = info and info.getEvent(0)
833                 except Exception, errormsg:
834                         Notifications.AddNotification(MessageBox, _("Getting Event Info failed!")+"\n\n%s" % errormsg, MessageBox.TYPE_ERROR, timeout=10)
835
836                 if event is not None:
837                         curEvent = parseEvent(event)
838                         self.pts_curevent_begin = int(curEvent[0])
839                         self.pts_curevent_end = int(curEvent[1])
840                         self.pts_curevent_name = curEvent[2]
841                         self.pts_curevent_description = curEvent[3]
842                         self.pts_curevent_eventid = curEvent[4]
843
844         def ptsFrontpanelActions(self, action=None):
845                 if self.session.nav.RecordTimer.isRecording() or SystemInfo.get("NumFrontpanelLEDs", 0) == 0:
846                         return
847
848                 try:
849                         if action == "start":
850                                 if fileExists("/proc/stb/fp/led_set_pattern"):
851                                         open("/proc/stb/fp/led_set_pattern", "w").write("0xa7fccf7a")
852                                 elif fileExists("/proc/stb/fp/led0_pattern"):
853                                         open("/proc/stb/fp/led0_pattern", "w").write("0x55555555")
854                                 if fileExists("/proc/stb/fp/led_pattern_speed"):
855                                         open("/proc/stb/fp/led_pattern_speed", "w").write("20")
856                                 elif fileExists("/proc/stb/fp/led_set_speed"):
857                                         open("/proc/stb/fp/led_set_speed", "w").write("20")
858                         elif action == "stop":
859                                 if fileExists("/proc/stb/fp/led_set_pattern"):
860                                         open("/proc/stb/fp/led_set_pattern", "w").write("0")
861                                 elif fileExists("/proc/stb/fp/led0_pattern"):
862                                         open("/proc/stb/fp/led0_pattern", "w").write("0")
863                 except Exception, errormsg:
864                         print "PTS Plugin: %s" % (errormsg)
865
866         def ptsCreateHardlink(self):
867                 for filename in os_listdir(config.usage.timeshift_path.value):
868                         if filename.startswith("timeshift.") and not filename.endswith(".del") and not filename.endswith(".copy"):
869                                 try:
870                                         statinfo = os_stat("%s/%s" % (config.usage.timeshift_path.value,filename))
871                                         if statinfo.st_mtime > (time()-5.0):
872                                                 try:
873                                                         self.eraseFile("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
874                                                         self.BgFileEraser.erase("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount))
875                                                 except Exception, errormsg:
876                                                         print "PTS Plugin: %s" % (errormsg)
877
878                                                 try:
879                                                         # Create link to pts_livebuffer file
880                                                         os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
881
882                                                         # Create a Meta File
883                                                         metafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_eventcount), "w")
884                                                         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)))
885                                                         metafile.close()
886                                                 except Exception, errormsg:
887                                                         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)
888
889                                                 # Create EIT File
890                                                 self.ptsCreateEITFile("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_eventcount))
891
892                                                 # Permanent Recording Hack
893                                                 if config.plugins.pts.permanentrecording.value:
894                                                         try:
895                                                                 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)
896                                                                 os_link("%s/%s" % (config.usage.timeshift_path.value,filename), "%s.ts" % (fullname))
897                                                                 # Create a Meta File
898                                                                 metafile = open("%s.ts.meta" % (fullname), "w")
899                                                                 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)))
900                                                                 metafile.close()
901                                                         except Exception, errormsg:
902                                                                 print "PTS Plugin: %s" % (errormsg)
903                                 except Exception, errormsg:
904                                         errormsg = str(errormsg)
905                                         if errormsg.find('Input/output error') != -1:
906                                                 errormsg += _("\nAn Input/output error usually indicates a corrupted filesystem! Please check the filesystem of your timeshift-device!")
907                                         Notifications.AddNotification(MessageBox, _("Creating Hardlink to Timeshift file failed!")+"\n%s" % (errormsg), MessageBox.TYPE_ERROR)
908
909         def ptsRecordCurrentEvent(self):
910                         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)
911                         recording.dontSave = True
912                         self.session.nav.RecordTimer.record(recording)
913                         self.recording.append(recording)
914
915         def ptsMergeRecords(self):
916                 if self.session.nav.RecordTimer.isRecording():
917                         self.pts_mergeRecords_timer.start(120000, True)
918                         return
919
920                 ptsmergeSRC = ""
921                 ptsmergeDEST = ""
922                 ptsmergeeventname = ""
923                 ptsgetnextfile = False
924                 ptsfilemerged = False
925
926                 filelist = os_listdir(config.usage.default_path.value)
927
928                 if filelist is not None:
929                         filelist.sort()
930
931                 for filename in filelist:
932                         if filename.endswith(".meta"):
933                                 # Get Event Info from meta file
934                                 readmetafile = open("%s/%s" % (config.usage.default_path.value,filename), "r")
935                                 servicerefname = readmetafile.readline()[0:-1]
936                                 eventname = readmetafile.readline()[0:-1]
937                                 eventtitle = readmetafile.readline()[0:-1]
938                                 eventtime = readmetafile.readline()[0:-1]
939                                 eventtag = readmetafile.readline()[0:-1]
940                                 readmetafile.close()
941
942                                 if ptsgetnextfile:
943                                         ptsgetnextfile = False
944                                         ptsmergeSRC = filename[0:-5]
945
946                                         if ASCIItranslit.legacyEncode(eventname) == ASCIItranslit.legacyEncode(ptsmergeeventname):
947                                                 # Copy EIT File
948                                                 if fileExists("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3])):
949                                                         copyfile("%s/%s.eit" % (config.usage.default_path.value, ptsmergeSRC[0:-3]),"%s/%s.eit" % (config.usage.default_path.value, ptsmergeDEST[0:-3]))
950
951                                                 # Delete AP and SC Files
952                                                 self.BgFileEraser.erase("%s/%s.ap" % (config.usage.default_path.value, ptsmergeDEST))
953                                                 self.BgFileEraser.erase("%s/%s.sc" % (config.usage.default_path.value, ptsmergeDEST))
954
955                                                 # Add Merge Job to JobManager
956                                                 JobManager.AddJob(MergeTimeshiftJob(self, "cat \"%s/%s\" >> \"%s/%s\"" % (config.usage.default_path.value,ptsmergeSRC,config.usage.default_path.value,ptsmergeDEST), ptsmergeSRC, ptsmergeDEST, eventname))
957                                                 config.plugins.pts.isRecording.value = True
958                                                 ptsfilemerged = True
959                                         else:
960                                                 ptsgetnextfile = True
961
962                                 if eventtag == "pts_merge" and not ptsgetnextfile:
963                                         ptsgetnextfile = True
964                                         ptsmergeDEST = filename[0:-5]
965                                         ptsmergeeventname = eventname
966                                         ptsfilemerged = False
967
968                                         # If still recording or transfering, try again later ...
969                                         if fileExists("%s/%s" % (config.usage.default_path.value,ptsmergeDEST)):
970                                                 statinfo = os_stat("%s/%s" % (config.usage.default_path.value,ptsmergeDEST))
971                                                 if statinfo.st_mtime > (time()-10.0):
972                                                         self.pts_mergeRecords_timer.start(120000, True)
973                                                         return
974
975                                         # Rewrite Meta File to get rid of pts_merge tag
976                                         metafile = open("%s/%s.meta" % (config.usage.default_path.value,ptsmergeDEST), "w")
977                                         metafile.write("%s\n%s\n%s\n%i\n" % (servicerefname,eventname.replace("\n", ""),eventtitle.replace("\n", ""),int(eventtime)))
978                                         metafile.close()
979
980                 # Merging failed :(
981                 if not ptsfilemerged and ptsgetnextfile:
982                         Notifications.AddNotification(MessageBox,_("PTS-Plugin: Merging records failed!"), MessageBox.TYPE_ERROR)
983
984         def ptsCreateAPSCFiles(self, filename):
985                 if fileExists(filename, 'r'):
986                         if fileExists(filename+".meta", 'r'):
987                                 # Get Event Info from meta file
988                                 readmetafile = open(filename+".meta", "r")
989                                 servicerefname = readmetafile.readline()[0:-1]
990                                 eventname = readmetafile.readline()[0:-1]
991                         else:
992                                 eventname = ""
993                         JobManager.AddJob(CreateAPSCFilesJob(self, "/usr/lib/enigma2/python/Plugins/Extensions/PermanentTimeshift/createapscfiles \"%s\"" % (filename), eventname))
994                 else:
995                         self.ptsSaveTimeshiftFinished()
996
997         def ptsCreateEITFile(self, filename):
998                 if self.pts_curevent_eventid is not None:
999                         try:
1000                                 import eitsave
1001                                 serviceref = ServiceReference(self.session.nav.getCurrentlyPlayingServiceReference()).ref.toString()
1002                                 eitsave.SaveEIT(serviceref, filename+".eit", self.pts_curevent_eventid, -1, -1)
1003                         except Exception, errormsg:
1004                                 print "PTS Plugin: %s" % (errormsg)
1005
1006         def ptsCopyFilefinished(self, srcfile, destfile):
1007                 # Erase Source File
1008                 if fileExists(srcfile):
1009                         self.eraseFile(srcfile)
1010
1011                 # Restart Merge Timer
1012                 if self.pts_mergeRecords_timer.isActive():
1013                         self.pts_mergeRecords_timer.stop()
1014                         self.pts_mergeRecords_timer.start(15000, True)
1015                 else:
1016                         # Create AP and SC Files
1017                         self.ptsCreateAPSCFiles(destfile)
1018
1019         def ptsMergeFilefinished(self, srcfile, destfile):
1020                 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
1021                         # Rename files and delete them later ...
1022                         self.pts_mergeCleanUp_timer.start(120000, True)
1023                         os_system("echo \"\" > \"%s.pts.del\"" % (srcfile[0:-3]))
1024                 else:
1025                         # Delete Instant Record permanently now ... R.I.P.
1026                         self.eraseFile("%s" % (srcfile))
1027                         self.BgFileEraser.erase("%s.ap" % (srcfile))
1028                         self.BgFileEraser.erase("%s.sc" % (srcfile))
1029                         self.BgFileEraser.erase("%s.meta" % (srcfile))
1030                         self.BgFileEraser.erase("%s.cuts" % (srcfile))
1031                         self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1032
1033                 # Create AP and SC Files
1034                 self.ptsCreateAPSCFiles(destfile)
1035
1036                 # Run Merge-Process one more time to check if there are more records to merge
1037                 self.pts_mergeRecords_timer.start(10000, True)
1038
1039         def ptsSaveTimeshiftFinished(self):
1040                 if not self.pts_mergeCleanUp_timer.isActive():
1041                         self.ptsFrontpanelActions("stop")
1042                         config.plugins.pts.isRecording.value = False
1043
1044                 if Screens.Standby.inTryQuitMainloop:
1045                         self.pts_QuitMainloop_timer.start(30000, True)
1046                 else:
1047                         Notifications.AddNotification(MessageBox, _("Timeshift saved to your harddisk!"), MessageBox.TYPE_INFO, timeout = 5)
1048
1049         def ptsMergePostCleanUp(self):
1050                 if self.session.nav.RecordTimer.isRecording() or len(JobManager.getPendingJobs()) >= 1:
1051                         config.plugins.pts.isRecording.value = True
1052                         self.pts_mergeCleanUp_timer.start(120000, True)
1053                         return
1054
1055                 self.ptsFrontpanelActions("stop")
1056                 config.plugins.pts.isRecording.value = False
1057
1058                 filelist = os_listdir(config.usage.default_path.value)
1059                 for filename in filelist:
1060                         if filename.endswith(".pts.del"):
1061                                 srcfile = config.usage.default_path.value + "/" + filename[0:-8] + ".ts"
1062                                 self.eraseFile("%s" % (srcfile))
1063                                 self.BgFileEraser.erase("%s.ap" % (srcfile))
1064                                 self.BgFileEraser.erase("%s.sc" % (srcfile))
1065                                 self.BgFileEraser.erase("%s.meta" % (srcfile))
1066                                 self.BgFileEraser.erase("%s.cuts" % (srcfile))
1067                                 self.BgFileEraser.erase("%s.eit" % (srcfile[0:-3]))
1068                                 self.BgFileEraser.erase("%s.pts.del" % (srcfile[0:-3]))
1069
1070                                 # Restart QuitMainloop Timer to give BgFileEraser enough time
1071                                 if Screens.Standby.inTryQuitMainloop and self.pts_QuitMainloop_timer.isActive():
1072                                         self.pts_QuitMainloop_timer.start(60000, True)
1073
1074         def ptsTryQuitMainloop(self):
1075                 if Screens.Standby.inTryQuitMainloop and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeCleanUp_timer.isActive()):
1076                         self.pts_QuitMainloop_timer.start(60000, True)
1077                         return
1078
1079                 if Screens.Standby.inTryQuitMainloop and self.session.ptsmainloopvalue:
1080                         self.session.dialog_stack = []
1081                         self.session.summary_stack = [None]
1082                         self.session.open(TryQuitMainloop, self.session.ptsmainloopvalue)
1083
1084         def ptsGetSeekInfo(self):
1085                 s = self.session.nav.getCurrentService()
1086                 return s and s.seek()
1087
1088         def ptsGetPosition(self):
1089                 seek = self.ptsGetSeekInfo()
1090                 if seek is None:
1091                         return None
1092                 pos = seek.getPlayPosition()
1093                 if pos[0]:
1094                         return 0
1095                 return pos[1]
1096
1097         def ptsGetLength(self):
1098                 seek = self.ptsGetSeekInfo()
1099                 if seek is None:
1100                         return None
1101                 length = seek.getLength()
1102                 if length[0]:
1103                         return 0
1104                 return length[1]
1105
1106         def ptsGetSaveTimeshiftStatus(self):
1107                 return self.save_current_timeshift
1108
1109         def ptsSeekPointerOK(self):
1110                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1111                         if not self.pvrstate_hide_timer.isActive():
1112                                 if self.seekstate != self.SEEK_STATE_PLAY:
1113                                         self.setSeekState(self.SEEK_STATE_PLAY)
1114                                 self.doShow()
1115                                 return
1116
1117                         length = self.ptsGetLength()
1118                         position = self.ptsGetPosition()
1119
1120                         if length is None or position is None:
1121                                 return
1122
1123                         cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1124                         jumptox = int(cur_pos[0]) - int(self.pts_seekpointer_MinX)
1125                         jumptoperc = round((jumptox / 400.0) * 100, 0)
1126                         jumptotime = int((length / 100) * jumptoperc)
1127                         jumptodiff = position - jumptotime
1128
1129                         self.doSeekRelative(-jumptodiff)
1130                 else:
1131                         return
1132
1133         def ptsSeekPointerLeft(self):
1134                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1135                         self.ptsMoveSeekPointer(direction="left")
1136                 else:
1137                         return
1138
1139         def ptsSeekPointerRight(self):
1140                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1141                         self.ptsMoveSeekPointer(direction="right")
1142                 else:
1143                         return
1144
1145         def ptsSeekPointerReset(self):
1146                 if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled:
1147                         self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX,self.pvrStateDialog["PTSSeekPointer"].position[1])
1148
1149         def ptsSeekPointerSetCurrentPos(self):
1150                 if not self.pts_pvrStateDialog == "PTSTimeshiftState" or not self.timeshift_enabled or not self.isSeekable():
1151                         return
1152
1153                 position = self.ptsGetPosition()
1154                 length = self.ptsGetLength()
1155
1156                 if length >= 1:
1157                         tpixels = int((float(int((position*100)/length))/100)*400)
1158                         self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX+tpixels, self.pvrStateDialog["PTSSeekPointer"].position[1])
1159
1160         def ptsMoveSeekPointer(self, direction=None):
1161                 if direction is None or self.pts_pvrStateDialog != "PTSTimeshiftState":
1162                         return
1163
1164                 isvalidjump = False
1165                 cur_pos = self.pvrStateDialog["PTSSeekPointer"].position
1166                 InfoBarTimeshiftState._mayShow(self)
1167
1168                 if direction == "left":
1169                         minmaxval = self.pts_seekpointer_MinX
1170                         movepixels = -15
1171                         if cur_pos[0]+movepixels > minmaxval:
1172                                 isvalidjump = True
1173                 elif direction == "right":
1174                         minmaxval = self.pts_seekpointer_MaxX
1175                         movepixels = 15
1176                         if cur_pos[0]+movepixels < minmaxval:
1177                                 isvalidjump = True
1178                 else:
1179                         return 0
1180
1181                 if isvalidjump:
1182                         self.pvrStateDialog["PTSSeekPointer"].setPosition(cur_pos[0]+movepixels,cur_pos[1])
1183                 else:
1184                         self.pvrStateDialog["PTSSeekPointer"].setPosition(minmaxval,cur_pos[1])
1185
1186         def ptsTimeshiftFileChanged(self):
1187                 # Reset Seek Pointer
1188                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value:
1189                         self.ptsSeekPointerReset()
1190
1191                 if self.pts_switchtolive:
1192                         self.pts_switchtolive = False
1193                         return
1194
1195                 if self.pts_seektoprevfile:
1196                         if self.pts_currplaying == 1:
1197                                 self.pts_currplaying = config.plugins.pts.maxevents.value
1198                         else:
1199                                 self.pts_currplaying -= 1
1200                 else:
1201                         if self.pts_currplaying == config.plugins.pts.maxevents.value:
1202                                 self.pts_currplaying = 1
1203                         else:
1204                                 self.pts_currplaying += 1
1205
1206                 if not fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,self.pts_currplaying), 'r'):
1207                         self.pts_currplaying = self.pts_eventcount
1208
1209                 # Set Eventname in PTS InfoBar
1210                 if config.plugins.pts.enabled.value and config.plugins.pts.showinfobar.value and self.pts_pvrStateDialog == "PTSTimeshiftState":
1211                         try:
1212                                 if self.pts_eventcount != self.pts_currplaying:
1213                                         readmetafile = open("%s/pts_livebuffer.%s.meta" % (config.usage.timeshift_path.value,self.pts_currplaying), "r")
1214                                         servicerefname = readmetafile.readline()[0:-1]
1215                                         eventname = readmetafile.readline()[0:-1]
1216                                         readmetafile.close()
1217                                         self.pvrStateDialog["eventname"].setText(eventname)
1218                                 else:
1219                                         self.pvrStateDialog["eventname"].setText("")
1220                         except Exception, errormsg:
1221                                 self.pvrStateDialog["eventname"].setText("")
1222
1223                 # Get next pts file ...
1224                 if self.pts_currplaying+1 > config.plugins.pts.maxevents.value:
1225                         nextptsfile = 1
1226                 else:
1227                         nextptsfile = self.pts_currplaying+1
1228
1229                 # Seek to previous file
1230                 if self.pts_seektoprevfile:
1231                         self.pts_seektoprevfile = False
1232
1233                         if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r'):
1234                                 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1235
1236                         self.ptsSeekBackHack()
1237                 else:
1238                         if fileExists("%s/pts_livebuffer.%s" % (config.usage.timeshift_path.value,nextptsfile), 'r') and nextptsfile <= self.pts_eventcount:
1239                                 self.ptsSetNextPlaybackFile("pts_livebuffer.%s" % (nextptsfile))
1240                         if nextptsfile == self.pts_currplaying:
1241                                 self.pts_switchtolive = True
1242                                 self.ptsSetNextPlaybackFile("")
1243
1244         def ptsSetNextPlaybackFile(self, nexttsfile):
1245                 ts = self.getTimeshift()
1246                 if ts is None:
1247                         return
1248
1249                 try:
1250                         ts.setNextPlaybackFile("%s/%s" % (config.usage.timeshift_path.value,nexttsfile))
1251                 except:
1252                         print "PTS-Plugin: setNextPlaybackFile() not supported by OE. Enigma2 too old !?"
1253
1254         def ptsSeekBackHack(self):
1255                 if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1256                         return
1257
1258                 self.setSeekState(self.SEEK_STATE_PAUSE)
1259                 self.doSeek(-90000*4) # seek ~4s before end
1260                 self.pts_SeekBack_timer.start(1000, True)
1261
1262         def ptsSeekBackTimer(self):
1263                 if self.pts_lastseekspeed == 0:
1264                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1265                 else:
1266                         self.setSeekState(self.makeStateBackward(int(-self.pts_lastseekspeed)))
1267
1268         def ptsCheckTimeshiftPath(self):
1269                 if self.pts_pathchecked:
1270                         return True
1271                 else:
1272                         if fileExists(config.usage.timeshift_path.value, 'w'):
1273                                 self.pts_pathchecked = True
1274                                 return True
1275                         else:
1276                                 Notifications.AddNotification(MessageBox, _("Could not activate Permanent-Timeshift!\nTimeshift-Path does not exist"), MessageBox.TYPE_ERROR, timeout=15)
1277                                 if self.pts_delay_timer.isActive():
1278                                         self.pts_delay_timer.stop()
1279                                 if self.pts_cleanUp_timer.isActive():
1280                                         self.pts_cleanUp_timer.stop()
1281                                 return False
1282
1283         def ptsTimerEntryStateChange(self, timer):
1284                 if not config.plugins.pts.enabled.value or not config.plugins.pts.stopwhilerecording.value:
1285                         return
1286
1287                 self.pts_record_running = self.session.nav.RecordTimer.isRecording()
1288
1289                 # Abort here when box is in standby mode
1290                 if self.session.screen["Standby"].boolean is True:
1291                         return
1292
1293                 # Stop Timeshift when Record started ...
1294                 if timer.state == TimerEntry.StateRunning and self.timeshift_enabled and self.pts_record_running:
1295                         if self.ptsLiveTVStatus() is False:
1296                                 self.timeshift_enabled = 0
1297                                 self.pts_LengthCheck_timer.stop()
1298                                 return
1299
1300                         if self.seekstate != self.SEEK_STATE_PLAY:
1301                                 self.setSeekState(self.SEEK_STATE_PLAY)
1302
1303                         if self.isSeekable():
1304                                 Notifications.AddNotification(MessageBox,_("Record started! Stopping timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1305
1306                         self.stopTimeshiftConfirmed(True, False)
1307
1308                 # Restart Timeshift when all records stopped
1309                 if timer.state == TimerEntry.StateEnded and not self.timeshift_enabled and not self.pts_record_running:
1310                         self.activatePermanentTimeshift()
1311
1312                 # Restart Merge-Timer when all records stopped
1313                 if timer.state == TimerEntry.StateEnded and self.pts_mergeRecords_timer.isActive():
1314                         self.pts_mergeRecords_timer.stop()
1315                         self.pts_mergeRecords_timer.start(15000, True)
1316
1317                 # Restart FrontPanel LED when still copying or merging files
1318                 # ToDo: Only do this on PTS Events and not events from other jobs
1319                 if timer.state == TimerEntry.StateEnded and (len(JobManager.getPendingJobs()) >= 1 or self.pts_mergeRecords_timer.isActive()):
1320                         self.ptsFrontpanelActions("start")
1321                         config.plugins.pts.isRecording.value = True
1322
1323         def ptsLiveTVStatus(self):
1324                 service = self.session.nav.getCurrentService()
1325                 info = service and service.info()
1326                 sTSID = info and info.getInfo(iServiceInformation.sTSID) or -1
1327
1328                 if sTSID is None or sTSID == -1:
1329                         return False
1330                 else:
1331                         return True
1332
1333         def ptsLengthCheck(self):
1334                 # Check if we are in TV Mode ...
1335                 if self.ptsLiveTVStatus() is False:
1336                         self.timeshift_enabled = 0
1337                         self.pts_LengthCheck_timer.stop()
1338                         return
1339
1340                 if config.plugins.pts.stopwhilerecording.value and self.pts_record_running:
1341                         return
1342
1343                 # Length Check
1344                 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):
1345                         if self.save_current_timeshift:
1346                                 self.saveTimeshiftActions("savetimeshift")
1347                                 self.activatePermanentTimeshift()
1348                                 self.save_current_timeshift = True
1349                         else:
1350                                 self.activatePermanentTimeshift()
1351                         Notifications.AddNotification(MessageBox,_("Maximum Timeshift length per Event reached!\nRestarting Timeshift now ..."), MessageBox.TYPE_INFO, timeout=5)
1352
1353 #Replace the InfoBar with our version ;)
1354 Screens.InfoBar.InfoBar = InfoBar
1355
1356 ################################
1357 ##### Class Standby Hack 1 #####
1358 ################################
1359 TryQuitMainloop_getRecordEvent = Screens.Standby.TryQuitMainloop.getRecordEvent
1360
1361 class TryQuitMainloopPTS(TryQuitMainloop):
1362         def __init__(self, session, retvalue=1, timeout=-1, default_yes = True):
1363                 TryQuitMainloop.__init__(self, session, retvalue, timeout, default_yes)
1364
1365                 self.session.ptsmainloopvalue = retvalue
1366
1367         def getRecordEvent(self, recservice, event):
1368                 if event == iRecordableService.evEnd and (config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1):
1369                         return
1370                 else:
1371                         TryQuitMainloop_getRecordEvent(self, recservice, event)
1372
1373 Screens.Standby.TryQuitMainloop = TryQuitMainloopPTS
1374
1375 ################################
1376 ##### Class Standby Hack 2 #####
1377 ################################
1378
1379 Screens_Standby_Standby = Screens.Standby.Standby
1380
1381 class StandbyPTS(Standby):
1382         def __init__(self, session):
1383                 if InfoBar and InfoBar.instance and InfoBar.ptsGetSaveTimeshiftStatus(InfoBar.instance):
1384                         self.skin = """<screen position="0,0" size="0,0"/>"""
1385                         Screen.__init__(self, session)
1386                         self.onFirstExecBegin.append(self.showMessageBox)
1387                         self.onHide.append(self.close)
1388                 else:
1389                         Standby.__init__(self, session)
1390                         self.skinName = "Standby"
1391
1392         def showMessageBox(self):
1393                 if InfoBar and InfoBar.instance:
1394                         InfoBar.saveTimeshiftActions(InfoBar.instance, postaction="standby")
1395
1396 Screens.Standby.Standby = StandbyPTS
1397
1398 ############
1399 #zapUp Hack#
1400 ############
1401 InfoBarChannelSelection_zapUp = InfoBarChannelSelection.zapUp
1402
1403 def zapUp(self):
1404         if self.pts_blockZap_timer.isActive():
1405                 return
1406
1407         if self.save_current_timeshift and self.timeshift_enabled:
1408                 InfoBar.saveTimeshiftActions(self, postaction="zapUp")
1409         else:
1410                 InfoBarChannelSelection_zapUp(self)
1411
1412 InfoBarChannelSelection.zapUp = zapUp
1413
1414 ##############
1415 #zapDown Hack#
1416 ##############
1417 InfoBarChannelSelection_zapDown = InfoBarChannelSelection.zapDown
1418
1419 def zapDown(self):
1420         if self.pts_blockZap_timer.isActive():
1421                 return
1422
1423         if self.save_current_timeshift and self.timeshift_enabled:
1424                 InfoBar.saveTimeshiftActions(self, postaction="zapDown")
1425         else:
1426                 InfoBarChannelSelection_zapDown(self)
1427
1428 InfoBarChannelSelection.zapDown = zapDown
1429
1430 ##################
1431 #historyBack Hack#
1432 ##################
1433 InfoBarChannelSelection_historyBack = InfoBarChannelSelection.historyBack
1434
1435 def historyBack(self):
1436         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1437                 InfoBarTimeshiftState._mayShow(self)
1438                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MinX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1439                 if self.seekstate != self.SEEK_STATE_PLAY:
1440                         self.setSeekState(self.SEEK_STATE_PLAY)
1441                 self.ptsSeekPointerOK()
1442         elif self.save_current_timeshift and self.timeshift_enabled:
1443                 InfoBar.saveTimeshiftActions(self, postaction="historyBack")
1444         else:
1445                 InfoBarChannelSelection_historyBack(self)
1446
1447 InfoBarChannelSelection.historyBack = historyBack
1448
1449 ##################
1450 #historyNext Hack#
1451 ##################
1452 InfoBarChannelSelection_historyNext = InfoBarChannelSelection.historyNext
1453
1454 def historyNext(self):
1455         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable():
1456                 InfoBarTimeshiftState._mayShow(self)
1457                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX, self.pvrStateDialog["PTSSeekPointer"].position[1])
1458                 if self.seekstate != self.SEEK_STATE_PLAY:
1459                         self.setSeekState(self.SEEK_STATE_PLAY)
1460                 self.ptsSeekPointerOK()
1461         elif self.save_current_timeshift and self.timeshift_enabled:
1462                 InfoBar.saveTimeshiftActions(self, postaction="historyNext")
1463         else:
1464                 InfoBarChannelSelection_historyNext(self)
1465
1466 InfoBarChannelSelection.historyNext = historyNext
1467
1468 ######################
1469 #switchChannelUp Hack#
1470 ######################
1471 InfoBarChannelSelection_switchChannelUp = InfoBarChannelSelection.switchChannelUp
1472
1473 def switchChannelUp(self):
1474         if self.save_current_timeshift and self.timeshift_enabled:
1475                 InfoBar.saveTimeshiftActions(self, postaction="switchChannelUp")
1476         else:
1477                 InfoBarChannelSelection_switchChannelUp(self)
1478
1479 InfoBarChannelSelection.switchChannelUp = switchChannelUp
1480
1481 ########################
1482 #switchChannelDown Hack#
1483 ########################
1484 InfoBarChannelSelection_switchChannelDown = InfoBarChannelSelection.switchChannelDown
1485
1486 def switchChannelDown(self):
1487         if self.save_current_timeshift and self.timeshift_enabled:
1488                 InfoBar.saveTimeshiftActions(self, postaction="switchChannelDown")
1489         else:
1490                 InfoBarChannelSelection_switchChannelDown(self)
1491
1492 InfoBarChannelSelection.switchChannelDown = switchChannelDown
1493
1494 ######################
1495 #openServiceList Hack#
1496 ######################
1497 InfoBarChannelSelection_openServiceList = InfoBarChannelSelection.openServiceList
1498
1499 def openServiceList(self):
1500         if self.save_current_timeshift and self.timeshift_enabled:
1501                 InfoBar.saveTimeshiftActions(self, postaction="openServiceList")
1502         else:
1503                 InfoBarChannelSelection_openServiceList(self)
1504
1505 InfoBarChannelSelection.openServiceList = openServiceList
1506
1507 ###########################
1508 #showRadioChannelList Hack#
1509 ###########################
1510 InfoBarChannelSelection_showRadioChannelList = InfoBarChannelSelection.showRadioChannelList
1511
1512 def showRadioChannelList(self, zap=False):
1513         if self.save_current_timeshift and self.timeshift_enabled:
1514                 InfoBar.saveTimeshiftActions(self, postaction="showRadioChannelList")
1515         else:
1516                 InfoBarChannelSelection_showRadioChannelList(self, zap)
1517
1518 InfoBarChannelSelection.showRadioChannelList = showRadioChannelList
1519
1520 #######################
1521 #InfoBarNumberZap Hack#
1522 #######################
1523 InfoBarNumberZap_keyNumberGlobal = InfoBarNumberZap.keyNumberGlobal
1524
1525 def keyNumberGlobal(self, number):
1526         if self.pts_pvrStateDialog == "PTSTimeshiftState" and self.timeshift_enabled and self.isSeekable() and number == 0:
1527                 InfoBarTimeshiftState._mayShow(self)
1528                 self.pvrStateDialog["PTSSeekPointer"].setPosition(self.pts_seekpointer_MaxX/2, self.pvrStateDialog["PTSSeekPointer"].position[1])
1529                 if self.seekstate != self.SEEK_STATE_PLAY:
1530                         self.setSeekState(self.SEEK_STATE_PLAY)
1531                 self.ptsSeekPointerOK()
1532                 return
1533
1534         if self.pts_blockZap_timer.isActive():
1535                 return
1536
1537         if self.save_current_timeshift and self.timeshift_enabled:
1538                 InfoBar.saveTimeshiftActions(self)
1539                 return
1540
1541         InfoBarNumberZap_keyNumberGlobal(self, number)
1542         if number and config.plugins.pts.enabled.value and self.timeshift_enabled and not self.isSeekable():
1543                 self.session.openWithCallback(self.numberEntered, NumberZap, number)
1544
1545 InfoBarNumberZap.keyNumberGlobal = keyNumberGlobal
1546
1547 #############################
1548 # getNextRecordingTime Hack #
1549 #############################
1550 RecordTimer_getNextRecordingTime = RecordTimer.getNextRecordingTime
1551
1552 def getNextRecordingTime(self):
1553         nextrectime = RecordTimer_getNextRecordingTime(self)
1554         faketime = time()+300
1555
1556         if config.plugins.pts.isRecording.value or len(JobManager.getPendingJobs()) >= 1:
1557                 if nextrectime > 0 and nextrectime < faketime:
1558                         return nextrectime
1559                 else:
1560                         return faketime
1561         else:
1562                 return nextrectime
1563
1564 RecordTimer.getNextRecordingTime = getNextRecordingTime
1565
1566 ############################
1567 #InfoBarTimeshiftState Hack#
1568 ############################
1569 def _mayShow(self):
1570         if self.execing and self.timeshift_enabled and self.isSeekable():
1571                 InfoBar.ptsSeekPointerSetCurrentPos(self)
1572                 self.pvrStateDialog.show()
1573
1574                 self.pvrstate_hide_timer = eTimer()
1575                 self.pvrstate_hide_timer.callback.append(self.pvrStateDialog.hide)
1576
1577                 if self.seekstate == self.SEEK_STATE_PLAY:
1578                         idx = config.usage.infobar_timeout.index
1579                         if not idx:
1580                                 idx = 5
1581                         self.pvrstate_hide_timer.start(idx*1000, True)
1582                 else:
1583                         self.pvrstate_hide_timer.stop()
1584         elif self.execing and self.timeshift_enabled and not self.isSeekable():
1585                 self.pvrStateDialog.hide()
1586
1587 InfoBarTimeshiftState._mayShow = _mayShow
1588
1589 ##################
1590 # seekBack Hack  #
1591 ##################
1592 InfoBarSeek_seekBack = InfoBarSeek.seekBack
1593
1594 def seekBack(self):
1595         InfoBarSeek_seekBack(self)
1596         self.pts_lastseekspeed = self.seekstate[1]
1597
1598 InfoBarSeek.seekBack = seekBack
1599
1600 ####################
1601 #instantRecord Hack#
1602 ####################
1603 InfoBarInstantRecord_instantRecord = InfoBarInstantRecord.instantRecord
1604
1605 def instantRecord(self):
1606         if not config.plugins.pts.enabled.value or not self.timeshift_enabled:
1607                 InfoBarInstantRecord_instantRecord(self)
1608                 return
1609
1610         dir = preferredInstantRecordPath()
1611         if not dir or not fileExists(dir, 'w'):
1612                 dir = defaultMoviePath()
1613         try:
1614                 stat = os_stat(dir)
1615         except:
1616                 self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1617                 return
1618
1619         if self.isInstantRecordRunning():
1620                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1621                         title=_("A recording is currently running.\nWhat do you want to do?"), \
1622                         list=((_("stop recording"), "stop"), \
1623                         (_("add recording (stop after current event)"), "event"), \
1624                         (_("add recording (indefinitely)"), "indefinitely"), \
1625                         (_("add recording (enter recording duration)"), "manualduration"), \
1626                         (_("add recording (enter recording endtime)"), "manualendtime"), \
1627                         (_("change recording (duration)"), "changeduration"), \
1628                         (_("change recording (endtime)"), "changeendtime"), \
1629                         (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1630                         (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1631                         (_("do nothing"), "no")))
1632         else:
1633                 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1634                         title=_("Start recording?"), \
1635                         list=((_("add recording (stop after current event)"), "event"), \
1636                         (_("add recording (indefinitely)"), "indefinitely"), \
1637                         (_("add recording (enter recording duration)"), "manualduration"), \
1638                         (_("add recording (enter recording endtime)"), "manualendtime"), \
1639                         (_("Timeshift")+" "+_("save recording (stop after current event)"), "savetimeshift"), \
1640                         (_("Timeshift")+" "+_("save recording (Select event)"), "savetimeshiftEvent"), \
1641                         (_("don't record"), "no")))
1642
1643 InfoBarInstantRecord.instantRecord = instantRecord
1644
1645 #############################
1646 #recordQuestionCallback Hack#
1647 #############################
1648 InfoBarInstantRecord_recordQuestionCallback = InfoBarInstantRecord.recordQuestionCallback
1649
1650 def recordQuestionCallback(self, answer):
1651         InfoBarInstantRecord_recordQuestionCallback(self, answer)
1652
1653         if config.plugins.pts.enabled.value:
1654                 if answer is not None and answer[1] == "savetimeshift":
1655                         if InfoBarSeek.isSeekable(self) and self.pts_eventcount != self.pts_currplaying:
1656                                 InfoBar.SaveTimeshift(self, timeshiftfile="pts_livebuffer.%s" % self.pts_currplaying)
1657                         else:
1658                                 Notifications.AddNotification(MessageBox,_("Timeshift will get saved at end of event!"), MessageBox.TYPE_INFO, timeout=5)
1659                                 self.save_current_timeshift = True
1660                                 config.plugins.pts.isRecording.value = True
1661                 if answer is not None and answer[1] == "savetimeshiftEvent":
1662                         InfoBar.saveTimeshiftEventPopup(self)
1663
1664                 if answer is not None and answer[1].startswith("pts_livebuffer") is True:
1665                         InfoBar.SaveTimeshift(self, timeshiftfile=answer[1])
1666
1667 InfoBarInstantRecord.recordQuestionCallback = recordQuestionCallback
1668
1669 ############################
1670 #####  SETTINGS SCREEN #####
1671 ############################
1672 class PermanentTimeShiftSetup(Screen, ConfigListScreen):
1673         def __init__(self, session):
1674                 Screen.__init__(self, session)
1675                 self.skinName = [ "PTSSetup", "Setup" ]
1676                 self.setup_title = _("Permanent Timeshift Settings")
1677
1678                 self.onChangedEntry = [ ]
1679                 self.list = [ ]
1680                 ConfigListScreen.__init__(self, self.list, session = session, on_change = self.changedEntry)
1681
1682                 self["actions"] = ActionMap(["SetupActions", "ColorActions"],
1683                 {
1684                         "ok": self.SaveSettings,
1685                         "green": self.SaveSettings,
1686                         "red": self.Exit,
1687                         "cancel": self.Exit
1688                 }, -2)
1689
1690                 self["key_green"] = StaticText(_("OK"))
1691                 self["key_red"] = StaticText(_("Cancel"))
1692
1693                 self.createSetup()
1694                 self.onLayoutFinish.append(self.layoutFinished)
1695
1696         def layoutFinished(self):
1697                 self.setTitle(self.setup_title)
1698
1699         def createSetup(self):
1700                 self.list = [ getConfigListEntry(_("Permanent Timeshift Enable"), config.plugins.pts.enabled) ]
1701                 if config.plugins.pts.enabled.value:
1702                         self.list.extend((
1703                                 getConfigListEntry(_("Permanent Timeshift Max Events"), config.plugins.pts.maxevents),
1704                                 getConfigListEntry(_("Permanent Timeshift Max Length"), config.plugins.pts.maxlength),
1705                                 getConfigListEntry(_("Permanent Timeshift Start Delay"), config.plugins.pts.startdelay),
1706                                 getConfigListEntry(_("Timeshift-Save Action on zap"), config.plugins.pts.favoriteSaveAction),
1707                                 getConfigListEntry(_("Stop timeshift while recording?"), config.plugins.pts.stopwhilerecording),
1708                                 getConfigListEntry(_("Show PTS Infobar while timeshifting?"), config.plugins.pts.showinfobar)
1709                         ))
1710
1711                 # Permanent Recording Hack
1712                 if fileExists("/usr/lib/enigma2/python/Plugins/Extensions/HouseKeeping/plugin.py"):
1713                         self.list.append(getConfigListEntry(_("Beta: Enable Permanent Recording?"), config.plugins.pts.permanentrecording))
1714
1715                 self["config"].list = self.list
1716                 self["config"].setList(self.list)
1717
1718         def keyLeft(self):
1719                 ConfigListScreen.keyLeft(self)
1720                 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1721                         self.createSetup()
1722
1723         def keyRight(self):
1724                 ConfigListScreen.keyRight(self)
1725                 if self["config"].getCurrent()[1] == config.plugins.pts.enabled:
1726                         self.createSetup()
1727
1728         def changedEntry(self):
1729                 for x in self.onChangedEntry:
1730                         x()
1731
1732         def getCurrentEntry(self):
1733                 return self["config"].getCurrent()[0]
1734
1735         def getCurrentValue(self):
1736                 return str(self["config"].getCurrent()[1].getText())
1737
1738         def createSummary(self):
1739                 return SetupSummary
1740
1741         def SaveSettings(self):
1742                 config.plugins.pts.save()
1743                 configfile.save()
1744                 self.close()
1745
1746         def Exit(self):
1747                 self.close()
1748
1749 #################################################
1750
1751 def startSetup(menuid):
1752         if menuid != "system":
1753                 return [ ]
1754         return [(_("Timeshift Settings"), PTSSetupMenu, "pts_setup", 50)]
1755
1756 def PTSSetupMenu(session, **kwargs):
1757         session.open(PermanentTimeShiftSetup)
1758
1759 def Plugins(path, **kwargs):
1760         return [ PluginDescriptor(name=_("Permanent Timeshift Settings"), description=_("Permanent Timeshift Settings"), where=PluginDescriptor.WHERE_MENU, fnc=startSetup) ]