示例#1
0
class Loader(YomboLibrary, object):
    """
    Responsible for loading libraries, and then delegating loading modules to
    the modules library.

    Libraries are never reloaded, however, during a reconfiguration,
    modules are unloaded, and then reloaded after configurations are done
    being downloaded.
    """
    @property
    def operation_mode(self):
        return self._operation_mode

    @operation_mode.setter
    def operation_mode(self, val):
        self.loadedLibraries['atoms']['loader.operation_mode'] = val
        self._operation_mode = val

    def __getitem__(self, component_requested):
        """
        """
        logger.debug("looking for: {component_requested}", component_requested=component_requested)
        if component_requested in self.loadedComponents:
            logger.debug("found by loadedComponents! {component_requested}", component_requested=component_requested)
            return self.loadedComponents[component_requested]
        elif component_requested in self.loadedLibraries:
            logger.debug("found by loadedLibraries! {component_requested}", component_requested=component_requested)
            return self.loadedLibraries[component_requested]
        elif component_requested in self._moduleLibrary:
            logger.debug("found by self._moduleLibrary! {component_requested}", component_requested=self._moduleLibrary)
            return self._moduleLibrary[component_requested]
        else:
            raise YomboWarning("Loader could not find requested component: {%s}"
                               % component_requested, '101', '__getitem__', 'loader')

    def __init__(self, testing=False):
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self}, .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self.__localModuleVars = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {}  # Store a list of hooks that exist or not. A cache.
        self._operation_mode = None  # One of: firstrun, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict()  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)
        # signal(SIGINT, self.shutdown2)

    def shutdown(self):
        """
        This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
        :return:
        """
        print "SIGINT received - twisted"
        self.sigint = True

    # def shutdown2(self, signum, frame):
    #     """
    #     This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
    #     :return:
    #     """
    #     print 'Signal handler called with signal %s' % signum
    #     print "WHAT!  I was called - signal"
    #     self.sigint = True
    #     reactor.stop()

    @inlineCallbacks
    def start(self):  #on startup, load libraried, then modules
        """
        This is effectively the main start function.

        This function is called when the gateway is to startup. In turn,
        this function will load all the components and modules of the gateway.
        """
        logger.info("Importing libraries, this can take a few moments.")
        yield self.import_libraries() # import and init all libraries
        # returnValue(None)

        # if self.sigint:
        #     return
        logger.debug("Calling load functions of libraries.")
        for name, config in HARD_LOAD.iteritems():
#            print "sigint: %s" % self.sigint
#             if self.sigint:
#                 return
            self._log_loader('debug', name, 'library', 'load', 'About to call _load_.')
            if self.check_operation_mode(config['operation_mode']):
                HARD_LOAD[name]['_load_'] = 'Starting'
                libraryName = name.lower()
                yield self.library_invoke(libraryName, "_load_", self)
                HARD_LOAD[name]['_load_'] = True
            else:
                HARD_LOAD[name]['_load_'] = False
            self._log_loader('debug', name, 'library', 'load', 'Finished call to _load_.')

        self._moduleLibrary = self.loadedLibraries['modules']
#        logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1Calling start function of libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'start', 'About to call _start_.')
            if self.check_operation_mode(config['operation_mode']):
                libraryName =  name.lower()
                yield self.library_invoke(libraryName, "_start_", self)
                HARD_LOAD[name]['_start_'] = True
            else:
                HARD_LOAD[name]['_start_'] = False

        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'started', 'About to call _started_.')
            if self.check_operation_mode(config['operation_mode']):
                libraryName =  name.lower()
                yield self.library_invoke(libraryName, "_started_", self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        yield self._moduleLibrary.load_modules()
        self.loadedLibraries['notifications'].add({'title': 'System started',
            'message': 'System successfully started.', 'timeout': 300, 'source': 'Yombo Gateway System',
            'persist': False,
            'always_show': False,
        })

    @inlineCallbacks
    def unload(self):
        """
        Called when the gateway should stop. This will gracefully stop the gateway.

        First, unload all modules, then unload all components.
        """
        self.sigint = True  # it's 99.999% true - usually only shutdown due to this.
        if self._moduleLibrary is not None:
            yield self._moduleLibrary.unload_modules()
        yield self.unload_components()

    def Times_i18n_atoms(self, **kwargs):
       return [
           {'loader.operation_mode': {
               'en': 'One of: firstrun, run, config',
               },
           },
       ]

    def check_component_status(self, name, function):
        if name in HARD_LOAD:
            if function in HARD_LOAD[name]:
                return HARD_LOAD[name][function]
        return None

    def _log_loader(self, level, label, type, method, msg=""):
        """
        A common log format for loading/unloading libraries and modules.

        :param level: Log level - debug, info, warn...
        :param label: Module label "x10", "messages"
        :param type: Type of item being loaded: library, module
        :param method: Method being called.
        :param msg: Optional message to include.
        :return:
        """
        logit = func = getattr(logger, level)
        logit("Loader: {label}({type})::{method} - {msg}", label=label, type=type, method=method, msg=msg)

    @inlineCallbacks
    def import_libraries(self):
        """
        Import then "init" all libraries. Call "loadLibraries" when done.
        """
        logger.debug("Importing server libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            HARD_LOAD[name]['__init__'] = 'Starting'
            pathName = "yombo.lib.%s" % name
            self.import_component(pathName, name, 'library')
            HARD_LOAD[name]['__init__'] = True

        logger.debug("Calling init functions of libraries.")
        for name, config in HARD_LOAD.iteritems():
            if self.sigint:
                return
            if self.check_operation_mode(config['operation_mode']) is False:
                HARD_LOAD[name]['_init_'] = False
                continue
            HARD_LOAD[name]['_init_'] = 'Starting'
            self._log_loader('debug', name, 'library', 'init', 'About to call _init_.')

            component = name.lower()
            library = self.loadedLibraries[component]
            library._AMQP = self.loadedLibraries['amqp']
            library._Atoms = self.loadedLibraries['atoms']
            library._Commands = self.loadedLibraries['commands']
            library._Configs = self.loadedLibraries['configuration']
            library._Devices = self.loadedLibraries['devices']
            library._DeviceTypes = self.loadedLibraries['devicetypes']
            library._InputTypes = self.loadedLibraries['inputtypes']
            library._Libraries = self.loadedLibraries
            library._Loader = self
            library._Modules = self._moduleLibrary
            library._Notifications = self.loadedLibraries['notifications']
            library._Localize = self.loadedLibraries['localize']
            library._MQTT = self.loadedLibraries['mqtt']
            library._SQLDict = self.loadedLibraries['sqldict']
            library._States = self.loadedLibraries['states']
            library._Statistics = self.loadedLibraries['statistics']
            library._YomboAPI = self.loadedLibraries['yomboapi']
            library._Variables = self.loadedLibraries['variables']
            if hasattr(library, '_init_') and callable(library._init_) \
                    and yombo.utils.get_method_definition_level(library._init_) != 'yombo.core.module.YomboModule':
                d = yield maybeDeferred(library._init_)
                self._log_loader('debug', name, 'library', 'init', 'Finished to call _init_.')
                # try:
                #     d = yield maybeDeferred(library._init_, self)
                # except YomboCritical, e:
                #     logger.error("---==(Critical Server Error in init function for library: {name})==----", name=name)
                #     logger.error("--------------------------------------------------------")
                #     logger.error("Error message: {e}", e=e)
                #     logger.error("--------------------------------------------------------")
                #     e.exit()
                # except:
                #     logger.error("-------==(Error in init function for library: {name})==---------", name=name)
                #     logger.error("1:: {e}", e=sys.exc_info())
                #     logger.error("---------------==(Traceback)==--------------------------")
                #     logger.error("{e}", e=traceback.print_exc(file=sys.stdout))
                #     logger.error("--------------------------------------------------------")
                HARD_LOAD[name]['_init_'] = True
            else:
                logger.error("----==(Library doesn't have init function: {name})==-----", name=name)

    def check_operation_mode(self, allowed):
        """
        Checks if something should be run based on the current operation_mode.
        :param config: Either string or list or posible operation_modes
        :return: True/False
        """
        op_mode = self.operation_mode

        if op_mode is None:
            return True

        def check_operation_mode_inside(mode, op_mode):
            if mode == 'all':
                return True
            elif mode == op_mode:
                return True
            return False

        if isinstance(allowed, basestring):  # we have a string
            return check_operation_mode_inside(allowed, op_mode)
        else: # we have something else
            for item in allowed:
                if check_operation_mode_inside(item, op_mode):
                    return True

    @inlineCallbacks
    def library_invoke(self, requested_library, hook, called_by=None, **kwargs):
        """
        Invokes a hook for a a given library. Passes kwargs in, returns the results to caller.
        """
        if requested_library not in self.loadedLibraries:
            returnValue(None)
        if called_by is not None:
            called_by = called_by._FullName
        else:
            called_by = 'Unknown'

        cache_key = requested_library + hook
        if cache_key in self._invoke_list_cache:
            if self._invoke_list_cache[cache_key] is False:
                # logger.warn("Cache hook ({cache_key})...SKIPPED", cache_key=cache_key)
                returnValue(None) # skip. We already know function doesn't exist.
        library = self.loadedLibraries[requested_library]
        if requested_library == 'Loader':
            returnValue(None)
        if not (hook.startswith("_") and hook.endswith("_")):
            hook = library._Name.lower() + "_" + hook
        if hasattr(library, hook):
            method = getattr(library, hook)
            self._log_loader('debug', requested_library, 'library', 'library_invoke', 'About to call: %s' % hook)
            if callable(method):
                if library._Name not in self.hook_counts:
                    self.hook_counts[library._Name] = {}
                if hook not in self.hook_counts:
                    self.hook_counts[library._Name][hook] = {'Total Count': {'count': 0}}
                # print "hook counts: %s" % self.hook_counts
                # print "hook counts: %s" % self.hook_counts[library._Name][hook]
                if called_by not in self.hook_counts[library._Name][hook]:
                    self.hook_counts[library._Name][hook][called_by] = {'count': 0}
                self.hook_counts[library._Name][hook][called_by]['count'] = self.hook_counts[library._Name][hook][called_by]['count'] + 1
                self.hook_counts[library._Name][hook]['Total Count']['count'] = self.hook_counts[library._Name][hook]['Total Count']['count'] + 1
                self._invoke_list_cache[cache_key] = True
                results = yield maybeDeferred(method, **kwargs)
                self._log_loader('debug', requested_library, 'library', 'library_invoke', 'Finished with call: %s' % hook)
                returnValue(results)
            else:
                logger.warn("Cache library hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
                logger.debug("----==(Library {library} doesn't have a callable function: {function})==-----", library=library._FullName, function=hook)
                raise YomboWarning("Hook is not callable: %s" % hook)
        else:
#            logger.debug("Cache hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
            self._invoke_list_cache[cache_key] = False

    def library_invoke_all(self, hook, fullName=False, called_by=None, **kwargs):
        """
        Calls library_invoke for all loaded libraries.
        """
        results = {}
        to_process = {}
        if 'components' in kwargs:
            to_process = kwargs['components']
        else:
            for library_name, library in self.loadedLibraries.iteritems():
#                print "library %s" % library
                label = library._FullName.lower() if fullName else library._Name.lower()
                to_process[library_name] = label

        for library_name, library in self.loadedLibraries.iteritems():
            # logger.debug("invoke all:{libraryName} -> {hook}", libraryName=library_name, hook=hook )
            try:
                d = self.library_invoke(library_name, hook, called_by=called_by, **kwargs)
                if isinstance(d, Deferred):
                    result = getattr(d, 'result', None)
                    if result is not None:
#                      logger.warn("1111aaa:: {libraryName} {hook} {result}", libraryName=libraryName, hook=hook, result=result)
                      results[library._FullName] = result
            except YomboWarning:
                pass
            except YomboHookStopProcessing as e:
                e.collected = results
                e.by_who =  label
                raise

        return results

    def import_component(self, pathName, componentName, componentType, componentUUID=None):
        """
        Load component of given name. Can be a core library, or a module.
        """
        pymodulename = pathName.lower()
        self._log_loader('debug', componentName, componentType, 'import', 'About to import.')
        try:
            pyclassname = ReSearch("(?<=\.)([^.]+)$", pathName).group(1)
        except AttributeError:
            self._log_loader('error', componentName, componentType, 'import', 'Not found. Path: %s' % pathName)
            logger.error("Library or Module not found: {pathName}", pathName=pathName)
            raise YomboCritical("Library or Module not found: %s", pathName)
        try:
#            print "pymodulename: %s" % pymodulename
            module_root = __import__(pymodulename, globals(), locals(), [], 0)
            pass
        except ImportError as detail:
            self._log_loader('error', componentName, componentType, 'import', 'Not found. Path: %s' % pathName)
            logger.error("--------==(Error: Library or Module not found)==--------")
            logger.error("----Name: {pathName},  Details: {detail}", pathName=pathName, detail=detail)
            logger.error("--------------------------------------------------------")
            logger.error("{error}", error=sys.exc_info())
            logger.error("---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.print_exc(file=sys.stdout))
            logger.error("--------------------------------------------------------")
            return

        module_tail = reduce(lambda p1, p2: getattr(p1, p2), [module_root, ]+pymodulename.split('.')[1:])
        # print "module_tail: %s   pyclassname: %s" % (module_tail, pyclassname)
        klass = getattr(module_tail, pyclassname)
        # print "klass: %s  " % klass

        # Put the component into various lists for mgmt
        if not callable(klass):
            logger.warn("Unable to start class '{classname}', it's not callable.", classname=pyclassname)
            return

        try:
            # Instantiate the class
            moduleinst = klass()  # start the class, only libraries get the loader
            if componentType == 'library':
                if componentName.lower() == 'modules':
                    self._moduleLibrary = moduleinst

                self.loadedComponents["yombo.gateway.lib." + str(componentName.lower())] = moduleinst
                self.loadedLibraries[str(componentName.lower())] = moduleinst
                # this is mostly for manhole module, but maybe useful elsewhere?
                temp = componentName.split(".")
                self.libraryNames[temp[-1]] = moduleinst
            else:
                self.loadedComponents["yombo.gateway.modules." + str(componentName.lower())] = moduleinst
                self._moduleLibrary.add_imported_module(componentUUID, str(componentName.lower()), moduleinst)

        except YomboCritical, e:
            logger.debug("@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            logger.debug("{e}", e=e)
            logger.debug("@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            e.exit()
            raise
示例#2
0
class VoiceCmds(YomboLibrary):
    """
    Store all voice commands here and perfrom searches.

    The purpose of this class is two fold: it provides a single repository for
    all possible noun/verb combinations, and provides an ability to *not* have
    to be 100% accurate when looking up noun/verb pairs.

    Convert "voice_cmd" strings into voice commands.

    Also, provides searching for voice commands.
    """
    def __getitem__(self, voice_command_requested):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        return self.get(voice_command_requested)

    def __len__(self):
        return len(self.__yombocommands)

    def __contains__(self, command_requested):
        try:
            self.get_command(command_requested)
            return True
        except:
            return False


    def _init_(self):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Libraries['commands'].get_commands_by_voice()

    def _module_prestart_(self, **kwargs):
        """
        Implements the _module_prestart_ and is called after _load_ is called for all the modules.

        Expects a list of events to subscribe to.

        **Hooks called**:

        * _voicecmds_add_ : Expects a list of message subscription events to subscrib to.

        **Usage**:

        .. code-block:: python

           def ModuleName_voice_cmds_load(self, **kwargs):
               return ['status']
        """
        voicecommands_to_add = global_invoke_all('_voicecmds_add_')
#        logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add)

        for componentName, voice_cmds in voicecommands_to_add.iteritems():
            if voice_cmds is None:
                continue
            for list in voice_cmds:
                logger.debug("For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}", voice_cmd=list['voice_cmd'], fullName=componentName, order=list['order'])
                self.add_by_string(list['voice_cmd'], list['call_back'], list['device'], list['order'])

    def get_all(self):
        """
        Returns a list of voice commands.
        :return:
        """
        results = {}
        for voice, voice_id in self.voice_command_strings.iteritems():
            results[voice] = self.voice_command_data[voice_id]
        return results

    def get(self, voice_command_requested, limiter_override=None):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        try:
            result = self.voice_commands.search2(voice_command_requested, limiter_override=limiter_override)
            self._Statistics.increment("lib.voicecmds.search.found", bucket_time=30, anon=True)
            return self.voice_command_data[result]
        except:
            self._Statistics.increment("lib.voicecmds.search.not_found", bucket_time=30, anon=True)
            return None

    def add_by_string(self, voice_string, call_back = None, device = None, order = 'devicecmd'):
        """
        Adds a voice command by using a string like "desk lamp [on, off]". This will add the following voice commands
        based on the value of 'order'.  If both is provided, the following will be added:

        * desklamp on
        * desklamp off
        * off desklamp
        * on desklamp

        You can specify 'order' as: both, devicecmd, cmddevice. This determines what ordering the voice commands
        are added. In the above example, specifying 'devicecmd', then only the first two items are added.

        Either a callback function must be provided or a device must be provided. Otherwise, the voice command
        will not be added and a YomboWarning will be raised if either or both are defined.

        :raises YomboWarning: If voiceString or destination is invalid.
        :param voice_string: Voice command string to process: "desklamp [on, off]"
        :type voice_string: string
        :param call_back: A function to send the voice command id, device, and command objects to.
        :type call_back: pointer to function
        :param device: A device id or device label to search for a matching device.
        :type device: string
        :param order: The ordering in which to add voice command text lookup. Default: devicecmd
        :type order: string
        """
        logger.debug("Adding voice command: {voice_string}", voice_string=voice_string)
        if call_back is None and device is None:
            raise YomboWarning("'call_back' and 'device' are mising.", 1000, 'add_by_string', 'voicecmds')

        if call_back is not None and device is not None:
            raise YomboWarning("Either specifiy 'call_back' or 'device', not both.", 1001, 'add_by_string', 'voicecmds')

        try:
            tag_re = re.compile('(%s.*?%s)' % (re.escape('['), re.escape(']')))
            string_parts = tag_re.split(voice_string)
            string_parts = filter(None, string_parts) # remove empty bits

            device_obj = None
            if device is not None:
                if isclass(device):
                    device_obj = device
                else:
                    device_obj = self._Devices[device]

            commands = []
        except:
            raise YomboWarning("Invalid format for 'voice_string'", 1010, 'add_by_string', 'voicecmds')


        if len(string_parts) > 2:
            raise YomboWarning("Invalid format for 'voice_string'", 1003, 'add_by_string', 'voicecmds')

        for part in range(len(string_parts)):
            string_parts[part] = string_parts[part].strip()
            if string_parts[part].startswith("[") and string_parts[part].endswith("]"):
                temp_commands = string_parts[part][1:-1].split(',')
                for cmd in range(len(temp_commands)):
                    cmd_temp = temp_commands[cmd].strip()
                    if len(cmd_temp) > 0:
                        commands.append(cmd_temp)
            else:
                device_label = string_parts[part]

        if len(commands) == 0:
            raise YomboWarning("No commands found in voice_string.", 1003, 'add_by_string', 'voicecmds')

        # logger.debug("commands by voice: {commandsByVoice}:{verb}", commandsByVoice=self.commandsByVoice, verb=verb)
        for cmd in commands:
            if cmd not in self.commandsByVoice:
                continue
            command = self.commandsByVoice[cmd]
            vc_id = random_string(length=12)

            if order == 'both':
                self.voice_command_strings["%s %s" % (device_label, cmd)] = vc_id
                self.voice_command_strings["%s %s" % (cmd, device_label)] = vc_id

            elif order == 'devicecmd':
                self.voice_command_strings["%s %s" % (device_label, cmd)] = vc_id

            else:
                self.voice_command_strings["%s %s" % (cmd, device_label)] = vc_id

            self.voice_command_data[vc_id] = {
                'id': vc_id,
                'cmd': command,
                'device': device_obj,
                'call_back': call_back
            }