infocalypse

(djk)
2010-02-07: Modified fn-setup to check FMS, added fn-setupfms command.

Modified fn-setup to check FMS, added fn-setupfms command. Features: Modified fn-setup to check the FMS configuration and update the config file. --fmshost, --fmsport, --fmsid set the host, port and fms id respectively. Added fn-setupfms command to test and update the FMS configuration.

diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -361,7 +361,8 @@ from infcmds import get_config_info, exe
      execute_push, execute_setup, execute_copy, execute_reinsert, \
      execute_info
 
-from fmscmds import execute_fmsread, execute_fmsnotify, get_uri_from_hash
+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
@@ -699,6 +700,16 @@ def infocalypse_setup(ui_, **opts):
                   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."""
@@ -795,6 +806,11 @@ FCP_OPTS = [('', 'fcphost', '', 'fcp hos
             ('', 'fcpport', 0, 'fcp port'),
 ]
 
+FMS_OPTS = [('', 'fmshost', '', 'fms host'),
+            ('', 'fmsport', 0, 'fms port'),
+]
+
+
 AGGRESSIVE_OPT = [('', 'aggressive', None, 'aggressively search for the '
                    + 'latest USK index'),]
 NOSEARCH_OPT = [('', 'nosearch', None, 'use USK version in URI'), ]
@@ -895,10 +911,19 @@ cmdtable = {
                   "[options]"),
 
     "fn-setup": (infocalypse_setup,
-                 [('', 'tmpdir', '~/infocalypse_tmp', 'temp directory'),]
-                 + FCP_OPTS,
+                 [('', 'tmpdir', '~/infocalypse_tmp', 'temp directory'),
+                  ('', 'nofms', None, 'skip FMS configuration'),
+                  ('', 'fmsid', '', "fmsid (only part before '@'!)"),
+                  ('', 'timeout', 30, "fms socket timeout in seconds")]
+                 + FCP_OPTS
+                 + FMS_OPTS,
                 "[options]"),
 
+    "fn-setupfms": (infocalypse_setupfms,
+                 [('', 'fmsid', '', "fmsid (only part before '@'!)"),
+                  ('', 'timeout', 30, "fms socket timeout in seconds"),]
+                 + FMS_OPTS,
+                "[options]"),
 
     "fn-archive": (infocalypse_archive,
                   [('', 'uri', '', 'Request URI for --pull, Insert URI ' +
@@ -915,10 +940,10 @@ cmdtable = {
                    + NOSEARCH_OPT
                    + AGGRESSIVE_OPT,
                    "[options]"),
-
     }
 
 
 commands.norepo += ' fn-setup'
+commands.norepo += ' fn-setupfms'
 commands.norepo += ' fn-genkey'
 commands.norepo += ' fn-archive'
diff --git a/infocalypse/config.py b/infocalypse/config.py
--- a/infocalypse/config.py
+++ b/infocalypse/config.py
@@ -81,6 +81,7 @@ def norm_path(dir_name):
     fixed = split[0].replace(':', '') + split[1]
     return fixed
 
+# REDFLAG: THis is an ancient hack.  Safe to back it out?
 # NOTE:
 # The bug prevents ConfigParser from even reading
 # the file. That's why I'm operating on the file
@@ -131,7 +132,7 @@ class Config:
         self.file_name = None
 
         # Use a dict instead of members to avoid pylint R0902.
-        self.defaults = {}
+        self.defaults = {} # REDFLAG: Why is this called defaults? BAD NAME
         self.defaults['HOST'] = '127.0.0.1'
         self.defaults['PORT'] = 9481
         self.defaults['TMP_DIR'] = None
diff --git a/infocalypse/devnotes.txt b/infocalypse/devnotes.txt
--- a/infocalypse/devnotes.txt
+++ b/infocalypse/devnotes.txt
@@ -1,8 +1,11 @@
 !!! experimental branch for testing wiki over hg idea !!!
 See (note updated uri)
-reenet:USK@Gq-FBhpgvr11VGpapG~y0rGFOAHVfzyW1WoKGwK-fFw,MpzFUh5Rmw6N~aMKwm9h2Uk~6aTRhYaY0shXVotgBUc,AQACAAE/fniki/2/
+reenet:USK@Gq-FBhpgvr11VGpapG~y0rGFOAHVfzyW1WoKGwK-fFw,MpzFUh5Rmw6N~aMKwm9h2Uk~6aTRhYaY0shXVotgBUc,AQACAAE/fniki/-22/
 !!!
 
+djk20100207
+BUG: fn-wiki will run without .infocalypse. audit other new commands, should abort cleanly.
+
 djk20100123
 Saw error reinserting fred staging mirror:
 {4}:008c3b951f:(102, 117, 0):PutSuccessful
diff --git a/infocalypse/fmscmds.py b/infocalypse/fmscmds.py
--- a/infocalypse/fmscmds.py
+++ b/infocalypse/fmscmds.py
@@ -21,6 +21,8 @@
 """
 
 # REDFLAG: Go back and fix all the places where you return instead of Abort()
+import socket
+
 from mercurial import util
 
 from fcpclient import get_usk_hash
@@ -416,3 +418,139 @@ def get_uri_from_hash(ui_, dummy, params
 
     return target_usk
 
+
+CRLF = '\x0d\x0a'
+FMS_TIMEOUT_SECS = 30
+FMS_SOCKET_ERR_MSG = """
+Socket level error.
+It looks like your FMS host or port might be wrong.
+Set them with --fmshost and/or --fmsport.
+"""
+
+def connect_to_fms(ui_, fms_host, fms_port, timeout):
+    """ INTERNAL: Helper, connects to fms and reads the login msg. """
+    ui_.status("Testing FMS connection [%s:%i]...\n" % (fms_host, fms_port))
+    try:
+        old_timeout = socket.getdefaulttimeout()
+        socket.setdefaulttimeout(timeout)
+        connected_socket = None
+        try:
+            connected_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            connected_socket.connect((fms_host, fms_port))
+            bytes =  ''
+            while bytes.find(CRLF) == -1:
+                bytes = bytes + connected_socket.recv(4096)
+        finally:
+            socket.setdefaulttimeout(old_timeout)
+            if connected_socket:
+                connected_socket.close()
+
+    except socket.error: # Not an IOError until 2.6.
+        ui_.warn(FMS_SOCKET_ERR_MSG)
+        return None
+
+    except IOError:
+        ui_.warn(FMS_SOCKET_ERR_MSG)
+        return None
+
+    return bytes
+
+
+# Passing opts instead of separate args to get around pylint
+# warning about long arg list.
+def get_fms_args(cfg, opts):
+    """ INTERNAL: Helper to extract args from Config/mercurial opts. """
+    def false_to_none(value):
+        """ INTERNAL: Return None if not bool(value),  value otherwise. """
+        if value:
+            return value
+        return None
+
+    fms_id = false_to_none(opts['fmsid'])
+    fms_host = false_to_none(opts['fmshost'])
+    fms_port = false_to_none(opts['fmsport'])
+    timeout = opts['timeout']
+
+    if not cfg is None:
+        if fms_id is None:
+            fms_id = cfg.defaults.get('FMS_ID', None)
+        if fms_host is None:
+            fms_host = cfg.defaults.get('FMS_HOST', None)
+        if fms_port is None:
+            fms_port = cfg.defaults.get('FMS_PORT', None)
+
+    if fms_id is None:
+        fms_id = 'None' # hmmm
+    if fms_host is None:
+        fms_host = '127.0.0.1'
+    if fms_port is None:
+        fms_port = 1119
+
+    return (fms_id, fms_host, fms_port, timeout)
+
+# DCI: clean up defaults
+def setup_fms_config(ui_, cfg, opts):
+    """ INTERNAL: helper tests the fms connection. """
+
+    fms_id, fms_host, fms_port, timeout = get_fms_args(cfg, opts)
+
+    ui_.status("Running FMS checks...\nChecking fms_id...\n")
+    if fms_id.find('@') != -1:
+        ui_.warn("\n")
+        ui_.warn("""   The FMS id should only contain the part before the '@'!
+   You won't be able to use fn-fmsnotify until this is fixed.
+   Run: hg fn-setupfms with the --fmsid argument.
+
+""")
+    elif fms_id.lower() == 'none':
+        ui_.warn("""   FMS id isn't set!
+   You won't be able to use fn-fmsnotify until this is fixed.
+   Run: hg fn-setupfms with the --fmsid argument.
+
+""")
+    else:
+        ui_.status("OK.\n\n") # hmmm... what if they manually edited the config?
+
+    bytes = connect_to_fms(ui_, fms_host, fms_port, timeout)
+    if not bytes:
+        if not bytes is None:
+            ui_.warn("Connected but no response. Are you sure that's "
+                     "an FMS server?\n")
+        return None
+
+    fields = bytes.split(' ')
+    if fields[0] != '200':
+        ui_.warn("Didn't get expected response from FMS server!\n")
+        return None
+
+    if not bytes.lower().find("posting allowed"):
+        ui_.warn("Didn't see expected 'posting allowed' message.\n")
+        ui_.warn("Check that FMS is setup to allow outgoing message.\n")
+        return None # Hmmm.. feeble, relying on message text.
+    else:
+        ui_.status("Got expected response from FMS. Looks good.\n")
+
+    return (fms_host, fms_port, fms_id)
+
+def execute_setupfms(ui_, opts):
+    """ Execute the fn-setupfms command. """
+    cfg = Config.from_ui(ui_)
+    result = setup_fms_config(ui_, cfg, opts)
+    if result:
+        cfg.defaults['FMS_ID'] = result[2]
+        cfg.defaults['FMS_HOST'] = result[0]
+        cfg.defaults['FMS_PORT'] = result[1]
+        ui_.status("""Updating config file:
+fms_id = %s
+fms_host = %s
+fms_port = %i
+""" % (result[2], result[0], result[1]))
+        Config.to_file(cfg)
+    else:
+        ui_.warn("""
+Run:
+   hg fn-setupfms
+with the appropriate arguments to try to fix the problem.
+
+""")
+
diff --git a/infocalypse/infcmds.py b/infocalypse/infcmds.py
--- a/infocalypse/infcmds.py
+++ b/infocalypse/infcmds.py
@@ -768,6 +768,7 @@ def setup_tmp_dir(ui_, tmp):
             ui_.warn(err)
     return tmp
 
+
 MSG_HGRC_SET = \
 """Read the config file name from the:
 
@@ -788,8 +789,13 @@ want to re-run setup.
 Consider before deleting it. It may contain
 the *only copy* of your private key.
 
+If you're just trying to update the FMS configuration run:
+
+hg fn-setupfms
+
+instead.
+
 """
-
 def execute_setup(ui_, host, port, tmp, cfg_file = None):
     """ Run the setup command. """
     def connection_failure(msg):
@@ -797,6 +803,7 @@ def execute_setup(ui_, host, port, tmp, 
         ui_.warn(msg)
         ui_.warn("It looks like your FCP host or port might be wrong.\n")
         ui_.warn("Set them with --fcphost and/or --fcpport and try again.\n")
+        raise util.Abort("Connection to FCP server failed.")
 
     # Fix defaults.
     if host == '':
@@ -820,8 +827,7 @@ def execute_setup(ui_, host, port, tmp, 
     tmp = setup_tmp_dir(ui_, tmp)
 
     if not is_writable(tmp):
-        ui_.warn("Can't write to temp dir: %s\n" % tmp)
-        return
+        raise util.Abort("Can't write to temp dir: %s\n" % tmp)
 
     # Test FCP connection.
     timeout_secs = 20
@@ -841,7 +847,6 @@ def execute_setup(ui_, host, port, tmp, 
         if not connection.is_connected():
             connection_failure(("\nGave up after waiting %i secs for an "
                                + "FCP NodeHello.\n") % timeout_secs)
-            return
 
         ui_.status("Looks good.\nGenerating a default private key...\n")
 
@@ -854,17 +859,14 @@ def execute_setup(ui_, host, port, tmp, 
     except FCPError:
         # Protocol error.
         connection_failure("\nMaybe that's not an FCP server?\n")
-        return
 
     except socket.error: # Not an IOError until 2.6.
         # Horked.
         connection_failure("\nSocket level error.\n")
-        return
 
     except IOError:
         # Horked.
         connection_failure("\nSocket level error.\n")
-        return
 
     cfg = Config()
     cfg.defaults['HOST'] = host
@@ -882,6 +884,8 @@ cfg file: %s
 Default private key:
 %s
 
+The config file was successfully written!
+
 """ % (host, port, tmp, cfg_file, default_private_key))