infocalypse

(Arne Babenhauserheide)
2012-06-26: merged freenet scheme branch.

merged freenet scheme branch.

diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -354,450 +354,16 @@ d kar bott at com cast dot net
 
 import os
 
-from binascii import hexlify
-from mercurial import commands, util
+from commands import *
 
-from infcmds import get_config_info, execute_create, execute_pull, \
-     execute_push, execute_setup, execute_copy, execute_reinsert, \
-     execute_info
+from mercurial import commands, extensions, util, hg, dispatch, discovery
+from mercurial.i18n import _
 
-from fmscmds import execute_fmsread, execute_fmsnotify, get_uri_from_hash, \
-     execute_setupfms
+import freenetrepo
 
-from sitecmds import execute_putsite, execute_genkey
-from wikicmds import execute_wiki, execute_wiki_apply
-from arccmds import execute_arc_create, execute_arc_pull, execute_arc_push, \
-     execute_arc_reinsert
-
-from config import read_freesite_cfg
-from validate import is_hex_string, is_fms_id
-
-def set_target_version(ui_, repo, opts, params, msg_fmt):
-    """ INTERNAL: Update TARGET_VERSION in params. """
-
-    revs = opts.get('rev') or None
-    if not revs is None:
-        for rev in revs:
-            repo.changectx(rev) # Fail if we don't have the rev.
-
-        params['TO_VERSIONS'] = tuple(revs)
-        ui_.status(msg_fmt % ' '.join([ver[:12] for ver in revs]))
-    else:
-        # REDFLAG: get rid of default versions arguments?
-        params['TO_VERSIONS'] = tuple([hexlify(head) for head in repo.heads()])
-        #print "set_target_version -- using all head"
-        #print params['TO_VERSIONS']
-
-def infocalypse_create(ui_, repo, **opts):
-    """ Create a new Infocalypse repository in Freenet. """
-    params, stored_cfg = get_config_info(ui_, opts)
-
-    insert_uri = opts['uri']
-    if insert_uri == '':
-        # REDFLAG: fix parameter definition so that it is required?
-        ui_.warn("Please set the insert URI with --uri.\n")
-        return
-
-    set_target_version(ui_, repo, opts, params,
-                       "Only inserting to version(s): %s\n")
-    params['INSERT_URI'] = insert_uri
-    execute_create(ui_, repo, params, stored_cfg)
-
-def infocalypse_copy(ui_, repo, **opts):
-    """ Copy an Infocalypse repository to a new URI. """
-    params, stored_cfg = get_config_info(ui_, opts)
-
-    insert_uri = opts['inserturi']
-    if insert_uri == '':
-        # REDFLAG: fix parameter definition so that it is required?
-        ui_.warn("Please set the insert URI with --inserturi.\n")
-        return
-
-    request_uri = opts['requesturi']
-    if request_uri == '':
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this repo.\n"
-                     "Please set one with the --requesturi option.\n")
-            return
-
-    params['INSERT_URI'] = insert_uri
-    params['REQUEST_URI'] = request_uri
-    execute_copy(ui_, repo, params, stored_cfg)
-
-def infocalypse_reinsert(ui_, repo, **opts):
-    """ Reinsert the current version of an Infocalypse repository. """
-    params, stored_cfg = get_config_info(ui_, opts)
-
-    request_uri = opts['uri']
-    if request_uri == '':
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this repo.\n"
-                     "Do a fn-pull from a repository USK and try again.\n")
-            return
-
-    level = opts['level']
-    if level < 1 or level > 5:
-        ui_.warn("level must be 1,2,3,4 or 5.\n")
-        return
-
-    insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
-    if not insert_uri:
-        if level == 1 or level == 4:
-            ui_.warn(("You can't re-insert at level %i without the "
-                     + "insert URI.\n") % level)
-            return
-
-        ui_.status("No insert URI. Will skip re-insert "
-                   +"of top key.\n")
-        insert_uri = None
-
-    params['INSERT_URI'] = insert_uri
-    params['REQUEST_URI'] = request_uri
-    params['REINSERT_LEVEL'] = level
-    execute_reinsert(ui_, repo, params, stored_cfg)
-
-def infocalypse_pull(ui_, repo, **opts):
-    """ Pull from an Infocalypse repository in Freenet.
-     """
-    params, stored_cfg = get_config_info(ui_, opts)
-
-    if opts['hash']:
-        # Use FMS to lookup the uri from the repo hash.
-        if opts['uri'] != '':
-            ui_.warn("Ignoring --uri because --hash is set!\n")
-        if len(opts['hash']) != 1:
-            raise util.Abort("Only one --hash value is allowed.")
-        params['FMSREAD_HASH'] = opts['hash'][0]
-        params['FMSREAD_ONLYTRUSTED'] = bool(opts['onlytrusted'])
-        request_uri = get_uri_from_hash(ui_, repo, params, stored_cfg)
-    else:
-        request_uri = opts['uri']
-
-    if request_uri == '':
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this repo.\n"
-                     "Please set one with the --uri option.\n")
-            return
-
-    params['REQUEST_URI'] = request_uri
-    # Hmmmm... can't really implement rev.
-    execute_pull(ui_, repo, params, stored_cfg)
-
-def infocalypse_push(ui_, repo, **opts):
-    """ Push to an Infocalypse repository in Freenet. """
-    params, stored_cfg = get_config_info(ui_, opts)
-    insert_uri = opts['uri']
-    if insert_uri == '':
-        insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
-        if not insert_uri:
-            ui_.warn("There is no stored insert URI for this repo.\n"
-                    "Please set one with the --uri option.\n")
-            return
-
-    set_target_version(ui_, repo, opts, params,
-                       "Only pushing to version(s): %s\n")
-    params['INSERT_URI'] = insert_uri
-    #if opts['requesturi'] != '':
-    #    # DOESN'T search the insert uri index.
-    #    ui_.status(("Copying from:\n%s\nTo:\n%s\n\nThis is an "
-    #                + "advanced feature. "
-    #                + "I hope you know what you're doing.\n") %
-    #               (opts['requesturi'], insert_uri))
-    #    params['REQUEST_URI'] = opts['requesturi']
-
-    execute_push(ui_, repo, params, stored_cfg)
-
-def infocalypse_info(ui_, repo, **opts):
-    """ Display information about an Infocalypse repository.
-     """
-    # FCP not required. Hmmm... Hack
-    opts['fcphost'] = ''
-    opts['fcpport'] = 0
-    params, stored_cfg = get_config_info(ui_, opts)
-    request_uri = opts['uri']
-    if request_uri == '':
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this repo.\n"
-                     "Please set one with the --uri option.\n")
-            return
-
-    params['REQUEST_URI'] = request_uri
-    execute_info(ui_, repo, params, stored_cfg)
-
-def parse_trust_args(params, opts):
-    """ INTERNAL: Helper function to parse  --hash and --fmsid. """
-    if opts.get('hash', []) == []:
-        raise util.Abort("Use --hash to set the USK hash.")
-    if len(opts['hash']) != 1:
-        raise util.Abort("Only one --hash value is allowed.")
-    if not is_hex_string(opts['hash'][0]):
-        raise util.Abort("[%s] doesn't look like a USK hash." %
-                         opts['hash'][0])
-
-    if opts.get('fmsid', []) == []:
-        raise util.Abort("Use --fmsid to set the FMS id.")
-    if len(opts['fmsid']) != 1:
-        raise util.Abort("Only one --fmsid value is allowed.")
-    if not is_fms_id(opts['fmsid'][0]):
-        raise util.Abort("[%s] doesn't look like an FMS id."
-                         % opts['fmsid'][0])
-
-    params['FMSREAD_HASH'] = opts['hash'][0]
-    params['FMSREAD_FMSID'] = opts['fmsid'][0]
-
-def parse_fmsread_subcmd(params, opts):
-    """ INTERNAL: Parse subcommand for fmsread."""
-    if opts['listall']:
-        params['FMSREAD'] = 'listall'
-    elif opts['list']:
-        params['FMSREAD'] = 'list'
-    elif opts['showtrust']:
-        params['FMSREAD'] = 'showtrust'
-    elif opts['trust']:
-        params['FMSREAD'] = 'trust'
-        parse_trust_args(params, opts)
-    elif opts['untrust']:
-        params['FMSREAD'] = 'untrust'
-        parse_trust_args(params, opts)
-    else:
-        params['FMSREAD'] = 'update'
-
-def infocalypse_fmsread(ui_, repo, **opts):
-    """ Read repository update information from fms.
-    """
-    # FCP not required. Hmmm... Hack
-    opts['fcphost'] = ''
-    opts['fcpport'] = 0
-    params, stored_cfg = get_config_info(ui_, opts)
-    request_uri = opts['uri']
-    if request_uri == '':
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.status("There is no stored request URI for this repo.\n")
-            request_uri = None
-    parse_fmsread_subcmd(params, opts)
-    params['DRYRUN'] = opts['dryrun']
-    params['REQUEST_URI'] = request_uri
-    execute_fmsread(ui_, params, stored_cfg)
-
-def infocalypse_fmsnotify(ui_, repo, **opts):
-    """ Post a msg with the current repository USK index to fms.
-    """
-    params, stored_cfg = get_config_info(ui_, opts)
-    insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
-    if not insert_uri and not (opts['submitbundle'] or
-                               opts['submitwiki']):
-        ui_.warn("You can't notify because there's no stored "
-                 + "insert URI for this repo.\n"
-                 + "Run from the directory you inserted from.\n")
-        return
-
-    params['ANNOUNCE'] = opts['announce']
-    params['SUBMIT_BUNDLE'] = opts['submitbundle']
-    params['SUBMIT_WIKI'] = opts['submitwiki']
-    if params['SUBMIT_WIKI'] or params['SUBMIT_BUNDLE']:
-        request_uri = stored_cfg.get_request_uri(repo.root)
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this repo.\n")
-            raise util.Abort("No request URI.")
-        params['REQUEST_URI'] = request_uri
-
-    params['DRYRUN'] = opts['dryrun']
-    params['INSERT_URI'] = insert_uri
-    execute_fmsnotify(ui_, repo, params, stored_cfg)
-
-MSG_BAD_INDEX = 'You must set --index to a value >= 0.'
-def infocalypse_putsite(ui_, repo, **opts):
-    """ Insert an update to a freesite.
-    """
-
-    if opts['createconfig']:
-        if opts['wiki']:
-            raise util.Abort("Use fn-wiki --createconfig.")
-        params = {'SITE_CREATE_CONFIG':True}
-        execute_putsite(ui_, repo, params)
-        return
-
-    params, stored_cfg = get_config_info(ui_, opts)
-    if opts['key'] != '': # order important
-        params['SITE_KEY'] = opts['key']
-        if not (params['SITE_KEY'].startswith('SSK') or
-                params['SITE_KEY'] == 'CHK@'):
-            raise util.Abort("--key must be a valid SSK "
-                             + "insert key or CHK@.")
-
-    params['ISWIKI'] = opts['wiki']
-    read_freesite_cfg(ui_, repo, params, stored_cfg)
-
-    try:
-        # --index not required for CHK@
-        if not params['SITE_KEY'].startswith('CHK'):
-            params['SITE_INDEX'] = int(opts['index'])
-            if params['SITE_INDEX'] < 0:
-                raise ValueError()
-        else:
-            params['SITE_INDEX'] = -1
-    except ValueError:
-        raise util.Abort(MSG_BAD_INDEX)
-    except TypeError:
-        raise util.Abort(MSG_BAD_INDEX)
-
-    params['DRYRUN'] = opts['dryrun']
-
-    if not params.get('SITE_KEY', None):
-        insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
-        if not insert_uri:
-            ui_.warn("You don't have the insert URI for this repo.\n"
-                     + "Supply a private key with --key or fn-push "
-                     + "the repo.\n")
-            return # REDFLAG: hmmm... abort?
-        params['SITE_KEY'] = 'SSK' + insert_uri.split('/')[0][3:]
-
-    execute_putsite(ui_, repo, params)
-
-def infocalypse_wiki(ui_, repo, **opts):
-    """ View and edit the current repository as a wiki. """
-    if os.getcwd() != repo.root:
-        raise util.Abort("You must be in the repository root directory.")
-
-    subcmds = ('run', 'createconfig', 'apply')
-    required = sum([bool(opts[cmd]) for cmd in subcmds])
-    if required == 0:
-        raise util.Abort("You must specify either --run, " +
-                         "--createconfig, --apply")
-    if required > 1:
-        raise util.Abort("Use either --run, --createconfig, or --apply")
-
-    if opts['apply'] != '':
-        params, stored_cfg = get_config_info(ui_, opts)
-        params['REQUEST_URI'] = opts['apply']
-        execute_wiki_apply(ui_, repo, params, stored_cfg)
-        return
-
-    if opts['fcphost'] != '' or opts['fcpport'] != 0:
-        raise util.Abort("--fcphost, --fcpport only for --apply")
-
-    # hmmmm.... useless copy?
-    params = {'WIKI' : [cmd for cmd in subcmds if opts[cmd]][0],
-              'HTTP_PORT': opts['http_port'],
-              'HTTP_BIND': opts['http_bind']}
-    execute_wiki(ui_, repo, params)
-
-def infocalypse_genkey(ui_, **opts):
-    """ Print a new SSK key pair. """
-    params, dummy = get_config_info(ui_, opts)
-    execute_genkey(ui_, params)
-
-def infocalypse_setup(ui_, **opts):
-    """ Setup the extension for use for the first time. """
-
-    execute_setup(ui_,
-                  opts['fcphost'],
-                  opts['fcpport'],
-                  opts['tmpdir'])
-
-    if not opts['nofms']:
-        execute_setupfms(ui_, opts)
-    else:
-        ui_.status("Skipped FMS configuration because --nofms was set.\n")
-
-def infocalypse_setupfms(ui_, **opts):
-    """ Setup or modify the fms configuration. """
-    # REQUIRES config file.
-    execute_setupfms(ui_, opts)
-
-#----------------------------------------------------------"
-def do_archive_create(ui_, opts, params, stored_cfg):
-    """ fn-archive --create."""
-    insert_uri = opts['uri']
-    if insert_uri == '':
-        raise util.Abort("Please set the insert URI with --uri.")
-
-    params['INSERT_URI'] = insert_uri
-    params['FROM_DIR'] = os.getcwd()
-    execute_arc_create(ui_, params, stored_cfg)
-
-def do_archive_push(ui_, opts, params, stored_cfg):
-    """ fn-archive --push."""
-    insert_uri = opts['uri']
-    if insert_uri == '':
-        insert_uri = (
-            stored_cfg.get_dir_insert_uri(params['ARCHIVE_CACHE_DIR']))
-        if not insert_uri:
-            ui_.warn("There is no stored insert URI for this archive.\n"
-                     "Please set one with the --uri option.\n")
-            raise util.Abort("No Insert URI.")
-
-    params['INSERT_URI'] = insert_uri
-    params['FROM_DIR'] = os.getcwd()
-
-    execute_arc_push(ui_, params, stored_cfg)
-
-def do_archive_pull(ui_, opts, params, stored_cfg):
-    """ fn-archive --pull."""
-    request_uri = opts['uri']
-
-    if request_uri == '':
-        request_uri = (
-            stored_cfg.get_request_uri(params['ARCHIVE_CACHE_DIR']))
-        if not request_uri:
-            ui_.warn("There is no stored request URI for this archive.\n"
-                     "Please set one with the --uri option.\n")
-            raise util.Abort("No request URI.")
-
-    params['REQUEST_URI'] = request_uri
-    params['TO_DIR'] = os.getcwd()
-    execute_arc_pull(ui_,  params, stored_cfg)
-
-ILLEGAL_FOR_REINSERT = ('uri', 'aggressive', 'nosearch')
-def do_archive_reinsert(ui_, opts, params, stored_cfg):
-    """ fn-archive --reinsert."""
-    illegal = [value for value in ILLEGAL_FOR_REINSERT
-               if value in opts and opts[value]]
-    if illegal:
-        raise util.Abort("--uri, --aggressive, --nosearch illegal " +
-                         "for reinsert.")
-    request_uri = stored_cfg.get_request_uri(params['ARCHIVE_CACHE_DIR'])
-    if request_uri is None:
-        ui_.warn("There is no stored request URI for this archive.\n" +
-                 "Run fn-archive --pull first!.\n")
-        raise util.Abort(" No request URI, can't re-insert")
-
-    insert_uri = stored_cfg.get_dir_insert_uri(params['ARCHIVE_CACHE_DIR'])
-    params['REQUEST_URI'] = request_uri
-    params['INSERT_URI'] = insert_uri
-    params['FROM_DIR'] = os.getcwd() # hmmm not used.
-    params['REINSERT_LEVEL'] = 3
-    execute_arc_reinsert(ui_, params, stored_cfg)
-
-ARCHIVE_SUBCMDS = {'create':do_archive_create,
-                   'push':do_archive_push,
-                   'pull':do_archive_pull,
-                   'reinsert':do_archive_reinsert}
-ARCHIVE_CACHE_DIR = '.ARCHIVE_CACHE'
-def infocalypse_archive(ui_, **opts):
-    """ Commands to maintain a non-hg incremental archive."""
-    subcmd = [value for value in ARCHIVE_SUBCMDS if opts[value]]
-    if len(subcmd) > 1:
-        raise util.Abort("--create, --pull, --push are mutally exclusive. " +
-                         "Only specify one.")
-    if len(subcmd) > 0:
-        subcmd = subcmd[0]
-    else:
-        subcmd = "pull"
-
-    params, stored_cfg = get_config_info(ui_, opts)
-    params['ARCHIVE_CACHE_DIR'] = os.path.join(os.getcwd(), ARCHIVE_CACHE_DIR)
-
-    if not subcmd in ARCHIVE_SUBCMDS:
-        raise util.Abort("Unhandled subcommand: " + subcmd)
-
-    # 2 qt?
-    ARCHIVE_SUBCMDS[subcmd](ui_, opts, params, stored_cfg)
+_freenetschemes = ('freenet', )
+for _scheme in _freenetschemes:
+    hg.schemes[_scheme] = freenetrepo
 
 #----------------------------------------------------------"
 
@@ -817,12 +383,14 @@ NOSEARCH_OPT = [('', 'nosearch', None, '
 # Allow mercurial naming convention for command table.
 # pylint: disable-msg=C0103
 
+PULL_OPTS = [('', 'hash', [], 'repo hash of repository to pull from'),
+             ('', 'onlytrusted', None, 'only use repo announcements from '
+              + 'known users')]
+
 cmdtable = {
     "fn-pull": (infocalypse_pull,
-                [('', 'uri', '', 'request URI to pull from'),
-                 ('', 'hash', [], 'repo hash of repository to pull from'),
-                 ('', 'onlytrusted', None, 'only use repo announcements from '
-                  + 'known users')]
+                [('', 'uri', '', 'request URI to pull from')]
+                + PULL_OPTS
                 + FCP_OPTS
                 + NOSEARCH_OPT
                 + AGGRESSIVE_OPT,
@@ -947,3 +515,240 @@ commands.norepo += ' fn-setup'
 commands.norepo += ' fn-setupfms'
 commands.norepo += ' fn-genkey'
 commands.norepo += ' fn-archive'
+
+
+## Wrap core commands for use with freenet keys.
+## Explicitely wrap functions to change local commands in case the remote repo is an FTP repo. See mercurial.extensions for more information.
+# Get the module which holds the functions to wrap
+# the new function: gets the original function as first argument and the originals args and kwds.
+def findcommonoutgoing(orig, *args, **opts):
+    repo = args[0]
+    remoterepo = args[1]
+    capable = getattr(remoterepo, 'capable', lambda x: False)
+    if capable('infocalypse'):
+        class fakeoutgoing(object):
+            def __init__(self):
+                self.excluded = []
+                self.missing = repo.heads()
+                self.missingheads = []
+                self.commonheads = []
+        return fakeoutgoing()
+    else:
+        return orig(*args, **opts)
+# really wrap the functions
+extensions.wrapfunction(discovery, 'findcommonoutgoing', findcommonoutgoing)
+
+# wrap the commands
+
+def freenetpathtouri(path):
+    path = path.replace("%7E", "~").replace("%2C", ",")
+    if path.startswith("freenet://"):
+        return path[len("freenet://"):]
+    if path.startswith("freenet:"):
+        return path[len("freenet:"):]
+    return path
+
+def freenetpull(orig, *args, **opts):
+    def parsepushargs(ui, repo, path=None):
+        return ui, repo, path
+    def isfreenetpath(path):
+        if path and path.startswith("freenet:") or path.startswith("USK@"):
+            return True
+        return False
+    ui, repo, path = parsepushargs(*args)
+    if not path:
+        path = ui.expandpath('default', 'default-push')
+    else:
+        path = ui.expandpath(path)
+    # only act differently, if the target is an infocalypse repo.
+    if not isfreenetpath(path):
+        return orig(*args, **opts)
+    uri = freenetpathtouri(path)
+    opts["uri"] = uri
+    opts["aggressive"] = True # always search for the latest revision.
+    return infocalypse_pull(ui, repo, **opts)
+
+def freenetpush(orig, *args, **opts):
+    def parsepushargs(ui, repo, path=None):
+        return ui, repo, path
+    def isfreenetpath(path):
+        if path and path.startswith("freenet:") or path.startswith("USK@"):
+            return True
+        return False
+    ui, repo, path = parsepushargs(*args)
+    if not path:
+        path = ui.expandpath('default-push', 'default')
+    else:
+        path = ui.expandpath(path)
+    # only act differently, if the target is an infocalypse repo.
+    if not isfreenetpath(path):
+        return orig(*args, **opts)
+    uri = freenetpathtouri(path)
+    opts["uri"] = uri
+    opts["aggressive"] = True # always search for the latest revision.
+    return infocalypse_push(ui, repo, **opts)
+
+def freenetclone(orig, *args, **opts):
+    def parsepushargs(ui, repo, path=None):
+        return ui, repo, path
+    def isfreenetpath(path):
+        if path and path.startswith("freenet:") or path.startswith("USK@"):
+            return True
+        return False
+    ui, source, dest = parsepushargs(*args)
+    # only act differently, if dest or source is an infocalypse repo.
+    if not isfreenetpath(source) and not isfreenetpath(dest):
+        return orig(*args, **opts)
+
+    if not dest:
+        if not isfreenetpath(source):
+            dest = hg.defaultdest(source)
+        else: # this is a freenet key.  It has a /# at the end and
+              # could contain .R1 or .R0 as pure technical identifiers
+              # which we do not need in the local name.
+            dest = source.split("/")[-2]
+            if dest.endswith(".R1") or dest.endswith(".R0"):
+                dest = dest[:-3]
+
+    # check whether to create, pull or copy
+    pulluri, pushuri = None, None
+    if isfreenetpath(source):
+        pulluri = freenetpathtouri(source)
+    if isfreenetpath(dest):
+        pushuri = freenetpathtouri(dest)
+
+    # decide which infocalypse command to use.
+    if pulluri and pushuri:
+        action = "copy"
+    elif pulluri:
+        action = "pull"
+    elif pushuri:
+        action = "create"
+    else: 
+        raise util.Abort("""Can't clone without source and target. This message should not be reached. If you see it, this is a bug.""")
+
+    if action == "copy":
+        raise util.Abort("""Cloning without intermediate local repo not yet supported in the simplified commands. Use fn-copy directly.""")
+    
+    if action == "create":
+        # if the pushuri is the short form (USK@/name/#), generate the key.
+        if pushuri.startswith("USK@/"):
+            ui.status("creating a new private key for the repo. If you want to use your default private key instead, call fn-create directly.\n")
+            from sitecmds import genkeypair
+            fcphost, fcpport = opts["fcphost"], opts["fcpport"]
+            if fcphost == '':
+                fcphost = '127.0.0.1'
+            if fcpport == 0:
+                fcpport = 9481
+
+            insert, request = genkeypair(fcphost, fcpport)
+            pushuri = "USK"+insert[3:]+pushuri[5:]
+        opts["uri"] = pushuri
+        repo = hg.repository(ui, ui.expandpath(source))
+        return infocalypse_create(ui, repo, **opts)
+
+    if action == "pull":
+        if os.path.exists(dest):
+            raise util.Abort(_("destination " + dest + " already exists."))
+        # create the repo
+        req = dispatch.request(["init", dest], ui=ui)
+        dispatch.dispatch(req)
+        # pull the data from freenet
+        origdest = ui.expandpath(dest)
+        dest, branch = hg.parseurl(origdest)
+        destrepo = hg.repository(ui, dest)
+        infocalypse_pull(ui, destrepo, aggressive=True, hash=None, uri=pulluri, **opts)
+        # store the request uri for future updates
+        with destrepo.opener("hgrc", "a", text=True) as f:
+            f.write("""[paths]
+default = freenet://""" + pulluri + """
+
+[ui]
+username = anonymous
+""" )
+        ui.warn("As basic protection, infocalypse automatically set the username 'anonymous' for commits in this repo. To change this, edit " + str(os.path.join(destrepo.root, ".hg", "hgrc")))
+        # and update the repo
+        return hg.update(destrepo, None)
+
+
+# really wrap the command
+entry = extensions.wrapcommand(commands.table, "push", freenetpush)
+entry[1].extend(FCP_OPTS)
+entry = extensions.wrapcommand(commands.table, "pull", freenetpull)
+entry[1].extend(PULL_OPTS)
+entry[1].extend(FCP_OPTS)
+entry = extensions.wrapcommand(commands.table, "clone", freenetclone)
+entry[1].extend(FCP_OPTS)
+
+
+# Starting an FTP repo. Not yet used, except for throwing errors for missing commands and faking the lock.
+
+from mercurial import repo, util
+try:
+    from mercurial.error import RepoError
+except ImportError:
+    from mercurial.repo import RepoError
+
+class InfocalypseRepository(repo.repository):
+    def __init__(self, ui, path, create):
+        self.create = create
+        self.ui = ui
+        self.path = path
+        self.capabilities = set(["infocalypse"])
+        self.branchmap = {}
+
+    def lock(self):
+        """We cannot really lock Infocalypse repos, yet.
+
+        TODO: Implement as locking the repo in the static site folder."""
+        class DummyLock:
+            def release(self):
+                pass
+        l = DummyLock()
+        return l
+
+    def url(self):
+        return self.path
+
+    def lookup(self, key):
+        return key
+
+    def cancopy(self):
+        return False
+
+    def heads(self, *args, **opts):
+        """
+        Whenever this function is hit, we abort. The traceback is useful for
+        figuring out where to intercept the functionality.
+        """
+        raise util.Abort('command heads unavailable for Infocalypse repositories')
+
+    def pushkey(self, namespace, key, old, new):
+        return False
+
+    def listkeys(self, namespace):
+        return {}
+
+    def push(self, remote, force=False, revs=None, newbranch=None):
+        raise util.Abort('command push unavailable for Infocalypse repositories')
+    
+    def pull(self, remote, heads=[], force=False):
+        raise util.Abort('command pull unavailable for Infocalypse repositories')
+    
+    def findoutgoing(self, remote, base=None, heads=None, force=False):
+        raise util.Abort('command findoutgoing unavailable for Infocalypse repositories')
+
+
+class RepoContainer(object):
+    def __init__(self):
+        pass
+
+    def __repr__(self):
+        return '<InfocalypseRepository>'
+
+    def instance(self, ui, url, create):
+        # Should this use urlmod.url(), or is manual parsing better?
+        #context = {}
+        return InfocalypseRepository(ui, url, create)
+
+hg.schemes["freenet"] = RepoContainer()
diff --git a/infocalypse/commands.py b/infocalypse/commands.py
new file mode 100644
--- /dev/null
+++ b/infocalypse/commands.py
@@ -0,0 +1,445 @@
+from binascii import hexlify
+from mercurial import util, hg
+
+from infcmds import get_config_info, execute_create, execute_pull, \
+     execute_push, execute_setup, execute_copy, execute_reinsert, \
+     execute_info
+
+from fmscmds import execute_fmsread, execute_fmsnotify, get_uri_from_hash, \
+     execute_setupfms
+
+from sitecmds import execute_putsite, execute_genkey
+from wikicmds import execute_wiki, execute_wiki_apply
+from arccmds import execute_arc_create, execute_arc_pull, execute_arc_push, \
+     execute_arc_reinsert
+
+from config import read_freesite_cfg
+from validate import is_hex_string, is_fms_id
+
+def set_target_version(ui_, repo, opts, params, msg_fmt):
+    """ INTERNAL: Update TARGET_VERSION in params. """
+
+    revs = opts.get('rev') or None
+    if not revs is None:
+        for rev in revs:
+            repo.changectx(rev) # Fail if we don't have the rev.
+
+        params['TO_VERSIONS'] = tuple(revs)
+        ui_.status(msg_fmt % ' '.join([ver[:12] for ver in revs]))
+    else:
+        # REDFLAG: get rid of default versions arguments?
+        params['TO_VERSIONS'] = tuple([hexlify(head) for head in repo.heads()])
+        #print "set_target_version -- using all head"
+        #print params['TO_VERSIONS']
+
+def infocalypse_create(ui_, repo, **opts):
+    """ Create a new Infocalypse repository in Freenet. """
+    params, stored_cfg = get_config_info(ui_, opts)
+
+    insert_uri = opts['uri']
+    if insert_uri == '':
+        # REDFLAG: fix parameter definition so that it is required?
+        ui_.warn("Please set the insert URI with --uri.\n")
+        return
+
+    set_target_version(ui_, repo, opts, params,
+                       "Only inserting to version(s): %s\n")
+    params['INSERT_URI'] = insert_uri
+    execute_create(ui_, repo, params, stored_cfg)
+
+def infocalypse_copy(ui_, repo, **opts):
+    """ Copy an Infocalypse repository to a new URI. """
+    params, stored_cfg = get_config_info(ui_, opts)
+
+    insert_uri = opts['inserturi']
+    if insert_uri == '':
+        # REDFLAG: fix parameter definition so that it is required?
+        ui_.warn("Please set the insert URI with --inserturi.\n")
+        return
+
+    request_uri = opts['requesturi']
+    if request_uri == '':
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this repo.\n"
+                     "Please set one with the --requesturi option.\n")
+            return
+
+    params['INSERT_URI'] = insert_uri
+    params['REQUEST_URI'] = request_uri
+    execute_copy(ui_, repo, params, stored_cfg)
+
+def infocalypse_reinsert(ui_, repo, **opts):
+    """ Reinsert the current version of an Infocalypse repository. """
+    params, stored_cfg = get_config_info(ui_, opts)
+
+    request_uri = opts['uri']
+    if request_uri == '':
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this repo.\n"
+                     "Do a fn-pull from a repository USK and try again.\n")
+            return
+
+    level = opts['level']
+    if level < 1 or level > 5:
+        ui_.warn("level must be 1,2,3,4 or 5.\n")
+        return
+
+    insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
+    if not insert_uri:
+        if level == 1 or level == 4:
+            ui_.warn(("You can't re-insert at level %i without the "
+                     + "insert URI.\n") % level)
+            return
+
+        ui_.status("No insert URI. Will skip re-insert "
+                   +"of top key.\n")
+        insert_uri = None
+
+    params['INSERT_URI'] = insert_uri
+    params['REQUEST_URI'] = request_uri
+    params['REINSERT_LEVEL'] = level
+    execute_reinsert(ui_, repo, params, stored_cfg)
+
+def infocalypse_pull(ui_, repo, **opts):
+    """ Pull from an Infocalypse repository in Freenet.
+     """
+    params, stored_cfg = get_config_info(ui_, opts)
+
+    if opts['hash']:
+        # Use FMS to lookup the uri from the repo hash.
+        if opts['uri'] != '':
+            ui_.warn("Ignoring --uri because --hash is set!\n")
+        if len(opts['hash']) != 1:
+            raise util.Abort("Only one --hash value is allowed.")
+        params['FMSREAD_HASH'] = opts['hash'][0]
+        params['FMSREAD_ONLYTRUSTED'] = bool(opts['onlytrusted'])
+        request_uri = get_uri_from_hash(ui_, repo, params, stored_cfg)
+    else:
+        request_uri = opts['uri']
+
+    if request_uri == '':
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this repo.\n"
+                     "Please set one with the --uri option.\n")
+            return
+
+    params['REQUEST_URI'] = request_uri
+    # Hmmmm... can't really implement rev.
+    execute_pull(ui_, repo, params, stored_cfg)
+
+def infocalypse_push(ui_, repo, **opts):
+    """ Push to an Infocalypse repository in Freenet. """
+    params, stored_cfg = get_config_info(ui_, opts)
+    insert_uri = opts['uri']
+    if insert_uri == '':
+        insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
+        if not insert_uri:
+            ui_.warn("There is no stored insert URI for this repo.\n"
+                    "Please set one with the --uri option.\n")
+            return
+
+    set_target_version(ui_, repo, opts, params,
+                       "Only pushing to version(s): %s\n")
+    params['INSERT_URI'] = insert_uri
+    #if opts['requesturi'] != '':
+    #    # DOESN'T search the insert uri index.
+    #    ui_.status(("Copying from:\n%s\nTo:\n%s\n\nThis is an "
+    #                + "advanced feature. "
+    #                + "I hope you know what you're doing.\n") %
+    #               (opts['requesturi'], insert_uri))
+    #    params['REQUEST_URI'] = opts['requesturi']
+
+    execute_push(ui_, repo, params, stored_cfg)
+
+def infocalypse_info(ui_, repo, **opts):
+    """ Display information about an Infocalypse repository.
+     """
+    # FCP not required. Hmmm... Hack
+    opts['fcphost'] = ''
+    opts['fcpport'] = 0
+    params, stored_cfg = get_config_info(ui_, opts)
+    request_uri = opts['uri']
+    if request_uri == '':
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this repo.\n"
+                     "Please set one with the --uri option.\n")
+            return
+
+    params['REQUEST_URI'] = request_uri
+    execute_info(ui_, repo, params, stored_cfg)
+
+def parse_trust_args(params, opts):
+    """ INTERNAL: Helper function to parse  --hash and --fmsid. """
+    if opts.get('hash', []) == []:
+        raise util.Abort("Use --hash to set the USK hash.")
+    if len(opts['hash']) != 1:
+        raise util.Abort("Only one --hash value is allowed.")
+    if not is_hex_string(opts['hash'][0]):
+        raise util.Abort("[%s] doesn't look like a USK hash." %
+                         opts['hash'][0])
+
+    if opts.get('fmsid', []) == []:
+        raise util.Abort("Use --fmsid to set the FMS id.")
+    if len(opts['fmsid']) != 1:
+        raise util.Abort("Only one --fmsid value is allowed.")
+    if not is_fms_id(opts['fmsid'][0]):
+        raise util.Abort("[%s] doesn't look like an FMS id."
+                         % opts['fmsid'][0])
+
+    params['FMSREAD_HASH'] = opts['hash'][0]
+    params['FMSREAD_FMSID'] = opts['fmsid'][0]
+
+def parse_fmsread_subcmd(params, opts):
+    """ INTERNAL: Parse subcommand for fmsread."""
+    if opts['listall']:
+        params['FMSREAD'] = 'listall'
+    elif opts['list']:
+        params['FMSREAD'] = 'list'
+    elif opts['showtrust']:
+        params['FMSREAD'] = 'showtrust'
+    elif opts['trust']:
+        params['FMSREAD'] = 'trust'
+        parse_trust_args(params, opts)
+    elif opts['untrust']:
+        params['FMSREAD'] = 'untrust'
+        parse_trust_args(params, opts)
+    else:
+        params['FMSREAD'] = 'update'
+
+def infocalypse_fmsread(ui_, repo, **opts):
+    """ Read repository update information from fms.
+    """
+    # FCP not required. Hmmm... Hack
+    opts['fcphost'] = ''
+    opts['fcpport'] = 0
+    params, stored_cfg = get_config_info(ui_, opts)
+    request_uri = opts['uri']
+    if request_uri == '':
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.status("There is no stored request URI for this repo.\n")
+            request_uri = None
+    parse_fmsread_subcmd(params, opts)
+    params['DRYRUN'] = opts['dryrun']
+    params['REQUEST_URI'] = request_uri
+    execute_fmsread(ui_, params, stored_cfg)
+
+def infocalypse_fmsnotify(ui_, repo, **opts):
+    """ Post a msg with the current repository USK index to fms.
+    """
+    params, stored_cfg = get_config_info(ui_, opts)
+    insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
+    if not insert_uri and not (opts['submitbundle'] or
+                               opts['submitwiki']):
+        ui_.warn("You can't notify because there's no stored "
+                 + "insert URI for this repo.\n"
+                 + "Run from the directory you inserted from.\n")
+        return
+
+    params['ANNOUNCE'] = opts['announce']
+    params['SUBMIT_BUNDLE'] = opts['submitbundle']
+    params['SUBMIT_WIKI'] = opts['submitwiki']
+    if params['SUBMIT_WIKI'] or params['SUBMIT_BUNDLE']:
+        request_uri = stored_cfg.get_request_uri(repo.root)
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this repo.\n")
+            raise util.Abort("No request URI.")
+        params['REQUEST_URI'] = request_uri
+
+    params['DRYRUN'] = opts['dryrun']
+    params['INSERT_URI'] = insert_uri
+    execute_fmsnotify(ui_, repo, params, stored_cfg)
+
+MSG_BAD_INDEX = 'You must set --index to a value >= 0.'
+def infocalypse_putsite(ui_, repo, **opts):
+    """ Insert an update to a freesite.
+    """
+
+    if opts['createconfig']:
+        if opts['wiki']:
+            raise util.Abort("Use fn-wiki --createconfig.")
+        params = {'SITE_CREATE_CONFIG':True}
+        execute_putsite(ui_, repo, params)
+        return
+
+    params, stored_cfg = get_config_info(ui_, opts)
+    if opts['key'] != '': # order important
+        params['SITE_KEY'] = opts['key']
+        if not (params['SITE_KEY'].startswith('SSK') or
+                params['SITE_KEY'] == 'CHK@'):
+            raise util.Abort("--key must be a valid SSK "
+                             + "insert key or CHK@.")
+
+    params['ISWIKI'] = opts['wiki']
+    read_freesite_cfg(ui_, repo, params, stored_cfg)
+
+    try:
+        # --index not required for CHK@
+        if not params['SITE_KEY'].startswith('CHK'):
+            params['SITE_INDEX'] = int(opts['index'])
+            if params['SITE_INDEX'] < 0:
+                raise ValueError()
+        else:
+            params['SITE_INDEX'] = -1
+    except ValueError:
+        raise util.Abort(MSG_BAD_INDEX)
+    except TypeError:
+        raise util.Abort(MSG_BAD_INDEX)
+
+    params['DRYRUN'] = opts['dryrun']
+
+    if not params.get('SITE_KEY', None):
+        insert_uri = stored_cfg.get_dir_insert_uri(repo.root)
+        if not insert_uri:
+            ui_.warn("You don't have the insert URI for this repo.\n"
+                     + "Supply a private key with --key or fn-push "
+                     + "the repo.\n")
+            return # REDFLAG: hmmm... abort?
+        params['SITE_KEY'] = 'SSK' + insert_uri.split('/')[0][3:]
+
+    execute_putsite(ui_, repo, params)
+
+def infocalypse_wiki(ui_, repo, **opts):
+    """ View and edit the current repository as a wiki. """
+    if os.getcwd() != repo.root:
+        raise util.Abort("You must be in the repository root directory.")
+
+    subcmds = ('run', 'createconfig', 'apply')
+    required = sum([bool(opts[cmd]) for cmd in subcmds])
+    if required == 0:
+        raise util.Abort("You must specify either --run, " +
+                         "--createconfig, --apply")
+    if required > 1:
+        raise util.Abort("Use either --run, --createconfig, or --apply")
+
+    if opts['apply'] != '':
+        params, stored_cfg = get_config_info(ui_, opts)
+        params['REQUEST_URI'] = opts['apply']
+        execute_wiki_apply(ui_, repo, params, stored_cfg)
+        return
+
+    if opts['fcphost'] != '' or opts['fcpport'] != 0:
+        raise util.Abort("--fcphost, --fcpport only for --apply")
+
+    # hmmmm.... useless copy?
+    params = {'WIKI' : [cmd for cmd in subcmds if opts[cmd]][0],
+              'HTTP_PORT': opts['http_port'],
+              'HTTP_BIND': opts['http_bind']}
+    execute_wiki(ui_, repo, params)
+
+def infocalypse_genkey(ui_, **opts):
+    """ Print a new SSK key pair. """
+    params, dummy = get_config_info(ui_, opts)
+    execute_genkey(ui_, params)
+
+def infocalypse_setup(ui_, **opts):
+    """ Setup the extension for use for the first time. """
+
+    execute_setup(ui_,
+                  opts['fcphost'],
+                  opts['fcpport'],
+                  opts['tmpdir'])
+
+    if not opts['nofms']:
+        execute_setupfms(ui_, opts)
+    else:
+        ui_.status("Skipped FMS configuration because --nofms was set.\n")
+
+def infocalypse_setupfms(ui_, **opts):
+    """ Setup or modify the fms configuration. """
+    # REQUIRES config file.
+    execute_setupfms(ui_, opts)
+
+#----------------------------------------------------------"
+def do_archive_create(ui_, opts, params, stored_cfg):
+    """ fn-archive --create."""
+    insert_uri = opts['uri']
+    if insert_uri == '':
+        raise util.Abort("Please set the insert URI with --uri.")
+
+    params['INSERT_URI'] = insert_uri
+    params['FROM_DIR'] = os.getcwd()
+    execute_arc_create(ui_, params, stored_cfg)
+
+def do_archive_push(ui_, opts, params, stored_cfg):
+    """ fn-archive --push."""
+    insert_uri = opts['uri']
+    if insert_uri == '':
+        insert_uri = (
+            stored_cfg.get_dir_insert_uri(params['ARCHIVE_CACHE_DIR']))
+        if not insert_uri:
+            ui_.warn("There is no stored insert URI for this archive.\n"
+                     "Please set one with the --uri option.\n")
+            raise util.Abort("No Insert URI.")
+
+    params['INSERT_URI'] = insert_uri
+    params['FROM_DIR'] = os.getcwd()
+
+    execute_arc_push(ui_, params, stored_cfg)
+
+def do_archive_pull(ui_, opts, params, stored_cfg):
+    """ fn-archive --pull."""
+    request_uri = opts['uri']
+
+    if request_uri == '':
+        request_uri = (
+            stored_cfg.get_request_uri(params['ARCHIVE_CACHE_DIR']))
+        if not request_uri:
+            ui_.warn("There is no stored request URI for this archive.\n"
+                     "Please set one with the --uri option.\n")
+            raise util.Abort("No request URI.")
+
+    params['REQUEST_URI'] = request_uri
+    params['TO_DIR'] = os.getcwd()
+    execute_arc_pull(ui_,  params, stored_cfg)
+
+ILLEGAL_FOR_REINSERT = ('uri', 'aggressive', 'nosearch')
+def do_archive_reinsert(ui_, opts, params, stored_cfg):
+    """ fn-archive --reinsert."""
+    illegal = [value for value in ILLEGAL_FOR_REINSERT
+               if value in opts and opts[value]]
+    if illegal:
+        raise util.Abort("--uri, --aggressive, --nosearch illegal " +
+                         "for reinsert.")
+    request_uri = stored_cfg.get_request_uri(params['ARCHIVE_CACHE_DIR'])
+    if request_uri is None:
+        ui_.warn("There is no stored request URI for this archive.\n" +
+                 "Run fn-archive --pull first!.\n")
+        raise util.Abort(" No request URI, can't re-insert")
+
+    insert_uri = stored_cfg.get_dir_insert_uri(params['ARCHIVE_CACHE_DIR'])
+    params['REQUEST_URI'] = request_uri
+    params['INSERT_URI'] = insert_uri
+    params['FROM_DIR'] = os.getcwd() # hmmm not used.
+    params['REINSERT_LEVEL'] = 3
+    execute_arc_reinsert(ui_, params, stored_cfg)
+
+ARCHIVE_SUBCMDS = {'create':do_archive_create,
+                   'push':do_archive_push,
+                   'pull':do_archive_pull,
+                   'reinsert':do_archive_reinsert}
+ARCHIVE_CACHE_DIR = '.ARCHIVE_CACHE'
+def infocalypse_archive(ui_, **opts):
+    """ Commands to maintain a non-hg incremental archive."""
+    subcmd = [value for value in ARCHIVE_SUBCMDS if opts[value]]
+    if len(subcmd) > 1:
+        raise util.Abort("--create, --pull, --push are mutally exclusive. " +
+                         "Only specify one.")
+    if len(subcmd) > 0:
+        subcmd = subcmd[0]
+    else:
+        subcmd = "pull"
+
+    params, stored_cfg = get_config_info(ui_, opts)
+    params['ARCHIVE_CACHE_DIR'] = os.path.join(os.getcwd(), ARCHIVE_CACHE_DIR)
+
+    if not subcmd in ARCHIVE_SUBCMDS:
+        raise util.Abort("Unhandled subcommand: " + subcmd)
+
+    # 2 qt?
+    ARCHIVE_SUBCMDS[subcmd](ui_, opts, params, stored_cfg)
+
diff --git a/infocalypse/freenetrepo.py b/infocalypse/freenetrepo.py
new file mode 100644
--- /dev/null
+++ b/infocalypse/freenetrepo.py
@@ -0,0 +1,51 @@
+# infocalypse
+#
+# Copyright 2012 Arne Babenhauserheide <arne_bab at web dot de>,
+#    though most of this file is taken from hg-git from Scott Chacon
+#    <schacon at gmail dot com> also some code (and help) borrowed
+#    from durin42
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial import repo, util
+try:
+    from mercurial.error import RepoError
+except ImportError:
+    from mercurial.repo import RepoError
+
+class freenetrepo(repo.repository):
+    capabilities = ['lookup']
+
+    def __init__(self, ui, path, create):
+        if create: # pragma: no cover
+            raise util.Abort('Cannot create a freenet repository, yet.')
+        self.ui = ui
+        self.path = path
+
+    def lookup(self, key):
+        if isinstance(key, str):
+            return key
+
+    def local(self):
+        # a freenet repo is never local
+        raise RepoError
+
+    def heads(self):
+        return []
+
+    def listkeys(self, namespace):
+        return {}
+
+    def pushkey(self, namespace, key, old, new):
+        return False
+
+    # used by incoming in hg <= 1.6
+    def branches(self, nodes):
+        return []
+
+instance = freenetrepo
+
+def islocal(path):
+    u = util.url(path)
+    return not u.scheme or u.scheme == 'file'
diff --git a/infocalypse/graph.py b/infocalypse/graph.py
--- a/infocalypse/graph.py
+++ b/infocalypse/graph.py
@@ -52,7 +52,7 @@ FREENET_BLOCK_LEN = 32 * 1024
 # Hmmm... arbitrary.
 MAX_REDUNDANT_LENGTH = 128 * 1024
 
-# HACK: Approximate multi-level splitfile boundry
+# HACK: Approximate multi-level splitfile boundary
 MAX_METADATA_HACK_LEN = 7 * 1024 * 1024
 
 ############################################################
diff --git a/infocalypse/sitecmds.py b/infocalypse/sitecmds.py
--- a/infocalypse/sitecmds.py
+++ b/infocalypse/sitecmds.py
@@ -176,13 +176,19 @@ And this is what needs to go into fn-cre
 %s
 """
 
+def genkeypair(fcphost, fcpport):
+    """ Generate a keypair 
+
+    :returns: inserturi, requesturi (strings)
+    """
+    client = FCPClient.connect(fcphost, fcpport)
+    client.message_callback = lambda x, y : None # silence.
+    resp = client.generate_ssk()
+    return resp[1]['InsertURI'], resp[1]['RequestURI']
+
 def execute_genkey(ui_, params):
     """ Run the genkey command. """
-    client = FCPClient.connect(params['FCP_HOST'],
-                               params['FCP_PORT'])
-
-    client.message_callback = lambda x, y : None # silence.
-    resp = client.generate_ssk()
-    ui_.status(MSG_FMT % (resp[1]['InsertURI'], resp[1]['RequestURI'],
-                          resp[1]['InsertURI'].split('/')[0] +'/',
-                          "U" + resp[1]['InsertURI'].split('/')[0][1:] +'/NAME/0'))
+    insert, request = genkeypair(params['FCP_HOST'], params['FCP_PORT'])
+    ui_.status(MSG_FMT % (insert, request,
+                          insert.split('/')[0] +'/',
+                          "U" + insert.split('/')[0][1:] +'/NAME.R1/0'))