Fix default recording path and usb recording.
authorhschang <chang@dev3>
Thu, 22 Feb 2018 05:14:01 +0000 (14:14 +0900)
committerhschang <chang@dev3>
Thu, 22 Feb 2018 05:14:01 +0000 (14:14 +0900)
RecordTimer.py
lib/python/Components/Harddisk.py
lib/python/Components/SystemInfo.py
lib/python/Components/UsageConfig.py
lib/python/Screens/InfoBarGenerics.py
lib/python/Screens/RecordPaths.py
lib/python/Tools/Directories.py
timer.py

index 32fb923..c3e9de7 100755 (executable)
@@ -18,6 +18,8 @@ from ServiceReference import ServiceReference
 from time import localtime, strftime, ctime, time
 from bisect import insort
 
 from time import localtime, strftime, ctime, time
 from bisect import insort
 
+import os
+
 # ok, for descriptions etc we have:
 # service reference  (to get the service name)
 # name               (title)
 # ok, for descriptions etc we have:
 # service reference  (to get the service name)
 # name               (title)
@@ -47,6 +49,25 @@ class AFTEREVENT:
        DEEPSTANDBY = 2
        AUTO = 3
 
        DEEPSTANDBY = 2
        AUTO = 3
 
+def findSafeRecordPath(dirname):
+       if not dirname:
+               return None
+
+       from Components import Harddisk
+       dirname = os.path.realpath(dirname)
+       mountpoint = Harddisk.findMountPoint(dirname)
+       if mountpoint in ('/', '/media'):
+               print '[RecordTimer] media is not mounted:', dirname
+               return None
+       if not os.path.isdir(dirname):
+               try:
+                       os.makedirs(dirname)
+               except Exception, ex:
+                       print '[RecordTimer] Failed to create dir "%s":' % dirname, ex
+                       return None
+
+       return dirname
+
 # please do not translate log messages
 class RecordTimerEntry(timer.TimerEntry, object):
 ######### the following static methods and members are only in use when the box is in (soft) standby
 # please do not translate log messages
 class RecordTimerEntry(timer.TimerEntry, object):
 ######### the following static methods and members are only in use when the box is in (soft) standby
@@ -164,21 +185,31 @@ class RecordTimerEntry(timer.TimerEntry, object):
                if config.recording.ascii_filenames.value:
                        filename = ASCIItranslit.legacyEncode(filename)
 
                if config.recording.ascii_filenames.value:
                        filename = ASCIItranslit.legacyEncode(filename)
 
-               if not self.dirname or not Directories.fileExists(self.dirname, 'w'):
-                       if self.dirname:
-                               self.dirnameHadToFallback = True
-                       dirname = defaultMoviePath()
+               if not self.dirname:
+                       dirname = findSafeRecordPath(defaultMoviePath())
                else:
                else:
-                       dirname = self.dirname
+                       dirname = findSafeRecordPath(self.dirname)
+                       if dirname is None:
+                               dirname = findSafeRecordPath(defaultMoviePath())
+                               self.dirnameHadToFallback = True
+
+               if not dirname:
+                       return None
+
                self.Filename = Directories.getRecordingFilename(filename, dirname)
                self.log(0, "Filename calculated as: '%s'" % self.Filename)
                #begin_date + " - " + service_name + description)
                self.Filename = Directories.getRecordingFilename(filename, dirname)
                self.log(0, "Filename calculated as: '%s'" % self.Filename)
                #begin_date + " - " + service_name + description)
+               return self.Filename
 
        def tryPrepare(self):
                if self.justplay:
                        return True
                else:
 
        def tryPrepare(self):
                if self.justplay:
                        return True
                else:
-                       self.calculateFilename()
+                       if not self.calculateFilename():
+                               self.do_backoff()
+                               self.start_prepare = time() + self.backoff
+                               return False
+
                        rec_ref = self.service_ref and self.service_ref.ref
                        if rec_ref and rec_ref.flags & eServiceReference.isGroup:
                                rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
                        rec_ref = self.service_ref and self.service_ref.ref
                        if rec_ref and rec_ref.flags & eServiceReference.isGroup:
                                rec_ref = getBestPlayableServiceReference(rec_ref, eServiceReference())
@@ -410,6 +441,18 @@ class RecordTimerEntry(timer.TimerEntry, object):
 
        record_service = property(lambda self: self.__record_service, setRecordService)
 
 
        record_service = property(lambda self: self.__record_service, setRecordService)
 
+       def isUsbRecordingPath(self):
+               dirname = None
+
+               if self.dirname:
+                       dirname = findSafeRecordPath(self.dirname)
+
+               if dirname is None:
+                       dirname = findSafeRecordPath(defaultMoviePath())
+
+               from Components import Harddisk
+               return Harddisk.isUsbStorage(dirname)
+
 def createTimer(xml):
        begin = int(xml.get("begin"))
        end = int(xml.get("end"))
 def createTimer(xml):
        begin = int(xml.get("begin"))
        end = int(xml.get("end"))
@@ -466,6 +509,18 @@ class RecordTimer(timer.Timer):
                        print "unable to load timers from file!"
 
        def doActivate(self, w):
                        print "unable to load timers from file!"
 
        def doActivate(self, w):
+               if w.state == RecordTimerEntry.StateWaiting:
+                       from Components.SystemInfo import SystemInfo
+                       if SystemInfo.get("DisableUsbRecord", True) and w.isUsbRecordingPath():
+                               service_name = w.service_ref.getServiceName()
+                               self.timer_list.remove(w)
+                               if w.dontSave is False:
+                                       w.resetState()
+                                       w.disable()
+                                       self.addTimerEntry(w)
+                               Notifications.AddNotification(MessageBox, _("Can not recording on a USB storage.\nService name : %s"% service_name), MessageBox.TYPE_ERROR)
+                               return
+
                # when activating a timer which has already passed,
                # simply abort the timer. don't run trough all the stages.
                if w.shouldSkip():
                # when activating a timer which has already passed,
                # simply abort the timer. don't run trough all the stages.
                if w.shouldSkip():
index 0e73dee..9b3a684 100755 (executable)
@@ -58,6 +58,56 @@ def enableUdevEvent(enable = True):
        print "CMD : ", cmd
        system(cmd)
 
        print "CMD : ", cmd
        system(cmd)
 
+def findMountPoint(path):
+       'Example: findMountPoint("/media/hdd/some/file") returns "/media/hdd"'
+       path = os.path.abspath(path)
+       while not os.path.ismount(path):
+               path = os.path.dirname(path)
+       return path
+
+def getDeviceFile(dev_path):
+       for parts in getProcMounts():
+               if os.path.realpath(parts[1]).startswith(dev_path):
+                       return parts[0]
+
+def getMountPath(mountPath):
+       mountPath = os.path.realpath(mountPath)
+       while not os.path.ismount(mountPath):
+               mountPath = os.path.dirname(mountPath)
+
+       return mountPath
+
+def getDeviceInterface(mountPath):
+       mountPath = getMountPath(mountPath)
+
+       if mountPath == '/':
+               return None
+
+       from Components.Harddisk import getDeviceFile
+       dev = getDeviceFile(mountPath)
+
+       if dev and dev.startswith("/dev/sd"):
+               dev = os.path.basename(dev)
+               phyPath = os.path.realpath('/sys/block/' + dev[:3])
+
+               # check usb
+               for x in glob.glob("/sys/bus/usb/devices/usb*"):
+                       if phyPath.find(os.path.realpath(x)) != -1:
+                               from Tools.HardwareInfo import HardwareInfo
+                               if (HardwareInfo().get_vu_device_name() == "zero4k") and (phyPath.find("f0b00500.ehci_v2/usb2/2-1") != -1):
+                                       return "ata"
+                               else:
+                                       return "usb"
+
+               # check ata
+               if phyPath.find("/ata") != -1:
+                       return "ata"
+
+       return None
+
+def isUsbStorage(dirname):
+       return getDeviceInterface(dirname) in (None, "usb")
+
 DEVTYPE_UDEV = 0
 DEVTYPE_DEVFS = 1
 
 DEVTYPE_UDEV = 0
 DEVTYPE_DEVFS = 1
 
index e4bae61..5b01c4c 100644 (file)
@@ -34,4 +34,5 @@ SystemInfo["WOWLSupport"] = HardwareInfo().get_vu_device_name() == "ultimo4k"
 SystemInfo["ScrambledPlayback"] = HardwareInfo().get_vu_device_name() in ("solo4k", "ultimo4k", "uno4kse", "zero4k")
 SystemInfo["FastChannelChange"] =  fileExists("/proc/stb/frontend/fbc/fcc")
 SystemInfo["MiniTV"] = fileExists("/proc/stb/lcd/live_enable")
 SystemInfo["ScrambledPlayback"] = HardwareInfo().get_vu_device_name() in ("solo4k", "ultimo4k", "uno4kse", "zero4k")
 SystemInfo["FastChannelChange"] =  fileExists("/proc/stb/frontend/fbc/fcc")
 SystemInfo["MiniTV"] = fileExists("/proc/stb/lcd/live_enable")
+SystemInfo["DisableUsbRecord"] = HardwareInfo().get_vu_device_name() in ("solo4k", "uno4kse", "zero4k")
 
 
index d262448..36db2ad 100644 (file)
@@ -1,6 +1,7 @@
 from Components.Harddisk import harddiskmanager
 from Components.NimManager import nimmanager
 from config import ConfigSubsection, ConfigYesNo, config, ConfigSelection, ConfigText, ConfigNumber, ConfigSet, ConfigLocations
 from Components.Harddisk import harddiskmanager
 from Components.NimManager import nimmanager
 from config import ConfigSubsection, ConfigYesNo, config, ConfigSelection, ConfigText, ConfigNumber, ConfigSet, ConfigLocations
+from Tools.Directories import defaultRecordingLocation
 from Tools.Directories import resolveFilename, SCOPE_HDD
 from enigma import Misc_Options, eEnv
 from enigma import setTunerTypePriorityOrder, setPreferredTuner
 from Tools.Directories import resolveFilename, SCOPE_HDD
 from enigma import Misc_Options, eEnv
 from enigma import setTunerTypePriorityOrder, setPreferredTuner
@@ -35,7 +36,7 @@ def InitUsageConfig():
                ("standard", _("standard")), ("swap", _("swap PiP and main picture")),
                ("swapstop", _("move PiP to main picture")), ("stop", _("stop PiP")) ])
 
                ("standard", _("standard")), ("swap", _("swap PiP and main picture")),
                ("swapstop", _("move PiP to main picture")), ("stop", _("stop PiP")) ])
 
-       config.usage.default_path = ConfigText(default = resolveFilename(SCOPE_HDD))
+       config.usage.default_path = ConfigText(default = "")
        config.usage.timer_path = ConfigText(default = "<default>")
        config.usage.instantrec_path = ConfigText(default = "<default>")
        config.usage.timeshift_path = ConfigText(default = "/media/hdd/")
        config.usage.timer_path = ConfigText(default = "<default>")
        config.usage.instantrec_path = ConfigText(default = "<default>")
        config.usage.timeshift_path = ConfigText(default = "/media/hdd/")
@@ -226,7 +227,7 @@ def updateChoices(sel, choices):
                sel.setChoices(map(str, choices), defval)
 
 def preferredPath(path):
                sel.setChoices(map(str, choices), defval)
 
 def preferredPath(path):
-       if config.usage.setup_level.index < 2 or path == "<default>":
+       if config.usage.setup_level.index < 2 or path == "<default>" or not path:
                return None  # config.usage.default_path.value, but delay lookup until usage
        elif path == "<current>":
                return config.movielist.last_videodir.value
                return None  # config.usage.default_path.value, but delay lookup until usage
        elif path == "<current>":
                return config.movielist.last_videodir.value
@@ -242,5 +243,5 @@ def preferredInstantRecordPath():
        return preferredPath(config.usage.instantrec_path.value)
 
 def defaultMoviePath():
        return preferredPath(config.usage.instantrec_path.value)
 
 def defaultMoviePath():
-       return config.usage.default_path.value
+       return defaultRecordingLocation(config.usage.default_path.value)
 
 
index cce091e..0b01bf8 100755 (executable)
@@ -39,7 +39,7 @@ from time import time, localtime, strftime
 from os import stat as os_stat, system as os_system
 from bisect import insort
 
 from os import stat as os_stat, system as os_system
 from bisect import insort
 
-from RecordTimer import RecordTimerEntry, RecordTimer
+from RecordTimer import RecordTimerEntry, RecordTimer, findSafeRecordPath
 
 # hack alert!
 from Menu import MainMenu, mdom
 
 # hack alert!
 from Menu import MainMenu, mdom
@@ -1223,6 +1223,13 @@ class InfoBarTimeshift:
                if self.timeshift_enabled:
                        print "hu, timeshift already enabled?"
                else:
                if self.timeshift_enabled:
                        print "hu, timeshift already enabled?"
                else:
+                       from Components import Harddisk
+                       if Harddisk.getMountPath(config.usage.timeshift_path.value) != '/' and \
+                               SystemInfo.get("DisableUsbRecord", True) and \
+                               Harddisk.isUsbStorage(config.usage.timeshift_path.value):
+                               self.session.open(MessageBox, _("Timeshift not possible on a USB storage."), MessageBox.TYPE_ERROR)
+                               return 0
+
                        if not ts.startTimeshift():
                                self.timeshift_enabled = 1
 
                        if not ts.startTimeshift():
                                self.timeshift_enabled = 1
 
@@ -1663,18 +1670,14 @@ class InfoBarInstantRecord:
                        self.session.nav.RecordTimer.timeChanged(entry)
 
        def instantRecord(self):
                        self.session.nav.RecordTimer.timeChanged(entry)
 
        def instantRecord(self):
-               dir = preferredInstantRecordPath()
-               if not dir or not fileExists(dir, 'w'):
-                       dir = defaultMoviePath()
-
                if not fileExists("/hdd", 0):
                        print "not found /hdd"
                        os_system("ln -s /media/hdd /hdd")
                if not fileExists("/hdd", 0):
                        print "not found /hdd"
                        os_system("ln -s /media/hdd /hdd")
-#
-               try:
-                       stat = os_stat(dir)
-               except:
-                       # XXX: this message is a little odd as we might be recording to a remote device
+
+               recPath = preferredInstantRecordPath()
+               if not findSafeRecordPath(recPath) and not findSafeRecordPath(defaultMoviePath()):
+                       if not recPath:
+                               recPath = ""
                        self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
                        return
 
                        self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
                        return
 
index 22ca9fc..55159d9 100644 (file)
@@ -36,16 +36,16 @@ class RecordPathsSettings(Screen,ConfigListScreen):
                }, -2)
 
        def checkReadWriteDir(self, configele):
                }, -2)
 
        def checkReadWriteDir(self, configele):
+               value = configele.value
                print "checkReadWrite: ", configele.value
                print "checkReadWrite: ", configele.value
-               if configele.value in [x[0] for x in self.styles] or fileExists(configele.value, "w"):
-                       configele.last_value = configele.value
+               if not value or value in [x[0] for x in self.styles] or fileExists(value, "w"):
+                       configele.last_value = value
                        return True
                else:
                        return True
                else:
-                       dir = configele.value
                        configele.value = configele.last_value
                        self.session.open(
                                MessageBox,
                        configele.value = configele.last_value
                        self.session.open(
                                MessageBox,
-                               _("The directory %s is not writable.\nMake sure you select a writable directory instead.")%dir,
+                               _("The directory %s is not writable.\nMake sure you select a writable directory instead.") % value,
                                type = MessageBox.TYPE_ERROR
                                )
                        return False
                                type = MessageBox.TYPE_ERROR
                                )
                        return False
@@ -55,11 +55,11 @@ class RecordPathsSettings(Screen,ConfigListScreen):
                styles_keys = [x[0] for x in self.styles]
                tmp = config.movielist.videodirs.value
                default = config.usage.default_path.value
                styles_keys = [x[0] for x in self.styles]
                tmp = config.movielist.videodirs.value
                default = config.usage.default_path.value
-               if default not in tmp:
+               if default and default not in tmp:
                        tmp = tmp[:]
                        tmp.append(default)
                print "DefaultPath: ", default, tmp
                        tmp = tmp[:]
                        tmp.append(default)
                print "DefaultPath: ", default, tmp
-               self.default_dirname = ConfigSelection(default = default, choices = tmp)
+               self.default_dirname = ConfigSelection(default = default, choices = [("", _("<Default movie location>"))] + tmp)
                tmp = config.movielist.videodirs.value
                default = config.usage.timer_path.value
                if default not in tmp and default not in styles_keys:
                tmp = config.movielist.videodirs.value
                default = config.usage.timer_path.value
                if default not in tmp and default not in styles_keys:
@@ -148,10 +148,10 @@ class RecordPathsSettings(Screen,ConfigListScreen):
                                styles_keys = [x[0] for x in self.styles]
                                tmp = config.movielist.videodirs.value
                                default = self.default_dirname.value
                                styles_keys = [x[0] for x in self.styles]
                                tmp = config.movielist.videodirs.value
                                default = self.default_dirname.value
-                               if default not in tmp:
+                               if default and default not in tmp:
                                        tmp = tmp[:]
                                        tmp.append(default)
                                        tmp = tmp[:]
                                        tmp.append(default)
-                               self.default_dirname.setChoices(tmp, default=default)
+                               self.default_dirname.setChoices([("", _("<Default movie location>"))] + tmp, default=default)
                                tmp = config.movielist.videodirs.value
                                default = self.timer_dirname.value
                                if default not in tmp and default not in styles_keys:
                                tmp = config.movielist.videodirs.value
                                default = self.timer_dirname.value
                                if default not in tmp and default not in styles_keys:
index f0ef0de..b886459 100755 (executable)
@@ -17,6 +17,8 @@ try:
 except:
        have_utime = False
 
 except:
        have_utime = False
 
+import os
+
 SCOPE_TRANSPONDERDATA = 0
 SCOPE_SYSETC = 1
 SCOPE_FONTS = 2
 SCOPE_TRANSPONDERDATA = 0
 SCOPE_SYSETC = 1
 SCOPE_FONTS = 2
@@ -151,11 +153,64 @@ def resolveFilename(scope, base = "", path_prefix = None):
        return path + base
        # this is only the BASE - an extension must be added later.
 
        return path + base
        # this is only the BASE - an extension must be added later.
 
-def pathExists(path):
-       return os_path.exists(path)
+pathExists = os.path.exists
+isMount = os.path.ismount
+
+def bestRecordingLocation(candidates):
+       path = ''
+
+       from Components import Harddisk
+       ata_devices = [candidate for candidate in candidates if Harddisk.getDeviceInterface(candidate[1]) == "ata"]
+
+       if len(ata_devices) == 1:
+               path = ata_devices[0][1]
+
+       elif len(ata_devices):
+               best = ""
+               for device in ata_devices:
+                       dev = os.path.basename(device[0])
+                       if not best or (best > dev):
+                               best = dev
+                               path = device[1]
+       else: # Find the largest usb disk
+               biggest = 0
+               for candidate in candidates:
+                       try:
+                               stat = os.statvfs(candidate[1])
+                               # must have some free space (i.e. not read-only)
+                               if stat.f_bavail:
+                                       # Free space counts double
+                                       size = (stat.f_blocks + stat.f_bavail) * stat.f_bsize
+                                       if size > biggest:
+                                               path = candidate[1]
+                                               biggest = size
+                       except Exception, e:
+                               print "[DRL]", e
 
 
-def isMount(path):
-       return os_path.ismount(path)
+       return path
+
+def defaultRecordingLocation(candidate=None):
+       if candidate and os.path.exists(candidate):
+               return candidate
+       # First, try whatever /hdd points to, or /media/hdd
+       try:
+               path = os.path.realpath('/hdd')
+       except:
+               path = '/media/hdd'
+       if not os.path.exists(path) or not os.path.ismount(path):
+               path = ''
+               from Components import Harddisk
+               mounts = [m for m in Harddisk.getProcMounts() if m[1].startswith('/media/')]
+               path = bestRecordingLocation([m for m in mounts if m[0].startswith('/dev/')])
+       if path:
+               # If there's a movie subdir, we'd probably want to use that.
+               movie = os.path.join(path, 'movie')
+               if os.path.isdir(movie):
+                       path = movie
+               if not path.endswith('/'):
+                       path += '/' # Bad habits die hard, old code relies on this
+
+       return path
 
 def createDir(path, makeParents = False):
        try:
 
 def createDir(path, makeParents = False):
        try:
@@ -188,6 +243,9 @@ def fileExists(f, mode='r'):
        return access(f, acc_mode)
 
 def getRecordingFilename(basename, dirname = None):
        return access(f, acc_mode)
 
 def getRecordingFilename(basename, dirname = None):
+       if not dirname.endswith('/'):
+               dirname += '/'
+
        # filter out non-allowed characters
        non_allowed_characters = "/.\\:*?<>|\""
        filename = ""
        # filter out non-allowed characters
        non_allowed_characters = "/.\\:*?<>|\""
        filename = ""
index aaae0b2..d58a83e 100644 (file)
--- a/timer.py
+++ b/timer.py
@@ -232,6 +232,10 @@ class Timer:
                self.setNextActivation(min)
        
        def timeChanged(self, timer):
                self.setNextActivation(min)
        
        def timeChanged(self, timer):
+               if timer not in (self.processed_timers + self.timer_list):
+                       print "timer not found"
+                       return
+
                print "time changed"
                timer.timeChanged()
                if timer.state == TimerEntry.StateEnded:
                print "time changed"
                timer.timeChanged()
                if timer.state == TimerEntry.StateEnded: