2007-07-11 02:37:37 +00:00
|
|
|
|
2007-08-17 19:54:47 +00:00
|
|
|
import os.path, re, sys
|
2007-07-11 02:37:37 +00:00
|
|
|
from twisted.python import usage
|
2008-05-20 21:36:04 +00:00
|
|
|
from allmydata.scripts.common import BaseOptions, get_aliases
|
2007-07-11 02:37:37 +00:00
|
|
|
|
2007-08-17 19:54:47 +00:00
|
|
|
NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
|
|
|
|
|
2007-08-16 19:50:19 +00:00
|
|
|
class VDriveOptions(BaseOptions, usage.Options):
|
2007-07-11 02:37:37 +00:00
|
|
|
optParameters = [
|
2007-10-11 07:30:36 +00:00
|
|
|
["node-directory", "d", "~/.tahoe",
|
|
|
|
"Look here to find out which Tahoe node should be used for all "
|
|
|
|
"operations. The directory should either contain a full Tahoe node, "
|
|
|
|
"or a file named node.url which points to some other Tahoe node. "
|
2008-06-03 00:54:56 +00:00
|
|
|
"It should also contain a file named private/aliases which contains "
|
|
|
|
"the mapping from alias name to root dirnode URI."
|
2007-10-11 07:30:36 +00:00
|
|
|
],
|
2007-08-16 23:53:27 +00:00
|
|
|
["node-url", "u", None,
|
2007-10-11 07:30:36 +00:00
|
|
|
"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 ."],
|
2008-05-22 00:55:32 +00:00
|
|
|
["dir-cap", None, None,
|
2008-05-20 02:28:50 +00:00
|
|
|
"Which dirnode URI should be used as the 'tahoe' alias."]
|
2007-07-11 02:37:37 +00:00
|
|
|
]
|
|
|
|
|
2007-08-17 19:54:47 +00:00
|
|
|
def postOptions(self):
|
2007-10-11 07:30:36 +00:00
|
|
|
# compute a node-url from the existing options, put in self['node-url']
|
|
|
|
if self['node-directory']:
|
2008-01-11 02:32:18 +00:00
|
|
|
if sys.platform == 'win32' and self['node-directory'] == '~/.tahoe':
|
|
|
|
from allmydata.windows import registry
|
|
|
|
self['node-directory'] = registry.get_base_dir_path()
|
|
|
|
else:
|
|
|
|
self['node-directory'] = os.path.expanduser(self['node-directory'])
|
2007-10-11 07:30:36 +00:00
|
|
|
if self['node-url']:
|
|
|
|
if (not isinstance(self['node-url'], basestring)
|
|
|
|
or not NODEURL_RE.match(self['node-url'])):
|
|
|
|
msg = ("--node-url is required to be a string and look like "
|
|
|
|
"\"http://HOSTNAMEORADDR:PORT\", not: %r" %
|
|
|
|
(self['node-url'],))
|
|
|
|
raise usage.UsageError(msg)
|
|
|
|
else:
|
|
|
|
node_url_file = os.path.join(self['node-directory'], "node.url")
|
|
|
|
self['node-url'] = open(node_url_file, "r").read().strip()
|
|
|
|
|
2008-05-20 21:36:04 +00:00
|
|
|
aliases = get_aliases(self['node-directory'])
|
2008-05-20 02:28:50 +00:00
|
|
|
if self['dir-cap']:
|
|
|
|
aliases["tahoe"] = self['dir-cap']
|
|
|
|
self.aliases = aliases # maps alias name to dircap
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2007-10-11 07:30:36 +00:00
|
|
|
|
2008-08-02 02:10:41 +00:00
|
|
|
class CreateAliasOptions(VDriveOptions):
|
|
|
|
def parseArgs(self, alias):
|
|
|
|
self.alias = alias
|
|
|
|
|
2008-05-20 21:36:04 +00:00
|
|
|
class ListAliasOptions(VDriveOptions):
|
|
|
|
pass
|
|
|
|
|
2007-07-11 02:37:37 +00:00
|
|
|
class ListOptions(VDriveOptions):
|
2008-05-20 02:28:50 +00:00
|
|
|
optFlags = [
|
|
|
|
("long", "l", "Use long format: show file sizes, and timestamps"),
|
2008-05-20 19:49:11 +00:00
|
|
|
("uri", "u", "Show file/directory URIs"),
|
|
|
|
("readonly-uri", None, "Show readonly file/directory URIs"),
|
2008-05-20 02:28:50 +00:00
|
|
|
("classify", "F", "Append '/' to directory names, and '*' to mutable"),
|
|
|
|
("json", None, "Show the raw JSON output"),
|
|
|
|
]
|
|
|
|
def parseArgs(self, where=""):
|
|
|
|
self.where = where
|
2007-07-11 02:37:37 +00:00
|
|
|
|
2007-08-16 19:50:19 +00:00
|
|
|
longdesc = """List the contents of some portion of the virtual drive."""
|
|
|
|
|
2007-07-11 02:37:37 +00:00
|
|
|
class GetOptions(VDriveOptions):
|
2008-05-20 02:28:50 +00:00
|
|
|
def parseArgs(self, arg1, arg2=None):
|
|
|
|
# tahoe get FOO |less # write to stdout
|
|
|
|
# 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
|
2007-07-11 02:37:37 +00:00
|
|
|
|
2007-07-11 17:26:19 +00:00
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
|
|
|
|
|
|
|
|
longdesc = """Retrieve a file from the virtual drive and write it to the
|
2007-08-16 19:15:38 +00:00
|
|
|
local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file
|
2007-07-11 17:26:19 +00:00
|
|
|
will be written to stdout."""
|
|
|
|
|
2008-06-03 00:54:56 +00:00
|
|
|
def getUsage(self, width=None):
|
|
|
|
t = VDriveOptions.getUsage(self, width)
|
|
|
|
t += """
|
|
|
|
Examples:
|
|
|
|
% tahoe get FOO |less # write to stdout
|
|
|
|
% tahoe get tahoe:FOO |less # same
|
|
|
|
% tahoe get FOO bar # write to local file
|
|
|
|
% tahoe get tahoe:FOO bar # same
|
|
|
|
"""
|
|
|
|
return t
|
|
|
|
|
2007-08-16 19:15:38 +00:00
|
|
|
class PutOptions(VDriveOptions):
|
2008-05-20 19:36:55 +00:00
|
|
|
optFlags = [
|
|
|
|
("mutable", "m", "Create a mutable file instead of an immutable one."),
|
|
|
|
]
|
|
|
|
|
2008-05-20 02:28:50 +00:00
|
|
|
def parseArgs(self, arg1=None, arg2=None):
|
|
|
|
# cat FILE > tahoe put # create unlinked file from stdin
|
2008-08-02 02:27:29 +00:00
|
|
|
# cat FILE > tahoe put - # same
|
|
|
|
# tahoe put bar # create unlinked file from local 'bar'
|
|
|
|
# cat FILE > tahoe put - FOO # create tahoe:FOO from stdin
|
2008-05-20 02:28:50 +00:00
|
|
|
# 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:
|
2008-08-02 02:27:29 +00:00
|
|
|
self.from_file = arg1 # might be "-"
|
|
|
|
self.to_file = None
|
2008-05-20 02:28:50 +00:00
|
|
|
else:
|
2008-08-02 02:27:29 +00:00
|
|
|
self.from_file = None
|
|
|
|
self.to_file = None
|
2008-05-20 02:28:50 +00:00
|
|
|
if self.from_file == "-":
|
|
|
|
self.from_file = None
|
2007-08-16 19:15:38 +00:00
|
|
|
|
|
|
|
def getSynopsis(self):
|
2007-08-17 00:47:24 +00:00
|
|
|
return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
|
2007-08-16 19:15:38 +00:00
|
|
|
|
|
|
|
longdesc = """Put a file into the virtual drive (copying the file's
|
2008-08-02 02:27:29 +00:00
|
|
|
contents from the local filesystem). If VDRIVE_FILE is missing, upload
|
|
|
|
the file but do not link it into a directory: prints the new filecap to
|
|
|
|
stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin.
|
|
|
|
VDRIVE_FILE is assumed to start with tahoe: unless otherwise specified."""
|
2008-06-03 00:54:56 +00:00
|
|
|
|
|
|
|
def getUsage(self, width=None):
|
|
|
|
t = VDriveOptions.getUsage(self, width)
|
|
|
|
t += """
|
|
|
|
Examples:
|
2008-08-02 02:27:29 +00:00
|
|
|
% cat FILE > tahoe put # create unlinked file from stdin
|
|
|
|
% cat FILE > tahoe - # same
|
|
|
|
% tahoe put bar # create unlinked file from local 'bar'
|
|
|
|
% cat FILE > tahoe put - FOO # create tahoe:FOO from stdin
|
|
|
|
% tahoe put bar FOO # copy local 'bar' to tahoe:FOO
|
|
|
|
% tahoe put bar tahoe:FOO # same
|
|
|
|
% tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place
|
2008-06-03 00:54:56 +00:00
|
|
|
"""
|
|
|
|
return t
|
2007-08-16 19:15:38 +00:00
|
|
|
|
2008-05-20 23:56:03 +00:00
|
|
|
class CpOptions(VDriveOptions):
|
|
|
|
optFlags = [
|
|
|
|
("recursive", "r", "Copy source directory recursively."),
|
2008-05-22 00:35:21 +00:00
|
|
|
("verbose", "v", "Be noisy about what is happening."),
|
2008-05-20 23:56:03 +00:00
|
|
|
]
|
|
|
|
def parseArgs(self, *args):
|
|
|
|
if len(args) < 2:
|
|
|
|
raise usage.UsageError("cp requires at least two arguments")
|
|
|
|
self.sources = args[:-1]
|
|
|
|
self.destination = args[-1]
|
|
|
|
|
2007-08-17 20:23:16 +00:00
|
|
|
class RmOptions(VDriveOptions):
|
2008-05-20 02:28:50 +00:00
|
|
|
def parseArgs(self, where):
|
|
|
|
self.where = where
|
2007-08-17 20:23:16 +00:00
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s rm VE_FILE" % (os.path.basename(sys.argv[0]),)
|
2007-08-16 19:15:38 +00:00
|
|
|
|
2007-10-12 03:31:48 +00:00
|
|
|
class MvOptions(VDriveOptions):
|
|
|
|
def parseArgs(self, frompath, topath):
|
2008-05-20 02:28:50 +00:00
|
|
|
self.from_file = frompath
|
|
|
|
self.to_file = topath
|
2007-10-12 03:31:48 +00:00
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
|
|
|
|
|
2008-05-20 20:30:31 +00:00
|
|
|
class LnOptions(VDriveOptions):
|
|
|
|
def parseArgs(self, frompath, topath):
|
|
|
|
self.from_file = frompath
|
|
|
|
self.to_file = topath
|
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
|
|
|
|
|
2008-01-05 01:34:10 +00:00
|
|
|
class WebopenOptions(VDriveOptions):
|
CLI: rework webopen, and moreover its tests w.r.t. path handling
in the recent reconciliation of webopen patches, I wound up adjusting
webopen to 'pass through' the state of the trailing slash on the given
argument to the resultant url passed to the browser. this change
removes the requirement that arguments must be directories, and allows
webopen to be used with files. it also broke the tests that assumed
that webopen would always normalise the url to have a trailing slash.
in fixing the tests, I realised that, IMHO, there's something deeply
awry with the way tahoe handles paths; specifically in the combination
of '/' being the name of the root path within an alias, but a leading
slash on paths, e.g. 'alias:/path', is catagorically incorrect. i.e.
'tahoe:' == 'tahoe:/' == '/'
but 'tahoe:/foo' is an invalid path, and must be 'tahoe:foo'
I wound up making the internals of webopen simply spot a 'path' of
'/' and smash it to '', which 'fixes' webopen to match the behaviour
of tahoe's path handling elsewhere, but that special case sort of
points to the weirdness.
(fwiw, I personally found the fact that the leading / in a path was
disallowed to be weird - I'm just used to seeing paths qualified by
the leading / I guess - so in a debate about normalising path handling
I'd vote to include the /)
2008-09-24 16:45:23 +00:00
|
|
|
def parseArgs(self, where=''):
|
2008-09-24 15:20:02 +00:00
|
|
|
self.where = where
|
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
|
2008-01-05 01:34:10 +00:00
|
|
|
|
|
|
|
longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive."""
|
2007-07-11 02:37:37 +00:00
|
|
|
|
2008-11-13 03:17:25 +00:00
|
|
|
class ManifestOptions(VDriveOptions):
|
|
|
|
optFlags = [
|
|
|
|
("storage-index", "s", "Only print storage index strings, not pathname+cap"),
|
|
|
|
]
|
|
|
|
def parseArgs(self, where=''):
|
|
|
|
self.where = where
|
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
|
|
|
|
|
|
|
|
longdesc = """Print a list of all files/directories reachable from the given starting point."""
|
|
|
|
|
2008-11-14 02:43:50 +00:00
|
|
|
class StatsOptions(VDriveOptions):
|
2008-11-18 05:02:19 +00:00
|
|
|
optFlags = [
|
2008-11-19 01:36:08 +00:00
|
|
|
("raw", "r", "Display raw JSON data instead of parsed"),
|
2008-11-18 05:02:19 +00:00
|
|
|
]
|
2008-11-14 02:43:50 +00:00
|
|
|
def parseArgs(self, where=''):
|
|
|
|
self.where = where
|
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
|
|
|
|
|
|
|
|
longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
|
|
|
|
|
2007-07-11 02:37:37 +00:00
|
|
|
subCommands = [
|
2008-05-20 02:28:50 +00:00
|
|
|
["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
|
|
|
|
["add-alias", None, AddAliasOptions, "Add a new alias cap"],
|
2008-08-02 02:10:41 +00:00
|
|
|
["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
|
2008-05-20 21:36:04 +00:00
|
|
|
["list-aliases", None, ListAliasOptions, "List all alias caps"],
|
2007-07-11 02:37:37 +00:00
|
|
|
["ls", None, ListOptions, "List a directory"],
|
2007-07-11 17:26:19 +00:00
|
|
|
["get", None, GetOptions, "Retrieve a file from the virtual drive."],
|
2007-08-16 19:15:38 +00:00
|
|
|
["put", None, PutOptions, "Upload a file into the virtual drive."],
|
2008-05-20 23:56:03 +00:00
|
|
|
["cp", None, CpOptions, "Copy one or more files."],
|
2007-08-17 20:23:16 +00:00
|
|
|
["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
|
2007-10-12 03:31:48 +00:00
|
|
|
["mv", None, MvOptions, "Move a file within the virtual drive."],
|
2008-05-20 20:30:31 +00:00
|
|
|
["ln", None, LnOptions, "Make an additional link to an existing file."],
|
2008-01-05 01:34:10 +00:00
|
|
|
["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
|
2008-11-13 03:17:25 +00:00
|
|
|
["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
|
2008-11-14 02:43:50 +00:00
|
|
|
["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
|
2007-07-11 02:37:37 +00:00
|
|
|
]
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def mkdir(options):
|
2008-05-20 02:28:50 +00:00
|
|
|
from allmydata.scripts import tahoe_mkdir
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_mkdir.mkdir(options)
|
2008-05-20 02:28:50 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def add_alias(options):
|
2008-05-20 02:28:50 +00:00
|
|
|
from allmydata.scripts import tahoe_add_alias
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_add_alias.add_alias(options)
|
2008-05-20 02:28:50 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-02 02:10:41 +00:00
|
|
|
def create_alias(options):
|
|
|
|
from allmydata.scripts import tahoe_add_alias
|
|
|
|
rc = tahoe_add_alias.create_alias(options)
|
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def list_aliases(options):
|
2008-05-20 21:36:04 +00:00
|
|
|
from allmydata.scripts import tahoe_add_alias
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_add_alias.list_aliases(options)
|
2008-05-20 21:36:04 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def list(options):
|
2007-07-11 02:37:37 +00:00
|
|
|
from allmydata.scripts import tahoe_ls
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_ls.list(options)
|
2007-07-11 02:37:37 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def get(options):
|
2007-07-11 02:37:37 +00:00
|
|
|
from allmydata.scripts import tahoe_get
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_get.get(options)
|
2007-07-11 17:26:19 +00:00
|
|
|
if rc == 0:
|
2008-08-01 18:46:24 +00:00
|
|
|
if options.to_file is None:
|
2007-07-11 17:26:19 +00:00
|
|
|
# be quiet, since the file being written to stdout should be
|
|
|
|
# proof enough that it worked, unless the user is unlucky
|
|
|
|
# enough to have picked an empty file
|
|
|
|
pass
|
|
|
|
else:
|
2008-08-01 18:46:24 +00:00
|
|
|
print >>options.stderr, "%s retrieved and written to %s" % \
|
|
|
|
(options.from_file, options.to_file)
|
2007-07-11 02:37:37 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def put(options):
|
2007-08-16 19:15:38 +00:00
|
|
|
from allmydata.scripts import tahoe_put
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_put.put(options)
|
2007-08-17 20:23:16 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def cp(options):
|
2008-05-20 23:56:03 +00:00
|
|
|
from allmydata.scripts import tahoe_cp
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_cp.copy(options)
|
2008-05-20 23:56:03 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def rm(options):
|
2007-08-17 20:23:16 +00:00
|
|
|
from allmydata.scripts import tahoe_rm
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_rm.rm(options)
|
2007-08-16 19:15:38 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def mv(options):
|
2007-10-12 03:31:48 +00:00
|
|
|
from allmydata.scripts import tahoe_mv
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_mv.mv(options, mode="move")
|
2008-05-20 20:30:31 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-01 18:46:24 +00:00
|
|
|
def ln(options):
|
2008-05-20 20:30:31 +00:00
|
|
|
from allmydata.scripts import tahoe_mv
|
2008-08-01 18:46:24 +00:00
|
|
|
rc = tahoe_mv.mv(options, mode="link")
|
2007-10-12 03:31:48 +00:00
|
|
|
return rc
|
|
|
|
|
2008-08-12 01:20:23 +00:00
|
|
|
def webopen(options, opener=None):
|
2008-09-24 15:20:02 +00:00
|
|
|
from allmydata.scripts import tahoe_webopen
|
|
|
|
rc = tahoe_webopen.webopen(options, opener=opener)
|
|
|
|
return rc
|
2008-01-05 01:34:10 +00:00
|
|
|
|
2008-11-13 03:17:25 +00:00
|
|
|
def manifest(options):
|
|
|
|
from allmydata.scripts import tahoe_manifest
|
|
|
|
rc = tahoe_manifest.manifest(options)
|
|
|
|
return rc
|
|
|
|
|
2008-11-14 02:43:50 +00:00
|
|
|
def stats(options):
|
|
|
|
from allmydata.scripts import tahoe_manifest
|
|
|
|
rc = tahoe_manifest.stats(options)
|
|
|
|
return rc
|
|
|
|
|
2007-07-11 02:37:37 +00:00
|
|
|
dispatch = {
|
2008-05-20 02:28:50 +00:00
|
|
|
"mkdir": mkdir,
|
|
|
|
"add-alias": add_alias,
|
2008-08-02 02:10:41 +00:00
|
|
|
"create-alias": create_alias,
|
2008-05-20 21:36:04 +00:00
|
|
|
"list-aliases": list_aliases,
|
2007-07-11 02:37:37 +00:00
|
|
|
"ls": list,
|
|
|
|
"get": get,
|
2007-08-16 19:15:38 +00:00
|
|
|
"put": put,
|
2008-05-20 23:56:03 +00:00
|
|
|
"cp": cp,
|
2007-08-17 20:23:16 +00:00
|
|
|
"rm": rm,
|
2007-10-12 03:31:48 +00:00
|
|
|
"mv": mv,
|
2008-05-20 20:30:31 +00:00
|
|
|
"ln": ln,
|
2008-01-05 01:34:10 +00:00
|
|
|
"webopen": webopen,
|
2008-11-13 03:17:25 +00:00
|
|
|
"manifest": manifest,
|
2008-11-14 02:43:50 +00:00
|
|
|
"stats": stats,
|
2007-07-11 02:37:37 +00:00
|
|
|
}
|
|
|
|
|