diff --git a/onionr/api.py b/onionr/api.py
index 7550f1c7..229df87e 100755
--- a/onionr/api.py
+++ b/onionr/api.py
@@ -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):
'''
diff --git a/onionr/communicator.py b/onionr/communicator.py
index 56d3bbd8..802a6ab6 100755
--- a/onionr/communicator.py
+++ b/onionr/communicator.py
@@ -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')
diff --git a/onionr/config.py b/onionr/config.py
index 793d8b9b..27002d34 100644
--- a/onionr/config.py
+++ b/onionr/config.py
@@ -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
diff --git a/onionr/onionr.py b/onionr/onionr.py
index 2792b9e1..563fd777 100755
--- a/onionr/onionr.py
+++ b/onionr/onionr.py
@@ -21,7 +21,7 @@
along with this program. If not, see .
'''
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] + ' ')
+
+ 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] + ' ')
+
+ 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!)')
diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py
new file mode 100644
index 00000000..2c148d8f
--- /dev/null
+++ b/onionr/onionrevents.py
@@ -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 .
+'''
+
+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
diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py
new file mode 100644
index 00000000..d3c3aefb
--- /dev/null
+++ b/onionr/onionrplugins.py
@@ -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 .
+'''
+
+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