Ticket #534: unicode-filenames-handling-v2.diff
File unicode-filenames-handling-v2.diff, 30.3 KB (added by francois, at 2010-05-17T08:07:44Z) |
---|
-
docs/frontends/CLI.txt
Sun May 16 23:43:37 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. ***END OF DESCRIPTION*** Place the long patch description above the ***END OF DESCRIPTION*** marker. The first line of this file will be the patch name. This patch contains the following changes: 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://([^:]*)(:([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]) … … 334 340 335 341 class WebopenOptions(VDriveOptions): 336 342 def parseArgs(self, where=''): 337 self.where = where343 self.where = argv_to_unicode(where) 338 344 339 345 def getSynopsis(self): 340 346 return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 350 356 ("raw", "r", "Display raw JSON data instead of parsed"), 351 357 ] 352 358 def parseArgs(self, where=''): 353 self.where = where359 self.where = argv_to_unicode(where) 354 360 355 361 def getSynopsis(self): 356 362 return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 363 369 ("raw", "r", "Display raw JSON data instead of parsed"), 364 370 ] 365 371 def parseArgs(self, where=''): 366 self.where = where372 self.where = argv_to_unicode(where) 367 373 368 374 def getSynopsis(self): 369 375 return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 379 385 ("add-lease", None, "Add/renew lease on all shares"), 380 386 ] 381 387 def parseArgs(self, where=''): 382 self.where = where388 self.where = argv_to_unicode(where) 383 389 384 390 def getSynopsis(self): 385 391 return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) … … 398 404 ("verbose", "v", "Be noisy about what is happening."), 399 405 ] 400 406 def parseArgs(self, where=''): 401 self.where = where407 self.where = argv_to_unicode(where) 402 408 403 409 def getSynopsis(self): 404 410 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('ascii') 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('ascii', 'ignore')): 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 … … 279 283 "work": "WA", 280 284 "c": "CA"} 281 285 def ga1(path): 282 return get_alias(aliases, path, "tahoe")286 return get_alias(aliases, path, u"tahoe") 283 287 uses_lettercolon = common.platform_uses_lettercolon_drivename() 284 288 self.failUnlessEqual(ga1("bare"), ("TA", "bare")) 285 289 self.failUnlessEqual(ga1("baredir/file"), ("TA", "baredir/file")) … … 374 378 # default set to something that isn't in the aliases argument should 375 379 # raise an UnknownAliasError. 376 380 def ga4(path): 377 return get_alias(aliases, path, "badddefault:")381 return get_alias(aliases, path, u"badddefault:") 378 382 self.failUnlessRaises(common.UnknownAliasError, ga4, "afile") 379 383 self.failUnlessRaises(common.UnknownAliasError, ga4, "a/dir/path/") 380 384 … … 382 386 old = common.pretend_platform_uses_lettercolon 383 387 try: 384 388 common.pretend_platform_uses_lettercolon = True 385 retval = get_alias(aliases, path, "baddefault:")389 retval = get_alias(aliases, path, u"baddefault:") 386 390 finally: 387 391 common.pretend_platform_uses_lettercolon = old 388 392 return retval 389 393 self.failUnlessRaises(common.UnknownAliasError, ga5, "C:\\Windows") 390 394 395 def test_listdir_unicode_good(self): 396 basedir = u"cli/common/listdir_unicode_good" 397 fileutil.make_dirs(basedir) 398 399 files = (u'Lôzane', u'Bern', u'Genève') 400 401 for file in files: 402 open(os.path.join(basedir, file), "w").close() 403 404 for file in listdir_unicode(basedir): 405 self.failUnlessEqual(file in files, True) 406 407 def test_listdir_unicode_bad(self): 408 if unicode_platform(): 409 raise unittest.SkipTest("This test doesn't make any sense on architecture which handle filenames natively as Unicode entities.") 410 411 basedir = u"cli/common/listdir_unicode_bad" 412 fileutil.make_dirs(basedir) 413 414 files = (u'Lôzane', u'Bern', u'Genève') 415 416 # We use a wrong encoding on purpose 417 if sys.getfilesystemencoding() == 'UTF-8': 418 encoding = 'latin1' 419 else: 420 encoding = 'UTF-8' 421 422 for file in files: 423 path = os.path.join(basedir, file).encode(encoding) 424 open(path, "w").close() 425 426 self.failUnlessRaises(FilenameEncodingError, listdir_unicode, basedir) 391 427 392 428 class Help(unittest.TestCase): 393 429 … … 582 618 self.failUnless(aliases["un-corrupted2"].startswith("URI:DIR2:")) 583 619 d.addCallback(_check_not_corrupted) 584 620 621 d.addCallback(lambda res: self.do_cli("create-alias", "études")) 622 def _check_create_unicode((rc,stdout,stderr)): 623 self.failUnlessEqual(rc, 0) 624 self.failIf(stderr) 625 626 # If stdout only supports ascii, accentuated characters are 627 # being replaced by '?' 628 if sys.stdout.encoding == "ANSI_X3.4-1968": 629 self.failUnless("Alias '?tudes' created" in stdout) 630 else: 631 self.failUnless("Alias 'études' created" in stdout) 632 633 aliases = get_aliases(self.get_clientdir()) 634 self.failUnless(aliases[u"études"].startswith("URI:DIR2:")) 635 d.addCallback(_check_create_unicode) 636 637 d.addCallback(lambda res: self.do_cli("ls", "études:")) 638 def _check_ls1((rc, stdout, stderr)): 639 self.failUnlessEqual(rc, 0) 640 self.failIf(stderr) 641 642 self.failUnlessEqual(stdout, "") 643 d.addCallback(_check_ls1) 644 645 d.addCallback(lambda res: self.do_cli("put", "-", "études:uploaded.txt", 646 stdin="Blah blah blah")) 647 648 d.addCallback(lambda res: self.do_cli("ls", "études:")) 649 def _check_ls2((rc, stdout, stderr)): 650 self.failUnlessEqual(rc, 0) 651 self.failIf(stderr) 652 653 self.failUnlessEqual(stdout, "uploaded.txt\n") 654 d.addCallback(_check_ls2) 655 656 d.addCallback(lambda res: self.do_cli("get", "études:uploaded.txt")) 657 def _check_get((rc, stdout, stderr)): 658 self.failUnlessEqual(rc, 0) 659 self.failIf(stderr) 660 self.failUnlessEqual(stdout, "Blah blah blah") 661 d.addCallback(_check_get) 662 585 663 return d 586 664 587 665 … … 855 933 return d 856 934 857 935 936 def test_immutable_from_file_unicode(self): 937 # tahoe put file.txt "à trier.txt" 938 self.basedir = os.path.dirname(self.mktemp()) 939 self.set_up_grid() 940 941 rel_fn = os.path.join(self.basedir, "DATAFILE") 942 abs_fn = os.path.abspath(rel_fn) 943 # we make the file small enough to fit in a LIT file, for speed 944 DATA = "short file" 945 f = open(rel_fn, "w") 946 f.write(DATA) 947 f.close() 948 949 d = self.do_cli("create-alias", "tahoe") 950 951 d.addCallback(lambda res: 952 self.do_cli("put", rel_fn, "à trier.txt")) 953 def _uploaded((rc,stdout,stderr)): 954 readcap = stdout.strip() 955 self.failUnless(readcap.startswith("URI:LIT:")) 956 self.failUnless("201 Created" in stderr, stderr) 957 self.readcap = readcap 958 d.addCallback(_uploaded) 959 960 d.addCallback(lambda res: 961 self.do_cli("get", "tahoe:à trier.txt")) 962 d.addCallback(lambda (rc,stdout,stderr): 963 self.failUnlessEqual(stdout, DATA)) 964 965 return d 966 858 967 class List(GridTestMixin, CLITestMixin, unittest.TestCase): 859 968 def test_list(self): 860 969 self.basedir = "cli/List/list" … … 1138 1247 def test_unicode_filename(self): 1139 1248 self.basedir = "cli/Cp/unicode_filename" 1140 1249 self.set_up_grid() 1250 d = self.do_cli("create-alias", "tahoe") 1251 1252 # Use unicode strings when calling os functions 1253 if sys.getfilesystemencoding() == "ANSI_X3.4-1968": 1254 fn1 = os.path.join(self.basedir, u"Artonwall") 1255 else: 1256 fn1 = os.path.join(self.basedir, u"Ärtonwall") 1141 1257 1142 fn1 = os.path.join(self.basedir, "Ärtonwall")1143 1258 DATA1 = "unicode file content" 1144 1259 fileutil.write(fn1, DATA1) 1260 d.addCallback(lambda res: self.do_cli("cp", fn1.encode('utf-8'), "tahoe:Ärtonwall")) 1261 1262 d.addCallback(lambda res: self.do_cli("get", "tahoe:Ärtonwall")) 1263 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA1)) 1145 1264 1146 fn2 = os.path.join(self.basedir, "Metallica") 1265 1266 fn2 = os.path.join(self.basedir, u"Metallica") 1147 1267 DATA2 = "non-unicode file content" 1148 1268 fileutil.write(fn2, DATA2) 1149 1269 1150 1270 # Bug #534 1151 1271 # Assure that uploading a file whose name contains unicode character doesn't 1152 1272 # prevent further uploads in the same directory 1153 d = self.do_cli("create-alias", "tahoe") 1154 d.addCallback(lambda res: self.do_cli("cp", fn1, "tahoe:")) 1155 d.addCallback(lambda res: self.do_cli("cp", fn2, "tahoe:")) 1156 1157 d.addCallback(lambda res: self.do_cli("get", "tahoe:Ärtonwall")) 1158 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA1)) 1273 d.addCallback(lambda res: self.do_cli("cp", fn2.encode('utf-8'), "tahoe:")) 1159 1274 1160 1275 d.addCallback(lambda res: self.do_cli("get", "tahoe:Metallica")) 1161 1276 d.addCallback(lambda (rc,out,err): self.failUnlessEqual(out, DATA2)) 1162 1277 1278 d.addCallback(lambda res: self.do_cli("ls", "tahoe:")) 1279 1163 1280 return d 1164 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."1165 1281 1166 1282 def test_dangling_symlink_vs_recursion(self): 1167 1283 if not hasattr(os, 'symlink'): … … 1268 1384 return d 1269 1385 1270 1386 1387 class Mkdir(GridTestMixin, CLITestMixin, unittest.TestCase): 1388 def test_unicode_mkdir(self): 1389 self.basedir = os.path.dirname(self.mktemp()) 1390 self.set_up_grid() 1391 1392 d = self.do_cli("create-alias", "tahoe") 1393 d.addCallback(lambda res: self.do_cli("mkdir", "tahoe:Motörhead")) 1394 1395 return d 1396 1397 1271 1398 class Backup(GridTestMixin, CLITestMixin, StallMixin, unittest.TestCase): 1272 1399 1273 1400 def writeto(self, path, data):