Added plugin functionality
This commit is contained in:
parent
02805290b0
commit
ced7e881b9
216
sbapp/main.py
216
sbapp/main.py
|
@ -1372,14 +1372,32 @@ class SidebandApp(MDApp):
|
|||
self.file_manager.show(path)
|
||||
|
||||
except Exception as e:
|
||||
self.sideband.config["map_storage_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
toast("Error reading directory, check permissions!")
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
toast("Error reading directory, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Attachment Error",
|
||||
text="Error reading directory, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
else:
|
||||
self.sideband.config["map_storage_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
toast("No file access, check permissions!")
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
toast("No file access, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Attachment Error",
|
||||
text="No file access, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
def message_attach_action(self, attach_type=None):
|
||||
self.attach_path = None
|
||||
|
@ -3781,6 +3799,158 @@ class SidebandApp(MDApp):
|
|||
|
||||
c_dialog.open()
|
||||
|
||||
### Plugins & Services screen
|
||||
######################################
|
||||
|
||||
def plugins_action(self, sender=None, direction="left"):
|
||||
if self.root.ids.screen_manager.has_screen("plugins_screen"):
|
||||
self.plugins_open(direction=direction)
|
||||
else:
|
||||
self.loader_action(direction=direction)
|
||||
def final(dt):
|
||||
self.plugins_init()
|
||||
def o(dt):
|
||||
self.plugins_open(no_transition=True)
|
||||
Clock.schedule_once(o, ll_ot)
|
||||
Clock.schedule_once(final, ll_ft)
|
||||
|
||||
def plugins_init(self):
|
||||
if not self.root.ids.screen_manager.has_screen("plugins_screen"):
|
||||
self.plugins_screen = Builder.load_string(layout_plugins_screen)
|
||||
self.plugins_screen.app = self
|
||||
self.root.ids.screen_manager.add_widget(self.plugins_screen)
|
||||
self.bind_clipboard_actions(self.plugins_screen.ids)
|
||||
|
||||
self.plugins_screen.ids.plugins_scrollview.effect_cls = ScrollEffect
|
||||
info = "You can extend Sideband functionality with command and service plugins. This lets you to add your own custom functionality, or add community-developed features.\n\n"
|
||||
info += "[b]Take extreme caution![/b]\nIf you add a plugin that you did not write yourself, make [b]absolutely[/b] sure you know what it is doing! Loaded plugins have full access to your Sideband application, and should only be added if you are completely certain they are trustworthy.\n\n"
|
||||
info += "Command plugins allow you to define custom commands that can be carried out in response to LXMF command messages, and they can respond with any kind of information or data to the requestor (or to any LXMF address).\n\n"
|
||||
info += "By using service plugins, you can start additional services or programs within the Sideband application context, that other plugins (or Sideband itself) can interact with."
|
||||
info += "Restart Sideband for changes to these settings to take effect."
|
||||
self.plugins_screen.ids.plugins_info.text = info
|
||||
|
||||
self.plugins_screen.ids.settings_command_plugins_enabled.active = self.sideband.config["command_plugins_enabled"]
|
||||
self.plugins_screen.ids.settings_service_plugins_enabled.active = self.sideband.config["service_plugins_enabled"]
|
||||
|
||||
def plugins_settings_save(sender=None, event=None):
|
||||
self.sideband.config["command_plugins_enabled"] = self.plugins_screen.ids.settings_command_plugins_enabled.active
|
||||
self.sideband.config["service_plugins_enabled"] = self.plugins_screen.ids.settings_service_plugins_enabled.active
|
||||
self.sideband.save_configuration()
|
||||
|
||||
self.plugins_screen.ids.settings_command_plugins_enabled.bind(active=plugins_settings_save)
|
||||
self.plugins_screen.ids.settings_service_plugins_enabled.bind(active=plugins_settings_save)
|
||||
|
||||
def plugins_open(self, sender=None, direction="left", no_transition=False):
|
||||
if no_transition:
|
||||
self.root.ids.screen_manager.transition = self.no_transition
|
||||
else:
|
||||
self.root.ids.screen_manager.transition = self.slide_transition
|
||||
self.root.ids.screen_manager.transition.direction = direction
|
||||
|
||||
self.root.ids.screen_manager.transition.direction = "left"
|
||||
self.root.ids.screen_manager.current = "plugins_screen"
|
||||
self.root.ids.nav_drawer.set_state("closed")
|
||||
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
|
||||
|
||||
if no_transition:
|
||||
self.root.ids.screen_manager.transition = self.slide_transition
|
||||
|
||||
def close_plugins_action(self, sender=None):
|
||||
self.open_conversations(direction="right")
|
||||
|
||||
def plugins_fm_got_path(self, path):
|
||||
self.plugins_fm_exited()
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
self.sideband.config["command_plugins_path"] = path
|
||||
self.sideband.save_configuration()
|
||||
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("Using \""+str(path)+"\" as plugin directory")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Directory Set",
|
||||
text="Using \""+str(path)+"\" as plugin directory",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while setting plugins directory to \"{path}\": "+str(e), RNS.LOG_ERROR)
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
toast("Could not set plugins directory to \""+str(path)+"\"")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
e_dialog = MDDialog(
|
||||
title="Error",
|
||||
text="Could not set plugins directory to \""+str(path)+"\"",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=e_dialog.dismiss)
|
||||
e_dialog.open()
|
||||
|
||||
def plugins_fm_exited(self, *args):
|
||||
self.manager_open = False
|
||||
self.file_manager.close()
|
||||
|
||||
def plugins_select_directory_action(self, sender=None):
|
||||
perm_ok = False
|
||||
if self.sideband.config["command_plugins_path"] == None:
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
perm_ok = self.check_storage_permission()
|
||||
path = primary_external_storage_path()
|
||||
|
||||
else:
|
||||
perm_ok = True
|
||||
path = os.path.expanduser("~")
|
||||
|
||||
else:
|
||||
perm_ok = True
|
||||
path = self.sideband.config["command_plugins_path"]
|
||||
|
||||
if perm_ok and path != None:
|
||||
try:
|
||||
self.file_manager = MDFileManager(
|
||||
exit_manager=self.plugins_fm_exited,
|
||||
select_path=self.plugins_fm_got_path,
|
||||
)
|
||||
|
||||
self.file_manager.show(path)
|
||||
|
||||
except Exception as e:
|
||||
self.sideband.config["command_plugins_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("Error reading directory, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Error",
|
||||
text="Could not read directory, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
else:
|
||||
self.sideband.config["command_plugins_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("No file access, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Error",
|
||||
text="No file access, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
|
||||
### Telemetry Screen
|
||||
######################################
|
||||
|
||||
|
@ -3929,7 +4099,19 @@ class SidebandApp(MDApp):
|
|||
self.sideband.config["map_storage_file"] = path
|
||||
self.sideband.config["map_storage_path"] = str(pathlib.Path(path).parent.resolve())
|
||||
self.sideband.save_configuration()
|
||||
toast("Using \""+os.path.basename(path)+"\" as offline map")
|
||||
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("Using \""+os.path.basename(path)+"\" as offline map")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Map Set",
|
||||
text="Using \""+os.path.basename(path)+"\" as offline map",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while loading map \"{path}\": "+str(e), RNS.LOG_ERROR)
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
|
@ -3988,12 +4170,32 @@ class SidebandApp(MDApp):
|
|||
except Exception as e:
|
||||
self.sideband.config["map_storage_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
toast("Error reading directory, check permissions!")
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("Error reading directory, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Error",
|
||||
text="Could not read directory, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
else:
|
||||
self.sideband.config["map_storage_path"] = None
|
||||
self.sideband.save_configuration()
|
||||
toast("No file access, check permissions!")
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
toast("No file access, check permissions!")
|
||||
else:
|
||||
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
ate_dialog = MDDialog(
|
||||
title="Error",
|
||||
text="No file access, check permissions!",
|
||||
buttons=[ ok_button ],
|
||||
)
|
||||
ok_button.bind(on_release=ate_dialog.dismiss)
|
||||
ate_dialog.open()
|
||||
|
||||
def map_get_offline_source(self):
|
||||
if self.offline_source != None:
|
||||
|
|
|
@ -7,6 +7,7 @@ import time
|
|||
import struct
|
||||
import sqlite3
|
||||
import random
|
||||
import shlex
|
||||
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
import RNS.Interfaces.Interface as Interface
|
||||
|
@ -16,6 +17,7 @@ import multiprocessing.connection
|
|||
from threading import Lock
|
||||
from .res import sideband_fb_data
|
||||
from .sense import Telemeter, Commands
|
||||
from .plugins import SidebandCommandPlugin, SidebandServicePlugin
|
||||
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
from jnius import autoclass, cast
|
||||
|
@ -243,6 +245,11 @@ class SidebandCore():
|
|||
RNS.Transport.register_announce_handler(self)
|
||||
RNS.Transport.register_announce_handler(self.propagation_detector)
|
||||
|
||||
self.active_command_plugins = {}
|
||||
self.active_service_plugins = {}
|
||||
if self.is_service or self.is_standalone:
|
||||
self.__load_plugins()
|
||||
|
||||
def clear_tmp_dir(self):
|
||||
if os.path.isdir(self.tmp_dir):
|
||||
for file in os.listdir(self.tmp_dir):
|
||||
|
@ -591,6 +598,13 @@ class SidebandCore():
|
|||
if not "telemetry_s_information_text" in self.config:
|
||||
self.config["telemetry_s_information_text"] = ""
|
||||
|
||||
if not "service_plugins_enabled" in self.config:
|
||||
self.config["service_plugins_enabled"] = False
|
||||
if not "command_plugins_enabled" in self.config:
|
||||
self.config["command_plugins_enabled"] = False
|
||||
if not "command_plugins_path" in self.config:
|
||||
self.config["command_plugins_path"] = None
|
||||
|
||||
if not "map_history_limit" in self.config:
|
||||
self.config["map_history_limit"] = 7*24*60*60
|
||||
if not "map_lat" in self.config:
|
||||
|
@ -656,6 +670,58 @@ class SidebandCore():
|
|||
if self.is_client:
|
||||
self.setstate("wants.settings_reload", True)
|
||||
|
||||
def __load_plugins(self):
|
||||
plugins_path = self.config["command_plugins_path"]
|
||||
command_plugins_enabled = self.config["command_plugins_enabled"] == True
|
||||
service_plugins_enabled = self.config["service_plugins_enabled"] == True
|
||||
plugins_enabled = service_plugins_enabled
|
||||
|
||||
if plugins_enabled:
|
||||
if plugins_path != None:
|
||||
if os.path.isdir(plugins_path):
|
||||
for file in os.listdir(plugins_path):
|
||||
if file.lower().endswith(".py"):
|
||||
plugin_globals = {}
|
||||
plugin_globals["SidebandServicePlugin"] = SidebandServicePlugin
|
||||
plugin_globals["SidebandCommandPlugin"] = SidebandCommandPlugin
|
||||
RNS.log("Loading plugin \""+str(file)+"\"", RNS.LOG_NOTICE)
|
||||
plugin_path = os.path.join(plugins_path, file)
|
||||
exec(open(plugin_path).read(), plugin_globals)
|
||||
plugin_class = plugin_globals["plugin_class"]
|
||||
|
||||
if plugin_class != None:
|
||||
plugin = plugin_class(self)
|
||||
plugin.start()
|
||||
|
||||
if plugin.is_running():
|
||||
if issubclass(type(plugin), SidebandCommandPlugin) and command_plugins_enabled:
|
||||
command_name = plugin.command_name
|
||||
if not command_name in self.active_command_plugins:
|
||||
self.active_command_plugins[command_name] = plugin
|
||||
RNS.log("Registered "+str(plugin)+" as handler for command \""+str(command_name)+"\"", RNS.LOG_NOTICE)
|
||||
else:
|
||||
RNS.log("Could not register "+str(plugin)+" as handler for command \""+str(command_name)+"\". Command name was already registered", RNS.LOG_ERROR)
|
||||
|
||||
elif issubclass(type(plugin), SidebandServicePlugin):
|
||||
service_name = plugin.service_name
|
||||
if not service_name in self.active_service_plugins:
|
||||
self.active_service_plugins[service_name] = plugin
|
||||
RNS.log("Registered "+str(plugin)+" for service \""+str(service_name)+"\"", RNS.LOG_NOTICE)
|
||||
else:
|
||||
RNS.log("Could not register "+str(plugin)+" for service \""+str(service_name)+"\". Service name was already registered", RNS.LOG_ERROR)
|
||||
try:
|
||||
plugin.stop()
|
||||
except Exception as e:
|
||||
pass
|
||||
del plugin
|
||||
|
||||
else:
|
||||
RNS.log("Unknown plugin type was loaded, ignoring it.", RNS.LOG_ERROR)
|
||||
else:
|
||||
RNS.log("Plugin "+str(plugin)+" failed to start, ignoring it.", RNS.LOG_ERROR)
|
||||
del plugin
|
||||
|
||||
|
||||
def reload_configuration(self):
|
||||
self.__reload_config()
|
||||
|
||||
|
@ -3325,6 +3391,8 @@ class SidebandCore():
|
|||
commands.append({Commands.SIGNAL_REPORT: True})
|
||||
elif content.startswith("ping"):
|
||||
commands.append({Commands.PING: True})
|
||||
else:
|
||||
commands.append({Commands.PLUGIN_COMMAND: content})
|
||||
|
||||
if len(commands) == 0:
|
||||
return False
|
||||
|
@ -3608,6 +3676,19 @@ class SidebandCore():
|
|||
except Exception as e:
|
||||
RNS.log("Error while ingesting LXMF message "+RNS.prettyhexrep(message.hash)+" to database: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def handle_plugin_command(self, command_string, message):
|
||||
try:
|
||||
call = shlex.split(command_string)
|
||||
command = call[0]
|
||||
arguments = call[1:]
|
||||
if command in self.active_command_plugins:
|
||||
RNS.log("Handling command \""+str(command)+"\" via command plugin "+str(self.active_command_plugins[command]), RNS.LOG_DEBUG)
|
||||
self.active_command_plugins[command].handle_command(arguments, message)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while handling a plugin command. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
def handle_commands(self, commands, message):
|
||||
try:
|
||||
context_dest = message.source_hash
|
||||
|
@ -3648,6 +3729,9 @@ class SidebandCore():
|
|||
|
||||
self.send_message(phy_str, context_dest, False, skip_fields=True, no_display=True)
|
||||
|
||||
elif self.config["command_plugins_enabled"] and Commands.PLUGIN_COMMAND in command:
|
||||
self.handle_plugin_command(command[Commands.PLUGIN_COMMAND], message)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while handling commands: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from .geo import orthodromic_distance, euclidian_distance
|
|||
from .geo import azalt, angle_to_horizon, radio_horizon, shared_radio_horizon
|
||||
|
||||
class Commands():
|
||||
PLUGIN_COMMAND = 0x00
|
||||
TELEMETRY_REQUEST = 0x01
|
||||
PING = 0x02
|
||||
ECHO = 0x03
|
||||
|
|
|
@ -133,6 +133,15 @@ MDNavigationLayout:
|
|||
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||
|
||||
|
||||
OneLineIconListItem:
|
||||
text: "Plugins"
|
||||
on_release: root.ids.screen_manager.app.plugins_action(self)
|
||||
|
||||
IconLeftWidget:
|
||||
icon: "google-circles-extended"
|
||||
on_release: root.ids.screen_manager.app.keys_action(self)
|
||||
|
||||
|
||||
OneLineIconListItem:
|
||||
text: "Guide"
|
||||
on_release: root.ids.screen_manager.app.guide_action(self)
|
||||
|
@ -1125,6 +1134,84 @@ MDScreen:
|
|||
on_release: root.app.identity_restore_action(self)
|
||||
"""
|
||||
|
||||
layout_plugins_screen = """
|
||||
MDScreen:
|
||||
name: "plugins_screen"
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDTopAppBar:
|
||||
title: "Plugins & Services"
|
||||
anchor_title: "left"
|
||||
elevation: 0
|
||||
left_action_items:
|
||||
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||
right_action_items:
|
||||
[
|
||||
['close', lambda x: root.app.close_plugins_action(self)],
|
||||
]
|
||||
|
||||
ScrollView:
|
||||
id:plugins_scrollview
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "vertical"
|
||||
spacing: "24dp"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: [dp(35), dp(35), dp(35), dp(35)]
|
||||
|
||||
|
||||
MDLabel:
|
||||
id: plugins_info
|
||||
markup: True
|
||||
text: ""
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
padding: [0,0,dp(26),dp(0)]
|
||||
height: dp(24)
|
||||
|
||||
MDLabel:
|
||||
text: "Enable Plugins"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
id: settings_service_plugins_enabled
|
||||
pos_hint: {"center_y": 0.3}
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
padding: [0,0,dp(26),dp(0)]
|
||||
height: dp(24)
|
||||
|
||||
MDLabel:
|
||||
text: "Enable Command Plugins"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
id: settings_command_plugins_enabled
|
||||
pos_hint: {"center_y": 0.3}
|
||||
active: False
|
||||
|
||||
MDRectangleFlatIconButton:
|
||||
id: plugins_display
|
||||
icon: "folder-cog-outline"
|
||||
text: "Select Plugins Directory"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.app.plugins_select_directory_action(self)
|
||||
"""
|
||||
|
||||
layout_settings_screen = """
|
||||
MDScreen:
|
||||
name: "settings_screen"
|
||||
|
|
|
@ -254,6 +254,9 @@ class Messages():
|
|||
extra_content = "[font=RobotoMono-Regular]> ping[/font]\n"
|
||||
if Commands.SIGNAL_REPORT in command:
|
||||
extra_content = "[font=RobotoMono-Regular]> sig[/font]\n"
|
||||
if Commands.PLUGIN_COMMAND in command:
|
||||
cmd_content = command[Commands.PLUGIN_COMMAND]
|
||||
extra_content = "[font=RobotoMono-Regular]> "+str(cmd_content)+"[/font]\n"
|
||||
extra_content = extra_content[:-1]
|
||||
force_markup = True
|
||||
except Exception as e:
|
||||
|
|
Loading…
Reference in New Issue