Merge pull request #6 from beardog108/plugins

Add plugin support
This commit is contained in:
Kevin Froman 2018-03-02 23:48:27 -06:00 committed by GitHub
commit f72cb5426c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 391 additions and 14 deletions

View File

@ -181,9 +181,13 @@ class API:
return resp
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...')
#logger.debug('Client token: ' + logger.colors.underline + self.clientToken)
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
try:
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
except Exception as e:
logger.error(str(e))
logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...')
exit(1)
def validateHost(self, hostType):
'''

View File

@ -29,12 +29,19 @@ class OnionrCommunicate:
This class handles communication with nodes in the Onionr network.
'''
self._core = core.Core()
self._utils = onionrutils.OnionrUtils(self._core)
self._crypto = onionrcrypto.OnionrCrypto(self._core)
logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2]))
self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2]))
logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight()))
try:
logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2]))
self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2]))
logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight()))
except:
logger.fatal('Failed to start Bitcoin Node, exiting...')
exit(1)
blockProcessTimer = 0
blockProcessAmount = 5
heartBeatTimer = 0
@ -48,6 +55,10 @@ class OnionrCommunicate:
if os.path.exists(self._core.queueDB):
self._core.clearDaemonQueue()
# Loads in and starts the enabled plugins
plugins.reload()
while True:
command = self._core.daemonQueue()
# Process blocks based on a timer
@ -63,7 +74,6 @@ class OnionrCommunicate:
self.lookupBlocks()
self.processBlocks()
blockProcessTimer = 0
#logger.debug('Communicator daemon heartbeat')
if command != False:
if command[0] == 'shutdown':
logger.warn('Daemon recieved exit command.')
@ -71,17 +81,19 @@ class OnionrCommunicate:
time.sleep(1)
return
def getNewPeers(self):
'''
Get new peers
'''
return
def lookupBlocks(self):
'''
Lookup blocks and merge new ones
'''
peerList = self._core.listAdders()
blocks = ''
for i in peerList:
@ -126,6 +138,7 @@ class OnionrCommunicate:
This is meant to be called from the communicator daemon on its timer.
'''
for i in self._core.getBlockList(True).split("\n"):
if i != "":
logger.warn('UNSAVED BLOCK: ' + i)
@ -137,6 +150,7 @@ class OnionrCommunicate:
'''
Download a block from random order of peers
'''
peerList = self._core.listAdders()
blocks = ''
for i in peerList:
@ -164,12 +178,14 @@ class OnionrCommunicate:
'''
URL encodes the data
'''
return urllib.parse.quote_plus(data)
def performGet(self, action, peer, data=None, peerType='tor'):
'''
Performs a request to a peer through Tor or i2p (currently only Tor)
'''
if not peer.endswith('.onion') and not peer.endswith('.onion/'):
raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion')

View File

@ -27,6 +27,7 @@ def get(key, default = None):
'''
Gets the key from configuration, or returns `default`
'''
if is_set(key):
return get_config()[key]
return default

View File

@ -21,7 +21,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform
import api, core, gui, config, logger
import api, core, gui, config, logger, onionrplugins as plugins
from onionrutils import OnionrUtils
from netcontroller import NetController
@ -130,26 +130,44 @@ class Onionr:
def getCommands(self):
return {
'': self.showHelpSuggestion,
'help': self.showHelp,
'version': self.version,
'config': self.configure,
'start': self.start,
'stop': self.killDaemon,
'stats': self.showStats,
'enable-plugin': self.enablePlugin,
'enplugin': self.enablePlugin,
'enableplugin': self.enablePlugin,
'enmod': self.enablePlugin,
'disable-plugin': self.disablePlugin,
'displugin': self.disablePlugin,
'disableplugin': self.disablePlugin,
'dismod': self.disablePlugin,
'reload-plugin': self.reloadPlugin,
'reloadplugin': self.reloadPlugin,
'reload-plugins': self.reloadPlugin,
'reloadplugins': self.reloadPlugin,
'listpeers': self.listPeers,
'list-peers': self.listPeers,
'': self.showHelpSuggestion,
'addmsg': self.addMessage,
'addmessage': self.addMessage,
'add-msg': self.addMessage,
'add-message': self.addMessage,
'pm': self.sendEncrypt,
'gui': self.openGUI,
'addpeer': self.addPeer,
'add-peer': self.addPeer,
'add-address': self.addAddress,
'connect': self.addAddress,
'addaddress': self.addAddress
'addaddress': self.addAddress,
'connect': self.addAddress
}
def getHelp(self):
@ -160,10 +178,13 @@ class Onionr:
'start': 'Starts the Onionr daemon',
'stop': 'Stops the Onionr daemon',
'stats': 'Displays node statistics',
'list-peers': 'Displays a list of peers (?)',
'enable-plugin': 'Enables and starts a plugin',
'disable-plugin': 'Disables and stops a plugin',
'reload-plugin': 'Reloads a plugin',
'list-peers': 'Displays a list of peers',
'add-peer': 'Adds a peer (?)',
'add-msg': 'Broadcasts a message to the Onionr network',
'pm': 'Adds a private message (?)',
'pm': 'Adds a private message to block',
'gui': 'Opens a graphical interface for Onionr'
}
@ -263,7 +284,9 @@ class Onionr:
else:
logger.info("Adding peer: " + logger.colors.underline + newPeer)
self.onionrCore.addPeer(newPeer)
return
def addAddress(self):
'''Adds a Onionr node address'''
try:
@ -277,6 +300,8 @@ class Onionr:
else:
logger.warn("Unable to add address")
return
def addMessage(self):
'''
Broadcasts a message to the Onionr network
@ -291,6 +316,52 @@ class Onionr:
self.onionrCore.addToBlockDB(addedHash, selfInsert=True)
self.onionrCore.setBlockType(addedHash, 'txt')
return
def enablePlugin(self):
'''
Enables and starts the given plugin
'''
if len(sys.argv) >= 3:
plugin_name = sys.argv[2]
logger.info('Enabling plugin \"' + plugin_name + '\"...')
plugins.enable(plugin_name)
else:
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
return
def disablePlugin(self):
'''
Disables and stops the given plugin
'''
if len(sys.argv) >= 3:
plugin_name = sys.argv[2]
logger.info('Disabling plugin \"' + plugin_name + '\"...')
plugins.disable(plugin_name)
else:
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
return
def reloadPlugin(self):
'''
Reloads (stops and starts) all plugins, or the given plugin
'''
if len(sys.argv) >= 3:
plugin_name = sys.argv[2]
logger.info('Reloading plugin \"' + plugin_name + '\"...')
plugins.stop(plugin_name)
plugins.start(plugin_name)
else:
logger.info('Reloading all plugins...')
plugins.reload()
return
def notFound(self):
'''
Displays a "command not found" message
@ -325,6 +396,7 @@ class Onionr:
'''
Starts the Onionr communication daemon
'''
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
if self._developmentMode:
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')

53
onionr/onionrevents.py Normal file
View File

@ -0,0 +1,53 @@
'''
Onionr - P2P Microblogging Platform & Social network
This file deals with configuration management.
'''
'''
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
'''
import config, logger, onionrplugins as plugins
def event(event_name, data = None, onionr = None):
'''
Calls an event on all plugins (if defined)
'''
for plugin in plugins.get_enabled_plugins():
try:
call(plugins.get_plugin(plugin), event_name, data, onionr)
except:
logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".')
def call(plugin, event_name, data = None, onionr = None):
'''
Calls an event on a plugin if one is defined
'''
if not plugin is None:
try:
attribute = 'on_' + str(event_name).lower()
# TODO: Use multithreading perhaps?
if hasattr(plugin, attribute):
logger.debug('Calling event ' + str(event_name))
getattr(plugin, attribute)(onionr, data)
return True
except:
logger.warn('Failed to call event ' + str(event_name) + ' on module.')
return False
else:
return True

231
onionr/onionrplugins.py Normal file
View File

@ -0,0 +1,231 @@
'''
Onionr - P2P Microblogging Platform & Social network
This file deals with management of modules/plugins.
'''
'''
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
'''
import os, re, importlib, config, logger
import onionrevents as events
_pluginsfolder = 'data/plugins/'
_instances = dict()
def reload(stop_event = True):
'''
Reloads all the plugins
'''
check()
try:
enabled_plugins = get_enabled_plugins()
if stop_event is True:
logger.debug('Reloading all plugins...')
else:
logger.debug('Loading all plugins...')
if stop_event is True:
for plugin in enabled_plugins:
stop(plugin)
for plugin in enabled_plugins:
start(plugin)
return True
except:
logger.error('Failed to reload plugins.')
return False
def enable(name, start_event = True):
'''
Enables a plugin
'''
check()
if exists(name):
enabled_plugins = get_enabled_plugins()
enabled_plugins.append(name)
config_plugins = config.get('plugins')
config_plugins['enabled'] = enabled_plugins
config.set('plugins', config_plugins, True)
events.call(get_plugin(name), 'enable')
if start_event is True:
start(name)
return True
else:
logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.')
disable(name)
return False
def disable(name, stop_event = True):
'''
Disables a plugin
'''
check()
if is_enabled(name):
enabled_plugins = get_enabled_plugins()
enabled_plugins.remove(name)
config_plugins = config.get('plugins')
config_plugins['enabled'] = enabled_plugins
config.set('plugins', config_plugins, True)
if exists(name):
events.call(get_plugin(name), 'disable')
if stop_event is True:
stop(name)
def start(name):
'''
Starts the plugin
'''
check()
if exists(name):
try:
plugin = get_plugin(name)
if plugin is None:
raise Exception('Failed to import module.')
else:
events.call(plugin, 'start')
return plugin
except:
logger.error('Failed to start module \"' + name + '\".')
else:
logger.error('Failed to start nonexistant module \"' + name + '\".')
return None
def stop(name):
'''
Stops the plugin
'''
check()
if exists(name):
try:
plugin = get_plugin(name)
if plugin is None:
raise Exception('Failed to import module.')
else:
events.call(plugin, 'stop')
return plugin
except:
logger.error('Failed to stop module \"' + name + '\".')
else:
logger.error('Failed to stop nonexistant module \"' + name + '\".')
return None
def get_plugin(name):
'''
Returns the instance of a module
'''
check()
if str(name).lower() in _instances:
return _instances[str(name).lower()]
else:
_instances[str(name).lower()] = importlib.import_module(get_plugins_folder(name, False).replace('/', '.') + 'main')
return get_plugin(name)
def get_plugins():
'''
Returns a list of plugins (deprecated)
'''
return _instances
def exists(name):
'''
Return value indicates whether or not the plugin exists
'''
check()
return os.path.isdir(get_plugins_folder(str(name).lower()))
def get_enabled_plugins():
'''
Returns a list of the enabled plugins
'''
check()
config.reload()
return config.get('plugins')['enabled']
def is_enabled(name):
'''
Return value indicates whether or not the plugin is enabled
'''
return name in get_enabled_plugins()
def get_plugins_folder(name = None, absolute = True):
'''
Returns the path to the plugins folder
'''
path = ''
if name is None:
path = _pluginsfolder
else:
# only allow alphanumeric characters
path = _pluginsfolder + re.sub('[^0-9a-zA-Z]+', '', str(name).lower()) + '/'
if absolute is True:
path = os.path.abspath(path)
return path
def check():
'''
Checks to make sure files exist
'''
config.reload()
if not config.is_set('plugins'):
logger.debug('Generating plugin config data...')
config.set('plugins', {'enabled': []}, True)
if not os.path.exists(os.path.dirname(get_plugins_folder())):
logger.debug('Generating plugin data folder...')
os.path.mkdirs(os.path.dirname(get_plugins_folder()))
return