mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2025-10-08 14:54:36 +00:00
Add plugin update functionality
This commit is contained in:
parent
8edc8c087a
commit
a336d693a3
1 changed files with 187 additions and 51 deletions
|
|
@ -21,6 +21,22 @@ PLUGIN_DIRECTORY = _env["python_directory_user"]
|
||||||
PLUGIN_ENTRYPOINT = "Main"
|
PLUGIN_ENTRYPOINT = "Main"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_config():
|
||||||
|
is_config_updated = False
|
||||||
|
if "Community Plugin Manager" not in ba.app.config:
|
||||||
|
ba.app.config["Community Plugin Manager"] = {}
|
||||||
|
if "Installed Plugins" not in ba.app.config["Community Plugin Manager"]:
|
||||||
|
ba.app.config["Community Plugin Manager"]["Installed Plugins"] = {}
|
||||||
|
is_config_updated = True
|
||||||
|
for plugin_name in ba.app.config["Community Plugin Manager"]["Installed Plugins"].keys():
|
||||||
|
plugin = PluginLocal(plugin_name)
|
||||||
|
if not plugin.is_installed:
|
||||||
|
del ba.app.config["Community Plugin Manager"]["Installed Plugins"][plugin_name]
|
||||||
|
is_config_updated = True
|
||||||
|
if is_config_updated:
|
||||||
|
ba.app.config.commit()
|
||||||
|
|
||||||
|
|
||||||
async def send_network_request(request):
|
async def send_network_request(request):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
response = await loop.run_in_executor(None, urllib.request.urlopen, request)
|
response = await loop.run_in_executor(None, urllib.request.urlopen, request)
|
||||||
|
|
@ -57,34 +73,112 @@ class CategoryAll(Category):
|
||||||
self._plugins = plugins
|
self._plugins = plugins
|
||||||
|
|
||||||
|
|
||||||
|
class PluginLocal:
|
||||||
|
def __init__(self, name):
|
||||||
|
"""
|
||||||
|
Initialize a plugin locally installed on the device.
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{name}.py")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_installed(self):
|
||||||
|
return os.path.isfile(self.install_path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_installed_via_plugin_manager(self):
|
||||||
|
return self.name in ba.app.config["Community Plugin Manager"]["Installed Plugins"]
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
if self.name not in ba.app.config["Community Plugin Manager"]["Installed Plugins"]:
|
||||||
|
ba.app.config["Community Plugin Manager"]["Installed Plugins"][self.name] = {}
|
||||||
|
return self
|
||||||
|
|
||||||
|
def uninstall(self):
|
||||||
|
try:
|
||||||
|
os.remove(self.install_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
del ba.app.config["Community Plugin Manager"]["Installed Plugins"][self.name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
ba.app.config.commit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
try:
|
||||||
|
version = ba.app.config["Community Plugin Manager"]["Installed Plugins"][self.name]["version"]
|
||||||
|
except KeyError:
|
||||||
|
version = None
|
||||||
|
return version
|
||||||
|
|
||||||
|
def set_version(self, version):
|
||||||
|
ba.app.config["Community Plugin Manager"]["Installed Plugins"][self.name]["version"] = version
|
||||||
|
return self
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
ba.app.config.commit()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
def __init__(self, plugin, base_download_url):
|
def __init__(self, plugin, base_download_url):
|
||||||
|
"""
|
||||||
|
Initialize a plugin from network repository.
|
||||||
|
"""
|
||||||
self.name, self.info = plugin
|
self.name, self.info = plugin
|
||||||
self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{self.name}.py")
|
self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{self.name}.py")
|
||||||
self.download_url = f"{base_download_url}/{self.name}.py"
|
self.download_url = f"{base_download_url}/{self.name}.py"
|
||||||
self.entry_point = f"{self.name}.{PLUGIN_ENTRYPOINT}"
|
self.entry_point = f"{self.name}.{PLUGIN_ENTRYPOINT}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Plugin({self.name})>"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def installed(self):
|
def is_installed(self):
|
||||||
return os.path.isfile(self.install_path)
|
return os.path.isfile(self.install_path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def is_enabled(self):
|
||||||
try:
|
try:
|
||||||
return ba.app.config["Plugins"][self.entry_point]["enabled"]
|
return ba.app.config["Plugins"][self.entry_point]["enabled"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def install(self):
|
@property
|
||||||
|
def latest_version(self):
|
||||||
|
return next(iter(self.info["versions"]))
|
||||||
|
|
||||||
|
def get_local(self):
|
||||||
|
if not self.is_installed:
|
||||||
|
raise ValueError("Plugin is not installed")
|
||||||
|
return PluginLocal(self.name)
|
||||||
|
|
||||||
|
async def _download_plugin(self):
|
||||||
response = await send_network_request(self.download_url)
|
response = await send_network_request(self.download_url)
|
||||||
with open(self.install_path, "wb") as fout:
|
with open(self.install_path, "wb") as fout:
|
||||||
fout.write(response.read())
|
fout.write(response.read())
|
||||||
|
(
|
||||||
|
self.get_local()
|
||||||
|
.initialize()
|
||||||
|
.set_version(self.latest_version)
|
||||||
|
.save()
|
||||||
|
)
|
||||||
|
|
||||||
|
async def install(self):
|
||||||
|
await self._download_plugin()
|
||||||
self.enable()
|
self.enable()
|
||||||
_ba.screenmessage("Plugin Installed")
|
ba.screenmessage("Plugin Installed")
|
||||||
|
|
||||||
def uninstall(self):
|
def uninstall(self):
|
||||||
os.remove(self.install_path)
|
self.get_local().uninstall()
|
||||||
_ba.screenmessage("Plugin Uninstalled")
|
ba.screenmessage("Plugin Uninstalled")
|
||||||
|
|
||||||
|
async def update(self):
|
||||||
|
await self._download_plugin()
|
||||||
|
ba.screenmessage("Plugin Updated")
|
||||||
|
|
||||||
def _set_status(self, to_enable=True):
|
def _set_status(self, to_enable=True):
|
||||||
if self.entry_point not in ba.app.config["Plugins"]:
|
if self.entry_point not in ba.app.config["Plugins"]:
|
||||||
|
|
@ -93,19 +187,17 @@ class Plugin:
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self._set_status(to_enable=True)
|
self._set_status(to_enable=True)
|
||||||
ba.app.config.apply_and_commit()
|
ba.screenmessage("Plugin Enabled")
|
||||||
_ba.screenmessage("Plugin Enabled")
|
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self._set_status(to_enable=False)
|
self._set_status(to_enable=False)
|
||||||
ba.app.config.commit()
|
ba.screenmessage("Plugin Disabled")
|
||||||
_ba.screenmessage("Plugin Disabled")
|
|
||||||
|
|
||||||
|
|
||||||
class PluginWindow(popup.PopupWindow):
|
class PluginWindow(popup.PopupWindow):
|
||||||
def __init__(self, plugin, origin_widget):
|
def __init__(self, plugin, origin_widget):
|
||||||
|
self.plugin = plugin
|
||||||
uiscale = ba.app.ui.uiscale
|
uiscale = ba.app.ui.uiscale
|
||||||
b_color = (0.6, 0.53, 0.63)
|
|
||||||
b_text_color = (0.75, 0.7, 0.8)
|
b_text_color = (0.75, 0.7, 0.8)
|
||||||
s = 1.1 if uiscale is ba.UIScale.SMALL else 1.27 if ba.UIScale.MEDIUM else 1.57
|
s = 1.1 if uiscale is ba.UIScale.SMALL else 1.27 if ba.UIScale.MEDIUM else 1.57
|
||||||
width = 360 * s
|
width = 360 * s
|
||||||
|
|
@ -124,9 +216,10 @@ class PluginWindow(popup.PopupWindow):
|
||||||
if uiscale is ba.UIScale.MEDIUM else 1.0),
|
if uiscale is ba.UIScale.MEDIUM else 1.0),
|
||||||
scale_origin_stack_offset=scale_origin)
|
scale_origin_stack_offset=scale_origin)
|
||||||
pos = height * 0.8
|
pos = height * 0.8
|
||||||
|
plugin_title = f"{plugin.name} (v{plugin.latest_version})"
|
||||||
ba.textwidget(parent=self._root_widget,
|
ba.textwidget(parent=self._root_widget,
|
||||||
position=(width * 0.49, pos), size=(0, 0),
|
position=(width * 0.49, pos), size=(0, 0),
|
||||||
h_align='center', v_align='center', text=plugin.name,
|
h_align='center', v_align='center', text=plugin_title,
|
||||||
scale=text_scale * 1.25, color=color,
|
scale=text_scale * 1.25, color=color,
|
||||||
maxwidth=width * 0.9)
|
maxwidth=width * 0.9)
|
||||||
pos -= 25
|
pos -= 25
|
||||||
|
|
@ -153,75 +246,99 @@ class PluginWindow(popup.PopupWindow):
|
||||||
text=plugin.info["description"],
|
text=plugin.info["description"],
|
||||||
scale=text_scale * 0.6, color=color,
|
scale=text_scale * 0.6, color=color,
|
||||||
maxwidth=width * 0.95)
|
maxwidth=width * 0.95)
|
||||||
b3_color = (0.8, 0.15, 0.35)
|
b1_color = (0.6, 0.53, 0.63)
|
||||||
# b3_color = (0.15, 0.80, 0.35)
|
b2_color = (0.8, 0.15, 0.35)
|
||||||
|
b3_color = (0.2, 0.8, 0.3)
|
||||||
pos = height * 0.1
|
pos = height * 0.1
|
||||||
button_size = (80 * s, 40 * s)
|
button_size = (80 * s, 40 * s)
|
||||||
|
|
||||||
if plugin.installed:
|
if plugin.is_installed:
|
||||||
if plugin.enabled:
|
if plugin.is_enabled:
|
||||||
button1_label = "Disable"
|
button1_label = "Disable"
|
||||||
self.button1_action = plugin.disable
|
button1_action = self.disable
|
||||||
else:
|
else:
|
||||||
button1_label = "Enable"
|
button1_label = "Enable"
|
||||||
self.button1_action = plugin.enable
|
button1_action = self.enable
|
||||||
button2_label = "Uninstall"
|
button2_label = "Uninstall"
|
||||||
self.button2_action = plugin.uninstall
|
button2_action = self.uninstall
|
||||||
|
has_update = plugin.get_local().version != plugin.latest_version
|
||||||
|
if has_update:
|
||||||
|
button3_label = "Update"
|
||||||
|
button3_action = self.update
|
||||||
else:
|
else:
|
||||||
button1_label = "Install"
|
button1_label = "Install"
|
||||||
loop = asyncio.get_event_loop()
|
button1_action = self.install
|
||||||
self.button1_action = ba.Call(loop.create_task, plugin.install())
|
|
||||||
# button1_action = asyncio.run, plugin.install
|
|
||||||
button3_label = "OK"
|
|
||||||
|
|
||||||
# button1 =
|
|
||||||
ba.buttonwidget(parent=self._root_widget,
|
ba.buttonwidget(parent=self._root_widget,
|
||||||
position=(width * 0.1, pos),
|
position=(width * 0.1, pos),
|
||||||
size=button_size,
|
size=button_size,
|
||||||
on_activate_call=self.button1_action_func,
|
on_activate_call=button1_action,
|
||||||
color=b_color,
|
color=b1_color,
|
||||||
textcolor=b_text_color,
|
textcolor=b_text_color,
|
||||||
button_type='square',
|
button_type='square',
|
||||||
text_scale=1,
|
text_scale=1,
|
||||||
label=button1_label)
|
label=button1_label)
|
||||||
|
|
||||||
if plugin.installed:
|
if plugin.is_installed:
|
||||||
# button2 =
|
|
||||||
ba.buttonwidget(parent=self._root_widget,
|
ba.buttonwidget(parent=self._root_widget,
|
||||||
position=(width * 0.4, pos),
|
position=(width * 0.4, pos),
|
||||||
size=button_size,
|
size=button_size,
|
||||||
on_activate_call=self.button2_action_func,
|
on_activate_call=button2_action,
|
||||||
color=b3_color,
|
color=b2_color,
|
||||||
textcolor=b_text_color,
|
textcolor=b_text_color,
|
||||||
button_type='square',
|
button_type='square',
|
||||||
text_scale=1,
|
text_scale=1,
|
||||||
label=button2_label)
|
label=button2_label)
|
||||||
|
|
||||||
|
if has_update:
|
||||||
button3 = ba.buttonwidget(parent=self._root_widget,
|
button3 = ba.buttonwidget(parent=self._root_widget,
|
||||||
position=(width * 0.7, pos),
|
position=(width * 0.7, pos),
|
||||||
size=button_size,
|
size=button_size,
|
||||||
on_activate_call=self.button3_action,
|
on_activate_call=button3_action,
|
||||||
|
color=b3_color,
|
||||||
|
textcolor=b_text_color,
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
button_type='square',
|
button_type='square',
|
||||||
text_scale=1,
|
text_scale=1,
|
||||||
label=button3_label)
|
label=button3_label)
|
||||||
ba.containerwidget(edit=self._root_widget,
|
ba.containerwidget(edit=self._root_widget,
|
||||||
on_cancel_call=button3.activate)
|
on_cancel_call=self.ok)
|
||||||
ba.containerwidget(edit=self._root_widget, selected_child=button3)
|
# ba.containerwidget(edit=self._root_widget, selected_child=button3)
|
||||||
ba.containerwidget(edit=self._root_widget, start_button=button3)
|
# ba.containerwidget(edit=self._root_widget, start_button=button3)
|
||||||
|
|
||||||
def button1_action_func(self) -> None:
|
def ok(self) -> None:
|
||||||
self.button1_action()
|
|
||||||
self.button3_action()
|
|
||||||
|
|
||||||
def button2_action_func(self) -> None:
|
|
||||||
self.button2_action()
|
|
||||||
self.button3_action()
|
|
||||||
|
|
||||||
def button3_action(self) -> None:
|
|
||||||
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def button(fn):
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
fn(self, *args, **kwargs)
|
||||||
|
self.ok()
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@button
|
||||||
|
def disable(self) -> None:
|
||||||
|
self.plugin.disable()
|
||||||
|
|
||||||
|
@button
|
||||||
|
def enable(self) -> None:
|
||||||
|
self.plugin.enable()
|
||||||
|
|
||||||
|
@button
|
||||||
|
def install(self):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.create_task(self.plugin.install())
|
||||||
|
|
||||||
|
@button
|
||||||
|
def uninstall(self):
|
||||||
|
self.plugin.uninstall()
|
||||||
|
self.ok()
|
||||||
|
|
||||||
|
@button
|
||||||
|
def update(self):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.create_task(self.plugin.update())
|
||||||
|
|
||||||
|
|
||||||
class PluginManager:
|
class PluginManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -542,11 +659,24 @@ class PluginManagerWindow(ba.Window, PluginManager):
|
||||||
|
|
||||||
plugins = await self.categories[self.selected_category].get_plugins()
|
plugins = await self.categories[self.selected_category].get_plugins()
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
|
if plugin.is_installed:
|
||||||
|
if plugin.is_enabled:
|
||||||
|
local_plugin = plugin.get_local()
|
||||||
|
if not local_plugin.is_installed_via_plugin_manager:
|
||||||
|
color = (0.8, 0.2, 0.2)
|
||||||
|
elif local_plugin.version == plugin.latest_version:
|
||||||
|
color = (0, 1, 0)
|
||||||
|
else:
|
||||||
|
color = (1, 0.6, 0)
|
||||||
|
else:
|
||||||
|
color = (0.5, 0.5, 0.5)
|
||||||
|
else:
|
||||||
|
color = (1, 1, 1)
|
||||||
ba.textwidget(parent=self._columnwidget,
|
ba.textwidget(parent=self._columnwidget,
|
||||||
size=(410, 30),
|
size=(410, 30),
|
||||||
selectable=True,
|
selectable=True,
|
||||||
always_highlight=True,
|
always_highlight=True,
|
||||||
color=(1, 1, 1),
|
color=color,
|
||||||
on_select_call=lambda: None,
|
on_select_call=lambda: None,
|
||||||
text=plugin.name,
|
text=plugin.name,
|
||||||
on_activate_call=ba.Call(PluginWindow, plugin, self._root_widget),
|
on_activate_call=ba.Call(PluginWindow, plugin, self._root_widget),
|
||||||
|
|
@ -874,6 +1004,7 @@ class NewAllSettingsWindow(ba.Window):
|
||||||
class EntryPoint(ba.Plugin):
|
class EntryPoint(ba.Plugin):
|
||||||
def on_app_running(self) -> None:
|
def on_app_running(self) -> None:
|
||||||
"""Called when the app is being launched."""
|
"""Called when the app is being launched."""
|
||||||
|
setup_config()
|
||||||
from bastd.ui.settings import allsettings
|
from bastd.ui.settings import allsettings
|
||||||
allsettings.AllSettingsWindow = NewAllSettingsWindow
|
allsettings.AllSettingsWindow = NewAllSettingsWindow
|
||||||
asyncio.set_event_loop(ba._asyncio._asyncio_event_loop)
|
asyncio.set_event_loop(ba._asyncio._asyncio_event_loop)
|
||||||
|
|
@ -893,3 +1024,8 @@ class EntryPoint(ba.Plugin):
|
||||||
def on_app_shutdown(self) -> None:
|
def on_app_shutdown(self) -> None:
|
||||||
"""Called before closing the application."""
|
"""Called before closing the application."""
|
||||||
print("shutdown")
|
print("shutdown")
|
||||||
|
# print(ba.app.config["Community Plugin Manager"])
|
||||||
|
# with open(_env["config_file_path"], "r") as fin:
|
||||||
|
# c = fin.read()
|
||||||
|
# import json
|
||||||
|
# print(json.loads(c)["Community Plugin Manager"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue