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