Unicode path fixes for drop-upload.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Daira Hopwood 2015-04-28 20:43:09 +01:00
parent 8cad9f4fd6
commit b37478195f
5 changed files with 65 additions and 45 deletions

View File

@ -493,11 +493,12 @@ class Client(node.Node, pollmixin.PollMixin):
"put the cap in a 'private/drop_upload_dircap' file, and delete this option.") "put the cap in a 'private/drop_upload_dircap' file, and delete this option.")
upload_dircap = self.get_or_create_private_config("drop_upload_dircap") upload_dircap = self.get_or_create_private_config("drop_upload_dircap")
local_dir_utf8 = self.get_config("drop_upload", "local.directory") local_dir_config = self.get_config("drop_upload", "local.directory").decode("utf-8")
local_dir = abspath_expanduser_unicode(local_dir_config, base=self.basedir)
try: try:
from allmydata.frontends import drop_upload from allmydata.frontends import drop_upload
s = drop_upload.DropUploader(self, upload_dircap, local_dir_utf8) s = drop_upload.DropUploader(self, upload_dircap, local_dir)
s.setServiceParent(self) s.setServiceParent(self)
s.startService() s.startService()
except Exception, e: except Exception, e:

View File

@ -8,41 +8,37 @@ from foolscap.api import eventually
from allmydata.interfaces import IDirectoryNode from allmydata.interfaces import IDirectoryNode
from allmydata.util.encodingutil import quote_output, get_filesystem_encoding from allmydata.util.fileutil import abspath_expanduser_unicode, precondition_abspath
from allmydata.util.fileutil import abspath_expanduser_unicode from allmydata.util.encodingutil import listdir_unicode, to_filepath, \
unicode_from_filepath, quote_local_unicode_path, FilenameEncodingError
from allmydata.immutable.upload import FileName from allmydata.immutable.upload import FileName
class DropUploader(service.MultiService): class DropUploader(service.MultiService):
name = 'drop-upload' name = 'drop-upload'
def __init__(self, client, upload_dircap, local_dir_utf8, inotify=None): def __init__(self, client, upload_dircap, local_dir, inotify=None):
precondition_abspath(local_dir)
service.MultiService.__init__(self) service.MultiService.__init__(self)
self._local_dir = abspath_expanduser_unicode(local_dir)
try:
local_dir_u = abspath_expanduser_unicode(local_dir_utf8.decode('utf-8'))
if sys.platform == "win32":
local_dir = local_dir_u
else:
local_dir = local_dir_u.encode(get_filesystem_encoding())
except (UnicodeEncodeError, UnicodeDecodeError):
raise AssertionError("The '[drop_upload] local.directory' parameter %s was not valid UTF-8 or "
"could not be represented in the filesystem encoding."
% quote_output(local_dir_utf8))
self._client = client self._client = client
self._stats_provider = client.stats_provider self._stats_provider = client.stats_provider
self._convergence = client.convergence self._convergence = client.convergence
self._local_path = FilePath(local_dir) self._local_path = to_filepath(self._local_dir)
if inotify is None: if inotify is None:
from twisted.internet import inotify from twisted.internet import inotify
self._inotify = inotify self._inotify = inotify
if not self._local_path.exists(): if not self._local_path.exists():
raise AssertionError("The '[drop_upload] local.directory' parameter was %s but there is no directory at that location." % quote_output(local_dir_u)) raise AssertionError("The '[drop_upload] local.directory' parameter was %s "
"but there is no directory at that location."
% quote_local_unicode_path(local_dir))
if not self._local_path.isdir(): if not self._local_path.isdir():
raise AssertionError("The '[drop_upload] local.directory' parameter was %s but the thing at that location is not a directory." % quote_output(local_dir_u)) raise AssertionError("The '[drop_upload] local.directory' parameter was %s "
"but the thing at that location is not a directory."
% quote_local_unicode_path(local_dir))
# TODO: allow a path rather than a cap URI. # TODO: allow a path rather than a cap URI.
self._parent = self._client.create_node_from_uri(upload_dircap) self._parent = self._client.create_node_from_uri(upload_dircap)

View File

@ -27,7 +27,7 @@ BASECONFIG_I = ("[client]\n"
"introducer.furl = %s\n" "introducer.furl = %s\n"
) )
class Basic(testutil.ReallyEqualMixin, unittest.TestCase): class Basic(testutil.ReallyEqualMixin, testutil.NonASCIIPathMixin, unittest.TestCase):
def test_loadable(self): def test_loadable(self):
basedir = "test_client.Basic.test_loadable" basedir = "test_client.Basic.test_loadable"
os.mkdir(basedir) os.mkdir(basedir)
@ -316,7 +316,8 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
self.patch(allmydata.frontends.drop_upload, 'DropUploader', MockDropUploader) self.patch(allmydata.frontends.drop_upload, 'DropUploader', MockDropUploader)
upload_dircap = "URI:DIR2:blah" upload_dircap = "URI:DIR2:blah"
local_dir_utf8 = u"loc\u0101l_dir".encode('utf-8') local_dir_u = self.unicode_or_fallback(u"loc\u0101l_dir", u"local_dir")
local_dir_utf8 = local_dir_u.encode('utf-8')
config = (BASECONFIG + config = (BASECONFIG +
"[storage]\n" + "[storage]\n" +
"enabled = false\n" + "enabled = false\n" +
@ -344,7 +345,7 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
self.failUnless(isinstance(uploader, MockDropUploader), uploader) self.failUnless(isinstance(uploader, MockDropUploader), uploader)
self.failUnlessReallyEqual(uploader.client, c1) self.failUnlessReallyEqual(uploader.client, c1)
self.failUnlessReallyEqual(uploader.upload_dircap, upload_dircap) self.failUnlessReallyEqual(uploader.upload_dircap, upload_dircap)
self.failUnlessReallyEqual(uploader.local_dir_utf8, local_dir_utf8) self.failUnlessReallyEqual(os.path.basename(uploader.local_dir), local_dir_u)
self.failUnless(uploader.inotify is None, uploader.inotify) self.failUnless(uploader.inotify is None, uploader.inotify)
self.failUnless(uploader.running) self.failUnless(uploader.running)

View File

@ -2,19 +2,20 @@
import os, sys import os, sys
from twisted.trial import unittest from twisted.trial import unittest
from twisted.python import filepath, runtime from twisted.python import runtime
from twisted.internet import defer from twisted.internet import defer
from allmydata.interfaces import IDirectoryNode, NoSuchChildError from allmydata.interfaces import IDirectoryNode, NoSuchChildError
from allmydata.util import fake_inotify from allmydata.util import fake_inotify, fileutil
from allmydata.util.encodingutil import get_filesystem_encoding from allmydata.util.encodingutil import get_filesystem_encoding, to_filepath
from allmydata.util.consumer import download_to_data from allmydata.util.consumer import download_to_data
from allmydata.test.no_network import GridTestMixin from allmydata.test.no_network import GridTestMixin
from allmydata.test.common_util import ReallyEqualMixin, NonASCIIPathMixin from allmydata.test.common_util import ReallyEqualMixin, NonASCIIPathMixin
from allmydata.test.common import ShouldFailMixin from allmydata.test.common import ShouldFailMixin
from allmydata.frontends.drop_upload import DropUploader from allmydata.frontends.drop_upload import DropUploader
from allmydata.util.fileutil import abspath_expanduser_unicode
class DropUploadTestMixin(GridTestMixin, ShouldFailMixin, ReallyEqualMixin, NonASCIIPathMixin): class DropUploadTestMixin(GridTestMixin, ShouldFailMixin, ReallyEqualMixin, NonASCIIPathMixin):
@ -23,6 +24,10 @@ class DropUploadTestMixin(GridTestMixin, ShouldFailMixin, ReallyEqualMixin, NonA
with the real INotify. with the real INotify.
""" """
def setUp(self):
GridTestMixin.setUp(self)
temp = self.mktemp()
self.basedir = abspath_expanduser_unicode(temp.decode(get_filesystem_encoding()))
def _get_count(self, name): def _get_count(self, name):
return self.stats_provider.get_stats()["counters"].get(name, 0) return self.stats_provider.get_stats()["counters"].get(name, 0)
@ -84,23 +89,21 @@ class DropUploadTestMixin(GridTestMixin, ShouldFailMixin, ReallyEqualMixin, NonA
# (otherwise we would get a defer.AlreadyCalledError). Should we be relying on that? # (otherwise we would get a defer.AlreadyCalledError). Should we be relying on that?
self.uploader.set_uploaded_callback(d.callback) self.uploader.set_uploaded_callback(d.callback)
path_u = os.path.join(self.local_dir, name_u) path_u = abspath_expanduser_unicode(name_u, base=self.local_dir)
if sys.platform == "win32": path = to_filepath(path_u)
path = filepath.FilePath(path_u)
else:
path = filepath.FilePath(path_u.encode(get_filesystem_encoding()))
# We don't use FilePath.setContent() here because it creates a temporary file that # We don't use FilePath.setContent() here because it creates a temporary file that
# is renamed into place, which causes events that the test is not expecting. # is renamed into place, which causes events that the test is not expecting.
f = open(path.path, "wb") f = open(path_u, "wb")
try: try:
if temporary and sys.platform != "win32": if temporary and sys.platform != "win32":
os.unlink(path.path) os.unlink(path_u)
f.write(data) f.write(data)
finally: finally:
f.close() f.close()
if temporary and sys.platform == "win32": if temporary and sys.platform == "win32":
os.unlink(path.path) os.unlink(path_u)
fileutil.flush_volume(path_u)
self.notify_close_write(path) self.notify_close_write(path)
if temporary: if temporary:
@ -123,10 +126,14 @@ class MockTest(DropUploadTestMixin, unittest.TestCase):
"""This can run on any platform, and even if twisted.internet.inotify can't be imported.""" """This can run on any platform, and even if twisted.internet.inotify can't be imported."""
def test_errors(self): def test_errors(self):
self.basedir = "drop_upload.MockTest.test_errors"
self.set_up_grid() self.set_up_grid()
errors_dir = os.path.join(self.basedir, "errors_dir")
errors_dir = abspath_expanduser_unicode(u"errors_dir", base=self.basedir)
os.mkdir(errors_dir) os.mkdir(errors_dir)
not_a_dir = abspath_expanduser_unicode(u"NOT_A_DIR", base=self.basedir)
fileutil.write(not_a_dir, "")
magicfolderdb = abspath_expanduser_unicode(u"magicfolderdb", base=self.basedir)
doesnotexist = abspath_expanduser_unicode(u"doesnotexist", base=self.basedir)
client = self.g.clients[0] client = self.g.clients[0]
d = client.create_dirnode() d = client.create_dirnode()
@ -135,16 +142,10 @@ class MockTest(DropUploadTestMixin, unittest.TestCase):
upload_dircap = n.get_uri() upload_dircap = n.get_uri()
readonly_dircap = n.get_readonly_uri() readonly_dircap = n.get_readonly_uri()
self.shouldFail(AssertionError, 'invalid local.directory', 'could not be represented',
DropUploader, client, upload_dircap, '\xFF', inotify=fake_inotify)
self.shouldFail(AssertionError, 'nonexistent local.directory', 'there is no directory', self.shouldFail(AssertionError, 'nonexistent local.directory', 'there is no directory',
DropUploader, client, upload_dircap, os.path.join(self.basedir, "Laputa"), inotify=fake_inotify) DropUploader, client, upload_dircap, doesnotexist, inotify=fake_inotify)
fp = filepath.FilePath(self.basedir).child('NOT_A_DIR')
fp.touch()
self.shouldFail(AssertionError, 'non-directory local.directory', 'is not a directory', self.shouldFail(AssertionError, 'non-directory local.directory', 'is not a directory',
DropUploader, client, upload_dircap, fp.path, inotify=fake_inotify) DropUploader, client, upload_dircap, not_a_dir, inotify=fake_inotify)
self.shouldFail(AssertionError, 'bad upload.dircap', 'does not refer to a directory', self.shouldFail(AssertionError, 'bad upload.dircap', 'does not refer to a directory',
DropUploader, client, 'bad', errors_dir, inotify=fake_inotify) DropUploader, client, 'bad', errors_dir, inotify=fake_inotify)
self.shouldFail(AssertionError, 'non-directory upload.dircap', 'does not refer to a directory', self.shouldFail(AssertionError, 'non-directory upload.dircap', 'does not refer to a directory',

View File

@ -8,6 +8,7 @@ from types import NoneType
from allmydata.util.assertutil import precondition from allmydata.util.assertutil import precondition
from twisted.python import usage from twisted.python import usage
from twisted.python.filepath import FilePath
from allmydata.util import log from allmydata.util import log
from allmydata.util.fileutil import abspath_expanduser_unicode from allmydata.util.fileutil import abspath_expanduser_unicode
@ -35,9 +36,10 @@ def check_encoding(encoding):
filesystem_encoding = None filesystem_encoding = None
io_encoding = None io_encoding = None
is_unicode_platform = False is_unicode_platform = False
use_unicode_filepath = False
def _reload(): def _reload():
global filesystem_encoding, io_encoding, is_unicode_platform global filesystem_encoding, io_encoding, is_unicode_platform, use_unicode_filepath
filesystem_encoding = canonical_encoding(sys.getfilesystemencoding()) filesystem_encoding = canonical_encoding(sys.getfilesystemencoding())
check_encoding(filesystem_encoding) check_encoding(filesystem_encoding)
@ -61,6 +63,8 @@ def _reload():
is_unicode_platform = sys.platform in ["win32", "darwin"] is_unicode_platform = sys.platform in ["win32", "darwin"]
use_unicode_filepath = sys.platform == "win32" or hasattr(FilePath, '_asTextPath')
_reload() _reload()
@ -245,6 +249,23 @@ def quote_local_unicode_path(path, quotemarks=True):
return quote_output(path, quotemarks=quotemarks, quote_newlines=True) return quote_output(path, quotemarks=quotemarks, quote_newlines=True)
def to_filepath(path):
precondition(isinstance(path, basestring), path=path)
if isinstance(path, unicode) and not use_unicode_filepath:
path = path.encode(filesystem_encoding)
return FilePath(path)
def unicode_from_filepath(fp):
precondition(isinstance(fp, FilePath), fp=fp)
path = fp.path
if isinstance(path, bytes):
path = path.decode(filesystem_encoding)
return path
def unicode_platform(): def unicode_platform():
""" """