Update metadata format

This commit is contained in:
Rikko 2022-08-10 01:02:10 +05:30
parent 40c4c4d6a8
commit 4f8180d06d
5 changed files with 191 additions and 159 deletions

View file

@ -1,19 +1,7 @@
{ {
"plugin_categories": [ "categories": [
{ "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/utilities.json",
"display_name": "Utilities", "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/minigames.json",
"meta": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/utilities.json", "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/maps.json"
"base_download_url": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/utilities"
},
{
"display_name": "Minigames",
"meta": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/minigames.json",
"base_download_url": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/minigames"
},
{
"display_name": "Maps",
"meta": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/maps.json",
"base_download_url": "https://github.com/bombsquad-community/mod-manager/raw/main/plugins/maps"
}
] ]
} }

View file

@ -15,7 +15,7 @@ from typing import Union, Optional
_env = _ba.env() _env = _ba.env()
_uiscale = ba.app.ui.uiscale _uiscale = ba.app.ui.uiscale
INDEX_META = "https://raw.githubusercontent.com/bombsquad-community/mod-manager/main/index.json" INDEX_META = "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/index.json"
HEADERS = { HEADERS = {
"User-Agent": _env["user_agent_string"], "User-Agent": _env["user_agent_string"],
} }
@ -83,49 +83,77 @@ def play_sound():
class Category: class Category:
def __init__(self, name, base_download_url, meta_url): def __init__(self, meta_url):
self.name = name
self.base_download_url = base_download_url
self.meta_url = meta_url self.meta_url = meta_url
self.request_headers = HEADERS self.request_headers = HEADERS
self._plugins = _CACHE.get("categories", {}).get(self.name) self._metadata = _CACHE.get("categories", {}).get(meta_url, {}).get("metadata")
self._plugins = _CACHE.get("categories", {}).get(meta_url, {}).get("plugins")
async def get_plugins(self): async def fetch_metadata(self):
if self._plugins is None: if self._metadata is None:
request = urllib.request.Request( request = urllib.request.Request(
self.meta_url, self.meta_url.format(content_type="raw"),
headers=self.request_headers, headers=self.request_headers,
) )
response = await async_send_network_request(request) response = await async_send_network_request(request)
plugins_info = json.loads(response.read()) self._metadata = json.loads(response.read())
self._plugins = ([Plugin(plugin_info, self.base_download_url) self.set_category_global_cache("metadata", self._metadata)
for plugin_info in plugins_info.items()]) return self
self.set_category_plugins_global_cache(self._plugins)
async def get_name(self):
if self._metadata is None:
await self.fetch_metadata()
return self._metadata["name"]
async def get_description(self):
if self._metadata is None:
await self.fetch_metadata()
return self._metadata["description"]
async def get_plugins_base_url(self):
if self._metadata is None:
await self.fetch_metadata()
return self._metadata["plugins_base_url"]
async def get_plugins(self):
if self._plugins is None:
if self._metadata is None:
await self.fetch_metadata()
self._plugins = ([
Plugin(plugin_info, f"{await self.get_plugins_base_url()}/{plugin_info[0]}.py")
for plugin_info in self._metadata["plugins"].items()
])
self.set_category_global_cache("plugins", self._plugins)
return self._plugins return self._plugins
def set_category_plugins_global_cache(self, plugins): def set_category_global_cache(self, key, value):
if "categories" not in _CACHE: if "categories" not in _CACHE:
_CACHE["categories"] = {} _CACHE["categories"] = {}
_CACHE["categories"][self.name] = plugins if self.meta_url not in _CACHE["categories"]:
_CACHE["categories"][self.meta_url] = {}
_CACHE["categories"][self.meta_url][key] = value
def unset_category_plugins_global_cache(self): def unset_category_global_cache(self):
try: try:
del _CACHE["categories"][self.name] del _CACHE["categories"][self.meta_url]
except KeyError: except KeyError:
pass pass
def refresh(self): async def refresh(self):
self._plugins.clear() self.cleanup()
self.unset_category_plugins_global_cache() await self.get_plugins()
async def cleanup(self): def cleanup(self):
self.refresh() self._metadata = None
return await self.get_plugins() self._plugins.clear()
self.unset_category_global_cache()
class CategoryAll(Category): class CategoryAll(Category):
def __init__(self, plugins={}): def __init__(self, plugins={}):
super().__init__(name="All", base_download_url=None, meta_url=None) super().__init__(meta_url=None)
self._name = "All"
self._description = "All plugins"
self._plugins = plugins self._plugins = plugins
@ -142,7 +170,7 @@ class PluginLocal:
self._content = None self._content = None
self._api_version = None self._api_version = None
self._entry_points = [] self._entry_points = []
self._contains_minigames = None self._has_minigames = None
@property @property
def is_installed(self): def is_installed(self):
@ -211,10 +239,10 @@ class PluginLocal:
return self._entry_points return self._entry_points
async def has_minigames(self): async def has_minigames(self):
if self._contains_minigames is None: if self._has_minigames is None:
content = await self.get_content() content = await self.get_content()
self._contains_minigames = REGEXP["minigames"].search(content) is not None self._has_minigames = REGEXP["minigames"].search(content) is not None
return self._contains_minigames return self._has_minigames
async def is_enabled(self): async def is_enabled(self):
""" """
@ -288,13 +316,14 @@ class PluginVersion:
class Plugin: class Plugin:
def __init__(self, plugin, base_download_url): def __init__(self, plugin, url):
""" """
Initialize a plugin from network repository. 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 = url.format(content_type="raw")
self.view_url = url.format(content_type="blob")
self._local_plugin = None self._local_plugin = None
def __repr__(self): def __repr__(self):
@ -475,7 +504,7 @@ class PluginWindow(popup.PopupWindow):
size=(40, 40), size=(40, 40),
button_type="square", button_type="square",
label="", label="",
on_activate_call=self.open_plugin_page) on_activate_call=lambda: ba.open_url(self.plugin.view_url))
ba.imagewidget(parent=self._root_widget, ba.imagewidget(parent=self._root_widget,
position=(open_pos_x, open_pos_y), position=(open_pos_x, open_pos_y),
size=(40, 40), size=(40, 40),
@ -489,9 +518,6 @@ class PluginWindow(popup.PopupWindow):
play_sound() play_sound()
ba.containerwidget(edit=self._root_widget, transition='out_scale') ba.containerwidget(edit=self._root_widget, transition='out_scale')
def open_plugin_page(self) -> None:
ba.open_url(f"https://github.com/bombsquad-community/plugin-manager/tree/main/plugins/utilities/{self.plugin.name}.py")
def button(fn): def button(fn):
async def asyncio_handler(fn, self, *args, **kwargs): async def asyncio_handler(fn, self, *args, **kwargs):
await fn(self, *args, **kwargs) await fn(self, *args, **kwargs)
@ -538,26 +564,47 @@ class PluginManager:
def __init__(self): def __init__(self):
self.request_headers = HEADERS self.request_headers = HEADERS
self._index = _CACHE.get("index", {}) self._index = _CACHE.get("index", {})
self.categories = {}
async def get_index(self): async def setup_index(self):
global _INDEX global _INDEX
if not self._index: if not self._index:
request = urllib.request.Request( request = urllib.request.Request(
INDEX_META, INDEX_META.format(content_type="raw"),
headers=self.request_headers, headers=self.request_headers,
) )
response = await async_send_network_request(request) response = await async_send_network_request(request)
self._index = json.loads(response.read()) self._index = json.loads(response.read())
self.set_index_global_cache(self._index) self.set_index_global_cache(self._index)
return self._index await self._setup_plugin_categories(self._index)
async def _setup_plugin_categories(self, plugin_index):
# A hack to have the "All" category show at the top.
self.categories["All"] = None
requests = []
for plugin_category_url in plugin_index["categories"]:
category = Category(plugin_category_url)
request = category.fetch_metadata()
requests.append(request)
categories = await asyncio.gather(*requests)
all_plugins = []
for category in categories:
self.categories[await category.get_name()] = category
all_plugins.extend(await category.get_plugins())
self.categories["All"] = CategoryAll(plugins=all_plugins)
def cleanup(self): def cleanup(self):
for category in self.categories.values():
category.cleanup()
self.categories.clear()
self._index.clear() self._index.clear()
self.unset_index_global_cache() self.unset_index_global_cache()
async def refresh(self): async def refresh(self):
self.cleanup() self.cleanup()
return await self.get_index() await self.setup_index()
def set_index_global_cache(self, index): def set_index_global_cache(self, index):
_CACHE["index"] = index _CACHE["index"] = index
@ -572,16 +619,15 @@ class PluginManager:
pass pass
class PluginManagerWindow(ba.Window, PluginManager): class PluginManagerWindow(ba.Window):
def __init__(self, transition: str = "in_right", origin_widget: ba.Widget = None): def __init__(self, transition: str = "in_right", origin_widget: ba.Widget = None):
PluginManager.__init__(self) self.plugin_manager = PluginManager()
self.categories = {}
self.category_selection_button = None self.category_selection_button = None
self.selected_category = None self.selected_category = None
self.plugins_in_current_view = {} self.plugins_in_current_view = {}
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(self.plugin_index()) loop.create_task(self.draw_index())
self._width = (490 if _uiscale is ba.UIScale.MEDIUM else 570) self._width = (490 if _uiscale is ba.UIScale.MEDIUM else 570)
self._height = (500 if _uiscale is ba.UIScale.SMALL self._height = (500 if _uiscale is ba.UIScale.SMALL
@ -634,11 +680,11 @@ class PluginManagerWindow(ba.Window, PluginManager):
v_align="center", v_align="center",
maxwidth=270, maxwidth=270,
) )
loading_pos_y = self._height - (235 if _uiscale is ba.UIScale.SMALL else loading_pos_y = self._height - (235 if _uiscale is ba.UIScale.SMALL else
220 if _uiscale is ba.UIScale.MEDIUM else 250) 220 if _uiscale is ba.UIScale.MEDIUM else 250)
self._loading_text = ba.textwidget( self._plugin_manager_status_text = ba.textwidget(
parent=self._root_widget, parent=self._root_widget,
position=(-5, loading_pos_y), position=(-5, loading_pos_y),
size=(self._width, 25), size=(self._width, 25),
@ -738,43 +784,24 @@ class PluginManagerWindow(ba.Window, PluginManager):
ba.app.ui.set_main_menu_window( ba.app.ui.set_main_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget()) AllSettingsWindow(transition='in_left').get_root_widget())
async def setup_plugin_categories(self, plugin_index): async def draw_index(self):
self.categories["All"] = None
requests = []
for plugin_category in plugin_index["plugin_categories"]:
category = Category(
plugin_category["display_name"],
plugin_category["base_download_url"],
plugin_category["meta"],
)
self.categories[plugin_category["display_name"]] = category
request = category.get_plugins()
requests.append(request)
categories = await asyncio.gather(*requests)
all_plugins = []
for plugins in categories:
all_plugins.extend(plugins)
self.categories["All"] = CategoryAll(plugins=all_plugins)
async def plugin_index(self):
try: try:
index = await super().get_index()
await self.draw_search_bar() await self.draw_search_bar()
await self.setup_plugin_categories(index) await self.draw_category_selection_button(post_label="All")
await self.select_category("All")
await self.draw_refresh_icon() await self.draw_refresh_icon()
await self.draw_settings_icon() await self.draw_settings_icon()
await self.plugin_manager.setup_index()
ba.textwidget(edit=self._plugin_manager_status_text,
text="")
await self.select_category("All")
except RuntimeError: except RuntimeError:
# User probably went back before the PluginManagerWindow could finish loading. # User probably went back before the PluginManagerWindow could finish loading.
pass pass
except urllib.error.URLError: except urllib.error.URLError:
ba.textwidget(edit=self._loading_text, ba.textwidget(edit=self._plugin_manager_status_text,
text="Make sure you are connected\n to the Internet and try again.") text="Make sure you are connected\n to the Internet and try again.")
else:
self._loading_text.delete()
async def draw_category_selection_button(self, label=None): async def draw_category_selection_button(self, post_label):
# v = (self._height - 75) if _uiscale is ba.UIScale.SMALL else (self._height - 105) # v = (self._height - 75) if _uiscale is ba.UIScale.SMALL else (self._height - 105)
# v = 395 # v = 395
# h = 440 # h = 440
@ -793,16 +820,14 @@ class PluginManagerWindow(ba.Window, PluginManager):
b_textcolor = (0.75, 0.7, 0.8) b_textcolor = (0.75, 0.7, 0.8)
b_color = (0.6, 0.53, 0.63) b_color = (0.6, 0.53, 0.63)
if label is None: label = f"Category: {post_label}"
label = self.selected_category
label = f"Category: {label}"
if self.category_selection_button is None: if self.category_selection_button is None:
self.category_selection_button = ba.buttonwidget(parent=self._root_widget, self.category_selection_button = ba.buttonwidget(parent=self._root_widget,
position=(category_pos_x, position=(category_pos_x,
category_pos_y), category_pos_y),
size=b_size, size=b_size,
on_activate_call=self.show_categories, on_activate_call=lambda: self.show_categories(),
label=label, label=label,
button_type="square", button_type="square",
color=b_color, color=b_color,
@ -875,7 +900,7 @@ class PluginManagerWindow(ba.Window, PluginManager):
texture=ba.gettexture("replayIcon"), texture=ba.gettexture("replayIcon"),
draw_controller=controller_button) draw_controller=controller_button)
async def draw_plugin_names(self): async def draw_plugin_names(self, category):
# v = (self._height - 75) if _uiscale is ba.UIScale.SMALL else (self._height - 105) # v = (self._height - 75) if _uiscale is ba.UIScale.SMALL else (self._height - 105)
# h = 440 # h = 440
# next 2 lines belong in 1 line # next 2 lines belong in 1 line
@ -889,7 +914,7 @@ class PluginManagerWindow(ba.Window, PluginManager):
for plugin in self._columnwidget.get_children(): for plugin in self._columnwidget.get_children():
plugin.delete() plugin.delete()
plugins = await self.categories[self.selected_category].get_plugins() plugins = await self.plugin_manager.categories[category].get_plugins()
plugin_names_to_draw = tuple(self.draw_plugin_name(plugin) for plugin in plugins) plugin_names_to_draw = tuple(self.draw_plugin_name(plugin) for plugin in plugins)
await asyncio.gather(*plugin_names_to_draw) await asyncio.gather(*plugin_names_to_draw)
@ -939,15 +964,15 @@ class PluginManagerWindow(ba.Window, PluginManager):
position=(200, 0), position=(200, 0),
scale=(2.3 if _uiscale is ba.UIScale.SMALL else scale=(2.3 if _uiscale is ba.UIScale.SMALL else
1.65 if _uiscale is ba.UIScale.MEDIUM else 1.23), 1.65 if _uiscale is ba.UIScale.MEDIUM else 1.23),
choices=self.categories.keys(), choices=self.plugin_manager.categories.keys(),
current_choice=self.selected_category, current_choice=self.selected_category,
delegate=self) delegate=self)
async def select_category(self, category): async def select_category(self, category):
self.selected_category = category self.selected_category = category
self.plugins_in_current_view.clear() self.plugins_in_current_view.clear()
await self.draw_category_selection_button(label=category) await self.draw_category_selection_button(post_label=category)
await self.draw_plugin_names() await self.draw_plugin_names(category)
def popup_menu_selected_choice(self, window, choice): def popup_menu_selected_choice(self, window, choice):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -957,19 +982,23 @@ class PluginManagerWindow(ba.Window, PluginManager):
pass pass
def cleanup(self): def cleanup(self):
super().cleanup() self.plugin_manager.cleanup()
self.categories.clear()
for plugin in self._columnwidget.get_children(): for plugin in self._columnwidget.get_children():
plugin.delete() plugin.delete()
self.plugins_in_current_view.clear() self.plugins_in_current_view.clear()
async def _refresh(self): async def _refresh(self):
index = await super().refresh() await self.plugin_manager.refresh()
await self.setup_plugin_categories(index) await self.plugin_manager.setup_index()
ba.textwidget(edit=self._plugin_manager_status_text,
text="")
await self.select_category(self.selected_category) await self.select_category(self.selected_category)
def refresh(self): def refresh(self):
play_sound() play_sound()
self.cleanup()
ba.textwidget(edit=self._plugin_manager_status_text,
text="Refreshing...")
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(self._refresh()) loop.create_task(self._refresh())

View file

@ -1 +1,6 @@
{} {
"name": "Maps",
"description": "Maps",
"plugins_base_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/maps",
"plugins": {}
}

View file

@ -1,21 +1,26 @@
{ {
"alliance_elimination": { "name": "Minigames",
"description": "Fight in groups of duo, trio, or more. Last remaining alive wins.", "description": "Minigames",
"external_url": "", "plugins_base_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/minigames",
"authors": [ "plugins": {
{ "alliance_elimination": {
"name": "Rikko", "description": "Fight in groups of duo, trio, or more. Last remaining alive wins.",
"email": "rikkolovescats@proton.me", "external_url": "",
"discord": "Rikko#7383" "authors": [
} {
], "name": "Rikko",
"versions": { "email": "rikkolovescats@proton.me",
"1.1.0": { "discord": "Rikko#7383"
"api_version": 7, }
"commit_sha": "cbdb3ead", ],
"dependencies": [], "versions": {
"released_on": "08-08-2022", "1.1.0": {
"md5sum": "11dbb3c7e37e97bda028ea1251529ea0" "api_version": 7,
"commit_sha": "cbdb3ead",
"dependencies": [],
"released_on": "08-08-2022",
"md5sum": "11dbb3c7e37e97bda028ea1251529ea0"
}
} }
} }
} }

View file

@ -1,46 +1,51 @@
{ {
"colorscheme": { "name": "Utilities",
"description": "Create custom UI colorschemes!", "description": "Utilities",
"external_url": "https://www.youtube.com/watch?v=qatwWrBAvjc", "plugins_base_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/main/plugins/utilities",
"authors": [ "plugins": {
{ "colorscheme": {
"name": "Rikko", "description": "Create custom UI colorschemes!",
"email": "rikkolovescats@proton.me", "external_url": "https://www.youtube.com/watch?v=qatwWrBAvjc",
"discord": "Rikko#7383" "authors": [
}, {
{ "name": "Rikko",
"name": "Vishal", "email": "rikkolovescats@proton.me",
"email": "vishal.u338@gmail.com", "discord": "Rikko#7383"
"discord": "𝑽𝑰𝑺𝑯𝑼𝑼𝑼#2921" },
{
"name": "Vishal",
"email": "vishal.u338@gmail.com",
"discord": "𝑽𝑰𝑺𝑯𝑼𝑼𝑼#2921"
}
],
"versions": {
"1.1.0": {
"api_version": 7,
"commit_sha": "13a9d128",
"dependencies": [],
"released_on": "03-06-2022",
"md5sum": "4b6bbb99037ebda4664da7c510b3717c"
}
} }
], },
"versions": { "store_event_specials": {
"1.1.0": { "description": "Exposes event-special store items not normally available for purchase",
"api_version": 7, "external_url": "",
"commit_sha": "13a9d128", "authors": [
"dependencies": [], {
"released_on": "03-06-2022", "name": "Rikko",
"md5sum": "4b6bbb99037ebda4664da7c510b3717c" "email": "rikkolovescats@proton.me",
} "discord": "Rikko#7383"
} }
}, ],
"store_event_specials": { "versions": {
"description": "Exposes event-special store items not normally available for purchase", "1.0.0": {
"external_url": "", "api_version": 7,
"authors": [ "commit_sha": "2aa6df31",
{ "dependencies": [],
"name": "Rikko", "released_on": "06-08-2022",
"email": "rikkolovescats@proton.me", "md5sum": "233dfaa7f0e9394d21454f4ffa7d0205"
"discord": "Rikko#7383" }
}
],
"versions": {
"1.0.0": {
"api_version": 7,
"commit_sha": "2aa6df31",
"dependencies": [],
"released_on": "06-08-2022",
"md5sum": "233dfaa7f0e9394d21454f4ffa7d0205"
} }
} }
} }