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