infocalypse

(djk)
2009-04-03: Added fn-copy command to copy from one repo URI to another.

Added fn-copy command to copy from one repo URI to another.

diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -121,7 +121,7 @@ import os
 from mercurial import commands, util
 
 from infcmds import get_config_info, execute_create, execute_pull, \
-     execute_push, execute_setup
+     execute_push, execute_setup, execute_copy
 
 def set_target_version(ui_, repo, opts, params, msg_fmt):
     """ INTERNAL: Update TARGET_VERSION in params. """
@@ -151,6 +151,27 @@ def infocalypse_create(ui_, repo, **opts
     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_pull(ui_, repo, **opts):
     """ Pull from an Infocalypse repository in Freenet.
      """
@@ -230,6 +251,11 @@ cmdtable = {
                    ('r', 'rev', [],'maximum rev to push'),]
                   + FCP_OPTS,
                 "[options]"),
+    "fn-copy": (infocalypse_copy,
+                [('', 'requesturi', '', 'request URI to copy from'),
+                 ('', 'inserturi', '', 'insert URI to copy to'), ]
+                + FCP_OPTS,
+                "[options]"),
 
     "fn-setup": (infocalypse_setup,
                  [('', 'tmpdir', '~/infocalypse_tmp', 'temp directory'),]
diff --git a/infocalypse/infcmds.py b/infocalypse/infcmds.py
--- a/infocalypse/infcmds.py
+++ b/infocalypse/infcmds.py
@@ -441,6 +441,32 @@ def execute_create(ui_, repo, params, st
     finally:
         cleanup(update_sm)
 
+# REDFLAG: LATER: make this work without a repo?
+def execute_copy(ui_, repo, params, stored_cfg):
+    """ Run the copy command. """
+    update_sm = None
+    try:
+        update_sm = setup(ui_, repo, params, stored_cfg)
+        handle_key_inversion(ui_, update_sm, params, stored_cfg)
+
+        ui_.status("%sInsert URI:\n%s\n" % (is_redundant(params['INSERT_URI']),
+                                            params['INSERT_URI']))
+        update_sm.start_copying(params['REQUEST_URI'],
+                                params['INSERT_URI'])
+
+        run_until_quiescent(update_sm, params['POLL_SECS'])
+
+        if update_sm.get_state(QUIESCENT).arrived_from(((FINISHING,))):
+            ui_.status("Copied to:\n%s\n" %
+                       '\n'.join(update_sm.get_state(INSERTING_URI).
+                                 get_request_uris()))
+        else:
+            ui_.status("Copy failed.\n")
+
+        handle_updating_config(repo, update_sm, params, stored_cfg)
+    finally:
+        cleanup(update_sm)
+
 # REDFLAG: move into fcpclient?
 #def usks_equal(usk_a, usk_b):
 #    assert is_usk(usk_a) and and is_usk(usk_b)
diff --git a/infocalypse/requestingbundles.py b/infocalypse/requestingbundles.py
--- a/infocalypse/requestingbundles.py
+++ b/infocalypse/requestingbundles.py
@@ -162,7 +162,7 @@ class RequestingBundles(RetryingRequestL
             else:
                 self._handle_failure(client, msg, candidate)
 
-        # Catch statemachine stalls.
+        # Catch state machine stalls.
         if (self.parent.current_state == self and
             self.is_stalled()):
             self.parent.transition(self.failure_state)
diff --git a/infocalypse/updatesm.py b/infocalypse/updatesm.py
--- a/infocalypse/updatesm.py
+++ b/infocalypse/updatesm.py
@@ -454,6 +454,15 @@ class InsertingGraph(StaticRequestList):
         StaticRequestList.reset(self)
         self.working_graph = None
 
+    def get_top_key_tuple(self):
+        """ Get the python rep of the data required to insert a new URI
+            with the updated graph CHK(s). """
+        graph = self.parent.ctx.graph
+        assert not graph is None
+        return ((self.get_result(0)[1]['URI'],
+                 self.get_result(1)[1]['URI']),
+                get_top_key_updates(graph))
+
 def get_top_key_updates(graph):
     """ Returns the update tuples needed to build the top key."""
 
@@ -499,16 +508,10 @@ class InsertingUri(StaticRequestList):
             This creates the binary rep for the top level key
             data and starts inserting it into Freenet.
         """
-        require_state(from_state, INSERTING_GRAPH)
+        if not hasattr(from_state, 'get_top_key_tuple'):
+            raise Exception("Illegal Transition from: %s" % from_state.name)
 
-        # Pull the graph CHKs out of the inserting
-        # graph state instance. REDFLAG: Hardcoded state name ok?
-        graph_insert = self.parent.get_state(INSERTING_GRAPH)
-        graph = self.parent.ctx.graph
-
-        top_key_tuple = ((graph_insert.get_result(0)[1]['URI'],
-                          graph_insert.get_result(1)[1]['URI']),
-                         get_top_key_updates(graph))
+        top_key_tuple = from_state.get_top_key_tuple()
 
         salt = {0:0x00, 1:0xff} # grrr.... less code.
         insert_uris = make_insert_uris(self.parent.ctx['INSERT_URI'])
@@ -716,6 +719,8 @@ class RequestingGraph(StaticRequestList)
         StaticRequestList.__init__(self, parent, name, success_state,
                                    failure_state)
 
+    # REDFLAG: remove this? why aren't I just calling get_top_key_tuple
+    # on REQUESTING_URI_4_INSERT???
     def get_top_key_tuple(self):
         """ Returns the Python rep of the data in the request uri. """
         results = [candidate[5] for candidate in
@@ -791,6 +796,7 @@ FINISHING = 'FINISHING'
 
 REQUESTING_URI = 'REQUESTING_URI'
 REQUESTING_BUNDLES = 'REQUESTING_BUNDLES'
+REQUESTING_URI_4_COPY = 'REQUESTING_URI_4_COPY'
 
 class UpdateStateMachine(RequestQueue, StateMachine):
     """ A StateMachine implementaion to create, push to and pull from
@@ -844,6 +850,14 @@ class UpdateStateMachine(RequestQueue, S
                                                  FAILING),
 
             FINISHING:CleaningUp(self, FINISHING, QUIESCENT),
+
+
+            # Copying.
+            # This doesn't verify that the graph chk(s) are fetchable.
+            REQUESTING_URI_4_COPY:RequestingUri(self, REQUESTING_URI_4_COPY,
+                                                INSERTING_URI,
+                                                FAILING),
+
             }
 
         self.current_state = self.get_state(QUIESCENT)
@@ -915,6 +929,22 @@ class UpdateStateMachine(RequestQueue, S
         self.ctx['REQUEST_URI'] = request_uri
         self.transition(REQUESTING_URI)
 
+
+    def start_copying(self, from_uri, to_insert_uri):
+        """ Start pulling changes from an Infocalypse repository URI
+            in Freenet into the local hg repository. """
+        self.require_state(QUIESCENT)
+        self.reset()
+        self.ctx.graph = None
+
+        assert not from_uri is None
+        assert not to_insert_uri is None
+
+        self.ctx['REQUEST_URI'] = from_uri
+        self.ctx['INSERT_URI'] = to_insert_uri
+        self.ctx['IS_KEYPAIR'] = False
+        self.transition(REQUESTING_URI_4_COPY)
+
     # REDFLAG: SSK case untested
     def start_inverting(self, insert_uri):
         """ Start inverting a Freenet URI into it's analogous