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