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