(Steve Dougherty)
2013-08-14: Add initial new protocol implementation. Add initial new protocol implementation. Replies to VoidQuery with VoidResult. Also nicely disconnects on SIGINT.
diff --git a/infocalypse/plugin_connect.py b/infocalypse/plugin_connect.py --- a/infocalypse/plugin_connect.py +++ b/infocalypse/plugin_connect.py @@ -1,11 +1,12 @@ +from signal import signal, SIGINT +from time import sleep import fcp import threading from mercurial import util -from config import Config +import sys PLUGIN_NAME = "org.freenetproject.plugin.dvcs_webui.main.Plugin" - def connect(ui, repo): node = fcp.FCPNode() @@ -15,28 +16,47 @@ def connect(ui, repo): # TODO: Where to document the spec? devnotes.txt? How to format? hi_there = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, plugin_params={'Message': 'Hello', - 'GetRepoList': 'true'})[0] + 'VoidQuery': 'true'})[0] if hi_there['header'] == 'Error': raise util.Abort("The DVCS web UI plugin is not loaded.") if hi_there['Replies.Message'] == 'Error': + # TODO: Debugging + print hi_there raise util.Abort("Another VCS instance is already connected.") - print "Connected." - import sys - sys.exit() + session_token = hi_there['Replies.SessionToken'] + + ui.status("Connected.\n") + + def disconnect(signum, frame): + ui.status("Disconnecting.\n") + node.fcpPluginMessage(plugin_name=PLUGIN_NAME, + plugin_params= + {'Message': 'Disconnect', + 'SessionToken': session_token}) + sys.exit() + + # Send Disconnect on interrupt instead of waiting on timeout. + signal(SIGINT, disconnect) def ping(): - pong = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, - plugin_params={'Message': 'Ping'})[0] - if pong['Replies.Message'] == 'Error': - raise util.Abort(pong['Replies.Description']) - elif pong['Replies.Message'] != 'Pong': - ui.warn("Got unrecognized Ping reply '{0}'.\n".format(pong[ - 'Replies.Message'])) - # Must be faster than the timeout threshold. (5 seconds) - threading.Timer(4.0, ping).start() + # Loop with delay. + while True: + pong = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, + plugin_params= + {'Message': 'Ping', + 'SessionToken': session_token})[0] + if pong['Replies.Message'] == 'Error': + raise util.Abort(pong['Replies.Description']) + elif pong['Replies.Message'] != 'Pong': + ui.warn("Got unrecognized Ping reply '{0}'.\n".format(pong[ + 'Replies.Message'])) + + # Wait for less than timeout threshold. In testing responses take + # a little over a second. + sleep(3.5) # Start self-perpetuating pinging in the background. t = threading.Timer(0.0, ping) @@ -46,35 +66,47 @@ def connect(ui, repo): t.start() while True: - sequenceID = node._getUniqueId() + query_identifier = node._getUniqueId() # The event-querying is single-threaded, which makes things slow as # everything waits on the completion of the current operation. # Asynchronous code would require changes on the plugin side but # potentially have much lower latency. + # TODO: Can wrap away PLUGIN_NAME, SessionToken, and QueryIdentifier? command = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, plugin_params= - {'Message': 'ClearToSend', - 'SequenceID': sequenceID})[0] - # TODO: Look up handlers in a dictionary. - print command - - # Reload the config each time - it may have changed between messages. - cfg = Config.from_ui(ui) + {'Message': 'Ready', + 'SessionToken': session_token, + 'QueryIdentifier': query_identifier})[0] response = command['Replies.Message'] if response == 'Error': raise util.Abort(command['Replies.Description']) - elif response == 'ListLocalRepos': - params = {'Message': 'RepoList', - 'SequenceID': sequenceID} - # Request USKs are keyed by repo path. - repo_index = 0 - for path in cfg.request_usks.iterkeys(): - params['Repo%s' % repo_index] = path - repo_index += 1 + if response not in handlers: + raise util.Abort("Unsupported query '{0}'\n") - ack = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, id=fcp_id, - plugin_params=params)[0] - print ack + # Handlers are indexed by the query message name, take the query + # message, and return (result_name, plugin_params). + result_name, plugin_params = handlers[response](command) + plugin_params['Message'] = result_name + plugin_params['QueryIdentifier'] = query_identifier + plugin_params['SessionToken'] = session_token + + ack = node.fcpPluginMessage(plugin_name=PLUGIN_NAME, + plugin_params=plugin_params)[0] + + if ack['Replies.Message'] != "Ack": + raise util.Abort("Received unexpected message instead of result " + "acknowledgement:\n{0}\n".format(ack)) + + +# Handlers return two items: result message name, message-specific parameters. +# The sending code handles the plugin name, required parameters and plugin name. + + +def VoidQuery(query): + return "VoidResult", {} + +# TODO: Perhaps look up method by name directly? +handlers = {'VoidQuery': VoidQuery}