mirror of
https://github.com/beetbox/beets.git
synced 2026-02-20 06:14:22 +01:00
make syspath/bytestring_path roundtrip on Windows
This is an alternative to #58 that makes bytestring_path perform more like the inverse of syspath on Windows. This way, we can convert to syspath, operate on the path, and then bring back to internal representation without data loss. This involves looking for the magic prefix on the Unicode string and removing it before encoding to the internal (UTF-8) representation.
This commit is contained in:
parent
1f8fff7445
commit
16f207e927
2 changed files with 46 additions and 15 deletions
|
|
@ -24,6 +24,7 @@ from collections import defaultdict
|
|||
import traceback
|
||||
|
||||
MAX_FILENAME_LENGTH = 200
|
||||
WINDOWS_MAGIC_PREFIX = u'\\\\?\\'
|
||||
|
||||
class HumanReadableException(Exception):
|
||||
"""An Exception that can include a human-readable error message to
|
||||
|
|
@ -108,7 +109,9 @@ def normpath(path):
|
|||
"""Provide the canonical form of the path suitable for storing in
|
||||
the database.
|
||||
"""
|
||||
return os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
path = syspath(path)
|
||||
path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
return bytestring_path(path)
|
||||
|
||||
def ancestry(path, pathmod=None):
|
||||
"""Return a list consisting of path's parent directory, its
|
||||
|
|
@ -256,14 +259,23 @@ def _fsencoding():
|
|||
encoding = 'utf8'
|
||||
return encoding
|
||||
|
||||
def bytestring_path(path):
|
||||
def bytestring_path(path, pathmod=None):
|
||||
"""Given a path, which is either a str or a unicode, returns a str
|
||||
path (ensuring that we never deal with Unicode pathnames).
|
||||
"""
|
||||
pathmod = pathmod or os.path
|
||||
windows = pathmod.__name__ == 'ntpath'
|
||||
|
||||
# Pass through bytestrings.
|
||||
if isinstance(path, str):
|
||||
return path
|
||||
|
||||
# On Windows, remove the magic prefix added by `syspath`. This makes
|
||||
# ``bytestring_path(syspath(X)) == X``, i.e., we can safely
|
||||
# round-trip through `syspath`.
|
||||
if windows and path.startswith(WINDOWS_MAGIC_PREFIX):
|
||||
path = path[len(WINDOWS_MAGIC_PREFIX):]
|
||||
|
||||
# Try to encode with default encodings, but fall back to UTF8.
|
||||
try:
|
||||
return path.encode(_fsencoding())
|
||||
|
|
@ -310,8 +322,8 @@ def syspath(path, pathmod=None):
|
|||
path = path.decode(encoding, 'replace')
|
||||
|
||||
# Add the magic prefix if it isn't already there
|
||||
if not path.startswith(u'\\\\?\\'):
|
||||
path = u'\\\\?\\' + path
|
||||
if not path.startswith(WINDOWS_MAGIC_PREFIX):
|
||||
path = WINDOWS_MAGIC_PREFIX + path
|
||||
|
||||
return path
|
||||
|
||||
|
|
|
|||
|
|
@ -341,17 +341,6 @@ class DestinationTest(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(self.lib.destination(self.i), np('one/three'))
|
||||
|
||||
def test_syspath_windows_format(self):
|
||||
path = ntpath.join('a', 'b', 'c')
|
||||
outpath = util.syspath(path, ntpath)
|
||||
self.assertTrue(isinstance(outpath, unicode))
|
||||
self.assertTrue(outpath.startswith(u'\\\\?\\'))
|
||||
|
||||
def test_syspath_posix_unchanged(self):
|
||||
path = posixpath.join('a', 'b', 'c')
|
||||
outpath = util.syspath(path, posixpath)
|
||||
self.assertEqual(path, outpath)
|
||||
|
||||
def test_sanitize_windows_replaces_trailing_space(self):
|
||||
p = util.sanitize_path(u'one/two /three', ntpath)
|
||||
self.assertFalse(' ' in p)
|
||||
|
|
@ -563,6 +552,36 @@ class DisambiguationTest(unittest.TestCase, PathFormattingMixin):
|
|||
self._setf(u'foo%aunique{albumartist album,albumtype}/$title')
|
||||
self._assert_dest('/base/foo [foo_bar]/the title', self.i1)
|
||||
|
||||
class PathConversionTest(unittest.TestCase):
|
||||
def test_syspath_windows_format(self):
|
||||
path = ntpath.join('a', 'b', 'c')
|
||||
outpath = util.syspath(path, ntpath)
|
||||
self.assertTrue(isinstance(outpath, unicode))
|
||||
self.assertTrue(outpath.startswith(u'\\\\?\\'))
|
||||
|
||||
def test_syspath_posix_unchanged(self):
|
||||
path = posixpath.join('a', 'b', 'c')
|
||||
outpath = util.syspath(path, posixpath)
|
||||
self.assertEqual(path, outpath)
|
||||
|
||||
def _windows_bytestring_path(self, path):
|
||||
old_gfse = sys.getfilesystemencoding
|
||||
sys.getfilesystemencoding = lambda: 'mbcs'
|
||||
try:
|
||||
return util.bytestring_path(path, ntpath)
|
||||
finally:
|
||||
sys.getfilesystemencoding = old_gfse
|
||||
|
||||
def test_bytestring_path_windows_encodes_utf8(self):
|
||||
path = u'caf\xe9'
|
||||
outpath = self._windows_bytestring_path(path)
|
||||
self.assertEqual(path, outpath.decode('utf8'))
|
||||
|
||||
def test_bytesting_path_windows_removes_magic_prefix(self):
|
||||
path = u'\\\\?\\C:\\caf\xe9'
|
||||
outpath = self._windows_bytestring_path(path)
|
||||
self.assertEqual(outpath, u'C:\\caf\xe9'.encode('utf8'))
|
||||
|
||||
class PluginDestinationTest(unittest.TestCase):
|
||||
# Mock the plugins.template_values(item) function.
|
||||
def _template_values(self, item):
|
||||
|
|
|
|||
Loading…
Reference in a new issue