Added fn-putsite command to insert freesites.
diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -197,12 +197,52 @@ fms can have pretty high latency. Be pat
take hours (sometimes a day!) for your notification
to appear. Don't send lots of redundant notifications.
+FREESITE INSERTION:
+hg fn-putsite --index <n>
+
+inserts a freesite based on the configuration in
+the freesite.cfg file in the root of the repository.
+
+Use:
+hg fn-putsite --createconfig
+
+to create a basic freesite.cfg file that you
+can modify. Look at the comments in it for an
+explanation of the supported parameters.
+
+The default freesite.cfg file inserts using the
+same private key as the repo and a site name
+of 'default'. Editing the name is highly
+recommended.
+
+You can use --key CHK@ to insert a test version of
+the site to a CHK key before writing to the USK.
+
+Limitations:
+o You MUST have fn-pushed the repo at least once
+ in order to insert using the repo's private key.
+ If you haven't fn-push'd you'll see this error:
+ "You don't have the insert URI for this repo.
+ Supply a private key with --key or fn-push the repo."
+o Inserts *all* files in the site_dir directory in
+ the freesite.cfg file. Run with --dryrun to make
+ sure that you aren't going to insert stuff you don't
+ want too.
+o You must manually specify the USK edition you want
+ to insert on. You will get a collision error
+ if you specify an index that was already inserted.
+o Don't use this for big sites. It should be fine
+ for notes on your project. If you have lots of images
+ or big binary files use a tool like jSite instead.
+o Don't modify site files while the fn-putsite is
+ running.
+
HINTS:
The -q, -v and --debug verbosity options are
supported.
Top level URIs ending in '.R1' are inserted redundantly.
-Don't use this if you are worried about correlation
+Don't use this if you're worried about correlation
attacks.
If you see 'abort: Connection refused' when you run
@@ -253,6 +293,8 @@ from infcmds import get_config_info, exe
from fmscmds import execute_fmsread, execute_fmsnotify
+from sitecmds import read_freesite_cfg, execute_putsite, execute_genkey
+
def set_target_version(ui_, repo, opts, params, msg_fmt):
""" INTERNAL: Update TARGET_VERSION in params. """
@@ -437,6 +479,56 @@ def infocalypse_fmsnotify(ui_, repo, **o
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']:
+ 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@.")
+ 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_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. """
@@ -511,6 +603,18 @@ cmdtable = {
+ FCP_OPTS, # Needs to invert the insert uri
"[options]"),
+ "fn-putsite": (infocalypse_putsite,
+ [('', 'dryrun', None, "don't insert site"),
+ ('', 'index', -1, "edition to insert"),
+ ('', 'createconfig', None, "create default freesite.cfg"),
+ ('', 'key', '', "private SSK to insert under"),]
+ + FCP_OPTS,
+ "[options]"),
+
+ "fn-genkey": (infocalypse_genkey,
+ FCP_OPTS,
+ "[options]"),
+
"fn-setup": (infocalypse_setup,
[('', 'tmpdir', '~/infocalypse_tmp', 'temp directory'),]
+ FCP_OPTS,
@@ -519,4 +623,5 @@ cmdtable = {
commands.norepo += ' fn-setup'
+commands.norepo += ' fn-genkey'
diff --git a/infocalypse/sitecmds.py b/infocalypse/sitecmds.py
new file mode 100644
--- /dev/null
+++ b/infocalypse/sitecmds.py
@@ -0,0 +1,209 @@
+""" Implementation of commands to insert freesites.
+
+ Copyright (C) 2009 Darrell Karbott
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2.0 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
+"""
+
+
+import os
+
+from ConfigParser import ConfigParser
+
+from mercurial import util
+
+from fcpconnection import FCPError
+from fcpclient import FCPClient, get_file_infos, set_index_file
+
+def write_default_config(ui_, repo):
+ """ Write a default freesite.cfg file into the repository root dir. """
+ file_name = os.path.join(repo.root, 'freesite.cfg')
+
+ if os.path.exists(file_name):
+ raise util.Abort("Already exists: %s" % file_name)
+
+ out_file = open(file_name, 'w')
+ try:
+ out_file.write("""[default]
+# Human readable site name.
+site_name = default
+# Directory to insert from relative to the repository root.
+site_dir = site_root
+# Optional external file to load the site key from, relative
+# to the directory your .infocalypse/infocalypse.ini file
+# is stored in. This file should contain ONLY the SSK insert
+# key up to the first slash.
+#
+# If this value is not set the insert SSK for the repo is
+# used.
+#site_key_file = example_freesite_key.txt
+#
+# Optional file to display by default. If this is not
+# set index.html is used.
+#default_file = index.html
+""")
+ finally:
+ out_file.close()
+
+ ui_.status('Created config file:\n%s\n' % file_name)
+ ui_.status('You probably want to edit at least the site_name.\n')
+
+def read_freesite_cfg(ui_, repo, params, stored_cfg):
+ """ Read param out of the freesite.cfg file. """
+ cfg_file = os.path.join(repo.root, 'freesite.cfg')
+
+ ui_.status('Using config file:\n%s\n' % cfg_file)
+ if not os.path.exists(cfg_file):
+ ui_.warn("Can't read: %s\n" % cfg_file)
+ raise util.Abort("Use --createconfig to create freesite.cfg")
+
+ parser = ConfigParser()
+ parser.read(cfg_file)
+ if not parser.has_section('default'):
+ raise util.Abort("Can't read default section of config file?")
+
+ params['SITE_NAME'] = parser.get('default', 'site_name')
+ params['SITE_DIR'] = parser.get('default', 'site_dir')
+ if parser.has_option('default','default_file'):
+ params['SITE_DEFAULT_FILE'] = parser.get('default', 'default_file')
+ else:
+ params['SITE_DEFAULT_FILE'] = 'index.html'
+
+ if params.get('SITE_KEY'):
+ return # key set on command line
+
+ if not parser.has_option('default','site_key_file'):
+ params['SITE_KEY'] = ''
+ return # Will use the insert SSK for the repo.
+
+ key_file = parser.get('default', 'site_key_file', 'default')
+ if key_file == 'default':
+ ui_.status('Using repo insert key as site key.\n')
+ params['SITE_KEY'] = 'default'
+ return # Use the insert SSK for the repo.
+ try:
+ # Read private key from specified key file relative
+ # to the directory the .infocalypse config file is stored in.
+ key_file = os.path.join(os.path.dirname(stored_cfg.file_name),
+ key_file)
+ ui_.status('Reading site key from:\n%s\n' % key_file)
+ params['SITE_KEY'] = open(key_file, 'rb').read().strip()
+ except IOError:
+ raise util.Abort("Couldn't read site key from: %s" % key_file)
+
+ if not params['SITE_KEY'].startswith('SSK@'):
+ raise util.Abort("Stored site key not an SSK?")
+
+def get_insert_uri(params):
+ """ Helper function builds the insert URI. """
+ if params['SITE_KEY'] == 'CHK@':
+ return 'CHK@/'
+ return '%s/%s-%i/' % (params['SITE_KEY'],
+ params['SITE_NAME'], params['SITE_INDEX'])
+
+# Convert SSK to USK so n00b5 don't phr34k out.
+def show_request_uri(ui_, params, uri):
+ """ Helper function to print the request URI."""
+ if uri.startswith('SSK@'):
+ request_uri = 'U%s/%s/%i/' % (uri.split('/')[0][1:],
+ params['SITE_NAME'],
+ params['SITE_INDEX'])
+ else:
+ request_uri = uri
+ ui_.status('RequestURI:\n%s\n' % request_uri)
+
+def execute_putsite(ui_, repo, params):
+ """ Run the putsite command. """
+ def progress(dummy, msg):
+ """ Message callback which writes to the hg ui instance."""
+
+ if msg[0] == 'SimpleProgress':
+ ui_.status("Progress: (%s/%s/%s)\n" % (msg[1]['Succeeded'],
+ msg[1]['Required'],
+ msg[1]['Total']))
+ else:
+ ui_.status("Progress: %s\n" % msg[0])
+
+
+ if params.get('SITE_CREATE_CONFIG', False):
+ write_default_config(ui_, repo)
+ return
+
+ # Remove trailing /
+ params['SITE_KEY'] = params['SITE_KEY'].split('/')[0].strip()
+ insert_uri = get_insert_uri(params)
+ site_root = os.path.join(repo.root, params['SITE_DIR'])
+
+ ui_.status('Default file: %s\n' % params['SITE_DEFAULT_FILE'])
+ ui_.status('Reading files from:\n%s\n' % site_root)
+
+ infos = get_file_infos(site_root)
+
+ try:
+ set_index_file(infos, params['SITE_DEFAULT_FILE'])
+ except ValueError:
+ raise util.Abort("Couldn't read %s" % params['SITE_DEFAULT_FILE'])
+
+ ui_.status('--- files ---\n')
+
+ for info in infos:
+ ui_.status('%s %s\n' % (info[0], info[1]))
+ ui_.status('---\n')
+
+ if params['DRYRUN']:
+ ui_.status('Would have inserted to:\n%s\n' % insert_uri)
+ ui_.status('But --dryrun was set.')
+ return
+
+ client = FCPClient.connect(params['FCP_HOST'],
+ params['FCP_PORT'])
+ client.in_params.default_fcp_params['DontCompress'] = False
+ client.message_callback = progress
+ try:
+ ui_.status('Inserting to:\n%s\n' % insert_uri)
+ try:
+ request_uri = client.put_complex_dir(insert_uri, infos)[1]['URI']
+ show_request_uri(ui_, params, request_uri)
+ except FCPError, err:
+ if err.is_code(9): # magick number for collision
+ ui_.warn('An update was already inserted on that index.\n'
+ + 'Set a later index with --index and try again.\n')
+ raise util.Abort("Key collision.")
+ else:
+ ui_.warn(str(err) + '\n')
+ raise util.Abort("FCP Error")
+ finally:
+ client.close()
+
+MSG_FMT = """InsertURI:
+%s
+RequestURI:
+%s
+
+This is what you need to put in a site_key_file file:
+%s
+"""
+
+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] +'/'))