Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Experior.Activity/sbrpcserver.py
diff options
context:
space:
mode:
Diffstat (limited to 'Experior.Activity/sbrpcserver.py')
-rwxr-xr-xExperior.Activity/sbrpcserver.py419
1 files changed, 419 insertions, 0 deletions
diff --git a/Experior.Activity/sbrpcserver.py b/Experior.Activity/sbrpcserver.py
new file mode 100755
index 0000000..e521a18
--- /dev/null
+++ b/Experior.Activity/sbrpcserver.py
@@ -0,0 +1,419 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+sbrpcserver.py
+
+This file is part of sugarbot.
+
+sugarbot 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 3 of the License, or
+(at your option) any later version.
+
+sugarbot 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 sugarbot. If not, see <http://www.gnu.org/licenses/>.
+"""
+import binascii
+import logging
+import optparse
+import os
+import random
+import sblog
+import sys
+import time
+import xmlrpclib
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+try:
+ from sbconfig import host, port
+except ImportError:
+ from sbconfig_sample import host, port
+
+def proxyString():
+ return "http://%s:%s/" % (host, str(port))
+
+def rebuildMarshalledObject(obj, dictionary):
+ for key in dictionary:
+ setattr(obj, key, dictionary[key])
+
+class sugarbotSession():
+ def __init__(self,ID=0):
+ self.id = ID
+ # self.responses = []
+ self.responses = {}
+ self.currentScript = -1
+ self.scriptName = ""
+ self.failureText = {}
+ # self.log = logging.getLogger("%s" % ID)
+
+ def getSuccessValue(self):
+ retval = 0
+
+ failed = [self.responses[k] for k in self.responses if not self.responses[k]]
+ if failed:
+ retval = 1
+ return retval
+
+ log = property(lambda self: logging.getLogger(str(self.id)))
+
+class sbRpcServer(SimpleXMLRPCServer):
+ sugarbotActivityVar = 'sugarActivityName'
+
+ """
+ Sugarbot RPC Server
+
+ Responsible for listening on the XML-RPC port (defined as
+ sbconfig.port). The object loads the scripts provided
+ on the command-line as individual text lines. It then provides those
+ lines of text over XML-RPC to the XML-RPC client. The client is
+ responsible for parsing the lines into meaningful objects/functions.
+
+ The list of files, as well as each file's contents, are kept entirely in
+ memory. This may lead to issues in the future with particularly large
+ script files, but this is currently not a problem.
+ """
+ def __init__(self, args=[], xmlport=port, kill=False, restart=False):
+ # Random port?
+ if xmlport is None:
+ xmlport = random.randint(1024,65000)
+ self.port = xmlport
+
+ # Create the RPC server
+ SimpleXMLRPCServer.__init__(self, (host, xmlport),
+ allow_none=True, logRequests=False)
+
+ # Create the logger
+ self.log = self.initializeLogging()
+ self.log.info("Listening on port %i" % xmlport)
+
+ # A listing of the filenames for each script
+ self.listOfScripts = []
+
+ # Keep list of all of the clients
+ self.clients = {0: sugarbotSession(0)}
+
+ # Misc other stuffs...
+ self.kill = kill
+ self.restart = restart
+ self.log.info("Kill: %s\tRestart: %s" % (kill,restart))
+
+ # All of the arguments should be filenames for files to parse.
+ for arg in args:
+ self.addScript(arg)
+
+ # Register all of the functions so that callers can use them
+ self.registerFunctions()
+
+ # Initialize the random seed for generating client ID's
+ self.initRandomSeed()
+
+ def testConnectivity(self):
+ """
+ This function exists so that the client can test the connection to the
+ XML-RPC server. The xmlrpclib throws a socket.error if a function is
+ called when the ServerProxy object is not connected.
+ This provides a simple method so that we don't have to worry
+ about screwing with the internal state of other stuff.
+ """
+ pass
+
+ def initRandomSeed(self):
+ """
+ Initializes the random seed using os.urandom.
+ """
+ # Initialize the random seed...
+ seed = 0
+ try:
+ seed = long(binascii.hexlify(os.urandom(64)), 16)
+ except NotImplementedError:
+ seed = time.time()
+ random.seed(seed)
+
+ def generateSessionID(self):
+ """
+ In the event a client does not specify an identifier, provide one.
+ """
+ return random.randint(0, sys.maxint)
+
+ def initializeLogging(self):
+ """
+ Initialize logging for the sbRpcServer object. This provides logging
+ of INFO and above to the terminal, and all statements go to
+ a file named sbRpcServer.log.
+ """
+ if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG,
+ format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
+ datefmt='%m-%d %H:%M',
+ filename='sbRpcServer.log')
+
+ # define a Handler which writes INFO messages or higher to the sys.stderr
+ console = logging.StreamHandler()
+ console.setLevel(logging.INFO)
+ # set a format which is simpler for console use
+ formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
+ # tell the handler to use this format
+ console.setFormatter(formatter)
+ # add the handler to the root logger
+ logging.getLogger('').addHandler(console)
+
+ return logging.getLogger('sbRpcServer')
+
+ def registerFunctions(self):
+ """
+ Register the functions for use with the XML server.
+ Recommended internal use only.
+ """
+ self.register_function(self.addScript)
+ self.register_function(self.completionStatus)
+ self.register_function(self.fail)
+ self.register_function(self.generateSessionID)
+ self.register_function(self.getActivityName)
+ self.register_function(self.getKillFlag)
+ self.register_function(self.getRestartFlag)
+ self.register_function(self.getScript)
+ self.register_function(self.numberOfScripts)
+ self.register_function(self.resetClientState)
+ self.register_function(self.startScript)
+ self.register_function(self.success)
+ self.register_function(self.testConnectivity)
+
+ def numberOfScripts(self):
+ """
+ Returns the number of scripts for execution.
+ """
+ return len(self.listOfScripts)
+
+ def haveClient(self,ID):
+ """
+ Determine whether or not we have a given client in tracking.
+ """
+ return self.clients.has_key(ID)
+
+ def getClient(self, ID):
+ """
+ Returns the sugarbotSession object corresponding to the provided ID.
+
+ If such a sugarbotSession object does not exist, it is created.
+ """
+ if not self.haveClient(ID):
+ self.clients[ID] = sugarbotSession(ID)
+
+ return self.clients[ID]
+
+ def completionStatus(self,ID=0):
+ """
+ Returns the completion status of all of the scripts as a list.
+ For example, if there were a total of three scripts, and the second
+ script had a command that did not complete successfully, the return
+ value would be:
+ [True, False, True]
+
+ If no failures were reported:
+ [True, True, True]
+
+ All values are initialized to False. If a script is still running,
+ then at least one of the result-values will be False.
+
+ Removes the client from tracking. When the client re-connects, it
+ will effectively have a clean slate.
+ """
+ retval = []
+ if self.haveClient(ID):
+ retval = self.clients[ID]
+ self.resetClientState(ID)
+ else:
+ retval = None
+
+ return retval
+
+ def resetClientState(self, ID=0):
+ """
+ Clears a client from tracking.
+
+ When the client re-connects, it will start with a clean slate.
+ """
+ if self.haveClient(ID):
+ client = self.getClient(ID)
+
+ # Get a completed ratio
+ r = client.responses
+ successes = len([r[k] for k in r if r[k] is True])
+ completed = len(r)
+ overall = len(self.listOfScripts)
+ self.clients[ID].log.info("Disconnected [%s\%s\%s]" % \
+ (successes, completed, overall))
+ del self.clients[ID]
+
+ def scriptOutOfBounds(self,ID):
+ """
+ Checks to see if a client's script index is out-of-bounds.
+ """
+ client = self.getClient(ID)
+
+ if client.currentScript < 0:
+ return True
+
+ if client.currentScript >= len(self.listOfScripts):
+ return True
+
+ def success(self, ID=0):
+ """
+ This method is called after the successful completion of a script
+ """
+ client = self.getClient(ID)
+ client.log.info("Success (%s)" % client.scriptName)
+ client.responses[client.scriptName] = True
+
+ def modifyTraceBackText(self, text, ID=0):
+ """
+ Replaces '<string>' with the script name in a backtrace report.
+
+ This is useful in determining _which_ script failed execution.
+ """
+ client = self.getClient(ID)
+
+ if "File \"<string>\"" in text:
+ # Get the text..
+ replacementText = "Sugarbot Script: '%s'" % client.scriptName
+
+ # Replace the string with the filename
+ text = text.replace("<string>", replacementText)
+ return text
+
+ def fail(self, status="No status provided", ID=0):
+ """
+ This method is called if, for some reason, the client needs to disconnect
+ prematurely. A status message/reason is required.
+ """
+ client = self.getClient(ID)
+
+ # If the client is sending us exception text, replace that select
+ # portions of the text to be more useful.
+ status = self.modifyTraceBackText(status, ID)
+
+ # Print the error
+ client.log.error("%s" % status)
+ client.responses[client.scriptName] = False
+ client.failureText[client.scriptName] = status
+
+ return status
+
+ def startScript(self,ID=0):
+ """
+ Increments the internal script counter, looping back to the first
+ script if necessary. This should be the first method that a client
+ calls.
+ """
+ # Get the client. This will create it if it does not exist.
+ client = self.getClient(ID)
+
+ # Next script! (Note that the value is initialized to be -1)
+ client.currentScript += 1
+
+ # Prevent overflows if, for some reason, we loop back to script 0
+ if self.scriptOutOfBounds(ID):
+ client.currentScript = 0
+
+ # Update the client's script name
+ client.scriptName = self.listOfScripts[client.currentScript]
+
+ # Pretty status message...
+ client.log.info("Starting %s" % client.scriptName)
+
+ # Make room for another response (default to failure)
+ client.responses[client.scriptName] = False
+
+ return True
+
+ def getScript(self,ID=0):
+ """
+ Returns the script that the client should execute.
+ """
+ client = self.getClient(ID)
+ client.log.info("Getting Script: %s" % client.scriptName )
+
+ f = open(self.listOfScripts[client.currentScript])
+ listOfLines = f.readlines()
+ longString = ''
+ for line in listOfLines:
+ longString += line
+
+ f.close()
+
+ return longString
+
+ def getActivityName(self, ID=0, which=None):
+ """
+ Imports from the specified script file in order to
+ """
+ client = self.getClient(ID)
+ if which is None:
+ which = client.currentScript
+
+ if which == -1:
+ raise IndexError, "Invalid script index. If calling " + \
+ "getActivityNameMake without arguments, make sure that " + \
+ "startScript gets called first."
+
+ try:
+ moduleName = self.listOfScripts[which]
+ index = moduleName.find(".py")
+ if index is not -1:
+ moduleName = moduleName[:index]
+
+ execStr = 'from %s import %s' % \
+ (moduleName, self.sugarbotActivityVar)
+
+ evalStr = '%s' % self.sugarbotActivityVar
+
+ exec execStr
+ return eval(evalStr)
+ except:
+ raise
+
+ def getKillFlag(self):
+ return self.kill
+
+ def getRestartFlag(self):
+ return self.restart
+
+ def addScript(self,scriptPath):
+ """
+ Adds a script to the internal list of scripts. This causes the parser
+ to load the script contents into memory for fast/easy access.
+ """
+ self.listOfScripts.append(scriptPath)
+
+ activityName = self.getActivityName(which=len(self.listOfScripts)-1 )
+ self.log.info("Added script %s [Activity %s]" \
+ % (scriptPath, activityName) )
+
+
+def main(argv=None):
+ p = optparse.OptionParser()
+ p.add_option('--no-restart', dest='restart', action='store_false',default=True,
+ help='do NOT restart sugarbot after execution has completed')
+ p.add_option('--no-kill', dest='kill', action='store_false',default=True,
+ help='do NOT kill sugar after all sugarbot scripts have executed')
+
+ (options,args) = p.parse_args()
+
+ if len(args) < 1:
+ p.print_help()
+ return 1
+
+ server = sbRpcServer(args, kill=options.kill, restart=options.restart)
+
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ print 'Ctrl+C pressed, exiting...'
+
+if __name__ == '__main__':
+ main() \ No newline at end of file