#!/usr/bin/env python # encoding: utf-8 """ sugarbot.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 . """ import gobject import gtk import logging import os import sblog import socket import sys from xmlrpclib import ServerProxy from sugar.activity import activity #from sugar.activity import registry from jarabe.model.bundleregistry import get_registry from sbrpcserver import sbRpcServer, proxyString # For reading GDK.Event's from sbgui import sbGUI class sugarbot(activity.Activity): def __dynamicImport(self, fullname, path=None): """ Handles emulation of the target Activity. Dynamically modifies the module-search path, and imports an object from a module. Some fancy work is done to get that to work properly. @param fullname: The module to import, example calculate.Calculate. @param path: If necessary, the path to the imported module. """ module = None module_name = "" class_name = "" if fullname is None: return self.__parentClass try: # ========================================================= # This could probably be cleaned up or refactored into separate # functions. However, the code is short enough that it is not # likely worth it. if path != None: sys.path.append(path) splitted_module = fullname.rsplit('.', 1) if len(splitted_module) >= 2: module_name = splitted_module[0] class_name = splitted_module[1] if module_name is not None and class_name is not None and \ len(module_name) > 0 and len(class_name) > 0: module = __import__(module_name) for comp in module_name.split('.')[1:]: module = getattr(module, comp) # ======================================================== except ImportError: raise ImportError, \ "Error in sugarbot._dynamicImport(%s,%s)" % (fullname,path) finally: if hasattr(module, class_name): self.__parentClass = getattr(module, class_name) else: # Failsafe self.__parentClass = activity.Activity return self.__parentClass def __cloneActivity(self,sugarHandle): """ Starts the cloned activity, where self.__parentClass is the activity class. This is performed by [1] inheriting from the parentClass, and [2] calling parentClass.__init__. """ if self.__parentClass is None: raise AttributeError, "self.__parentClass not defined properly." else: sugarbot.__bases__ = (self.__parentClass,) self.__parentClass.__init__(self,sugarHandle) def __getActivityList(self): """ Gets a list of activities from the registry. Stores this list in self.__activityList. """ # Prevent looking up all of the activities multiple times. if not hasattr(self,"__activityList"): #self.__activityList = registry.get_registry().get_activities() self.__activityList = get_registry()._bundles return self.__activityList # If we didn't get any activities, something went wrong. if len(self.__activityList) < 1: raise "Activity list is empty. Cannot get activity info!" return None def __selectActivity(self,name): """ Selects an activity from the activityList by name. This allows simpler access to the list of activities. (For example, one can specify only 'Calculate' instead of 'org.laptop.Calculate' or 'calculate.Calculate'). @param name: The name of the activity (e.g. 'Calculate') """ # Get the list of activities if it does not already exist activityList = self.__getActivityList() # Initialization of some variables... self.__path = None self.__importClass = None self.__className = None # Iterate through the activity list for activity in activityList: if activity.get_name() == name: self.__path = activity.get_path() self.__importClass = activity.get_command().split()[-1] self.__className = self.__importClass.split(".")[-1] self.log.debug("Importing class %s" % name) return # If we ever get here, that means we didn't find anything... if (self.__path is None) and (self.__importClass is None) and \ (self.__className is None): raise NameError, "Could not find '%s' in activity list." % name def __initializeScript(self): """ Initialize the script on the XML-RPC server. Returns the name of the Activity that should be instantiated """ rpc = self.__xmlRPC try: self.sessID = os.environ['SUGARBOT_ID'] except KeyError: self.sessID = 0 # Start the script if not rpc.startScript(self.sessID): rpc.fail("Could not start the script!", self.sessID) exit() # Get our activity name activityName = rpc.getActivityName(self.sessID) if activityName is None: rpc.fail('Bad activity name provided', self.sessID) exit() self.log.info("Activity is %s" % activityName) return activityName def __init__(self, handle): """ Performs setup, and runs the specified activity. """ self.log = logging.getLogger('sugarbot') # Set up threading... gtk.gdk.threads_init() # Handle is set to 'None' for testing purposes via Nose. Obviously, # if Sugar sets the handle to None, there are other problems... if handle is None: return try: # Create the RPC connection object self.__xmlRPC = ServerProxy(proxyString()) # Set up the sbGUI object for automation self.__sbgui = sbGUI(self, self.__xmlRPC) # Get our activity name activityName = self.__initializeScript() # Actually clone the activity self.__selectActivity(activityName) self.__dynamicImport(self.__importClass,self.__path) self.__cloneActivity(handle) except socket.error: sys.log.error("====== COULD NOT CONNECT TO XML-RPC SERVER ======") sys.exit(-1)