You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

446 lines
13 KiB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
**Project Name:** MakeHuman
**Product Home Page:** http://www.makehumancommunity.org/
**Github Code Home Page:** https://github.com/makehumancommunity/
**Authors:** Jonas Hauquier, Glynn Clements, Joel Palmius, Marc Flerackers
**Copyright(c):** MakeHuman Team 2001-2020
**Licensing:** AGPL3
This file is part of MakeHuman Community (www.makehumancommunity.org).
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Abstract
--------
Utility module for finding the user home path.
"""
import os
import sys
from library.xdg_parser import XDG_PATHS
__home_path = None
# Search for an optional configuration file, providing another location for the home folder.
# The encoding of the file must be utf-8 and an absolute path is expected.
if sys.platform.startswith("linux"):
configFile = os.path.expanduser("~/.config/makehuman.conf")
elif sys.platform.startswith("darwin"):
configFile = os.path.expanduser("~/Library/Application Support/MakeHuman/makehuman.conf")
elif sys.platform.startswith("win32"):
configFile = os.path.join(os.getenv("LOCALAPPDATA", ""), "makehuman.conf")
else:
configFile = ""
configPath = ""
if os.path.isfile(configFile):
with open(configFile, "r", encoding="utf-8") as f:
configPath = f.readline().strip()
if os.path.isdir(configPath):
__home_path = os.path.normpath(configPath).replace("\\", "/")
def pathToUnicode(path):
"""
Unicode representation of the filename.
Bytes is decoded with the codeset used by the filesystem of the operating
system.
Unicode representations of paths are fit for use in GUI.
"""
if isinstance(path, bytes):
# Approach for bytes string type
try:
return str(path, "utf-8")
except UnicodeDecodeError:
pass
try:
return str(path, sys.getfilesystemencoding())
except UnicodeDecodeError:
pass
try:
return str(path, sys.getdefaultencoding())
except UnicodeDecodeError:
pass
try:
import locale
return str(path, locale.getpreferredencoding())
except UnicodeDecodeError:
return path
else:
return path
def formatPath(path):
if path is None:
return None
return pathToUnicode(os.path.normpath(path).replace("\\", "/"))
def canonicalPath(path):
"""
Return canonical name for location specified by path.
Useful for comparing paths.
"""
return formatPath(os.path.realpath(path))
def localPath(path):
"""
Returns the path relative to the MH program directory,
i.e. the inverse of canonicalPath.
"""
path = os.path.realpath(path)
root = os.path.realpath(getSysPath())
return formatPath(os.path.relpath(path, root))
def getHomePath():
"""
Find the user home path.
Note: If you are looking for MakeHuman data, you probably want getPath()!
"""
# Cache the home path
global __home_path
# The environment variable MH_HOME_LOCATION will supersede any other settings for the home folder.
alt_home_path = os.environ.get("MH_HOME_LOCATION", "")
if os.path.isdir(alt_home_path):
__home_path = formatPath(alt_home_path)
if __home_path is not None:
return __home_path
# Windows
if sys.platform.startswith("win"):
import winreg
keyname = r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
# name = 'Personal'
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, keyname) as k:
try:
value, type_ = winreg.QueryValueEx(k, "Personal")
except FileNotFoundError:
value, type_ = r"%USERPROFILE%\Documents", winreg.REG_EXPAND_SZ
if type_ == winreg.REG_EXPAND_SZ:
__home_path = formatPath(winreg.ExpandEnvironmentStrings(value))
return __home_path
elif type_ == winreg.REG_SZ:
__home_path = formatPath(value)
return __home_path
else:
raise RuntimeError("Couldn't determine user folder")
# Linux
elif sys.platform.startswith("linux"):
doc_folder = XDG_PATHS.get("DOCUMENTS", "")
if os.path.isdir(doc_folder):
__home_path = doc_folder
else:
__home_path = pathToUnicode(os.path.expanduser("~"))
# Darwin
else:
__home_path = os.path.expanduser("~")
return __home_path
def getPath(subPath=""):
"""
Get MakeHuman folder that contains per-user files, located in the user home
path.
"""
path = getHomePath()
# Windows
if sys.platform.startswith("win"):
path = os.path.join(path, "makehuman")
# MAC OSX
elif sys.platform.startswith("darwin"):
path = os.path.join(path, "Documents")
path = os.path.join(path, "MakeHuman")
# Unix/Linux
else:
path = os.path.join(path, "makehuman")
path = os.path.join(path, "v1py3")
if subPath:
path = os.path.join(path, subPath)
return formatPath(path)
def getDataPath(subPath=""):
"""
Path to per-user data folder, should always be the same as getPath('data').
"""
if subPath:
path = getPath(os.path.join("data", subPath))
else:
path = getPath("data")
return formatPath(path)
def getSysDataPath(subPath=""):
"""
Path to the data folder that is installed with MakeHuman system-wide.
NOTE: do NOT assume that getSysPath("data") == getSysDataPath()!
"""
if subPath:
path = getSysPath(os.path.join("data", subPath))
else:
path = getSysPath("data")
return formatPath(path)
def getSysPath(subPath=""):
"""
Path to the system folder where MakeHuman is installed (it is possible that
data is stored in another path).
Writing to this folder or modifying this data usually requires admin rights,
contains system-wide data (for all users).
"""
if subPath:
path = os.path.join(".", subPath)
else:
path = "."
return formatPath(path)
def _allnamesequal(name):
return all(n == name[0] for n in name[1:])
def commonprefix(paths, sep="/"):
"""
Implementation of os.path.commonprefix that works as you would expect.
Source: http://rosettacode.org/wiki/Find_Common_Directory_Path#Python
"""
from itertools import takewhile
bydirectorylevels = list(zip(*[p.split(sep) for p in paths]))
return sep.join(x[0] for x in takewhile(_allnamesequal, bydirectorylevels))
def isSubPath(subpath, path):
"""
Verifies whether subpath is within path.
"""
subpath = canonicalPath(subpath)
path = canonicalPath(path)
return commonprefix([subpath, path]) == path
def isSamePath(path1, path2):
"""
Determines whether two paths point to the same location.
"""
return canonicalPath(path1) == canonicalPath(path2)
def getRelativePath(path, relativeTo=[getDataPath(), getSysDataPath()], strict=False):
"""
Return a relative file path, relative to one of the specified search paths.
First valid path is returned, so order in which search paths are given matters.
"""
if not isinstance(relativeTo, list):
relativeTo = [relativeTo]
relto = None
for p in relativeTo:
if isSubPath(path, p):
relto = p
if relto is None:
if strict:
return None
else:
return path
relto = os.path.abspath(os.path.realpath(relto))
path = os.path.abspath(os.path.realpath(path))
rpath = os.path.relpath(path, relto)
return formatPath(rpath)
def findFile(relPath, searchPaths=[getDataPath(), getSysDataPath()], strict=False):
"""
Inverse of getRelativePath: find an absolute path from specified relative
path in one of the search paths.
First occurence is returned, so order in which search paths are given matters.
Note: does NOT treat the path as relative to the current working dir, unless
you explicitly specify '.' as one of the searchpaths.
"""
if not isinstance(searchPaths, list):
searchPaths = [searchPaths]
for dataPath in searchPaths:
path = os.path.join(dataPath, relPath)
if os.path.isfile(path):
return formatPath(path)
if strict:
return None
else:
return relPath
def thoroughFindFile(filename, searchPaths=[], searchDefaultPaths=True):
"""
Extensively search the data paths to find a file with matching filename in
as much cases as possible. If file is found, returns absolute filename.
If nothing is found return the most probable filename.
"""
# Ensure unix style path
filename.replace("\\", "/")
if not isinstance(searchPaths, list):
searchPaths = [searchPaths]
if searchDefaultPaths:
# Search in user / sys data, and user / sys root folders
searchPaths = list(searchPaths)
searchPaths.extend([getDataPath(), getSysDataPath(), getPath(), getSysPath()])
path = findFile(filename, searchPaths, strict=True)
if path:
return canonicalPath(path)
# Treat as absolute path or search relative to application path
if os.path.isfile(filename):
return canonicalPath(filename)
# Strip leading data/ folder if present (for the scenario where sysDataPath is not in sysPath)
if filename.startswith("data/"):
result = thoroughFindFile(filename[5:], searchPaths, False)
if os.path.isfile(result):
return result
# Nothing found
return formatPath(filename)
def search(paths, extensions, recursive=True, mutexExtensions=False):
"""
Search for files with specified extensions in specified paths.
If mutexExtensions is True, no duplicate files with only differing extension
will be returned. Instead, only the file with highest extension precedence
(extensions occurs earlier in the extensions list) is kept.
"""
if isinstance(paths, str):
paths = [paths]
if isinstance(extensions, str):
extensions = [extensions]
extensions = [e[1:].lower() if e.startswith(".") else e.lower() for e in extensions]
if mutexExtensions:
discovered = dict()
def _aggregate_files_mutexExt(filepath):
basep, ext = os.path.splitext(filepath)
ext = ext[1:]
if basep in discovered:
if extensions.index(ext) < extensions.index(discovered[basep]):
discovered[basep] = ext
else:
discovered[basep] = ext
if recursive:
for path in paths:
for root, dirs, files in os.walk(path):
for f in files:
ext = os.path.splitext(f)[1][1:].lower()
if ext in extensions:
if mutexExtensions:
_aggregate_files_mutexExt(os.path.join(root, f))
else:
yield pathToUnicode(os.path.join(root, f))
else:
for path in paths:
if not os.path.isdir(path):
continue
for f in os.listdir(path):
f = os.path.join(path, f)
if os.path.isfile(f):
ext = os.path.splitext(f)[1][1:].lower()
if ext in extensions:
if mutexExtensions:
_aggregate_files_mutexExt(f)
else:
yield pathToUnicode(f)
if mutexExtensions:
for f in ["%s.%s" % (p, e) for p, e in list(discovered.items())]:
yield pathToUnicode(f)
def getJailedPath(filepath, relativeTo, jailLimits=[getDataPath(), getSysDataPath()]):
"""
Get a path to filepath, relative to relativeTo path, confined within the
jailLimits folders. Returns None if the path would fall outside of the jail.
This is a portable path which can be used for distributing eg. materials
(texture paths are portable).
Returns None if the filepath falls outside of the jail folders. Returns
a path to filename relative to relativeTo path if it is a subpath of it,
else returns a path relative to the jailLimits.
"""
def _withinJail(path):
for j in jailLimits:
if isSubPath(path, j):
return True
return False
# These paths may become messed up when using symlinks for user home.
# Make sure we use the same paths when calculating relative paths.
filepath = os.path.realpath(filepath)
if relativeTo is str:
relativeTo = os.path.realpath(relativeTo)
output = None
if _withinJail(filepath):
relPath = getRelativePath(filepath, relativeTo, strict=True)
if relPath:
output = relPath
else:
output = getRelativePath(filepath, jailLimits)
return output