Ticket #534: unicode-filenames-handling-v4.diff
File unicode-filenames-handling-v4.diff, 31.7 KB (added by francois, at 2010-05-20T00:55:07Z) |
---|
-
docs/frontends/CLI.txt
Thu May 20 02:43:56 CEST 2010 Francois Deppierraz <francois@ctrlaltdel.ch> * Fix handling of correctly encoded unicode filenames (#534) Tahoe CLI commands working on local files, for instance 'tahoe cp' or 'tahoe backup', have been improved to correctly handle filenames containing non-ASCII characters. In the case where Tahoe encounters a filename which cannot be decoded using the system encoding, an error will be returned and the operation will fail. Under Linux, this typically happens when the filesystem contains filenames encoded with another encoding, for instance latin1, than the system locale, for instance UTF-8. In such case, you'll need to fix your system with tools such as 'convmv' before using Tahoe CLI. All CLI commands have been improved to support non-ASCII parameters such as filenames and aliases on all supported Operating Systems except Windows as of now. diff -rN -u old-tahoe-534/docs/frontends/CLI.txt new-tahoe-534/docs/frontends/CLI.txt
old new 123 123 perspective on the graph of files and directories. 124 124 125 125 Each tahoe node remembers a list of starting points, named "aliases", 126 in a file named ~/.tahoe/private/aliases . These aliases are short 127 strings that stand in for a directory read- or write- cap. If you use 128 the command line "ls" without any "[STARTING_DIR]:" argument, then it 129 will use the default alias, which is "tahoe", therefore "tahoe ls" has 130 the same effect as "tahoe ls tahoe:". The same goes for the other 131 commands which can reasonably use a default alias: get, put, mkdir,132 m v, and rm.126 in a file named ~/.tahoe/private/aliases . These aliases are short UTF-8 127 encoded strings that stand in for a directory read- or write- cap. If 128 you use the command line "ls" without any "[STARTING_DIR]:" argument, 129 then it will use the default alias, which is "tahoe", therefore "tahoe 130 ls" has the same effect as "tahoe ls tahoe:". The same goes for the 131 other commands which can reasonably use a default alias: get, put, 132 mkdir, mv, and rm. 133 133 134 134 For backwards compatibility with Tahoe-1.0, if the "tahoe": alias is not 135 135 found in ~/.tahoe/private/aliases, the CLI will use the contents of -
NEWS
diff -rN -u old-tahoe-534/NEWS new-tahoe-534/NEWS
old new 1 1 User visible changes in Tahoe-LAFS. -*- outline -*- 2 2 3 * Release 1.7.0 4 5 ** Bugfixes 6 7 *** Unicode filenames handling 8 9 Tahoe CLI commands working on local files, for instance 'tahoe cp' or 'tahoe 10 backup', have been improved to correctly handle filenames containing non-ASCII 11 characters. 12 13 In the case where Tahoe encounters a filename which cannot be decoded using the 14 system encoding, an error will be returned and the operation will fail. Under 15 Linux, this typically happens when the filesystem contains filenames encoded 16 with another encoding, for instance latin1, than the system locale, for 17 instance UTF-8. In such case, you'll need to fix your system with tools such 18 as 'convmv' before using Tahoe CLI. 19 20 All CLI commands have been improved to support non-ASCII parameters such as 21 filenames and aliases on all supported Operating Systems except Windows as of 22 now. 23 3 24 * Release 1.6.1 (2010-02-27) 4 25 5 26 ** Bugfixes -
src/allmydata/scripts/cli.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/cli.py new-tahoe-534/src/allmydata/scripts/cli.py
old new 1 1 import os.path, re, sys, fnmatch 2 2 from twisted.python import usage 3 3 from allmydata.scripts.common import BaseOptions, get_aliases 4 from allmydata.util.stringutils import argv_to_unicode 4 5 5 6 NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?") 6 7 … … 49 50 50 51 class MakeDirectoryOptions(VDriveOptions): 51 52 def parseArgs(self, where=""): 52 self.where = where53 self.where = argv_to_unicode(where) 53 54 longdesc = """Create a new directory, either unlinked or as a subdirectory.""" 54 55 55 56 class AddAliasOptions(VDriveOptions): 56 57 def parseArgs(self, alias, cap): 57 self.alias = a lias58 self.alias = argv_to_unicode(alias) 58 59 self.cap = cap 59 60 60 61 def getSynopsis(self): … … 64 65 65 66 class CreateAliasOptions(VDriveOptions): 66 67 def parseArgs(self, alias): 67 self.alias = a lias68 self.alias = argv_to_unicode(alias) 68 69 69 70 def getSynopsis(self): 70 71 return "%s create-alias ALIAS" % (os.path.basename(sys.argv[0]),) … … 83 84 ("json", None, "Show the raw JSON output"), 84 85 ] 85 86 def parseArgs(self, where=""): 86 self.where = where87 self.where = argv_to_unicode(where) 87 88 88 89 longdesc = """ 89 90 List the contents of some portion of the grid. … … 118 119 # tahoe get FOO bar # write to local file 119 120 # tahoe get tahoe:FOO bar # same 120 121 121 self.from_file = arg1 122 self.to_file = arg2 122 self.from_file = argv_to_unicode(arg1) 123 124 if arg2: 125 self.to_file = argv_to_unicode(arg2) 126 else: 127 self.to_file = None 128 123 129 if self.to_file == "-": 124 130 self.to_file = None 125 131 … … 151 157 # see Examples below 152 158 153 159 if arg1 is not None and arg2 is not None: 154 self.from_file = arg 1155 self.to_file = arg2160 self.from_file = argv_to_unicode(arg1) 161 self.to_file = argv_to_unicode(arg2) 156 162 elif arg1 is not None and arg2 is None: 157 self.from_file = arg 1# might be "-"163 self.from_file = argv_to_unicode(arg1) # might be "-" 158 164 self.to_file = None 159 165 else: 160 166 self.from_file = None 161 167 self.to_file = None 162 if self.from_file == "-":168 if self.from_file == u"-": 163 169 self.from_file = None 164 170 165 171 def getSynopsis(self): … … 197 203 def parseArgs(self, *args): 198 204 if len(args) < 2: 199 205 raise usage.UsageError("cp requires at least two arguments") 200 self.sources = args[:-1]201 self.destination = arg s[-1]206 self.sources = map(argv_to_unicode, args[:-1]) 207 self.destination = argv_to_unicode(args[-1]) 202 208 def getSynopsis(self): 203 209 return "Usage: tahoe [options] cp FROM.. TO" 204 210 longdesc = """ … … 228 234 229 235 class RmOptions(VDriveOptions): 230 236 def parseArgs(self, where): 231 self.where = where237 self.where = argv_to_unicode(where) 232 238 233 239 def getSynopsis(self): 234 240 return "%s rm REMOTE_FILE" % (os.path.basename(sys.argv[0]),) 235 241 236 242 class MvOptions(VDriveOptions): 237 243 def parseArgs(self, frompath, topath): 238 self.from_file = frompath239 self.to_file = topath244 self.from_file = argv_to_unicode(frompath) 245 self.to_file = argv_to_unicode(topath) 240 246 241 247 def getSynopsis(self): 242 248 return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),) … … 254 260 255 261 class LnOptions(VDriveOptions): 256 262 def parseArgs(self, frompath, topath): 257 self.from_file = frompath258 self.to_file = topath263 self.from_file = argv_to_unicode(frompath) 264 self.to_file = argv_to_unicode(topath) 259 265 260 266 def getSynopsis(self): 261 267 return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),) … … 279 285 self['exclude'] = set() 280 286 281 287 def parseArgs(self, localdir, topath): 282 self.from_dir = localdir283 self.to_dir = topath288 self.from_dir = argv_to_unicode(localdir) 289 self.to_dir = argv_to_unicode(topath) 284 290 285 291 def getSynopsis(Self): 286 292 return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0]) … … 337 343 ("info", "i", "Open the t=info page for the file"), 338 344 ] 339 345 def parseArgs(self, where=''): 340 self.where = where346 self.where = argv_to_unicode(where) 341 347 342 348 def getSynopsis(self): 343 349 return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 354 360 ("raw", "r", "Display raw JSON data instead of parsed"), 355 361 ] 356 362 def parseArgs(self, where=''): 357 self.where = where363 self.where = argv_to_unicode(where) 358 364 359 365 def getSynopsis(self): 360 366 return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 367 373 ("raw", "r", "Display raw JSON data instead of parsed"), 368 374 ] 369 375 def parseArgs(self, where=''): 370 self.where = where376 self.where = argv_to_unicode(where) 371 377 372 378 def getSynopsis(self): 373 379 return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 383 389 ("add-lease", None, "Add/renew lease on all shares"), 384 390 ] 385 391 def parseArgs(self, where=''): 386 self.where = where392 self.where = argv_to_unicode(where) 387 393 388 394 def getSynopsis(self): 389 395 return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 402 408 ("verbose", "v", "Be noisy about what is happening."), 403 409 ] 404 410 def parseArgs(self, where=''): 405 self.where = where411 self.where = argv_to_unicode(where) 406 412 407 413 def getSynopsis(self): 408 414 return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) -
src/allmydata/scripts/common.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/common.py new-tahoe-534/src/allmydata/scripts/common.py
old new 1 1 2 2 import os, sys, urllib 3 import codecs 3 4 from twisted.python import usage 4 5 from allmydata.util.stringutils import unicode_to_url 6 from allmydata.util.assertutil import precondition 5 7 6 8 class BaseOptions: 7 9 # unit tests can override these to point at StringIO instances … … 100 102 except EnvironmentError: 101 103 pass 102 104 try: 103 f = open(aliasfile, "r")105 f = codecs.open(aliasfile, "r", "utf-8") 104 106 for line in f.readlines(): 105 107 line = line.strip() 106 108 if line.startswith("#") or not line: 107 109 continue 108 110 name, cap = line.split(":", 1) 109 111 # normalize it: remove http: prefix, urldecode 110 cap = cap.strip() 112 cap = cap.strip().encode('utf-8') 111 113 aliases[name] = uri.from_string_dirnode(cap).to_string() 112 114 except EnvironmentError: 113 115 pass … … 138 140 # and default is not found in aliases, an UnknownAliasError is 139 141 # raised. 140 142 path = path.strip() 141 if uri.has_uri_prefix(path ):143 if uri.has_uri_prefix(path.encode('utf-8')): 142 144 # We used to require "URI:blah:./foo" in order to get a subpath, 143 145 # stripping out the ":./" sequence. We still allow that for compatibility, 144 146 # but now also allow just "URI:blah/foo". … … 180 182 181 183 def escape_path(path): 182 184 segments = path.split("/") 183 return "/".join([urllib.quote( s) for s in segments])185 return "/".join([urllib.quote(unicode_to_url(s)) for s in segments]) -
src/allmydata/scripts/tahoe_add_alias.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_add_alias.py new-tahoe-534/src/allmydata/scripts/tahoe_add_alias.py
old new 1 1 2 2 import os.path 3 import codecs 4 import sys 3 5 from allmydata import uri 4 6 from allmydata.scripts.common_http import do_http, check_http_error 5 7 from allmydata.scripts.common import get_aliases 6 8 from allmydata.util.fileutil import move_into_place 9 from allmydata.util.stringutils import unicode_to_stdout 10 7 11 8 12 def add_line_to_aliasfile(aliasfile, alias, cap): 9 13 # we use os.path.exists, rather than catching EnvironmentError, to avoid 10 14 # clobbering the valuable alias file in case of spurious or transient 11 15 # filesystem errors. 12 16 if os.path.exists(aliasfile): 13 f = open(aliasfile, "r")17 f = codecs.open(aliasfile, "r", "utf-8") 14 18 aliases = f.read() 15 19 f.close() 16 20 if not aliases.endswith("\n"): … … 18 22 else: 19 23 aliases = "" 20 24 aliases += "%s: %s\n" % (alias, cap) 21 f = open(aliasfile+".tmp", "w")25 f = codecs.open(aliasfile+".tmp", "w", "utf-8") 22 26 f.write(aliases) 23 27 f.close() 24 28 move_into_place(aliasfile+".tmp", aliasfile) … … 41 45 42 46 add_line_to_aliasfile(aliasfile, alias, cap) 43 47 44 print >>stdout, "Alias '%s' added" % ( alias,)48 print >>stdout, "Alias '%s' added" % (unicode_to_stdout(alias),) 45 49 return 0 46 50 47 51 def create_alias(options): … … 74 78 75 79 add_line_to_aliasfile(aliasfile, alias, new_uri) 76 80 77 print >>stdout, "Alias '%s' created" % ( alias,)81 print >>stdout, "Alias '%s' created" % (unicode_to_stdout(alias),) 78 82 return 0 79 83 80 84 def list_aliases(options): -
src/allmydata/scripts/tahoe_backup.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_backup.py new-tahoe-534/src/allmydata/scripts/tahoe_backup.py
old new 9 9 from allmydata.scripts.common_http import do_http 10 10 from allmydata.util import time_format 11 11 from allmydata.scripts import backupdb 12 import sys 13 from allmydata.util.stringutils import unicode_to_stdout, listdir_unicode, open_unicode 14 from allmydata.util.assertutil import precondition 15 from twisted.python import usage 16 12 17 13 18 class HTTPError(Exception): 14 19 pass … … 154 159 155 160 def verboseprint(self, msg): 156 161 if self.verbosity >= 2: 162 if isinstance(msg, unicode): 163 msg = unicode_to_stdout(msg) 164 157 165 print >>self.options.stdout, msg 158 166 159 167 def warn(self, msg): 160 168 print >>self.options.stderr, msg 161 169 162 170 def process(self, localpath): 171 precondition(isinstance(localpath, unicode), localpath) 163 172 # returns newdircap 164 173 165 174 self.verboseprint("processing %s" % localpath) … … 167 176 compare_contents = {} # childname -> rocap 168 177 169 178 try: 170 children = os.listdir(localpath)179 children = listdir_unicode(localpath) 171 180 except EnvironmentError: 172 181 self.directories_skipped += 1 173 182 self.warn("WARNING: permission denied on directory %s" % localpath) … … 283 292 284 293 # This function will raise an IOError exception when called on an unreadable file 285 294 def upload(self, childpath): 295 precondition(isinstance(childpath, unicode), childpath) 296 286 297 #self.verboseprint("uploading %s.." % childpath) 287 298 metadata = get_local_metadata(childpath) 288 299 … … 291 302 292 303 if must_upload: 293 304 self.verboseprint("uploading %s.." % childpath) 294 infileobj = open (os.path.expanduser(childpath), "rb")305 infileobj = open_unicode(os.path.expanduser(childpath), "rb") 295 306 url = self.options['node-url'] + "uri" 296 307 resp = do_http("PUT", url, infileobj) 297 308 if resp.status not in (200, 201): -
src/allmydata/scripts/tahoe_cp.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_cp.py new-tahoe-534/src/allmydata/scripts/tahoe_cp.py
old new 2 2 import os.path 3 3 import urllib 4 4 import simplejson 5 import sys 5 6 from cStringIO import StringIO 6 7 from twisted.python.failure import Failure 7 8 from allmydata.scripts.common import get_alias, escape_path, \ 8 9 DefaultAliasMarker, UnknownAliasError 9 10 from allmydata.scripts.common_http import do_http 10 11 from allmydata import uri 12 from twisted.python import usage 13 from allmydata.util.stringutils import unicode_to_url, listdir_unicode, open_unicode 14 from allmydata.util.assertutil import precondition 15 11 16 12 17 def ascii_or_none(s): 13 18 if s is None: … … 70 75 71 76 class LocalFileSource: 72 77 def __init__(self, pathname): 78 precondition(isinstance(pathname, unicode), pathname) 73 79 self.pathname = pathname 74 80 75 81 def need_to_copy_bytes(self): … … 80 86 81 87 class LocalFileTarget: 82 88 def __init__(self, pathname): 89 precondition(isinstance(pathname, unicode), pathname) 83 90 self.pathname = pathname 84 91 def put_file(self, inf): 85 92 outf = open(self.pathname, "wb") … … 92 99 93 100 class LocalMissingTarget: 94 101 def __init__(self, pathname): 102 precondition(isinstance(pathname, unicode), pathname) 95 103 self.pathname = pathname 96 104 97 105 def put_file(self, inf): … … 105 113 106 114 class LocalDirectorySource: 107 115 def __init__(self, progressfunc, pathname): 116 precondition(isinstance(pathname, unicode), pathname) 117 108 118 self.progressfunc = progressfunc 109 119 self.pathname = pathname 110 120 self.children = None … … 113 123 if self.children is not None: 114 124 return 115 125 self.children = {} 116 children = os.listdir(self.pathname)126 children = listdir_unicode(self.pathname) 117 127 for i,n in enumerate(children): 118 128 self.progressfunc("examining %d of %d" % (i, len(children))) 119 129 pn = os.path.join(self.pathname, n) … … 130 140 131 141 class LocalDirectoryTarget: 132 142 def __init__(self, progressfunc, pathname): 143 precondition(isinstance(pathname, unicode), pathname) 144 133 145 self.progressfunc = progressfunc 134 146 self.pathname = pathname 135 147 self.children = None … … 138 150 if self.children is not None: 139 151 return 140 152 self.children = {} 141 children = os.listdir(self.pathname)153 children = listdir_unicode(self.pathname) 142 154 for i,n in enumerate(children): 143 155 self.progressfunc("examining %d of %d" % (i, len(children))) 144 156 pn = os.path.join(self.pathname, n) … … 161 173 return LocalDirectoryTarget(self.progressfunc, pathname) 162 174 163 175 def put_file(self, name, inf): 176 precondition(isinstance(name, unicode), name) 164 177 pathname = os.path.join(self.pathname, name) 165 outf = open (pathname, "wb")178 outf = open_unicode(pathname, "wb") 166 179 while True: 167 180 data = inf.read(32768) 168 181 if not data: … … 355 368 if self.writecap: 356 369 url = self.nodeurl + "/".join(["uri", 357 370 urllib.quote(self.writecap), 358 urllib.quote( name.encode('utf-8'))])371 urllib.quote(unicode_to_url(name))]) 359 372 self.children[name] = TahoeFileTarget(self.nodeurl, mutable, 360 373 writecap, readcap, url) 361 374 elif data[0] == "dirnode": -
src/allmydata/scripts/tahoe_ls.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_ls.py new-tahoe-534/src/allmydata/scripts/tahoe_ls.py
old new 4 4 from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path, \ 5 5 UnknownAliasError 6 6 from allmydata.scripts.common_http import do_http 7 from allmydata.util.stringutils import unicode_to_stdout 7 8 8 9 def list(options): 9 10 nodeurl = options['node-url'] … … 130 131 line.append(ctime_s) 131 132 if not options["classify"]: 132 133 classify = "" 133 line.append( name+ classify)134 line.append(unicode_to_stdout(name) + classify) 134 135 if options["uri"]: 135 136 line.append(uri) 136 137 if options["readonly-uri"]: -
src/allmydata/scripts/tahoe_manifest.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_manifest.py new-tahoe-534/src/allmydata/scripts/tahoe_manifest.py
old new 85 85 try: 86 86 print >>stdout, d["cap"], "/".join(d["path"]) 87 87 except UnicodeEncodeError: 88 print >>stdout, d["cap"], "/".join([ p.encode("utf-8")88 print >>stdout, d["cap"], "/".join([unicode_to_stdout(p) 89 89 for p in d["path"]]) 90 90 91 91 def manifest(options): -
src/allmydata/scripts/tahoe_mkdir.py
diff -rN -u old-tahoe-534/src/allmydata/scripts/tahoe_mkdir.py new-tahoe-534/src/allmydata/scripts/tahoe_mkdir.py
old new 2 2 import urllib 3 3 from allmydata.scripts.common_http import do_http, check_http_error 4 4 from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, UnknownAliasError 5 from allmydata.util.stringutils import unicode_to_url 5 6 6 7 def mkdir(options): 7 8 nodeurl = options['node-url'] … … 35 36 path = path[:-1] 36 37 # path (in argv) must be "/".join([s.encode("utf-8") for s in segments]) 37 38 url = nodeurl + "uri/%s/%s?t=mkdir" % (urllib.quote(rootcap), 38 urllib.quote( path))39 urllib.quote(unicode_to_url(path))) 39 40 resp = do_http("POST", url) 40 41 check_http_error(resp, stderr) 41 42 new_uri = resp.read().strip() -
src/allmydata/test/test_cli.py
diff -rN -u old-tahoe-534/src/allmydata/test/test_cli.py new-tahoe-534/src/allmydata/test/test_cli.py
old new 6 6 import urllib 7 7 import re 8 8 import simplejson 9 import sys 9 10 10 11 from allmydata.util import fileutil, hashutil, base32 11 12 from allmydata import uri … … 26 27 from twisted.internet import threads # CLI tests use deferToThread 27 28 from twisted.python import usage 28 29 30 from allmydata.util.stringutils import listdir_unicode, open_unicode, \ 31 unicode_platform, FilenameEncodingError 32 29 33 timeout = 480 # deep_check takes 360s on Zandr's linksys box, others take > 240s 30 34 31 35 … … 284 288 "work": "WA", 285 289 "c": "CA"} 286 290 def ga1(path): 287 return get_alias(aliases, path, "tahoe")291 return get_alias(aliases, path, u"tahoe") 288 292 uses_lettercolon = common.platform_uses_lettercolon_drivename() 289 293 self.failUnlessEqual(ga1("bare"), ("TA", "bare")) 290 294 self.failUnlessEqual(ga1("baredir/file"), ("TA", "baredir/file")) … … 379 383 # default set to something that isn't in the aliases argument should 380 384 # raise an UnknownAliasError. 381 385 def ga4(path): 382 return get_alias(aliases, path, "badddefault:")386 return get_alias(aliases, path, u"badddefault:") 383 387 self.failUnlessRaises(common.UnknownAliasError, ga4, "afile") 384 388 self.failUnlessRaises(common.UnknownAliasError, ga4, "a/dir/path/") 385 389 … … 387 391 old = common.pretend_platform_uses_lettercolon 388 392 try: 389 393 common.pretend_platform_uses_lettercolon = True 390 retval = get_alias(aliases, path, "baddefault:")394 retval = get_alias(aliases, path, u"baddefault:") 391 395 finally: 392 396 common.pretend_platform_uses_lettercolon = old 393 397 return retval 394 398 self.failUnlessRaises(common.UnknownAliasError, ga5, "C:\\Windows") 395 399 400 def test_listdir_unicode_good(self): 401 basedir = u"cli/common/listdir_unicode_good" 402 fileutil.make_dirs(basedir) 403 404 files = (u'Lôzane', u'Bern', u'Genève') 405 406 for file in files: 407 open(os.path.join(basedir, file), "w").close() 408 409 for file in listdir_unicode(basedir): 410 self.failUnlessEqual(file in files, True) 411 412 def test_listdir_unicode_bad(self): 413 if unicode_platform(): 414 raise unittest.SkipTest("This test doesn't make any sense on architecture which handle filenames natively as Unicode entities.") 415 416 basedir = u"cli/common/listdir_unicode_bad" 417 fileutil.make_dirs(basedir) 418 419 files = (u'Lôzane', u'Bern', u'Genève') 420 421 # We use a wrong encoding on purpose 422 if sys.getfilesystemencoding() == 'UTF-8': 423 encoding = 'latin1' 424 else: 425 encoding = 'UTF-8' 426 427 for file in files: 428 path = os.path.join(basedir, file).encode(encoding) 429 open(path, "w").close() 430 431 self.failUnlessRaises(FilenameEncodingError, listdir_unicode, basedir) 396 432 397 433 class Help(unittest.TestCase): 398 434 … … 592 628 self.failUnless(aliases["un-corrupted2"].startswith("URI:DIR2:")) 593 629 d.addCallback(_check_not_corrupted) 594 630 595 return d596 631 632 def test_create_unicode(self): 633 if sys.getfilesystemencoding() not in ('UTF-8', 'mbcs'): 634 raise unittest.SkipTest("Arbitrary filenames are not supported by this platform") 635 636 if sys.stdout.encoding not in ('UTF-8'): 637 raise unittest.SkipTest("Arbitrary command-line arguments (argv) are not supported by this platform") 638 639 self.basedir = "cli/CreateAlias/create_unicode" 640 self.set_up_grid() 641 aliasfile = os.path.join(self.get_clientdir(), "private", "aliases") 642 643 d = self.do_cli("create-alias", "études") 644 def _check_create_unicode((rc,stdout,stderr)): 645 self.failUnlessEqual(rc, 0) 646 self.failIf(stderr) 647 648 # If stdout only supports ascii, accentuated characters are 649 # being replaced by '?' 650 if sys.stdout.encoding == "ANSI_X3.4-1968": 651 self.failUnless("Alias '?tudes' created" in stdout) 652 else: 653 self.failUnless("Alias 'études' created" in stdout) 654 655 aliases = get_aliases(self.get_clientdir()) 656 self.failUnless(aliases[u"études"].startswith("URI:DIR2:")) 657 d.addCallback(_check_create_unicode) 658 659 d.addCallback(lambda res: self.do_cli("ls", "études:")) 660 def _check_ls1((rc, stdout, stderr)): 661 self.failUnlessEqual(rc, 0) 662 self.failIf(stderr) 663 664 self.failUnlessEqual(stdout, "") 665 d.addCallback(_check_ls1) 666 667 d.addCallback(lambda res: self.do_cli("put", "-", "études:uploaded.txt", 668 stdin="Blah blah blah")) 669 670 d.addCallback(lambda res: self.do_cli("ls", "études:")) 671 def _check_ls2((rc, stdout, stderr)): 672 self.failUnlessEqual(rc, 0) 673 self.failIf(stderr) 674 675 self.failUnlessEqual(stdout, "uploaded.txt\n") 676 d.addCallback(_check_ls2) 677 678 d.addCallback(lambda res: self.do_cli("get", "études:uploaded.txt")) 679 def _check_get((rc, stdout, stderr)): 680 self.failUnlessEqual(rc, 0) 681 self.failIf(stderr) 682 self.failUnlessEqual(stdout, "Blah blah blah") 683 d.addCallback(_check_get) 684 685 # Ensure that an Unicode filename in an Unicode alias works as expected 686 d.addCallback(lambda res: self.do_cli("put", "-", "études:lumière.txt", 687 stdin="Let the sunshine In!")) 688 689 d.addCallback(lambda res: self.do_cli("get", 690 get_aliases(self.get_clientdir())[u"études"] + "/lumière.txt")) 691 def _check_get((rc, stdout, stderr)): 692 self.failUnlessEqual(rc, 0) 693 self.failIf(stderr) 694 self.failUnlessEqual(stdout, "Let the sunshine In!") 695 d.addCallback(_check_get) 696 697 return d 597 698 598 699 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase): 599 700 def _create_test_file(self): … … 865 966 return d 866 967 867 968 969 def test_immutable_from_file_unicode(self): 970 if sys.stdout.encoding not in ('UTF-8'): 971 raise unittest.SkipTest("Arbitrary command-line arguments (argv) are not supported by this platform") 972 973 # tahoe put file.txt "à trier.txt" 974 self.basedir = os.path.dirname(self.mktemp()) 975 self.set_up_grid() 976 977 rel_fn = os.path.join(self.basedir, "DATAFILE") 978 abs_fn = os.path.abspath(rel_fn) 979 # we make the file small enough to fit in a LIT file, for speed 980 DATA = "short file" 981 f = open(rel_fn, "w") 982 f.write(DATA) 983 f.close() 984 985 d = self.do_cli("create-alias", "tahoe") 986 987 d.addCallback(lambda res: 988 self.do_cli("put", rel_fn, "à trier.txt")) 989 def _uploaded((rc,stdout,stderr)): 990 readcap = stdout.strip() 991 self.failUnless(readcap.startswith("URI:LIT:")) 992 self.failUnless("201 Created" in stderr, stderr) 993 self.readcap = readcap 994 d.addCallback(_uploaded) 995 996 d.addCallback(lambda res: 997 self.do_cli("get", "tahoe:à trier.txt")) 998 d.addCallback(lambda (rc,stdout,stderr): 999 self.failUnlessEqual(stdout, DATA)) 1000 1001 return d 1002 868 1003 class List(GridTestMixin, CLITestMixin, unittest.TestCase): 869 1004 def test_list(self): 870 1005 self.basedir = "cli/List/list" … … 1146 1281 o.parseOptions, ["onearg"]) 1147 1282 1148 1283 def test_unicode_filename(self): 1284 if sys.getfilesystemencoding() not in ('UTF-8', 'mbcs'): 1285 raise unittest.SkipTest("Arbitrary filenames are not supported by this platform") 1286 1287 if sys.stdout.encoding not in ('UTF-8'): 1288 raise unittest.SkipTest("Arbitrary command-line arguments (argv) are not supported by this platform") 1289 1149 1290 self.basedir = "cli/Cp/unicode_filename" 1150 1291 self.set_up_grid() 1292 d = self.do_cli("create-alias", "tahoe") 1151 1293 1152 fn1 = os.path.join(self.basedir, "Ärtonwall") 1294 # Use unicode strings when calling os functions 1295 fn1 = os.path.join(self.basedir, u"Ärtonwall") 1153 1296 DATA1 = "unicode file content" 1154 1297 fileutil.write(fn1, DATA1) 1155 1298 1156 fn2 = os.path.join(self.basedir, "Metallica") 1157 DATA2 = "non-unicode file content" 1158 fileutil.write(fn2, DATA2) 1159 1160 # Bug #534 1161 # Assure that uploading a file whose name contains unicode character 1162 # doesn't prevent further uploads in the same directory 1163 d = self.do_cli("create-alias", "tahoe") 1164 d.addCallback(lambda res: self.do_cli("cp", fn1, "tahoe:")) 1165 d.addCallback(lambda res: self.do_cli("cp", fn2, "tahoe:")) 1299 d.addCallback(lambda res: self.do_cli("cp", fn1.encode('utf-8'), "tahoe:")) 1166 1300 1167 1301 d.addCallback(lambda res: self.do_cli("get", "tahoe:Ärtonwall")) 1168 1302 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA1)) 1169 1303 1304 fn2 = os.path.join(self.basedir, u"Metallica") 1305 DATA2 = "non-unicode file content" 1306 fileutil.write(fn2, DATA2) 1307 1308 d.addCallback(lambda res: self.do_cli("cp", fn2.encode('utf-8'), "tahoe:")) 1309 1170 1310 d.addCallback(lambda res: self.do_cli("get", "tahoe:Metallica")) 1171 1311 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA2)) 1172 1312 1313 d.addCallback(lambda res: self.do_cli("ls", "tahoe:")) 1314 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, "Metallica\nÄrtonwall\n")) 1315 1173 1316 return d 1174 test_unicode_filename.todo = "This behavior is not yet supported, although it does happen to work (for reasons that are ill-understood) on many platforms. See issue ticket #534."1175 1317 1176 1318 def test_dangling_symlink_vs_recursion(self): 1177 1319 if not hasattr(os, 'symlink'): … … 1278 1420 return d 1279 1421 1280 1422 1423 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase): 1424 def test_unicode_mkdir(self): 1425 self.basedir = os.path.dirname(self.mktemp()) 1426 self.set_up_grid() 1427 1428 d = self.do_cli("create-alias", "tahoe") 1429 d.addCallback(lambda res: self.do_cli("mkdir", "tahoe:Motörhead")) 1430 1431 return d 1432 1433 1281 1434 class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): 1282 1435 1283 1436 def writeto(self, path, data):