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'))