overhaul CLI: not quite complete but it works a lot better than it used to. The new scheme uses 'tahoe add-alias' and rsync/scp-style 'alias:foo/bar.txt' arguments

This commit is contained in:
Brian Warner 2008-05-19 19:28:50 -07:00
parent 9662e6d986
commit 8e92dfcb50
14 changed files with 489 additions and 254 deletions

View File

@ -140,7 +140,7 @@ use the following command to create a new directory and set it as your
tahoe set-alias tahoe `tahoe mkdir` tahoe set-alias tahoe `tahoe mkdir`
After thatm you can use "tahoe ls tahoe:" and "tahoe cp local.txt tahoe:", After that you can use "tahoe ls tahoe:" and "tahoe cp local.txt tahoe:",
and both will refer to the directory that you've just created. and both will refer to the directory that you've just created.
=== Command Syntax Summary === === Command Syntax Summary ===

View File

@ -17,11 +17,8 @@ class VDriveOptions(BaseOptions, usage.Options):
["node-url", "u", None, ["node-url", "u", None,
"URL of the tahoe node to use, a URL like \"http://127.0.0.1:8123\". " "URL of the tahoe node to use, a URL like \"http://127.0.0.1:8123\". "
"This overrides the URL found in the --node-directory ."], "This overrides the URL found in the --node-directory ."],
["dir-cap", "r", "root", ["dir-cap", "r", None,
"Which dirnode URI should be used as a root directory. The " "Which dirnode URI should be used as the 'tahoe' alias."]
"string 'root' is special, and means we should use the "
"directory found in the 'root_dir.cap' file in the 'private' "
"subdirectory of the --node-directory ."],
] ]
def postOptions(self): def postOptions(self):
@ -43,46 +40,71 @@ class VDriveOptions(BaseOptions, usage.Options):
node_url_file = os.path.join(self['node-directory'], "node.url") node_url_file = os.path.join(self['node-directory'], "node.url")
self['node-url'] = open(node_url_file, "r").read().strip() self['node-url'] = open(node_url_file, "r").read().strip()
rootdircap = None aliases = self.get_aliases(self['node-directory'])
if self['dir-cap'] == 'root': if self['dir-cap']:
uri_file = os.path.join(self['node-directory'], 'private', "root_dir.cap") aliases["tahoe"] = self['dir-cap']
try: self.aliases = aliases # maps alias name to dircap
rootdircap = open(uri_file, "r").read().strip()
except EnvironmentError, le:
raise usage.UsageError("\n"
"If --dir-cap is absent or is 'root', then the node directory's 'private'\n"
"subdirectory is required to contain a file named 'root_dir.cap' which is\n"
"required to contain a dir cap, but when we tried to open that file we got:\n"
"'%s'." % (le,))
else:
rootdircap = self['dir-cap']
from allmydata import uri
try:
parsed = uri.NewDirectoryURI.init_from_human_encoding(rootdircap)
except:
try:
parsed = uri.ReadonlyNewDirectoryURI.init_from_human_encoding(rootdircap)
except:
if self['dir-cap'] == 'root':
raise usage.UsageError("\n"
"If --dir-cap is absent or is 'root', then the node directory's 'private'\n"
"subdirectory's 'root_dir.cap' is required to contain a dir cap, but we found\n"
"'%s'." % (rootdircap,))
else:
raise usage.UsageError("--dir-cap is required to be a dir cap (or \"root\"), but we got '%s'." % (self['dir-cap'],))
self['dir-cap'] = parsed.to_string()
def get_aliases(self, nodedir):
from allmydata import uri
aliases = {}
aliasfile = os.path.join(nodedir, "private", "aliases")
rootfile = os.path.join(nodedir, "private", "root_dir.cap")
try:
f = open(rootfile, "r")
rootcap = f.read().strip()
if rootcap:
aliases["tahoe"] = uri.from_string_dirnode(rootcap).to_string()
except EnvironmentError:
pass
try:
f = open(aliasfile, "r")
for line in f.readlines():
line = line.strip()
if line.startswith("#"):
continue
name, cap = line.split(":", 1)
# normalize it: remove http: prefix, urldecode
cap = cap.strip()
aliases[name] = uri.from_string_dirnode(cap).to_string()
except EnvironmentError:
pass
return aliases
class MakeDirectoryOptions(VDriveOptions):
def parseArgs(self, where=""):
self.where = where
longdesc = """Create a new directory, either unlinked or as a subdirectory."""
class AddAliasOptions(VDriveOptions):
def parseArgs(self, alias, cap):
self.alias = alias
self.cap = cap
class ListOptions(VDriveOptions): class ListOptions(VDriveOptions):
def parseArgs(self, vdrive_pathname=""): optFlags = [
self['vdrive_pathname'] = vdrive_pathname ("long", "l", "Use long format: show file sizes, and timestamps"),
("uri", "u", "Show file URIs"),
("classify", "F", "Append '/' to directory names, and '*' to mutable"),
("json", None, "Show the raw JSON output"),
]
def parseArgs(self, where=""):
self.where = where
longdesc = """List the contents of some portion of the virtual drive.""" longdesc = """List the contents of some portion of the virtual drive."""
class GetOptions(VDriveOptions): class GetOptions(VDriveOptions):
def parseArgs(self, vdrive_filename, local_filename="-"): def parseArgs(self, arg1, arg2=None):
self['vdrive_filename'] = vdrive_filename # tahoe get FOO |less # write to stdout
self['local_filename'] = local_filename # tahoe get tahoe:FOO |less # same
# tahoe get FOO bar # write to local file
# tahoe get tahoe:FOO bar # same
self.from_file = arg1
self.to_file = arg2
if self.to_file == "-":
self.to_file = None
def getSynopsis(self): def getSynopsis(self):
return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),) return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
@ -92,9 +114,24 @@ class GetOptions(VDriveOptions):
will be written to stdout.""" will be written to stdout."""
class PutOptions(VDriveOptions): class PutOptions(VDriveOptions):
def parseArgs(self, local_filename, vdrive_filename): def parseArgs(self, arg1=None, arg2=None):
self['local_filename'] = local_filename # cat FILE > tahoe put # create unlinked file from stdin
self['vdrive_filename'] = vdrive_filename # cat FILE > tahoe put FOO # create tahoe:FOO from stdin
# cat FILE > tahoe put tahoe:FOO # same
# tahoe put bar FOO # copy local 'bar' to tahoe:FOO
# tahoe put bar tahoe:FOO # same
if arg1 is not None and arg2 is not None:
self.from_file = arg1
self.to_file = arg2
elif arg1 is not None and arg2 is None:
self.from_file = None
self.to_file = arg1
else:
self.from_file = arg1
self.to_file = arg2
if self.from_file == "-":
self.from_file = None
def getSynopsis(self): def getSynopsis(self):
return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),) return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
@ -104,16 +141,16 @@ class PutOptions(VDriveOptions):
local file (it can't be stdin).""" local file (it can't be stdin)."""
class RmOptions(VDriveOptions): class RmOptions(VDriveOptions):
def parseArgs(self, vdrive_pathname): def parseArgs(self, where):
self['vdrive_pathname'] = vdrive_pathname self.where = where
def getSynopsis(self): def getSynopsis(self):
return "%s rm VE_FILE" % (os.path.basename(sys.argv[0]),) return "%s rm VE_FILE" % (os.path.basename(sys.argv[0]),)
class MvOptions(VDriveOptions): class MvOptions(VDriveOptions):
def parseArgs(self, frompath, topath): def parseArgs(self, frompath, topath):
self['from'] = frompath self.from_file = frompath
self['to'] = topath self.to_file = topath
def getSynopsis(self): def getSynopsis(self):
return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),) return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
@ -128,6 +165,8 @@ class ReplOptions(usage.Options):
pass pass
subCommands = [ subCommands = [
["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
["add-alias", None, AddAliasOptions, "Add a new alias cap"],
["ls", None, ListOptions, "List a directory"], ["ls", None, ListOptions, "List a directory"],
["get", None, GetOptions, "Retrieve a file from the virtual drive."], ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
["put", None, PutOptions, "Upload a file into the virtual drive."], ["put", None, PutOptions, "Upload a file into the virtual drive."],
@ -137,72 +176,82 @@ subCommands = [
["repl", None, ReplOptions, "Open a python interpreter"], ["repl", None, ReplOptions, "Open a python interpreter"],
] ]
def mkdir(config, stdout, stderr):
from allmydata.scripts import tahoe_mkdir
rc = tahoe_mkdir.mkdir(config['node-url'],
config.aliases,
config.where,
stdout, stderr)
return rc
def add_alias(config, stdout, stderr):
from allmydata.scripts import tahoe_add_alias
rc = tahoe_add_alias.add_alias(config['node-directory'],
config.alias,
config.cap,
stdout, stderr)
return rc
def list(config, stdout, stderr): def list(config, stdout, stderr):
from allmydata.scripts import tahoe_ls from allmydata.scripts import tahoe_ls
rc = tahoe_ls.list(config['node-url'], rc = tahoe_ls.list(config['node-url'],
config['dir-cap'], config.aliases,
config['vdrive_pathname'], config.where,
config,
stdout, stderr) stdout, stderr)
return rc return rc
def get(config, stdout, stderr): def get(config, stdout, stderr):
from allmydata.scripts import tahoe_get from allmydata.scripts import tahoe_get
vdrive_filename = config['vdrive_filename']
local_filename = config['local_filename']
rc = tahoe_get.get(config['node-url'], rc = tahoe_get.get(config['node-url'],
config['dir-cap'], config.aliases,
vdrive_filename, config.from_file,
local_filename, config.to_file,
stdout, stderr) stdout, stderr)
if rc == 0: if rc == 0:
if local_filename is None or local_filename == "-": if config.to_file is None:
# be quiet, since the file being written to stdout should be # be quiet, since the file being written to stdout should be
# proof enough that it worked, unless the user is unlucky # proof enough that it worked, unless the user is unlucky
# enough to have picked an empty file # enough to have picked an empty file
pass pass
else: else:
print >>stderr, "%s retrieved and written to %s" % \ print >>stderr, "%s retrieved and written to %s" % \
(vdrive_filename, local_filename) (config.from_file, config.to_file)
return rc return rc
def put(config, stdout, stderr): def put(config, stdout, stderr, stdin=sys.stdin):
from allmydata.scripts import tahoe_put from allmydata.scripts import tahoe_put
vdrive_filename = config['vdrive_filename']
local_filename = config['local_filename']
if config['quiet']: if config['quiet']:
verbosity = 0 verbosity = 0
else: else:
verbosity = 2 verbosity = 2
rc = tahoe_put.put(config['node-url'], rc = tahoe_put.put(config['node-url'],
config['dir-cap'], config.aliases,
local_filename, config.from_file,
vdrive_filename, config.to_file,
verbosity, verbosity,
stdout, stderr) stdin, stdout, stderr)
return rc return rc
def rm(config, stdout, stderr): def rm(config, stdout, stderr):
from allmydata.scripts import tahoe_rm from allmydata.scripts import tahoe_rm
vdrive_pathname = config['vdrive_pathname']
if config['quiet']: if config['quiet']:
verbosity = 0 verbosity = 0
else: else:
verbosity = 2 verbosity = 2
rc = tahoe_rm.rm(config['node-url'], rc = tahoe_rm.rm(config['node-url'],
config['dir-cap'], config.aliases,
vdrive_pathname, config.where,
verbosity, verbosity,
stdout, stderr) stdout, stderr)
return rc return rc
def mv(config, stdout, stderr): def mv(config, stdout, stderr):
from allmydata.scripts import tahoe_mv from allmydata.scripts import tahoe_mv
frompath = config['from']
topath = config['to']
rc = tahoe_mv.mv(config['node-url'], rc = tahoe_mv.mv(config['node-url'],
config['dir-cap'], config.aliases,
frompath, config.from_file,
topath, config.to_file,
stdout, stderr) stdout, stderr)
return rc return rc
@ -222,6 +271,8 @@ def repl(config, stdout, stderr):
return code.interact() return code.interact()
dispatch = { dispatch = {
"mkdir": mkdir,
"add-alias": add_alias,
"ls": list, "ls": list,
"get": get, "get": get,
"put": put, "put": put,

View File

@ -1,5 +1,5 @@
import os, sys import os, sys, urllib
from twisted.python import usage from twisted.python import usage
@ -62,4 +62,29 @@ class NoDefaultBasedirMixin(BasedirMixin):
if not self.basedirs: if not self.basedirs:
raise usage.UsageError("--basedir must be provided") raise usage.UsageError("--basedir must be provided")
DEFAULT_ALIAS = "tahoe"
def get_alias(aliases, path, default):
# transform "work:path/filename" into (aliases["work"], "path/filename")
# We special-case URI:
if path.startswith("URI:"):
# The only way to get a sub-path is to use URI:blah:./foo, and we
# strip out the :./ sequence.
sep = path.find(":./")
if sep != -1:
return path[:sep], path[sep+3:]
return path, ""
colon = path.find(":")
if colon == -1:
# no alias
return aliases[default], path
alias = path[:colon]
if "/" in alias:
# no alias, but there's a colon in a dirname/filename, like
# "foo/bar:7"
return aliases[default], path
return aliases[alias], path[colon+1:]
def escape_path(path):
segments = path.split("/")
return "/".join([urllib.quote(s) for s in segments])

View File

@ -58,3 +58,8 @@ def do_http(method, url, body=""):
c.send(data) c.send(data)
return c.getresponse() return c.getresponse()
def check_http_error(resp, stderr):
if resp.status < 200 or resp.status >= 300:
print >>stderr, "error %d during HTTP request" % resp.status
return 1

View File

@ -56,11 +56,8 @@ def create_client(basedir, config, out=sys.stdout, err=sys.stderr):
f = open(os.path.join(basedir, "webport"), "w") f = open(os.path.join(basedir, "webport"), "w")
f.write(config['webport'] + "\n") f.write(config['webport'] + "\n")
f.close() f.close()
# Create an empty root_dir.cap file, indicating that the node
# should fill it with the URI after creating the directory.
from allmydata.util import fileutil from allmydata.util import fileutil
fileutil.make_dirs(os.path.join(basedir, "private"), 0700) fileutil.make_dirs(os.path.join(basedir, "private"), 0700)
open(os.path.join(basedir, "private", "root_dir.cap"), "w")
print >>out, "client created in %s" % basedir print >>out, "client created in %s" % basedir
print >>out, " please copy introducer.furl into the directory" print >>out, " please copy introducer.furl into the directory"

View File

@ -0,0 +1,16 @@
import os.path
from allmydata import uri
def add_alias(nodedir, alias, cap, stdout, stderr):
aliasfile = os.path.join(nodedir, "private", "aliases")
cap = uri.from_string_dirnode(cap).to_string()
assert ":" not in alias
assert " " not in alias
# probably check for others..
f = open(aliasfile, "a")
f.write("%s: %s\n" % (alias, cap))
f.close()
print >>stdout, "Alias '%s' added" % (alias,)
return 0

View File

@ -1,26 +1,37 @@
import urllib import urllib
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path
from allmydata.scripts.common_http import do_http
def get(nodeurl, dir_uri, vdrive_fname, local_file, stdout, stderr): def get(nodeurl, aliases, from_file, to_file, stdout, stderr):
if nodeurl[-1] != "/": if nodeurl[-1] != "/":
nodeurl += "/" nodeurl += "/"
url = nodeurl + "uri/%s/" % urllib.quote(dir_uri) rootcap, path = get_alias(aliases, from_file, DEFAULT_ALIAS)
if vdrive_fname: url = nodeurl + "uri/%s" % urllib.quote(rootcap)
url += urllib.quote(vdrive_fname) if path:
url += "/" + escape_path(path)
if local_file is None or local_file == "-": if to_file:
outf = open(to_file, "wb")
close_outf = True
else:
outf = stdout outf = stdout
close_outf = False close_outf = False
else:
outf = open(local_file, "wb") resp = do_http("GET", url)
close_outf = True if resp.status in (200, 201,):
inf = urllib.urlopen(url)
while True: while True:
data = inf.read(4096) data = resp.read(4096)
if not data: if not data:
break break
outf.write(data) outf.write(data)
rc = 0
else:
print >>stderr, "Error, got %s %s" % (resp.status, resp.reason)
print >>stderr, resp.read()
rc = 1
if close_outf: if close_outf:
outf.close() outf.close()
return 0 return rc

View File

@ -1,28 +1,71 @@
import urllib import urllib, time
import simplejson import simplejson
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path
def list(nodeurl, dir_uri, vdrive_pathname, stdout, stderr): def list(nodeurl, aliases, where, config, stdout, stderr):
if nodeurl[-1] != "/": if not nodeurl.endswith("/"):
nodeurl += "/" nodeurl += "/"
url = nodeurl + "uri/%s/" % urllib.quote(dir_uri) if where.endswith("/"):
if vdrive_pathname: where = where[:-1]
url += urllib.quote(vdrive_pathname) rootcap, path = get_alias(aliases, where, DEFAULT_ALIAS)
url = nodeurl + "uri/%s" % urllib.quote(rootcap)
if path:
# move where.endswith check here?
url += "/" + escape_path(path)
assert not url.endswith("/")
url += "?t=json" url += "?t=json"
data = urllib.urlopen(url).read() data = urllib.urlopen(url).read()
if config['json']:
print >>stdout, data
return
parsed = simplejson.loads(data) parsed = simplejson.loads(data)
nodetype, d = parsed nodetype, d = parsed
children = {}
if nodetype == "dirnode": if nodetype == "dirnode":
childnames = sorted(d['children'].keys()) children = d['children']
for name in childnames:
child = d['children'][name]
childtype = child[0]
if childtype == "dirnode":
print >>stdout, "%10s %s/" % ("", name)
else:
assert childtype == "filenode"
size = child[1]['size']
print >>stdout, "%10s %s" % (size, name)
elif nodetype == "filenode": elif nodetype == "filenode":
print >>stdout, "%10s %s" % (d['size'], vdrive_pathname) childname = path.split("/")[-1]
children = {childname: d}
childnames = sorted(children.keys())
now = time.time()
for name in childnames:
child = children[name]
childtype = child[0]
ctime = child[1]["metadata"].get("ctime")
mtime = child[1]["metadata"].get("mtime")
if ctime:
# match for formatting that GNU 'ls' does
if (now - ctime) > 6*30*24*60*60:
# old files
fmt = "%b %d %Y"
else:
fmt = "%b %d %H:%M"
ctime_s = time.strftime(fmt, time.localtime(ctime))
else:
ctime_s = "-"
if childtype == "dirnode":
t = "d---------"
size = "-"
classify = "/"
elif childtype == "filenode":
t = "----------"
size = child[1]['size']
classify = ""
if "rw_uri" in child[1]:
classify = "*" # mutable
uri = child[1].get("rw_uri", child[1].get("ro_uri", "-"))
line = []
if config["long"]:
line.append("%s %10s %12s" % (t, size, ctime_s))
if config["uri"]:
line.append(uri)
line.append(name)
if config["classify"]:
line[-1] += classify
print >>stdout, " ".join(line)

View File

@ -0,0 +1,34 @@
import urllib
from allmydata.scripts.common_http import do_http, check_http_error
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS
def mkdir(nodeurl, aliases, where, stdout, stderr):
if not nodeurl.endswith("/"):
nodeurl += "/"
if where:
rootcap, path = get_alias(aliases, where, DEFAULT_ALIAS)
if not where or not path:
# create a new unlinked directory
url = nodeurl + "uri?t=mkdir"
resp = do_http("POST", url)
rc = check_http_error(resp, stderr)
if rc:
return rc
new_uri = resp.read().strip()
# emit its write-cap
print >>stdout, new_uri
return 0
# create a new directory at the given location
if path.endswith("/"):
path = path[:-1]
# path (in argv) must be "/".join([s.encode("utf-8") for s in segments])
url = nodeurl + "uri/%s/%s?t=mkdir" % (urllib.quote(rootcap),
urllib.quote(path))
resp = do_http("POST", url)
check_http_error(resp, stderr)
new_uri = resp.read().strip()
print >>stdout, new_uri
return 0

View File

@ -2,31 +2,42 @@
import re import re
import urllib import urllib
import simplejson import simplejson
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path
from allmydata.scripts.common_http import do_http from allmydata.scripts.common_http import do_http
def mv(nodeurl, dir_uri, frompath, topath, stdout, stderr): def mv(nodeurl, aliases, from_file, to_file, stdout, stderr):
frompath = urllib.quote(frompath)
topath = urllib.quote(topath)
if nodeurl[-1] != "/": if nodeurl[-1] != "/":
nodeurl += "/" nodeurl += "/"
url = nodeurl + "uri/%s/" % urllib.quote(dir_uri) rootcap, path = get_alias(aliases, from_file, DEFAULT_ALIAS)
data = urllib.urlopen(url + frompath + "?t=json").read() from_url = nodeurl + "uri/%s" % urllib.quote(rootcap)
if path:
from_url += "/" + escape_path(path)
# figure out the source cap
data = urllib.urlopen(from_url + "?t=json").read()
nodetype, attrs = simplejson.loads(data) nodetype, attrs = simplejson.loads(data)
uri = attrs.get("rw_uri") or attrs["ro_uri"] cap = attrs.get("rw_uri") or attrs["ro_uri"]
# simplejson always returns unicode, but we know that it's really just a # simplejson always returns unicode, but we know that it's really just an
# bytestring. # ASCII file-cap.
uri = str(uri) cap = str(cap)
put_url = url + topath + "?t=uri" # now get the target
resp = do_http("PUT", put_url, uri) rootcap, path = get_alias(aliases, to_file, DEFAULT_ALIAS)
to_url = nodeurl + "uri/%s" % urllib.quote(rootcap)
if path:
to_url += "/" + escape_path(path)
if path.endswith("/"):
# "mv foo.txt bar/" == "mv foo.txt bar/foo.txt"
pass # TODO
to_url += "?t=uri"
resp = do_http("PUT", to_url, cap)
status = resp.status status = resp.status
if not re.search(r'^2\d\d$', str(status)): if not re.search(r'^2\d\d$', str(status)):
print >>stderr, "error, got %s %s" % (resp.status, resp.reason) print >>stderr, "error, got %s %s" % (resp.status, resp.reason)
print >>stderr, resp.read() print >>stderr, resp.read()
# now remove the original # now remove the original
resp = do_http("DELETE", url + frompath) resp = do_http("DELETE", from_url)
if not re.search(r'^2\d\d$', str(status)): if not re.search(r'^2\d\d$', str(status)):
print >>stderr, "error, got %s %s" % (resp.status, resp.reason) print >>stderr, "error, got %s %s" % (resp.status, resp.reason)
print >>stderr, resp.read() print >>stderr, resp.read()

View File

@ -1,9 +1,11 @@
from cStringIO import StringIO
import urllib import urllib
from allmydata.scripts.common_http import do_http from allmydata.scripts.common_http import do_http
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path
def put(nodeurl, dir_uri, local_fname, vdrive_fname, verbosity, def put(nodeurl, aliases, from_file, to_file, verbosity,
stdout, stderr): stdin, stdout, stderr):
""" """
@param verbosity: 0, 1, or 2, meaning quiet, verbose, or very verbose @param verbosity: 0, 1, or 2, meaning quiet, verbose, or very verbose
@ -11,15 +13,28 @@ def put(nodeurl, dir_uri, local_fname, vdrive_fname, verbosity,
""" """
if nodeurl[-1] != "/": if nodeurl[-1] != "/":
nodeurl += "/" nodeurl += "/"
url = nodeurl + "uri/%s/" % urllib.quote(dir_uri) if to_file:
if vdrive_fname: rootcap, path = get_alias(aliases, to_file, DEFAULT_ALIAS)
url += urllib.quote(vdrive_fname) url = nodeurl + "uri/%s/" % urllib.quote(rootcap)
if path:
url += escape_path(path)
else:
url = nodeurl + "uri"
if from_file:
infileobj = open(from_file, "rb")
else:
# do_http() can't use stdin directly: for one thing, we need a
# Content-Length field. So we currently must copy it.
if verbosity > 0:
print >>stderr, "waiting for file data on stdin.."
data = stdin.read()
infileobj = StringIO(data)
infileobj = open(local_fname, "rb")
resp = do_http("PUT", url, infileobj) resp = do_http("PUT", url, infileobj)
if resp.status in (200, 201,): if resp.status in (200, 201,):
print >>stdout, "%s %s" % (resp.status, resp.reason) print >>stderr, "%s %s" % (resp.status, resp.reason)
print >>stdout, resp.read()
return 0 return 0
print >>stderr, "error, got %s %s" % (resp.status, resp.reason) print >>stderr, "error, got %s %s" % (resp.status, resp.reason)

View File

@ -1,8 +1,9 @@
import urllib import urllib
from allmydata.scripts.common_http import do_http from allmydata.scripts.common_http import do_http
from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path
def rm(nodeurl, dir_uri, vdrive_pathname, verbosity, stdout, stderr): def rm(nodeurl, aliases, where, verbosity, stdout, stderr):
""" """
@param verbosity: 0, 1, or 2, meaning quiet, verbose, or very verbose @param verbosity: 0, 1, or 2, meaning quiet, verbose, or very verbose
@ -10,9 +11,10 @@ def rm(nodeurl, dir_uri, vdrive_pathname, verbosity, stdout, stderr):
""" """
if nodeurl[-1] != "/": if nodeurl[-1] != "/":
nodeurl += "/" nodeurl += "/"
url = nodeurl + "uri/%s/" % urllib.quote(dir_uri) rootcap, path = get_alias(aliases, where, DEFAULT_ALIAS)
if vdrive_pathname: assert path
url += urllib.quote(vdrive_pathname) url = nodeurl + "uri/%s" % urllib.quote(rootcap)
url += "/" + escape_path(path)
resp = do_http("DELETE", url) resp = do_http("DELETE", url)

View File

@ -9,10 +9,13 @@ from allmydata import uri
# at least import the CLI scripts, even if we don't have any real tests for # at least import the CLI scripts, even if we don't have any real tests for
# them yet. # them yet.
from allmydata.scripts import tahoe_ls, tahoe_get, tahoe_put, tahoe_rm from allmydata.scripts import tahoe_ls, tahoe_get, tahoe_put, tahoe_rm
from allmydata.scripts.common import DEFAULT_ALIAS
_hush_pyflakes = [tahoe_ls, tahoe_get, tahoe_put, tahoe_rm] _hush_pyflakes = [tahoe_ls, tahoe_get, tahoe_put, tahoe_rm]
from allmydata.scripts import cli, debug from allmydata.scripts import cli, debug
# this test case only looks at argument-processing and simple stuff.
# test_system contains all the CLI tests that actually use a real node.
class CLI(unittest.TestCase): class CLI(unittest.TestCase):
def test_options(self): def test_options(self):
@ -27,27 +30,22 @@ class CLI(unittest.TestCase):
o = cli.ListOptions() o = cli.ListOptions()
o.parseOptions(["--node-directory", "cli/test_options"]) o.parseOptions(["--node-directory", "cli/test_options"])
self.failUnlessEqual(o['node-url'], "http://localhost:8080/") self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
self.failUnlessEqual(o['dir-cap'], private_uri) self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
self.failUnlessEqual(o['vdrive_pathname'], "") self.failUnlessEqual(o.where, "")
o = cli.ListOptions() o = cli.ListOptions()
o.parseOptions(["--node-directory", "cli/test_options", o.parseOptions(["--node-directory", "cli/test_options",
"--node-url", "http://example.org:8111/"]) "--node-url", "http://example.org:8111/"])
self.failUnlessEqual(o['node-url'], "http://example.org:8111/") self.failUnlessEqual(o['node-url'], "http://example.org:8111/")
self.failUnlessEqual(o['dir-cap'], private_uri) self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], private_uri)
self.failUnlessEqual(o['vdrive_pathname'], "") self.failUnlessEqual(o.where, "")
o = cli.ListOptions() o = cli.ListOptions()
o.parseOptions(["--node-directory", "cli/test_options", o.parseOptions(["--node-directory", "cli/test_options",
"--dir-cap", "root"]) "--dir-cap", "root"])
self.failUnlessEqual(o['node-url'], "http://localhost:8080/") self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
self.failUnlessEqual(o['dir-cap'], private_uri) self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], "root")
self.failUnlessEqual(o['vdrive_pathname'], "") self.failUnlessEqual(o.where, "")
o = cli.ListOptions()
o.parseOptions(["--node-directory", "cli/test_options"])
self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
self.failUnlessEqual(o['vdrive_pathname'], "")
o = cli.ListOptions() o = cli.ListOptions()
other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16, other_filenode_uri = uri.WriteableSSKFileURI(writekey="\x11"*16,
@ -56,15 +54,15 @@ class CLI(unittest.TestCase):
o.parseOptions(["--node-directory", "cli/test_options", o.parseOptions(["--node-directory", "cli/test_options",
"--dir-cap", other_uri]) "--dir-cap", other_uri])
self.failUnlessEqual(o['node-url'], "http://localhost:8080/") self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
self.failUnlessEqual(o['dir-cap'], other_uri) self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
self.failUnlessEqual(o['vdrive_pathname'], "") self.failUnlessEqual(o.where, "")
o = cli.ListOptions() o = cli.ListOptions()
o.parseOptions(["--node-directory", "cli/test_options", o.parseOptions(["--node-directory", "cli/test_options",
"--dir-cap", other_uri, "subdir"]) "--dir-cap", other_uri, "subdir"])
self.failUnlessEqual(o['node-url'], "http://localhost:8080/") self.failUnlessEqual(o['node-url'], "http://localhost:8080/")
self.failUnlessEqual(o['dir-cap'], other_uri) self.failUnlessEqual(o.aliases[DEFAULT_ALIAS], other_uri)
self.failUnlessEqual(o['vdrive_pathname'], "subdir") self.failUnlessEqual(o.where, "subdir")
def _dump_cap(self, *args): def _dump_cap(self, *args):
out,err = StringIO(), StringIO() out,err = StringIO(), StringIO()

View File

@ -12,7 +12,7 @@ from allmydata import client, uri, download, upload, storage, offloaded
from allmydata.introducer import IntroducerNode from allmydata.introducer import IntroducerNode
from allmydata.util import deferredutil, fileutil, idlib, mathutil, testutil from allmydata.util import deferredutil, fileutil, idlib, mathutil, testutil
from allmydata.util import log from allmydata.util import log
from allmydata.scripts import runner from allmydata.scripts import runner, cli
from allmydata.interfaces import IDirectoryNode, IFileNode, IFileURI from allmydata.interfaces import IDirectoryNode, IFileNode, IFileURI
from allmydata.mutable.common import NotMutableError from allmydata.mutable.common import NotMutableError
from allmydata.mutable import layout as mutable_layout from allmydata.mutable import layout as mutable_layout
@ -69,9 +69,8 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
s.setServiceParent(self.sparent) s.setServiceParent(self.sparent)
return s return s
def set_up_nodes(self, NUMCLIENTS=5, createprivdir=False): def set_up_nodes(self, NUMCLIENTS=5):
self.numclients = NUMCLIENTS self.numclients = NUMCLIENTS
self.createprivdir = createprivdir
iv_dir = self.getdir("introducer") iv_dir = self.getdir("introducer")
if not os.path.isdir(iv_dir): if not os.path.isdir(iv_dir):
fileutil.make_dirs(iv_dir) fileutil.make_dirs(iv_dir)
@ -143,9 +142,6 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
open(os.path.join(basedir, "webport"), "w").write("tcp:0:interface=127.0.0.1") open(os.path.join(basedir, "webport"), "w").write("tcp:0:interface=127.0.0.1")
kgf = "%s\n" % (self.key_generator_furl,) kgf = "%s\n" % (self.key_generator_furl,)
open(os.path.join(basedir, "key_generator.furl"), "w").write(kgf) open(os.path.join(basedir, "key_generator.furl"), "w").write(kgf)
if self.createprivdir:
fileutil.make_dirs(os.path.join(basedir, "private"))
open(os.path.join(basedir, "private", "root_dir.cap"), "w")
open(os.path.join(basedir, "introducer.furl"), "w").write(self.introducer_furl) open(os.path.join(basedir, "introducer.furl"), "w").write(self.introducer_furl)
open(os.path.join(basedir, "stats_gatherer.furl"), "w").write(self.stats_gatherer_furl) open(os.path.join(basedir, "stats_gatherer.furl"), "w").write(self.stats_gatherer_furl)
@ -875,7 +871,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
def test_vdrive(self): def test_vdrive(self):
self.basedir = "system/SystemTest/test_vdrive" self.basedir = "system/SystemTest/test_vdrive"
self.data = LARGE_DATA self.data = LARGE_DATA
d = self.set_up_nodes(createprivdir=True) d = self.set_up_nodes()
d.addCallback(self._test_introweb) d.addCallback(self._test_introweb)
d.addCallback(self.log, "starting publish") d.addCallback(self.log, "starting publish")
d.addCallback(self._do_publish1) d.addCallback(self._do_publish1)
@ -1528,7 +1524,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
nodeargs = [ nodeargs = [
"--node-directory", client0_basedir, "--node-directory", client0_basedir,
"--dir-cap", private_uri, #"--dir-cap", private_uri,
] ]
public_nodeargs = [ public_nodeargs = [
"--node-url", self.webish_url, "--node-url", self.webish_url,
@ -1538,44 +1534,147 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
d = defer.succeed(None) d = defer.succeed(None)
def _ls_root(res): # for compatibility with earlier versions, private/root_dir.cap is
argv = ["ls"] + nodeargs # supposed to be treated as an alias named "tahoe:". Start by making
return self._run_cli(argv) # sure that works, before we add other aliases.
d.addCallback(_ls_root)
root_file = os.path.join(client0_basedir, "private", "root_dir.cap")
f = open(root_file, "w")
f.write(private_uri)
f.close()
def run(ignored, verb, *args):
newargs = [verb] + nodeargs + list(args)
return self._run_cli(newargs)
def _check_ls((out,err), expected_children, unexpected_children=[]):
self.failUnlessEqual(err, "")
for s in expected_children:
self.failUnless(s in out, s)
for s in unexpected_children:
self.failIf(s in out, s)
def _check_ls_root((out,err)): def _check_ls_root((out,err)):
self.failUnless("personal" in out) self.failUnless("personal" in out)
self.failUnless("s2-ro" in out) self.failUnless("s2-ro" in out)
self.failUnless("s2-rw" in out) self.failUnless("s2-rw" in out)
self.failUnlessEqual(err, "") self.failUnlessEqual(err, "")
d.addCallback(_check_ls_root)
def _ls_subdir(res): # this should reference private_uri
argv = ["ls"] + nodeargs + ["personal"] d.addCallback(run, "ls")
return self._run_cli(argv) d.addCallback(_check_ls, ["personal", "s2-ro", "s2-rw"])
d.addCallback(_ls_subdir)
def _check_ls_subdir((out,err)):
self.failUnless("sekrit data" in out)
self.failUnlessEqual(err, "")
d.addCallback(_check_ls_subdir)
def _ls_public_subdir(res): # now that that's out of the way, remove root_dir.cap and work with
argv = ["ls"] + public_nodeargs + ["subdir1"] # new files
return self._run_cli(argv) d.addCallback(lambda res: os.unlink(root_file))
d.addCallback(_ls_public_subdir)
def _check_ls_public_subdir((out,err)):
self.failUnless("subdir2" in out)
self.failUnless("mydata567" in out)
self.failUnlessEqual(err, "")
d.addCallback(_check_ls_public_subdir)
def _ls_file(res): d.addCallback(run, "mkdir")
argv = ["ls"] + public_nodeargs + ["subdir1/mydata567"] def _got_dir( (out,err) ):
return self._run_cli(argv) self.failUnless(uri.from_string_dirnode(out.strip()))
d.addCallback(_ls_file) return out.strip()
def _check_ls_file((out,err)): d.addCallback(_got_dir)
self.failUnlessEqual(out.strip(), "112 subdir1/mydata567") d.addCallback(lambda newcap: run(None, "add-alias", "tahoe", newcap))
def _check_empty_dir((out,err)):
self.failUnlessEqual(out, "")
self.failUnlessEqual(err, "") self.failUnlessEqual(err, "")
d.addCallback(_check_ls_file) d.addCallback(run, "ls")
d.addCallback(_check_empty_dir)
files = []
datas = []
for i in range(10):
fn = os.path.join(self.basedir, "file%d" % i)
files.append(fn)
data = "data to be uploaded: file%d\n" % i
datas.append(data)
open(fn,"w").write(data)
# test all both forms of put: from a file, and from stdin
# tahoe put bar FOO
d.addCallback(run, "put", files[0], "tahoe-file0")
def _put_out((out,err)):
self.failUnless("URI:LIT:" in out, out)
self.failUnless("201 Created" in err, err)
uri0 = out.strip()
return run(None, "get", uri0)
d.addCallback(_put_out)
d.addCallback(lambda (out,err): self.failUnlessEqual(out, datas[0]))
d.addCallback(run, "put", files[1], "subdir/tahoe-file1")
# tahoe put bar tahoe:FOO
d.addCallback(run, "put", files[2], "tahoe:file2")
def _put_from_stdin(res, data, *args):
args = nodeargs + list(args)
o = cli.PutOptions()
o.parseOptions(args)
stdin = StringIO(data)
stdout, stderr = StringIO(), StringIO()
d = threads.deferToThread(cli.put, o,
stdout=stdout, stderr=stderr, stdin=stdin)
def _done(res):
return stdout.getvalue(), stderr.getvalue()
d.addCallback(_done)
return d
# tahoe put FOO
STDIN_DATA = "This is the file to upload from stdin."
d.addCallback(_put_from_stdin,
STDIN_DATA,
"tahoe-file-stdin")
# tahoe put tahoe:FOO
d.addCallback(_put_from_stdin,
"Other file from stdin.",
"tahoe:from-stdin")
d.addCallback(run, "ls")
d.addCallback(_check_ls, ["tahoe-file0", "file2", "subdir",
"tahoe-file-stdin", "from-stdin"])
d.addCallback(run, "ls", "subdir")
d.addCallback(_check_ls, ["tahoe-file1"])
# tahoe mkdir FOO
d.addCallback(run, "mkdir", "subdir2")
d.addCallback(run, "ls")
# TODO: extract the URI, set an alias with it
d.addCallback(_check_ls, ["subdir2"])
# tahoe get: (to stdin and to a file)
d.addCallback(run, "get", "tahoe-file0")
d.addCallback(lambda (out,err):
self.failUnlessEqual(out, "data to be uploaded: file0\n"))
d.addCallback(run, "get", "tahoe:subdir/tahoe-file1")
d.addCallback(lambda (out,err):
self.failUnlessEqual(out, "data to be uploaded: file1\n"))
outfile0 = os.path.join(self.basedir, "outfile0")
d.addCallback(run, "get", "file2", outfile0)
def _check_outfile0((out,err)):
data = open(outfile0,"rb").read()
self.failUnlessEqual(data, "data to be uploaded: file2\n")
d.addCallback(_check_outfile0)
outfile1 = os.path.join(self.basedir, "outfile0")
d.addCallback(run, "get", "tahoe:subdir/tahoe-file1", outfile1)
def _check_outfile1((out,err)):
data = open(outfile1,"rb").read()
self.failUnlessEqual(data, "data to be uploaded: file1\n")
d.addCallback(_check_outfile1)
d.addCallback(run, "rm", "tahoe-file0")
d.addCallback(run, "rm", "tahoe:file2")
d.addCallback(run, "ls")
d.addCallback(_check_ls, [], ["tahoe-file0", "file2"])
d.addCallback(run, "ls", "-l")
def _check_ls_l((out,err)):
lines = out.split("\n")
for l in lines:
if "tahoe-file-stdin" in l:
self.failUnless(" %d " % len(STDIN_DATA) in l)
d.addCallback(_check_ls_l)
d.addCallback(run, "mv", "tahoe-file-stdin", "tahoe-moved")
d.addCallback(run, "ls")
d.addCallback(_check_ls, ["tahoe-moved"], ["tahoe-file-stdin"])
# tahoe_ls doesn't currently handle the error correctly: it tries to # tahoe_ls doesn't currently handle the error correctly: it tries to
# JSON-parse a traceback. # JSON-parse a traceback.
@ -1589,82 +1688,10 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, testutil.StallMixin,
## self.failUnlessEqual(err, "") ## self.failUnlessEqual(err, "")
## d.addCallback(_check_ls_missing) ## d.addCallback(_check_ls_missing)
def _put(res):
tdir = self.getdir("cli_put")
fileutil.make_dirs(tdir)
fn = os.path.join(tdir, "upload_me")
f = open(fn, "wb")
f.write(TESTDATA)
f.close()
argv = ["put"] + nodeargs + [fn, "test_put/upload.txt"]
return self._run_cli(argv)
d.addCallback(_put)
def _check_put((out,err)):
self.failUnless("201 Created" in out, out)
self.failUnlessEqual(err, "")
d = self._private_node.get_child_at_path(u"test_put/upload.txt")
d.addCallback(lambda filenode: filenode.download_to_data())
def _check_put2(res):
self.failUnlessEqual(res, TESTDATA)
d.addCallback(_check_put2)
return d
d.addCallback(_check_put)
def _get_to_stdout(res):
argv = ["get"] + nodeargs + ["test_put/upload.txt"]
return self._run_cli(argv)
d.addCallback(_get_to_stdout)
def _check_get_to_stdout((out,err)):
self.failUnlessEqual(out, TESTDATA)
self.failUnlessEqual(err, "")
d.addCallback(_check_get_to_stdout)
get_to_file_target = self.basedir + "/get.downfile"
def _get_to_file(res):
argv = ["get"] + nodeargs + ["test_put/upload.txt",
get_to_file_target]
return self._run_cli(argv)
d.addCallback(_get_to_file)
def _check_get_to_file((out,err)):
data = open(get_to_file_target, "rb").read()
self.failUnlessEqual(data, TESTDATA)
self.failUnlessEqual(out, "")
self.failUnlessEqual(err, "test_put/upload.txt retrieved and written to system/SystemTest/test_vdrive/get.downfile\n")
d.addCallback(_check_get_to_file)
def _mv(res):
argv = ["mv"] + nodeargs + ["test_put/upload.txt",
"test_put/moved.txt"]
return self._run_cli(argv)
d.addCallback(_mv)
def _check_mv((out,err)):
self.failUnless("OK" in out)
self.failUnlessEqual(err, "")
d = self.shouldFail2(KeyError, "test_cli._check_rm", "'upload.txt'", self._private_node.get_child_at_path, u"test_put/upload.txt")
d.addCallback(lambda res:
self._private_node.get_child_at_path(u"test_put/moved.txt"))
d.addCallback(lambda filenode: filenode.download_to_data())
def _check_mv2(res):
self.failUnlessEqual(res, TESTDATA)
d.addCallback(_check_mv2)
return d
d.addCallback(_check_mv)
def _rm(res):
argv = ["rm"] + nodeargs + ["test_put/moved.txt"]
return self._run_cli(argv)
d.addCallback(_rm)
def _check_rm((out,err)):
self.failUnless("200 OK" in out)
self.failUnlessEqual(err, "")
d = self.shouldFail2(KeyError, "test_cli._check_rm", "'moved.txt'", self._private_node.get_child_at_path, u"test_put/moved.txt")
return d
d.addCallback(_check_rm)
return d return d
def _run_cli(self, argv): def _run_cli(self, argv):
#print "CLI:", argv
stdout, stderr = StringIO(), StringIO() stdout, stderr = StringIO(), StringIO()
d = threads.deferToThread(runner.runner, argv, run_by_human=False, d = threads.deferToThread(runner.runner, argv, run_by_human=False,
stdout=stdout, stderr=stderr) stdout=stdout, stderr=stderr)