Let plugins read their own files directly

This commit is contained in:
Tulir Asokan 2021-06-11 16:03:52 +03:00
parent 8f806a2594
commit 6487a7a8a5
6 changed files with 52 additions and 22 deletions

View File

@ -16,6 +16,7 @@
from typing import Optional, Union, IO from typing import Optional, Union, IO
from io import BytesIO from io import BytesIO
import zipfile import zipfile
import glob
import os import os
from ruamel.yaml import YAML, YAMLError from ruamel.yaml import YAML, YAMLError
@ -94,8 +95,9 @@ def write_plugin(meta: PluginMeta, output: Union[str, IO]) -> None:
else: else:
print(Fore.YELLOW + f"Module {module} not found, skipping" + Fore.RESET) print(Fore.YELLOW + f"Module {module} not found, skipping" + Fore.RESET)
for file in meta.extra_files: for pattern in meta.extra_files:
zip.write(file) for file in glob.iglob(pattern):
zip.write(file)
def upload_plugin(output: Union[str, IO], server: str) -> None: def upload_plugin(output: Union[str, IO], server: str) -> None:

View File

@ -199,7 +199,7 @@ class PluginInstance:
self.config = config_class(self.load_config, base_cfg_func, self.save_config) self.config = config_class(self.load_config, base_cfg_func, self.save_config)
self.plugin = cls(client=self.client.client, loop=self.loop, http=self.client.http_client, self.plugin = cls(client=self.client.client, loop=self.loop, http=self.client.http_client,
instance_id=self.id, log=self.log, config=self.config, instance_id=self.id, log=self.log, config=self.config,
database=self.inst_db, webapp=self.inst_webapp, database=self.inst_db, loader=self.loader, webapp=self.inst_webapp,
webapp_url=self.inst_webapp_url) webapp_url=self.inst_webapp_url)
try: try:
await self.plugin.internal_start() await self.plugin.internal_start()

View File

@ -1,2 +1,2 @@
from .abc import PluginLoader, PluginClass, IDConflictError, PluginMeta from .abc import BasePluginLoader, PluginLoader, PluginClass, IDConflictError, PluginMeta
from .zip import ZippedPluginLoader, MaubotZipImportError from .zip import ZippedPluginLoader, MaubotZipImportError

View File

@ -1,5 +1,5 @@
# maubot - A plugin-based Matrix bot system. # maubot - A plugin-based Matrix bot system.
# Copyright (C) 2019 Tulir Asokan # Copyright (C) 2021 Tulir Asokan
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -65,7 +65,30 @@ class PluginMeta(SerializableAttrs['PluginMeta']):
soft_dependencies: List[str] = [] soft_dependencies: List[str] = []
class PluginLoader(ABC): class BasePluginLoader(ABC):
meta: PluginMeta
@property
@abstractmethod
def source(self) -> str:
pass
def sync_read_file(self, path: str) -> bytes:
raise NotImplementedError("This loader doesn't support synchronous operations")
@abstractmethod
async def read_file(self, path: str) -> bytes:
pass
def sync_list_files(self, directory: str) -> List[str]:
raise NotImplementedError("This loader doesn't support synchronous operations")
@abstractmethod
async def list_files(self, directory: str) -> List[str]:
pass
class PluginLoader(BasePluginLoader, ABC):
id_cache: Dict[str, 'PluginLoader'] = {} id_cache: Dict[str, 'PluginLoader'] = {}
meta: PluginMeta meta: PluginMeta
@ -85,15 +108,6 @@ class PluginLoader(ABC):
"instances": [instance.to_dict() for instance in self.references], "instances": [instance.to_dict() for instance in self.references],
} }
@property
@abstractmethod
def source(self) -> str:
pass
@abstractmethod
async def read_file(self, path: str) -> bytes:
pass
async def stop_instances(self) -> None: async def stop_instances(self) -> None:
await asyncio.gather(*[instance.stop() for instance await asyncio.gather(*[instance.stop() for instance
in self.references if instance.started]) in self.references if instance.started])

View File

@ -1,5 +1,5 @@
# maubot - A plugin-based Matrix bot system. # maubot - A plugin-based Matrix bot system.
# Copyright (C) 2019 Tulir Asokan # Copyright (C) 2021 Tulir Asokan
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -106,9 +106,20 @@ class ZippedPluginLoader(PluginLoader):
f"meta={self.meta} " f"meta={self.meta} "
f"loaded={self._loaded is not None}>") f"loaded={self._loaded is not None}>")
async def read_file(self, path: str) -> bytes: def sync_read_file(self, path: str) -> bytes:
return self._file.read(path) return self._file.read(path)
async def read_file(self, path: str) -> bytes:
return self.sync_read_file(path)
def sync_list_files(self, directory: str) -> List[str]:
directory = directory.rstrip("/")
return [file.filename for file in self._file.filelist
if os.path.dirname(file.filename) == directory]
async def list_files(self, directory: str) -> List[str]:
return self.sync_list_files(directory)
@staticmethod @staticmethod
def _read_meta(source) -> Tuple[ZipFile, PluginMeta]: def _read_meta(source) -> Tuple[ZipFile, PluginMeta]:
try: try:

View File

@ -1,5 +1,5 @@
# maubot - A plugin-based Matrix bot system. # maubot - A plugin-based Matrix bot system.
# Copyright (C) 2019 Tulir Asokan # Copyright (C) 2021 Tulir Asokan
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -15,7 +15,6 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Type, Optional, TYPE_CHECKING from typing import Type, Optional, TYPE_CHECKING
from abc import ABC from abc import ABC
from logging import Logger
from asyncio import AbstractEventLoop from asyncio import AbstractEventLoop
from sqlalchemy.engine.base import Engine from sqlalchemy.engine.base import Engine
@ -23,26 +22,29 @@ from aiohttp import ClientSession
from yarl import URL from yarl import URL
if TYPE_CHECKING: if TYPE_CHECKING:
from mautrix.util.logging import TraceLogger
from mautrix.util.config import BaseProxyConfig from mautrix.util.config import BaseProxyConfig
from .client import MaubotMatrixClient from .client import MaubotMatrixClient
from .plugin_server import PluginWebApp from .plugin_server import PluginWebApp
from .loader import BasePluginLoader
class Plugin(ABC): class Plugin(ABC):
client: 'MaubotMatrixClient' client: 'MaubotMatrixClient'
http: ClientSession http: ClientSession
id: str id: str
log: Logger log: 'TraceLogger'
loop: AbstractEventLoop loop: AbstractEventLoop
loader: 'BasePluginLoader'
config: Optional['BaseProxyConfig'] config: Optional['BaseProxyConfig']
database: Optional[Engine] database: Optional[Engine]
webapp: Optional['PluginWebApp'] webapp: Optional['PluginWebApp']
webapp_url: Optional[URL] webapp_url: Optional[URL]
def __init__(self, client: 'MaubotMatrixClient', loop: AbstractEventLoop, http: ClientSession, def __init__(self, client: 'MaubotMatrixClient', loop: AbstractEventLoop, http: ClientSession,
instance_id: str, log: Logger, config: Optional['BaseProxyConfig'], instance_id: str, log: 'TraceLogger', config: Optional['BaseProxyConfig'],
database: Optional[Engine], webapp: Optional['PluginWebApp'], database: Optional[Engine], webapp: Optional['PluginWebApp'],
webapp_url: Optional[str]) -> None: webapp_url: Optional[str], loader: 'BasePluginLoader') -> None:
self.client = client self.client = client
self.loop = loop self.loop = loop
self.http = http self.http = http
@ -52,6 +54,7 @@ class Plugin(ABC):
self.database = database self.database = database
self.webapp = webapp self.webapp = webapp
self.webapp_url = URL(webapp_url) if webapp_url else None self.webapp_url = URL(webapp_url) if webapp_url else None
self.loader = loader
self._handlers_at_startup = [] self._handlers_at_startup = []
def register_handler_class(self, obj) -> None: def register_handler_class(self, obj) -> None: