class BasePlugin(object): # pylint: disable=too-many-instance-attributes """ a base class for plugins """ def __init__(self, name, sname, modpath, basepath, fullimploc): # pylint: disable=too-many-arguments """ initialize the instance The following things should not be done in __init__ in a plugin Interacting with anything in the api except api.add, api.overload or dependency.add """ # Examples: # name : 'Actions' - from plugin file variable NAME (long name) # sname : 'actions' - from plugin file variable SNAME (short name) # modpath : '/client/actions.py' - path relative to the plugins directory # basepath : '/home/src/games/bastproxy/bp/plugins' - the full path to the # plugins directory # fullimploc : 'plugins.client.actions' - import location self.author = '' self.purpose = '' self.version = 0 self.priority = 100 self.name = name self.sname = sname self.dependencies = [] self.versionfuncs = {} self.reloaddependents = False self.summarytemplate = "%20s : %s" self.canreload = True self.canreset = True self.resetflag = True self.api = API() self.firstactiveprio = None self.loadedtime = time.time() self.savedir = os.path.join(self.api.BASEPATH, 'data', 'plugins', self.sname) try: os.makedirs(self.savedir) except OSError: pass self.savefile = os.path.join(self.api.BASEPATH, 'data', 'plugins', self.sname, 'settingvalues.txt') self.modpath = modpath self.basepath = basepath self.fullimploc = fullimploc self.pluginfile = os.path.join(basepath, modpath[1:]) self.pluginlocation = os.path.normpath( os.path.join(self.api.BASEPATH, 'plugins') + \ os.sep + os.path.dirname(self.modpath)) self.package = fullimploc.split('.')[1] self.settings = {} self.settingvalues = PersistentDictEvent(self, self.savefile, 'c') self._dump_shallow_attrs = ['api'] self.api.overload('dependency', 'add', self._api_dependencyadd) self.api.overload('setting', 'add', self._api_settingadd) self.api.overload('setting', 'gets', self._api_settinggets) self.api.overload('setting', 'change', self._api_settingchange) self.api.overload('api', 'add', self._api_add) def _loadcommands(self): """ load the commands """ parser = argp.ArgumentParser( add_help=False, formatter_class=argp.RawDescriptionHelpFormatter, description=textwrap.dedent(""" change a setting in the plugin if there are no arguments or 'list' is the first argument then it will list the settings for the plugin""")) parser.add_argument('name', help='the setting name', default='list', nargs='?') parser.add_argument('value', help='the new value of the setting', default='', nargs='?') self.api('commands.add')('set', self._cmd_set, parser=parser, group='Base', showinhistory=False) if self.canreset: parser = argp.ArgumentParser(add_help=False, description='reset the plugin') self.api('commands.add')('reset', self._cmd_reset, parser=parser, group='Base') parser = argp.ArgumentParser(add_help=False, description='save the plugin state') self.api('commands.add')('save', self._cmd_save, parser=parser, group='Base') parser = argp.ArgumentParser(add_help=False, description='show plugin stats') self.api('commands.add')('stats', self._cmd_stats, parser=parser, group='Base') parser = argp.ArgumentParser(add_help=False, description='inspect a plugin') parser.add_argument('-m', "--method", help="get code for a method", default='') parser.add_argument( '-o', "--object", help="show an object of the plugin, can be method or variable", default='') parser.add_argument('-s', "--simple", help="show a simple output", action="store_true") self.api('commands.add')('inspect', self._cmd_inspect, parser=parser, group='Base') parser = argp.ArgumentParser( add_help=False, description='show help info for this plugin') parser.add_argument('-a', "--api", help="show functions this plugin has in the api", action="store_true") parser.add_argument('-c', "--commands", help="show commands in this plugin", action="store_true") self.api('commands.add')('help', self._cmd_help, parser=parser, group='Base') parser = argp.ArgumentParser(add_help=False, description='list functions in the api') parser.add_argument('api', help='api to get details of', default='', nargs='?') self.api('commands.add')('api', self._cmd_api, parser=parser, group='Base') def load(self): """ load stuff, do most things here """ self.settingvalues.pload() if '_version' in self.settingvalues and \ self.settingvalues['_version'] != self.version: self._updateversion(self.settingvalues['_version'], self.version) self.api('log.adddtype')(self.sname) self._loadcommands() self.api('events.register')('%s_plugin_loaded' % self.sname, self.__afterload) self.api('events.register')('muddisconnect', self.__disconnect) self.api('events.register')('plugin_%s_savestate' % self.sname, self.__savestate) self.resetflag = False def _updateversion(self, oldversion, newversion): """ update plugin data """ if oldversion != newversion and newversion > oldversion: for i in range(oldversion + 1, newversion + 1): self.api('send.msg')('%s: upgrading to version %s' % (self.sname, i), secondary='upgrade') if i in self.versionfuncs: self.versionfuncs[i]() else: self.api('send.msg')( '%s: no function to upgrade to version %s' % (self.sname, i), secondary='upgrade') self.settingvalues.sync() def _cmd_inspect(self, args): # pylint: disable=too-many-branches """ show the plugin as it currently is in memory """ from libs.objectdump import dumps as dumper tmsg = [] if args['method']: try: tmeth = getattr(self, args['method']) tmsg.append(inspect.getsource(tmeth)) except AttributeError: tmsg.append('There is no method named %s' % args['method']) elif args['object']: tobj = args['object'] key = None if ':' in tobj: tobj, key = tobj.split(':') obj = getattr(self, tobj) if obj: if key: if key not in obj: try: key = int(key) except ValueError: pass if key in obj: obj = obj[key] if args['simple']: tvars = pprint.pformat(obj) else: tvars = dumper(obj) tmsg.append(tvars) else: if args['simple']: tvars = pprint.pformat(vars(self)) else: tvars = dumper(self) tmsg.append('@M' + '-' * 60 + '@x') tmsg.append('Variables') tmsg.append('@M' + '-' * 60 + '@x') tmsg.append(tvars) tmsg.append('@M' + '-' * 60 + '@x') tmsg.append('Methods') tmsg.append('@M' + '-' * 60 + '@x') tmsg.append( pprint.pformat(inspect.getmembers(self, inspect.ismethod))) return True, tmsg def _cmd_api(self, args): """ list functions in the api for a plugin """ tmsg = [] if args['api']: tmsg.extend( self.api('api.detail')("%s.%s" % (self.sname, args['api']))) else: apilist = self.api('api.list')(self.sname) if not apilist: tmsg.append('nothing in the api') else: tmsg.extend(apilist) return True, tmsg def __afterload(self, args): # pylint: disable=unused-argument """ do something after the load function is run """ # go through each variable and raise var_%s_changed self.settingvalues.raiseall() mud = self.api('managers.getm')('mud') if mud and mud.connected: if self.api('api.has')('connect.firstactive'): if self.api('connect.firstactive')(): self.afterfirstactive() else: self.api('events.register')('firstactive', self.afterfirstactive, prio=self.firstactiveprio) else: self.api('events.register')('firstactive', self.afterfirstactive, prio=self.firstactiveprio) def __disconnect(self, args=None): # pylint: disable=unused-argument """ re-register to firstactive on disconnect """ self.api('send.msg')('baseplugin, disconnect') self.api('events.register')('firstactive', self.afterfirstactive) def afterfirstactive(self, args=None): # pylint: disable=unused-argument """ if we are connected do """ self.api('send.msg')('baseplugin, firstactive') if self.api('events.isregistered')('firstactive', self.afterfirstactive): self.api('events.unregister')('firstactive', self.afterfirstactive) # get the vaule of a setting def _api_settinggets(self, setting): """ get the value of a setting @Ysetting@w = the setting value to get this function returns the value of the setting, None if not found""" try: return self.api('utils.verify')(self.settingvalues[setting], self.settings[setting]['stype']) except KeyError: return None # add a plugin dependency def _api_dependencyadd(self, dependency): """ add a depencency @Ydependency@w = the name of the plugin that will be a dependency this function returns no values""" if dependency not in self.dependencies: self.dependencies.append(dependency) # change the value of a setting def _api_settingchange(self, setting, value): """ change a setting @Ysetting@w = the name of the setting to change @Yvalue@w = the value to set it as this function returns True if the value was changed, False otherwise""" if value == 'default': value = self.settings[setting]['default'] if setting in self.settings: self.settingvalues[setting] = self.api('utils.verify')( value, self.settings[setting]['stype']) self.settingvalues.sync() return True return False def getstats(self): """ get the stats for the plugin """ stats = {} stats['Base Sizes'] = {} stats['Base Sizes']['showorder'] = ['Class', 'Variables', 'Api'] stats['Base Sizes']['Variables'] = '%s bytes' % \ sys.getsizeof(self.settingvalues) stats['Base Sizes']['Class'] = '%s bytes' % sys.getsizeof(self) stats['Base Sizes']['Api'] = '%s bytes' % sys.getsizeof(self.api) return stats def _cmd_stats(self, args=None): # pylint: disable=unused-argument """ @G%(name)s@w - @B%(cmdname)s@w show stats, memory, profile, etc.. for this plugin @CUsage@w: stats """ stats = self.getstats() tmsg = [] for header in stats: tmsg.append(self.api('utils.center')(header, '=', 60)) for subtype in stats[header]['showorder']: tmsg.append('%-20s : %s' % (subtype, stats[header][subtype])) return True, tmsg def unload(self, _=None): """ unload stuff """ self.api('send.msg')('unloading %s' % self.name) # remove anything out of the api self.api('api.remove')(self.sname) #save the state self.savestate() def __savestate(self, _=None): """ save the settings state """ self.settingvalues.sync() def savestate(self, _=None): """ save all settings for the plugin do not overload! attach to the plugin_<pluginname>_savestate event """ self.api('events.eraise')('plugin_%s_savestate' % self.sname) def _cmd_set(self, args): """ @G%(name)s@w - @B%(cmdname)s@w List or set vars @CUsage@w: var @Y<varname>@w @Y<varvalue>@w @Ysettingname@w = The setting to set @Ysettingvalue@w = The value to set it to if there are no arguments or 'list' is the first argument then it will list the settings for the plugin """ msg = [] if args['name'] == 'list': return True, self._listvars() elif args['name'] and args['value']: var = args['name'] val = args['value'] if var in self.settings: if 'readonly' in self.settings[var] \ and self.settings[var]['readonly']: return True, ['%s is a readonly setting' % var] else: try: self.api('setting.change')(var, val) tvar = self.settingvalues[var] if self.settings[var]['nocolor']: tvar = tvar.replace('@', '@@') elif self.settings[var]['stype'] == 'color': tvar = '%s%s@w' % (val, val.replace('@', '@@')) elif self.settings[var]['stype'] == 'timelength': tvar = self.api('utils.formattime')( self.api('utils.verify')(val, 'timelength')) return True, ['%s is now set to %s' % (var, tvar)] except ValueError: msg = ['Cannot convert %s to %s' % \ (val, self.settings[var]['stype'])] return True, msg return True, self._listvars() else: msg = ['plugin setting %s does not exist' % var] return False, msg def _cmd_save(self, args): # pylint: disable=unused-argument """ @G%(name)s@w - @B%(cmdname)s@w save plugin state @CUsage@w: save """ self.savestate() return True, ['Plugin settings saved'] def _cmd_help(self, args): """ test command """ msg = [] if '.__init__' in self.fullimploc: imploc = self.fullimploc.replace('.__init__', '') else: imploc = self.fullimploc msg.extend(sys.modules[imploc].__doc__.split('\n')) if args['commands']: cmdlist = self.api('commands.list')(self.sname) if cmdlist: msg.extend(self.api('commands.list')(self.sname)) msg.append('@G' + '-' * 60 + '@w') msg.append('') if args['api']: apilist = self.api('api.list')(self.sname) if apilist: msg.append('API functions in %s' % self.sname) msg.append('@G' + '-' * 60 + '@w') msg.extend(self.api('api.list')(self.sname)) return True, msg def _listvars(self): """ return a list of strings that list all settings """ tmsg = [] if not self.settingvalues: tmsg.append('There are no settings defined') else: tform = '%-15s : %-15s - %s' for i in self.settings: val = self.settingvalues[i] if 'nocolor' in self.settings[i] and self.settings[i][ 'nocolor']: val = val.replace('@', '@@') elif self.settings[i]['stype'] == 'color': val = '%s%s@w' % (val, val.replace('@', '@@')) elif self.settings[i]['stype'] == 'timelength': val = self.api('utils.formattime')( self.api('utils.verify')(val, 'timelength')) tmsg.append(tform % (i, val, self.settings[i]['help'])) return tmsg # add a setting to the plugin def _api_settingadd(self, name, default, stype, shelp, **kwargs): """ remove a command @Yname@w = the name of the setting @Ydefault@w = the default value of the setting @Ystype@w = the type of the setting @Yshelp@w = the help associated with the setting Keyword Arguments @Ynocolor@w = if True, don't parse colors when showing value @Yreadonly@w = if True, can't be changed by a client this function returns no values""" if 'nocolor' in kwargs: nocolor = kwargs['nocolor'] else: nocolor = False if 'readonly' in kwargs: readonly = kwargs['readonly'] else: readonly = False if name not in self.settingvalues: self.settingvalues[name] = default self.settings[name] = { 'default': default, 'help': shelp, 'stype': stype, 'nocolor': nocolor, 'readonly': readonly } def _cmd_reset(self, _=None): """ @G%(name)s@w - @B%(cmdname)s@w reset the plugin @CUsage@w: reset """ if self.canreset: self.reset() return True, ['Plugin reset'] return True, ['This plugin cannot be reset'] def ischangedondisk(self): """ check to see if the file this plugin is based on has changed on disk """ ftime = os.path.getmtime(self.pluginfile) if ftime > self.loadedtime: return True return False def reset(self): """ internal function to reset data """ if self.canreset: self.resetflag = True self.settingvalues.clear() for i in self.settings: self.settingvalues[i] = self.settings[i]['default'] self.settingvalues.sync() self.resetflag = False # add a function to the api def _api_add(self, name, func): """ add a command to the api """ # we call the non overloaded versions self.api.add(self.sname, name, func)
if tcommand[-1] != '\n': tcommand = tcommand + '\n' API('send.msg')('sending %s to the mud' % tcommand.strip(), primary='inputparse') API('events.eraise')('to_mud_event', {'data':tcommand, 'dtype':'fromclient', 'history':history}) # send data directly to the mud def api_tomud(data): """ send data directly to the mud This does not go through the interpreter @Ydata@w = the data to send this function returns no values """ if data[-1] != '\n': data = data + '\n' API('events.eraise')('to_mud_event', {'data':data, 'dtype':'fromclient'}) API.add('send', 'msg', api_msg) API.add('send', 'error', api_error) API.add('send', 'traceback', api_traceback) API.add('send', 'client', api_client) API.add('send', 'mud', api_tomud) API.add('send', 'execute', api_execute)
tcommand = tcommand + '\n' API('send.msg')('sending %s to the mud' % tcommand.strip(), primary='inputparse') API('events.eraise')('to_mud_event', { 'data': tcommand, 'dtype': 'fromclient', 'history': history }) # send data directly to the mud def api_tomud(data): """ send data directly to the mud This does not go through the interpreter @Ydata@w = the data to send this function returns no values """ if data[-1] != '\n': data = data + '\n' API('events.eraise')('to_mud_event', {'data': data, 'dtype': 'fromclient'}) API.add('send', 'msg', api_msg) API.add('send', 'error', api_error) API.add('send', 'traceback', api_traceback) API.add('send', 'client', api_client) API.add('send', 'mud', api_tomud) API.add('send', 'execute', api_execute)
class BasePlugin(object): # pylint: disable=too-many-public-methods,too-many-instance-attributes """ a base class for plugins """ def __init__(self, name, sname, modpath, basepath, fullimploc): # pylint: disable=too-many-arguments """ initialize the instance The following things should not be done in __init__ in a plugin Interacting with anything in the api except api.add, api.overload or dependency.add """ self.author = '' self.purpose = '' self.version = 0 self.priority = 100 self.name = name self.sname = sname self.dependencies = [] self.versionfuncs = {} self.reloaddependents = False self.canreload = True self.resetflag = True self.api = API() self.loadedtime = time.time() self.savedir = os.path.join(self.api.BASEPATH, 'data', 'plugins', self.sname) try: os.makedirs(self.savedir) except OSError: pass self.savefile = os.path.join(self.api.BASEPATH, 'data', 'plugins', self.sname, 'settingvalues.txt') self.modpath = modpath self.basepath = basepath self.fullimploc = fullimploc self.pluginfile = os.path.join(basepath, modpath[1:]) self.pluginlocation = os.path.normpath( os.path.join(self.api.BASEPATH, 'plugins') + \ os.sep + os.path.dirname(self.modpath)) self.package = fullimploc.split('.')[1] self.settings = {} self.settingvalues = PersistentDictEvent(self, self.savefile, 'c') self._dump_shallow_attrs = ['api'] self.api.overload('dependency', 'add', self.api_dependencyadd) self.api.overload('setting', 'add', self.api_settingadd) self.api.overload('setting', 'gets', self.api_settinggets) self.api.overload('setting', 'change', self.api_settingchange) self.api.overload('api', 'add', self.api_add) def load(self): """ load stuff, do most things here """ self.settingvalues.pload() if '_version' in self.settingvalues and \ self.settingvalues['_version'] != self.version: self.updateversion(self.settingvalues['_version'], self.version) self.api.get('log.adddtype')(self.sname) parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent(""" change a setting in the plugin if there are no arguments or 'list' is the first argument then it will list the settings for the plugin""")) parser.add_argument('name', help='the setting name', default='list', nargs='?') parser.add_argument('value', help='the new value of the setting', default='', nargs='?') self.api.get('commands.add')('set', self.cmd_set, parser=parser, group='Base', history=False) parser = argparse.ArgumentParser(add_help=False, description='reset the plugin') self.api.get('commands.add')('reset', self.cmd_reset, parser=parser, group='Base') parser = argparse.ArgumentParser(add_help=False, description='save the plugin state') self.api.get('commands.add')('save', self.cmd_save, parser=parser, group='Base') parser = argparse.ArgumentParser(add_help=False, description='show plugin stats') self.api.get('commands.add')('stats', self.cmd_stats, parser=parser, group='Base') parser = argparse.ArgumentParser(add_help=False, description='inspect a plugin') parser.add_argument('-m', "--method", help="get code for a method", default='') parser.add_argument('-o', "--object", help="show an object of the plugin, can be method or variable", default='') parser.add_argument('-s', "--simple", help="show a simple output", action="store_true") self.api.get('commands.add')('inspect', self.cmd_inspect, parser=parser, group='Base') parser = argparse.ArgumentParser(add_help=False, description='show help info for this plugin') parser.add_argument('-a', "--api", help="show functions this plugin has in the api", action="store_true") parser.add_argument('-c', "--commands", help="show commands in this plugin", action="store_true") self.api.get('commands.add')('help', self.cmd_help, parser=parser, group='Base') parser = argparse.ArgumentParser(add_help=False, description='list functions in the api') parser.add_argument('api', help='api to get details of', default='', nargs='?') self.api.get('commands.add')('api', self.cmd_api, parser=parser, group='Base') self.api.get('events.register')('%s_plugin_loaded' % self.sname, self.afterload) self.api.get('events.register')('muddisconnect', self.disconnect) self.resetflag = False def updateversion(self, oldversion, newversion): """ update plugin data """ if oldversion != newversion and newversion > oldversion: for i in range(oldversion + 1, newversion + 1): self.api.get('send.msg')( '%s: upgrading to version %s' % (self.sname, i), secondary='upgrade') if i in self.versionfuncs: self.versionfuncs[i]() else: self.api.get('send.msg')( '%s: no function to upgrade to version %s' % (self.sname, i), secondary='upgrade') self.settingvalues.sync() def cmd_inspect(self, args): # pylint: disable=too-many-branches """ show the plugin as it currently is in memory """ from libs.objectdump import dumps as dumper tmsg = [] if args['method']: try: tmeth = getattr(self, args['method']) tmsg.append(inspect.getsource(tmeth)) except AttributeError: tmsg.append('There is no method named %s' % args['method']) elif args['object']: tobj = args['object'] key = None if ':' in tobj: tobj, key = tobj.split(':') obj = getattr(self, tobj) if obj: if key: if key not in obj: try: key = int(key) except ValueError: pass if key in obj: obj = obj[key] if args['simple']: tvars = pprint.pformat(obj) else: tvars = dumper(obj) tmsg.append(tvars) else: if args['simple']: tvars = pprint.pformat(vars(self)) else: tvars = dumper(self) tmsg.append('@M' + '-' * 60 + '@x') tmsg.append('Variables') tmsg.append('@M' + '-' * 60 + '@x') tmsg.append(tvars) tmsg.append('@M' + '-' * 60 + '@x') tmsg.append('Methods') tmsg.append('@M' + '-' * 60 + '@x') tmsg.append(pprint.pformat(inspect.getmembers(self, inspect.ismethod))) return True, tmsg def cmd_api(self, args): """ list functions in the api for a plugin """ tmsg = [] if args['api']: tmsg.extend(self.api.get('api.detail')("%s.%s" % (self.sname, args['api']))) else: apilist = self.api.get('api.list')(self.sname) if not apilist: tmsg.append('nothing in the api') else: tmsg.extend(apilist) return True, tmsg def afterload(self, args): # pylint: disable=unused-argument """ do something after the load function is run """ proxy = self.api.get('managers.getm')('proxy') if proxy and proxy.connected: if self.api.get('api.has')('connect.firstactive'): if self.api.get('connect.firstactive')(): self.afterfirstactive() else: self.api.get('events.register')('firstactive', self.afterfirstactive) else: self.api.get('events.register')('firstactive', self.afterfirstactive) def disconnect(self, args=None): # pylint: disable=unused-argument """ re-register to firstactive on disconnect """ self.api.get('send.msg')('baseplugin, disconnect') self.api.get('events.register')('firstactive', self.afterfirstactive) # get the vaule of a setting def api_settinggets(self, setting): """ get the value of a setting @Ysetting@w = the setting value to get this function returns the value of the setting, None if not found""" try: return self.api.get('utils.verify')(self.settingvalues[setting], self.settings[setting]['stype']) except KeyError: return None # add a plugin dependency def api_dependencyadd(self, dependency): """ add a depencency @Ydependency@w = the name of the plugin that will be a dependency this function returns no values""" if dependency not in self.dependencies: self.dependencies.append(dependency) # change the value of a setting def api_settingchange(self, setting, value): """ change a setting @Ysetting@w = the name of the setting to change @Yvalue@w = the value to set it as this function returns True if the value was changed, False otherwise""" if value == 'default': value = self.settings[setting]['default'] if setting in self.settings: self.settingvalues[setting] = self.api.get('utils.verify')( value, self.settings[setting]['stype']) self.settingvalues.sync() return True return False def getstats(self): """ get the stats for the plugin """ stats = {} stats['Base Sizes'] = {} stats['Base Sizes']['showorder'] = ['Class', 'Variables', 'Api'] stats['Base Sizes']['Variables'] = '%s bytes' % \ sys.getsizeof(self.settingvalues) stats['Base Sizes']['Class'] = '%s bytes' % sys.getsizeof(self) stats['Base Sizes']['Api'] = '%s bytes' % sys.getsizeof(self.api) return stats def cmd_stats(self, args=None): # pylint: disable=unused-argument """ @G%(name)s@w - @B%(cmdname)s@w show stats, memory, profile, etc.. for this plugin @CUsage@w: stats """ stats = self.getstats() tmsg = [] for header in stats: tmsg.append(self.api.get('utils.center')(header, '=', 60)) for subtype in stats[header]['showorder']: tmsg.append('%-20s : %s' % (subtype, stats[header][subtype])) return True, tmsg def unload(self, _=None): """ unload stuff """ self.api.get('send.msg')('unloading %s' % self.name) # remove anything out of the api self.api.get('api.remove')(self.sname) #save the state self.savestate() def savestate(self): """ save the state """ self.settingvalues.sync() def cmd_set(self, args): """ @G%(name)s@w - @B%(cmdname)s@w List or set vars @CUsage@w: var @Y<varname>@w @Y<varvalue>@w @Ysettingname@w = The setting to set @Ysettingvalue@w = The value to set it to if there are no arguments or 'list' is the first argument then it will list the settings for the plugin """ msg = [] if args['name'] == 'list': return True, self.listvars() elif args['name'] and args['value']: var = args['name'] val = args['value'] if var in self.settings: if 'readonly' in self.settings[var] \ and self.settings[var]['readonly']: return True, ['%s is a readonly setting' % var] else: try: self.api.get('setting.change')(var, val) tvar = self.settingvalues[var] if self.settings[var]['nocolor']: tvar = tvar.replace('@', '@@') elif self.settings[var]['stype'] == 'color': tvar = '%s%s@w' % (val, val.replace('@', '@@')) elif self.settings[var]['stype'] == 'timelength': tvar = self.api.get('utils.formattime')( self.api.get('utils.verify')(val, 'timelength')) return True, ['%s is now set to %s' % (var, tvar)] except ValueError: msg = ['Cannot convert %s to %s' % \ (val, self.settings[var]['stype'])] return True, msg return True, self.listvars() else: msg = ['plugin setting %s does not exist' % var] return False, msg def cmd_save(self, args): # pylint: disable=unused-argument """ @G%(name)s@w - @B%(cmdname)s@w save plugin state @CUsage@w: save """ self.savestate() return True, ['Plugin settings saved'] def cmd_help(self, args): """ test command """ msg = [] msg.extend(sys.modules[self.fullimploc].__doc__.split('\n')) if args['commands']: cmdlist = self.api.get('commands.list')(self.sname) if cmdlist: msg.extend(self.api.get('commands.list')(self.sname)) msg.append('@G' + '-' * 60 + '@w') msg.append('') if args['api']: apilist = self.api.get('api.list')(self.sname) if apilist: msg.append('API functions in %s' % self.sname) msg.append('@G' + '-' * 60 + '@w') msg.extend(self.api.get('api.list')(self.sname)) return True, msg def listvars(self): """ return a list of strings that list all settings """ tmsg = [] if len(self.settingvalues) == 0: tmsg.append('There are no settings defined') else: tform = '%-15s : %-15s - %s' for i in self.settings: val = self.settingvalues[i] if 'nocolor' in self.settings[i] and self.settings[i]['nocolor']: val = val.replace('@', '@@') elif self.settings[i]['stype'] == 'color': val = '%s%s@w' % (val, val.replace('@', '@@')) elif self.settings[i]['stype'] == 'timelength': val = self.api.get('utils.formattime')( self.api.get('utils.verify')(val, 'timelength')) tmsg.append(tform % (i, val, self.settings[i]['help'])) return tmsg # add a setting to the plugin def api_settingadd(self, name, default, stype, shelp, **kwargs): """ remove a command @Yname@w = the name of the setting @Ydefault@w = the default value of the setting @Ystype@w = the type of the setting @Yshelp@w = the help associated with the setting Keyword Arguments @Ynocolor@w = if True, don't parse colors when showing value @Yreadonly@w = if True, can't be changed by a client this function returns no values""" if 'nocolor' in kwargs: nocolor = kwargs['nocolor'] else: nocolor = False if 'readonly' in kwargs: readonly = kwargs['readonly'] else: readonly = False if name not in self.settingvalues: self.settingvalues[name] = default self.settings[name] = { 'default':default, 'help':shelp, 'stype':stype, 'nocolor':nocolor, 'readonly':readonly } def cmd_reset(self, _=None): """ @G%(name)s@w - @B%(cmdname)s@w reset the plugin @CUsage@w: reset """ self.reset() return True, ['Plugin reset'] def ischangedondisk(self): """ check to see if the file this plugin is based on has changed on disk """ ftime = os.path.getmtime(self.pluginfile) if ftime > self.loadedtime: return True return False def reset(self): """ internal function to reset data """ self.resetflag = True self.settingvalues.clear() for i in self.settings: self.settingvalues[i] = self.settings[i]['default'] self.settingvalues.sync() self.resetflag = False def afterfirstactive(self, _=None): """ if we are connected do """ self.api.get('send.msg')('baseplugin, firstactive') self.api.get('events.unregister')('firstactive', self.afterfirstactive) # add a function to the api def api_add(self, name, func): """ add a command to the api """ # we call the non overloaded versions self.api.add(self.sname, name, func)
class PluginMgr(object): # pylint: disable=too-many-public-methods """ a class to manage plugins """ def __init__(self): """ initialize the instance """ self.plugins = {} self.pluginl = {} self.pluginm = {} self.pluginp = {} self.options = {} self.plugininfo = {} index = __file__.rfind(os.sep) if index == -1: self.basepath = "." + os.sep else: self.basepath = __file__[:index] self.api = API() self.savefile = os.path.join(self.api.BASEPATH, 'data', 'plugins', 'loadedplugins.txt') self.loadedplugins = PersistentDict(self.savefile, 'c') self.sname = 'plugins' self.lname = 'Plugin Manager' self.api.add(self.sname, 'isinstalled', self.api_isinstalled) self.api.add(self.sname, 'getp', self.api_getp) self.api.add(self.sname, 'module', self.api_getmodule) self.api.add(self.sname, 'allplugininfo', self.api_allplugininfo) self.api.add(self.sname, 'savestate', self.savestate) # return the dictionary of all plugins def api_allplugininfo(self): """ return the plugininfo dictionary """ return self.plugininfo def findplugin(self, name): """ find a plugin file """ if '.' in name: tlist = name.split('.') name = tlist[-1] del tlist[-1] npath = os.sep.join(tlist) _module_list = find_files(self.basepath, name + ".py") if len(_module_list) == 1: return _module_list[0], self.basepath else: for i in _module_list: if npath in i: return i, self.basepath return '', '' def findloadedplugin(self, plugin): """ find a plugin """ if plugin and plugin in self.plugins: return plugin fullimploc = 'plugins.' + plugin for tplugin in self.plugins: if self.plugins[tplugin].fullimploc == fullimploc: return tplugin return None # get a plugin instance def api_getmodule(self, pluginname): """ returns the module of a plugin @Ypluginname@w = the plugin to check for""" if pluginname in self.pluginm: return self.pluginm[pluginname] return None # get a plugin instance def api_getp(self, pluginname): """ get a plugin instance @Ypluginname@w = the plugin to get for""" if isinstance(pluginname, basestring): if pluginname in self.plugins: return self.plugins[pluginname] if pluginname in self.pluginl: return self.pluginl[pluginname] if pluginname in self.pluginm: return self.pluginm[pluginname] if pluginname in self.pluginp: return self.pluginp[pluginname] elif isinstance(pluginname, BasePlugin): return pluginname return None # check if a plugin is installed def api_isinstalled(self, pluginname): """ check if a plugin is installed @Ypluginname@w = the plugin to check for""" if pluginname in self.plugins or pluginname in self.pluginl: return True return False def loaddependencies(self, pluginname, dependencies): """ load a list of modules """ for i in dependencies: if i in self.plugins or i in self.pluginl: continue self.api.get('send.msg')('%s: loading dependency %s' % (pluginname, i), pluginname) name, path = self.findplugin(i) if name: modpath = name.replace(path, '') self.load_module(modpath, path, force=True) def getnotloadedplugins(self): """ create a message of all not loaded plugins """ msg = [] badplugins = self.updateallplugininfo() for modpath in sorted(self.plugininfo.keys()): sname = self.plugininfo[modpath]['sname'] fullimploc = self.plugininfo[modpath]['fullimploc'] if sname not in self.plugins: msg.append("%-20s : %-25s %-10s %-5s %s@w" % \ (fullimploc.replace('plugins.', ''), self.plugininfo[modpath]['name'], self.plugininfo[modpath]['author'], self.plugininfo[modpath]['version'], self.plugininfo[modpath]['purpose'])) if len(msg) > 0: msg.insert(0, '-' * 75) msg.insert(0, "%-20s : %-25s %-10s %-5s %s@w" % \ ('Location', 'Name', 'Author', 'Vers', 'Purpose')) msg.insert(0, 'The following plugins are not loaded') if badplugins: msg.append('') msg.append('The following files would not import') for bad in badplugins: msg.append(bad.replace('plugins.', '')) return msg def getchangedplugins(self): """ create a message of plugins that are changed on disk """ msg = [] plugins = sorted(self.plugins.values(), key=operator.attrgetter('package')) packageheader = [] msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ ('Short Name', 'Name', 'Author', 'Vers', 'Purpose')) msg.append('-' * 75) for tpl in plugins: if tpl.ischangedondisk(): if tpl.package not in packageheader: if len(packageheader) > 0: msg.append('') packageheader.append(tpl.package) limp = 'plugins.%s' % tpl.package mod = __import__(limp) try: desc = getattr(mod, tpl.package).DESCRIPTION except AttributeError: desc = '' msg.append('@GPackage: %s%s@w' % \ (tpl.package, ' - ' + desc if desc else '')) msg.append('@G' + '-' * 75 + '@w') msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ (tpl.sname, tpl.name, tpl.author, tpl.version, tpl.purpose)) return msg def getpackageplugins(self, package): """ create a message of plugins in a package """ msg = [] plist = [] for plugin in self.plugins.values(): if plugin.package == package: plist.append(plugin) if len(plist) > 0: plugins = sorted(plist, key=operator.attrgetter('sname')) limp = 'plugins.%s' % package mod = __import__(limp) try: desc = getattr(mod, package).DESCRIPTION except AttributeError: desc = '' msg.append('@GPackage: %s%s@w' % \ (package, ' - ' + desc if desc else '')) msg.append('@G' + '-' * 75 + '@w') msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ ('Short Name', 'Name', 'Author', 'Vers', 'Purpose')) msg.append('-' * 75) for tpl in plugins: msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ (tpl.sname, tpl.name, tpl.author, tpl.version, tpl.purpose)) else: msg.append('That is not a valid package') return msg def getallplugins(self): """ create a message of all plugins """ msg = [] plugins = sorted(self.plugins.values(), key=operator.attrgetter('package')) packageheader = [] msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ ('Short Name', 'Name', 'Author', 'Vers', 'Purpose')) msg.append('-' * 75) for tpl in plugins: if tpl.package not in packageheader: if len(packageheader) > 0: msg.append('') packageheader.append(tpl.package) limp = 'plugins.%s' % tpl.package mod = __import__(limp) try: desc = getattr(mod, tpl.package).DESCRIPTION except AttributeError: desc = '' msg.append('@GPackage: %s%s@w' % \ (tpl.package, ' - ' + desc if desc else '')) msg.append('@G' + '-' * 75 + '@w') msg.append("%-10s : %-25s %-10s %-5s %s@w" % \ (tpl.sname, tpl.name, tpl.author, tpl.version, tpl.purpose)) return msg def cmd_list(self, args): """ @G%(name)s@w - @B%(cmdname)s@w List plugins @CUsage@w: list """ msg = [] if args['notloaded']: msg.extend(self.getnotloadedplugins()) elif args['changed']: msg.extend(self.getchangedplugins()) elif args['package']: msg.extend(self.getpackageplugins(args['package'])) else: msg.extend(self.getallplugins()) return True, msg def cmd_load(self, args): """ @G%(name)s@w - @B%(cmdname)s@w Load a plugin @CUsage@w: load @Yplugin@w @Yplugin@w = the name of the plugin to load use the name without the .py """ tmsg = [] plugin = args['plugin'] if plugin: fname = plugin.replace('.', os.sep) _module_list = find_files(self.basepath, fname + ".py") if len(_module_list) > 1: tmsg.append('There is more than one module that matches: %s' % \ plugin) elif len(_module_list) == 0: tmsg.append('There are no modules that match: %s' % plugin) else: modpath = _module_list[0].replace(self.basepath, '') sname, reason = self.load_module(modpath, self.basepath, True) if sname: if reason == 'already': tmsg.append('Module %s is already loaded' % sname) else: tmsg.append('Load complete: %s - %s' % \ (sname, self.plugins[sname].name)) else: tmsg.append('Could not load: %s' % plugin) return True, tmsg else: return False, ['@Rplease specify a plugin@w'] def cmd_unload(self, args): """ @G%(name)s@w - @B%(cmdname)s@w unload a plugin @CUsage@w: unload @Yplugin@w @Yplugin@w = the shortname of the plugin to load """ tmsg = [] plugina = args['plugin'] if not plugina: return False, ['@Rplease specify a plugin@w'] plugin = self.findloadedplugin(plugina) if plugin and plugin in self.plugins: if self.plugins[plugin].canreload: if self.unload_module(self.plugins[plugin].fullimploc): tmsg.append("Unloaded: %s" % plugin) else: tmsg.append("Could not unload:: %s" % plugin) else: tmsg.append("That plugin can not be unloaded") return True, tmsg elif plugin: tmsg.append('plugin %s does not exist' % plugin) return True, tmsg return False, ['@Rplease specify a plugin@w'] def cmd_reload(self, args): """ @G%(name)s@w - @B%(cmdname)s@w reload a plugin @CUsage@w: reload @Yplugin@w @Yplugin@w = the shortname of the plugin to reload """ tmsg = [] plugina = args['plugin'] if not plugina: return False, ['@Rplease specify a plugin@w'] plugin = self.findloadedplugin(plugina) if plugin and plugin in self.plugins: if self.plugins[plugin].canreload: tret, _ = self.reload_module(plugin, True) if tret and tret != True: tmsg.append("Reload complete: %s" % self.plugins[tret].fullimploc) return True, tmsg else: tmsg.append("That plugin cannot be reloaded") return True, tmsg else: tmsg.append('plugin %s does not exist' % plugin) return True, tmsg return False, tmsg def load_modules(self, tfilter): """ load modules in all directories under plugins """ _module_list = find_files(self.basepath, tfilter) _module_list.sort() load = False for fullpath in _module_list: modpath = fullpath.replace(self.basepath, '') force = False if modpath in self.loadedplugins: force = True modname, dummy = self.load_module(modpath, self.basepath, force=force, runload=load) if modname == 'log': self.api.get('log.adddtype')(self.sname) self.api.get('log.console')(self.sname) self.api.get('log.adddtype')('upgrade') self.api.get('log.console')('upgrade') if not load: testsort = sorted(self.plugins.values(), key=operator.attrgetter('priority')) for i in testsort: try: #check dependencies here self.loadplugin(i) except Exception: # pylint: disable=broad-except self.api.get('send.traceback')( "load: had problems running the load method for %s." \ % i.fullimploc) del sys.modules[i.fullimploc] def updateallplugininfo(self): """ find plugins that are not in self.plugininfo """ _module_list = find_files(self.basepath, '*.py') _module_list.sort() self.plugininfo = {} badplugins = [] for fullpath in _module_list: modpath = fullpath.replace(self.basepath, '') imploc, modname = get_module_name(modpath) if not modname.startswith("_"): fullimploc = "plugins" + '.' + imploc if fullimploc in sys.modules: self.plugininfo[modpath] = {} self.plugininfo[modpath]['sname'] = self.pluginp[ modpath].sname self.plugininfo[modpath]['name'] = self.pluginp[ modpath].name self.plugininfo[modpath]['purpose'] = self.pluginp[ modpath].purpose self.plugininfo[modpath]['author'] = self.pluginp[ modpath].author self.plugininfo[modpath]['version'] = self.pluginp[ modpath].version self.plugininfo[modpath]['modpath'] = modpath self.plugininfo[modpath]['fullimploc'] = fullimploc else: try: _module = __import__(fullimploc) _module = sys.modules[fullimploc] self.plugininfo[modpath] = {} self.plugininfo[modpath]['sname'] = _module.SNAME self.plugininfo[modpath]['name'] = _module.NAME self.plugininfo[modpath]['purpose'] = _module.PURPOSE self.plugininfo[modpath]['author'] = _module.AUTHOR self.plugininfo[modpath]['version'] = _module.VERSION self.plugininfo[modpath]['modpath'] = modpath self.plugininfo[modpath]['fullimploc'] = fullimploc del sys.modules[fullimploc] except Exception: # pylint: disable=broad-except badplugins.append(fullimploc) return badplugins def load_module(self, modpath, basepath, force=False, runload=True): # pylint: disable=too-many-branches """ load a single module """ if basepath in modpath: modpath = modpath.replace(basepath, '') imploc, modname = get_module_name(modpath) if modname.startswith("_"): return False, 'dev' try: fullimploc = "plugins" + '.' + imploc if fullimploc in sys.modules: return sys.modules[fullimploc].SNAME, 'already' self.api.get('send.msg')('importing %s' % fullimploc, self.sname) _module = __import__(fullimploc) _module = sys.modules[fullimploc] self.api.get('send.msg')('imported %s' % fullimploc, self.sname) load = True if 'AUTOLOAD' in _module.__dict__ and not force: if not _module.AUTOLOAD: load = False elif 'AUTOLOAD' not in _module.__dict__: load = False if modpath not in self.plugininfo: self.plugininfo[modpath] = {} self.plugininfo[modpath]['sname'] = _module.SNAME self.plugininfo[modpath]['name'] = _module.NAME self.plugininfo[modpath]['purpose'] = _module.PURPOSE self.plugininfo[modpath]['author'] = _module.AUTHOR self.plugininfo[modpath]['version'] = _module.VERSION self.plugininfo[modpath]['modpath'] = modpath self.plugininfo[modpath]['fullimploc'] = fullimploc if load: if "Plugin" in _module.__dict__: self.add_plugin(_module, modpath, basepath, fullimploc, runload) else: self.api.get('send.msg')('Module %s has no Plugin class' % \ _module.NAME, self.sname) _module.__dict__["proxy_import"] = 1 return _module.SNAME, 'Loaded' else: if fullimploc in sys.modules: del sys.modules[fullimploc] self.api.get('send.msg')( 'Not loading %s (%s) because autoload is False' % \ (_module.NAME, fullimploc), self.sname) return True, 'not autoloaded' except Exception: # pylint: disable=broad-except if fullimploc in sys.modules: del sys.modules[fullimploc] self.api.get('send.traceback')( "Module '%s' refuses to import/load." % fullimploc) return False, 'error' def unload_module(self, fullimploc): """ unload a module """ if fullimploc in sys.modules: _module = sys.modules[fullimploc] try: if "proxy_import" in _module.__dict__: self.api.get('send.client')('unload: unloading %s' % fullimploc) if "unload" in _module.__dict__: try: _module.unload() except Exception: # pylint: disable=broad-except self.api.get('send.traceback')( "unload: module %s didn't unload properly." % fullimploc) if not self.remove_plugin(_module.SNAME): self.api.get('send.client')( 'could not remove plugin %s' % fullimploc) del sys.modules[fullimploc] self.api.get('send.client')("unload: unloaded %s." % fullimploc) except Exception: # pylint: disable=broad-except self.api.get('send.traceback')( "unload: had problems unloading %s." % fullimploc) return False return True def reload_module(self, modname, force=False): """ reload a module """ if modname in self.plugins: plugin = self.plugins[modname] fullimploc = plugin.fullimploc basepath = plugin.basepath modpath = plugin.modpath sname = plugin.sname try: reloaddependents = plugin.reloaddependents except Exception: # pylint: disable=broad-except reloaddependents = False plugin = None if not self.unload_module(fullimploc): return False, '' if modpath and basepath: retval = self.load_module(modpath, basepath, force) if retval and reloaddependents: self.reloaddependents(sname) return retval else: return False, '' def reloaddependents(self, reloadedplugin): """ reload all dependents """ testsort = sorted(self.plugins.values(), key=operator.attrgetter('priority')) for plugin in testsort: if plugin.sname != reloadedplugin: if reloadedplugin in plugin.dependencies: self.api.get('send.msg')('reloading dependent %s of %s' % \ (plugin.sname, reloadedplugin)) plugin.savestate() self.reload_module(plugin.sname, True) def loadplugin(self, plugin): """ check dependencies and run the load function """ self.api.get('send.msg')('loading dependencies for %s' % \ plugin.fullimploc, self.sname) self.loaddependencies(plugin.sname, plugin.dependencies) self.api.get('send.client')("load: loading %s with priority %s" % \ (plugin.fullimploc, plugin.priority)) self.api.get('send.msg')('loading %s (%s: %s)' % \ (plugin.fullimploc, plugin.sname, plugin.name), self.sname) plugin.load() self.api.get('send.client')("load: loaded %s" % plugin.fullimploc) self.api.get('send.msg')('loaded %s (%s: %s)' % \ (plugin.fullimploc, plugin.sname, plugin.name), self.sname) self.api.get('events.eraise')('%s_plugin_loaded' % plugin.sname, {}) self.api.get('events.eraise')('plugin_loaded', { 'plugin': plugin.sname }) def add_plugin(self, module, modpath, basepath, fullimploc, load=True): # pylint: disable=too-many-arguments """ add a plugin to be managed """ module.__dict__["lyntin_import"] = 1 plugin = module.Plugin(module.NAME, module.SNAME, modpath, basepath, fullimploc) plugin.author = module.AUTHOR plugin.purpose = module.PURPOSE plugin.version = module.VERSION try: plugin.priority = module.PRIORITY except AttributeError: pass if plugin.name in self.pluginl: self.api.get('send.msg')('Plugin %s already exists' % plugin.name, self.sname) return False if plugin.sname in self.plugins: self.api.get('send.msg')('Plugin %s already exists' % plugin.sname, self.sname) return False if load: try: #check dependencies here self.loadplugin(plugin) except Exception: # pylint: disable=broad-except self.api.get('send.traceback')( "load: had problems running the load method for %s." \ % fullimploc) del sys.modules[fullimploc] return False self.pluginl[plugin.name] = plugin self.plugins[plugin.sname] = plugin self.pluginm[plugin.sname] = module self.pluginp[modpath] = plugin self.loadedplugins[modpath] = True self.loadedplugins.sync() return True def remove_plugin(self, pluginname): """ remove a plugin """ plugin = None if pluginname in self.plugins: plugin = self.plugins[pluginname] try: plugin.unload() self.api.get('events.eraise')('%s_plugin_unload' % plugin.sname, {}) self.api.get('events.eraise')('plugin_unloaded', { 'name': plugin.sname }) self.api.get('send.msg')('Plugin %s unloaded' % plugin.sname, self.sname, plugin.sname) except Exception: # pylint: disable=broad-except self.api.get('send.traceback')( "unload: had problems running the unload method for %s." \ % plugin.sname) return False del self.plugins[plugin.sname] del self.pluginl[plugin.name] del self.pluginm[plugin.sname] del self.loadedplugins[plugin.modpath] self.loadedplugins.sync() plugin = None return True else: return False # save all plugins def savestate(self): """ save all plugins """ for i in self.plugins: self.plugins[i].savestate() def load(self): """ load various things """ self.load_modules("*.py") parser = argparse.ArgumentParser(add_help=False, description="list plugins") parser.add_argument('-n', "--notloaded", help="list plugins that are not loaded", action="store_true") parser.add_argument( '-c', "--changed", help="list plugins that are load but are changed on disk", action="store_true") parser.add_argument('package', help='the to list', default='', nargs='?') self.api.get('commands.add')('list', self.cmd_list, lname='Plugin Manager', parser=parser) parser = argparse.ArgumentParser(add_help=False, description="load a plugin") parser.add_argument('plugin', help='the plugin to load, don\'t include the .py', default='', nargs='?') self.api.get('commands.add')('load', self.cmd_load, lname='Plugin Manager', parser=parser) parser = argparse.ArgumentParser(add_help=False, description="unload a plugin") parser.add_argument('plugin', help='the plugin to unload', default='', nargs='?') self.api.get('commands.add')('unload', self.cmd_unload, lname='Plugin Manager', parser=parser) parser = argparse.ArgumentParser(add_help=False, description="reload a plugin") parser.add_argument('plugin', help='the plugin to reload', default='', nargs='?') self.api.get('commands.add')('reload', self.cmd_reload, lname='Plugin Manager', parser=parser) self.api.get('commands.default')('list', self.sname) self.api.get('events.register')('savestate', self.savestate, plugin=self.sname) self.api.get('timers.add')('save', self.savestate, 60, nodupe=True, log=False)