Cleaned up plugin subsystem

This commit is contained in:
Eric Froemling 2020-10-16 11:28:01 -07:00
parent 0f9fe41542
commit 05c93cc776
7 changed files with 95 additions and 52 deletions

View File

@ -1,6 +1,8 @@
### 1.5.27 (20218)
- Language functionality has been consolidated into a LanguageSubsystem object at ba.app.lang
- ba.get_valid_languages() is now an attr: ba.app.lang.available_languages
- Achievement functionality has been consolidated into an AchievementSubsystem object at ba.app.ach
- Plugin functionality has been consolidated into a PluginSubsystem obj at ba.app.plugins
### 1.5.26 (20217)
- Simplified licensing header on python scripts.

View File

@ -20,7 +20,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
set_analytics_screen, charstr, textwidget, time, timer,
open_url, widget)
from ba._activity import Activity
from ba._plugin import PotentialPlugin, Plugin
from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem
from ba._actor import Actor
from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation
from ba._nodeactor import NodeActor

View File

@ -175,6 +175,7 @@ class App:
from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
from ba._achievement import AchievementSubsystem
from ba._plugin import PluginSubsystem
# Config.
self.config_file_healthy = False
@ -198,10 +199,6 @@ class App:
self.iircade_mode: bool = self._env['iircade_mode']
assert isinstance(self.headless_mode, bool)
# Plugins.
self.potential_plugins: List[ba.PotentialPlugin] = []
self.active_plugins: Dict[str, ba.Plugin] = {}
# Misc.
self.metascan: Optional[_meta.ScanResults] = None
self.tips: List[str] = []
@ -241,6 +238,7 @@ class App:
self.last_ad_purpose = 'invalid'
self.attempted_first_ad = False
self.plugins = PluginSubsystem()
self.music = MusicSubsystem()
self.lang = LanguageSubsystem()
self.ach = AchievementSubsystem()
@ -291,7 +289,6 @@ class App:
(internal)"""
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
# pylint: disable=too-many-statements
from ba import _apputils
from ba import _appconfig
from ba import _achievement
@ -401,49 +398,13 @@ class App:
_ba.pushcall(do_auto_sign_in)
# Load up our plugins and go ahead and call their on_app_launch calls.
self.load_plugins()
for plugin in self.active_plugins.values():
try:
plugin.on_app_launch()
except Exception:
from ba import _error
_error.print_exception('Error in plugin on_app_launch()')
self.plugins.on_app_launch()
self.ran_on_app_launch = True
# from ba._dependency import test_depset
# test_depset()
def load_plugins(self) -> None:
"""(internal)"""
from ba._general import getclass
from ba._plugin import Plugin
# Note: the plugins we load is purely based on what's enabled
# in the app config. Our meta-scan gives us a list of available
# plugins, but that is only used to give the user a list of plugins
# that they can enable. (we wouldn't want to look at meta-scan here
# anyway because it may not be done yet at this point in the launch)
plugstates: Dict[str, Dict] = self.config.get('Plugins', {})
assert isinstance(plugstates, dict)
plugkeys: List[str] = sorted(key for key, val in plugstates.items()
if val.get('enabled', False))
for plugkey in plugkeys:
try:
cls = getclass(plugkey, Plugin)
except Exception as exc:
_ba.log(f"Error loading plugin class '{plugkey}': {exc}",
to_server=False)
continue
try:
plugin = cls()
assert plugkey not in self.active_plugins
self.active_plugins[plugkey] = plugin
except Exception:
from ba import _error
_error.print_exception(f'Error loading plugin: {plugkey}')
def read_config(self) -> None:
"""(internal)"""
from ba import _appconfig
@ -558,7 +519,6 @@ class App:
"""Run when the app resumes from a suspended state."""
self.music.on_app_resume()
self.fg_state += 1
# Mark our cached tourneys as invalid so anyone using them knows
@ -657,13 +617,13 @@ class App:
# If we're still not signed in and have pending codes,
# inform the user that they need to sign in to use them.
if _ba.app.pending_promo_codes:
if self.pending_promo_codes:
_ba.screenmessage(
Lstr(resource='signInForPromoCodeText'),
color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
_ba.app.pending_promo_codes.append(code)
self.pending_promo_codes.append(code)
_ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL)
return
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
@ -681,7 +641,7 @@ class App:
def _test_https(self) -> None:
"""Testing https support.
(would be nice to get this working on our custom python builds; need
(would be nice to get this working on our custom Python builds; need
to wrangle certificates somehow).
"""
import urllib.request

View File

@ -68,6 +68,7 @@ def handle_scan_results(results: ScanResults) -> None:
_ba.log(textwrap.indent(results.errors, 'Error (meta-scan): '))
# Handle plugins.
plugs = _ba.app.plugins
config_changed = False
found_new = False
plugstates: Dict[str, Dict] = _ba.app.config.setdefault('Plugins', {})
@ -75,7 +76,7 @@ def handle_scan_results(results: ScanResults) -> None:
# Create a potential-plugin for each class we found in the scan.
for class_path in results.plugins:
_ba.app.potential_plugins.append(
plugs.potential_plugins.append(
PotentialPlugin(display_name=Lstr(value=class_path),
class_path=class_path,
available=True))
@ -90,12 +91,12 @@ def handle_scan_results(results: ScanResults) -> None:
enabled = plugstate.get('enabled', False)
assert isinstance(enabled, bool)
if enabled and class_path not in results.plugins:
_ba.app.potential_plugins.append(
plugs.potential_plugins.append(
PotentialPlugin(display_name=Lstr(value=class_path),
class_path=class_path,
available=False))
_ba.app.potential_plugins.sort(key=lambda p: p.class_path)
plugs.potential_plugins.sort(key=lambda p: p.class_path)
if found_new:
_ba.screenmessage(Lstr(resource='pluginsDetectedText'),

View File

@ -6,11 +6,65 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass
import _ba
if TYPE_CHECKING:
from typing import List, Dict
import ba
class PluginSubsystem:
"""Subsystem for plugin handling in the app.
Category: App Classes
Access the single shared instance of this class at 'ba.app.plugins'.
"""
def __init__(self) -> None:
self.potential_plugins: List[ba.PotentialPlugin] = []
self.active_plugins: Dict[str, ba.Plugin] = {}
def on_app_launch(self) -> None:
"""Should be called at app launch time."""
# Load up our plugins and go ahead and call their on_app_launch calls.
self.load_plugins()
for plugin in self.active_plugins.values():
try:
plugin.on_app_launch()
except Exception:
from ba import _error
_error.print_exception('Error in plugin on_app_launch()')
def load_plugins(self) -> None:
"""(internal)"""
from ba._general import getclass
# Note: the plugins we load is purely based on what's enabled
# in the app config. Our meta-scan gives us a list of available
# plugins, but that is only used to give the user a list of plugins
# that they can enable. (we wouldn't want to look at meta-scan here
# anyway because it may not be done yet at this point in the launch)
plugstates: Dict[str, Dict] = _ba.app.config.get('Plugins', {})
assert isinstance(plugstates, dict)
plugkeys: List[str] = sorted(key for key, val in plugstates.items()
if val.get('enabled', False))
for plugkey in plugkeys:
try:
cls = getclass(plugkey, Plugin)
except Exception as exc:
_ba.log(f"Error loading plugin class '{plugkey}': {exc}",
to_server=False)
continue
try:
plugin = cls()
assert plugkey not in self.active_plugins
self.active_plugins[plugkey] = plugin
except Exception:
from ba import _error
_error.print_exception(f'Error loading plugin: {plugkey}')
@dataclass
class PotentialPlugin:
"""Represents a ba.Plugin which can potentially be loaded.

View File

@ -97,11 +97,11 @@ class PluginSettingsWindow(ba.Window):
ba.screenmessage('Still scanning plugins; please try again.',
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
pluglist = ba.app.potential_plugins
pluglist = ba.app.plugins.potential_plugins
plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {})
assert isinstance(plugstates, dict)
for i, availplug in enumerate(pluglist):
active = availplug.class_path in ba.app.active_plugins
active = availplug.class_path in ba.app.plugins.active_plugins
plugstate = plugstates.setdefault(availplug.class_path, {})
checked = plugstate.get('enabled', False)

View File

@ -158,6 +158,7 @@
<li><a href="#class_ba_MusicPlayer">ba.MusicPlayer</a></li>
<li><a href="#class_ba_MusicSubsystem">ba.MusicSubsystem</a></li>
<li><a href="#class_ba_Plugin">ba.Plugin</a></li>
<li><a href="#class_ba_PluginSubsystem">ba.PluginSubsystem</a></li>
<li><a href="#class_ba_PotentialPlugin">ba.PotentialPlugin</a></li>
<li><a href="#class_ba_ServerController">ba.ServerController</a></li>
<li><a href="#class_ba_UISubsystem">ba.UISubsystem</a></li>
@ -4734,6 +4735,31 @@ the type-checker properly identifies the returned value as one.</p>
<p>Called when the app is being launched.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_PluginSubsystem">ba.PluginSubsystem</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Subsystem for plugin handling in the app.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> Access the single shared instance of this class at 'ba.app.plugins'.
</p>
<h3>Methods:</h3>
<h5><a href="#method_ba_PluginSubsystem____init__">&lt;constructor&gt;</a>, <a href="#method_ba_PluginSubsystem__on_app_launch">on_app_launch()</a></h5>
<dl>
<dt><h4><a name="method_ba_PluginSubsystem____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.PluginSubsystem()</span></p>
</dd>
<dt><h4><a name="method_ba_PluginSubsystem__on_app_launch">on_app_launch()</a></dt></h4><dd>
<p><span>on_app_launch(self) -&gt; None</span></p>
<p>Should be called at app launch time.</p>
</dd>
</dl>
<hr>