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