infocalypse
 
(Steve Dougherty)
2013-06-07: Add initial WoT support to fn-pull.

Add initial WoT support to fn-pull. * Refactor WoT module to allow functions for parsing responses. * WoT not currently supported, but LCWoT is. * See https://bugs.freenetproject.org/view.php?id=5729

diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -510,6 +510,43 @@ def infocalypse_pull(ui_, repo, **opts):
         params['FMSREAD_HASH'] = opts['hash'][0]
         params['FMSREAD_ONLYTRUSTED'] = bool(opts['onlytrusted'])
         request_uri = get_uri_from_hash(ui_, repo, params, stored_cfg)
+    elif opts['wot']:
+        import wot
+        truster = stored_cfg.get_wot_identity(
+            stored_cfg.get_dir_insert_uri(repo.root))
+        # TODO: Require repo name, not full path - look it up from the XML.
+
+        # Expecting <id stuff>/reponame.R1/edition
+        wot_id, repo_path = opts['wot'].split('/', 1)
+
+        nickname_prefix = ''
+        key_prefix=''
+        # Could be nick@key, nick, @key
+        split = wot_id.split('@')
+        nickname_prefix = split[0]
+
+        if len(split) == 2:
+            key_prefix = split[1]
+
+        attributes = wot.resolve_identity(ui_,
+                                          truster=truster,
+                                          nickname_prefix=nickname_prefix,
+                                          key_prefix=key_prefix)
+        if attributes is None:
+            return
+
+        # Expecting [freenet:]?USK@key/WebOfTrust/edition
+        request_uri = attributes['RequestURI']
+        # See similar note in fn-create: trim off LCWoT URI prefix.
+        # TODO: Semantically meaningful key classes.
+        prefix = "freenet:"
+        if request_uri.startswith(prefix):
+            request_uri = request_uri[len(prefix):]
+
+        request_uri = request_uri.split('/', 1)[0]
+
+        request_uri = request_uri + '/' + repo_path
+
     else:
         request_uri = opts['uri']
 
diff --git a/infocalypse/config.py b/infocalypse/config.py
--- a/infocalypse/config.py
+++ b/infocalypse/config.py
@@ -201,6 +201,13 @@ class Config:
         """
         self.wot_identities[normalize(for_usk_or_id)] = wot_identity
 
+    def get_wot_identity(self, for_usk_or_id):
+        """
+        Return the WoT identity associated with the request USK,
+        or the default if none is set.
+        """
+        return self.wot_identities[normalize(for_usk_or_id)]
+
     # Hmmm... really nescessary?
     def get_dir_insert_uri(self, repo_dir):
         """ Return the insert USK for repo_dir or None. """
diff --git a/infocalypse/wot.py b/infocalypse/wot.py
--- a/infocalypse/wot.py
+++ b/infocalypse/wot.py
@@ -51,10 +51,93 @@ def resolve_local_identity(ui, nickname_
         ui.warn("No nicknames start with '{0}'.\n".format(nickname_prefix))
         return
 
+    return read_local_identity(response, id_num)
+
+def resolve_identity(ui, truster, nickname_prefix=None, key_prefix=''):
+    """
+    If using LCWoT, either the nickname prefix should be enough to be
+    unambiguous, or failing that enough of the key.
+    If using WoT, partial search is not supported, and the entire key must be
+    specified.
+
+    Returns a dictionary of the nickname, request URI,
+    and identity that matches the given criteria.
+    In the case of an error prints a message and returns None.
+
+    :param ui: Mercurial ui for error messages.
+    :param truster: Check trust list of this local identity.
+    :param nickname_prefix: Partial (prefix) of nickname. Can be whole.
+    :param key_prefix: Partial (prefix) of key. Can be empty.
+    """
+    # TODO: Support different FCP IP / port.
+    node = fcp.FCPNode()
+
+    # Test for GetIdentitiesByPartialNickname support. currently LCWoT-only.
+    # https://github.com/tmarkus/LessCrappyWebOfTrust/blob/master/src/main/java/plugins/WebOfTrust/fcp/GetIdentitiesByPartialNickname.java
+    params = {'Message': 'GetIdentitiesByPartialNickname',
+              'Truster': truster,
+              'PartialNickname': nickname_prefix,
+              'PartialID': key_prefix,
+              'MaxIdentities': 1,  # Match must be unambiguous.
+              'Context': 'vcs'}
+    response =\
+        node.fcpPluginMessage(async=False,
+                              plugin_name="plugins.WebOfTrust.WebOfTrust",
+                              plugin_params=params)[0]
+
+    if response['header'] != 'FCPPluginReply' or\
+       'Replies.Message' not in response:
+            ui.warn('Unexpected reply. Got {0}\n'.format(response))
+            return
+    elif response['Replies.Message'] == 'Identities':
+        return read_identity(response, 0)
+    elif response['Replies.Message'] == 'Error':
+        # The difficulty here is that the message type is Error for both an
+        # unrecognized message type and ambiguous search terms.
+        # TODO: This seems likely to break - the Description seems intended
+        # for human readers and will probably change.
+        if response['Replies.Description'].startswith('Number of matched'):
+            # Supported version of LCWoT - terms ambiguous.
+            ui.warn("'{0}@{1}' is ambiguous.".format(nickname_prefix,
+                                                     key_prefix))
+            return
+        elif response['Replies.Description'].startswith('Unknown message') or\
+             response['Replies.Description'].startswith('Could not match'):
+            # Not supported; check for exact identity.
+            ui.warn('Searching by partial nickname/key not supported.')
+
+    # Attempt to search failed - check for exact key. Here key_prefix must be
+    # a complete key for the lookup to succeed.
+    params = {'Message': 'GetIdentity',
+              'Truster': truster,
+              'Identity': key_prefix}
+    response =\
+        node.fcpPluginMessage(async=False,
+                              plugin_name="plugins.WebOfTrust.WebOfTrust",
+                              plugin_params=params)[0]
+
+    # There should be only one result.
+    # Depends on https://bugs.freenetproject.org/view.php?id=5729
+    print read_identity(response, 0)
+
+def read_local_identity(message, id_num):
+    """
+    Reads an FCP response from a WoT plugin describing a local identity and
+    returns a dictionary of Nickname, InsertURI, RequestURI, and Identity.
+    """
+    result = read_identity(message, id_num)
+    result['InsertURI'] = message['Replies.InsertURI{0}'.format(id_num)]
+    return result
+
+def read_identity(message, id_num):
+    """
+    Reads an FCP response from a WoT plugin describing an identity and
+    returns a dictionary of Nickname, RequestURI, and Identity.
+    """
     # Return properties for the selected identity. (by number)
     result = {}
-    for item in [ 'Nickname', 'InsertURI','RequestURI', 'Identity' ]:
-        result[item] = response['Replies.{0}{1}'.format(item, id_num)]
+    for item in [ 'Nickname', 'RequestURI', 'Identity' ]:
+        result[item] = message['Replies.{0}{1}'.format(item, id_num)]
 
     # LCWoT also puts these things as properties, which would be nicer to
     # depend on and would allow just returning all properties for the identity.