2025-12-02 13:16:23 -05:00

898 lines
38 KiB
Python

import os
import threading
import time
from typing import Any, Dict, List, Tuple
import sdl2
import sdl2.ext
from models import Rom
if os.path.exists(os.path.join(os.path.dirname(__file__), "__version__.py")):
from __version__ import version
else:
version = "unknown"
from api import API
from config import (
BUTTON_CONFIGS,
get_controller_layout,
save_controller_layout,
set_controller_layout,
)
from filesystem import Filesystem
from glyps import glyphs
from input import Input
from status import Filter, Status, View
from ui import (
UserInterface,
color_menu_bg,
color_text,
)
from update import Update
ButtonConfig = Dict[str, str]
class StartMenuOptions:
ABORT_DOWNLOAD = f"{glyphs.abort} Abort downloads"
SD_SWITCH = f"{glyphs.microsd} Switch SD card"
TOGGLE_LAYOUT = f"{glyphs.user} Toggle button layout"
EXIT = f"{glyphs.exit} Exit"
class RomM:
running: bool = True
spinner_speed = 0.05
def __init__(self) -> None:
self.api = API()
self.fs = Filesystem()
self.input = Input()
self.status = Status()
self.ui = UserInterface()
self.updater = Update(self.ui)
self.contextual_menu_options: list[Tuple[str, int, Any]] = []
self.start_menu_selected_position = 0
self.contextual_menu_selected_position = 0
self.platforms_selected_position = 0
self.collections_selected_position = 0
self.roms_selected_position = 0
self.max_n_platforms = 10
self.max_n_collections = 10
self.max_n_roms = 10
self.buttons_config: List[ButtonConfig] = []
self.controller_layout = get_controller_layout()
self.last_spinner_update = time.time()
self.current_spinner_status = next(glyphs.spinner)
# Set update variables
self.awaiting_input = False
self.latest_version = None
self.download_url = None
# Set start menu options
self.start_menu_options = [
(StartMenuOptions.ABORT_DOWNLOAD, 0),
(StartMenuOptions.SD_SWITCH, 1 if self.fs._sd2_roms_storage_path else -1),
(
StartMenuOptions.TOGGLE_LAYOUT,
2 if self.fs._sd2_roms_storage_path else 1,
),
(StartMenuOptions.EXIT, 3 if self.fs._sd2_roms_storage_path else 2),
]
def draw_buttons(self):
# Button rendering with adjusted spacing
pos_x = 20 # Starting x position
radius = 20 # Diameter of button circle
char_width = 6 # Pixels per character (font=15, adjust as needed)
padding = 10 # Fixed spacing between buttons
for config in self.buttons_config:
self.ui.button_circle(
(pos_x, 460), config["key"], config["label"], color=config["color"]
)
# Calculate width: circle + margin to text + text length
label_length = len(config["label"])
text_width = label_length * char_width
total_width = (
radius + 20 + text_width
) # 20 is label_margin_l from button_circle
pos_x += total_width + padding
def _check_for_updates(self):
# Get latest release from GitHub API
release_info = self.updater.get_latest_release_info()
if release_info is None:
return
latest_tag = release_info.get("tag_name", "")
if not latest_tag:
print("Failed to find latest release tag")
return
latest_version = latest_tag.lstrip("v")
download_url = None
for asset in release_info.get("assets", []):
if "browser_download_url" in asset:
download_url = asset["browser_download_url"]
break
if not download_url:
print("Failed to find download URL")
return
print(f"Current version: {self.updater.current_version}")
print(f"Latest version: {latest_version}")
if self.updater.update_available(self.updater.current_version, latest_version):
self.ui.draw_clear()
self.status.updating.set()
self.latest_version = latest_version
self.download_url = download_url
self.awaiting_input = True
def _handle_update_confirmation(self):
if self.awaiting_input:
sdl2.SDL_Delay(100)
self.ui.draw_clear()
self.ui.draw_text(
(self.ui.screen_width / 2, self.ui.screen_height / 2 - 20),
f"New RomM App version available: v{self.latest_version}",
color=color_text,
anchor="mm",
)
self.ui.draw_text(
(self.ui.screen_width / 2, self.ui.screen_height / 2 + 20),
"Download update?",
color=color_text,
anchor="mm",
)
self.buttons_config = [
{
"key": self.controller_layout["a"]["btn"],
"label": "Yes",
"color": self.controller_layout["a"]["color"],
},
{
"key": self.controller_layout["b"]["btn"],
"label": "No",
"color": self.controller_layout["b"]["color"],
},
]
self.draw_buttons()
if self.input.key(self.controller_layout["a"]["key"]):
self.awaiting_input = False
self.ui.draw_clear()
if self.updater.download_update(self.download_url):
self.ui.draw_log(
text_line_1="Update downloaded successfully!",
text_line_2="App will now exit...",
)
self.ui.render_to_screen()
sdl2.SDL_Delay(1000)
raise SystemExit
else:
self.ui.draw_log(text_line_1="Update failed")
self.ui.render_to_screen()
sdl2.SDL_Delay(1000)
self.status.updating.clear()
elif self.input.key(self.controller_layout["b"]["key"]):
self.awaiting_input = False
self.status.updating.clear()
self.ui.draw_clear()
def _render_platforms_view(self):
if self.status.updating.is_set():
return
if self.status.platforms_ready.is_set():
self.ui.draw_platforms_list(
self.platforms_selected_position,
self.max_n_platforms,
self.status.platforms,
)
if not self.status.platforms_ready.is_set():
current_time = time.time()
if current_time - self.last_spinner_update >= self.spinner_speed:
self.last_spinner_update = current_time
self.current_spinner_status = next(glyphs.spinner)
self.ui.draw_log(
text_line_1=f"{self.current_spinner_status} Fetching platforms"
)
elif not self.status.download_rom_ready.is_set():
if self.status.extracting_rom and self.status.downloading_rom:
self.ui.draw_loader(
self.status.extracted_percent,
color=self.controller_layout["b"]["color"],
)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.extracted_percent:.2f}% | Extracting {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif self.status.downloading_rom:
self.ui.draw_loader(self.status.downloaded_percent)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.downloaded_percent:.2f}% | {glyphs.download} {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif not self.status.valid_host:
self.ui.draw_log(
text_line_1=f"Error: Can't connect to host {self.api.host}",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_host = True
elif not self.status.valid_credentials:
self.ui.draw_log(
text_line_1="Error: Permission denied",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_credentials = True
else:
self.buttons_config = [
{
"key": self.controller_layout["a"]["btn"],
"label": "Select",
"color": self.controller_layout["a"]["color"],
},
{
"key": self.controller_layout["y"]["btn"],
"label": "Refresh",
"color": self.controller_layout["y"]["color"],
},
{
"key": self.controller_layout["x"]["btn"],
"label": "Collections",
"color": self.controller_layout["x"]["color"],
},
]
self.draw_buttons()
def _update_platforms_view(self):
if self.input.key(self.controller_layout["a"]["key"]):
if self.status.roms_ready.is_set() and len(self.status.platforms) > 0:
self.status.roms_ready.clear()
self.status.roms = []
self.status.selected_platform = self.status.platforms[
self.platforms_selected_position
]
self.status.current_view = View.ROMS
threading.Thread(target=self.api.fetch_roms).start()
elif self.input.key(self.controller_layout["y"]["key"]):
if self.status.platforms_ready.is_set():
self.status.platforms_ready.clear()
threading.Thread(target=self.api.fetch_platforms).start()
elif self.input.key(self.controller_layout["x"]["key"]):
self.status.current_view = View.COLLECTIONS
elif self.input.key("START"):
self.status.show_contextual_menu = not self.status.show_contextual_menu
if self.status.show_contextual_menu and len(self.status.platforms) > 0:
self.contextual_menu_options = [
(
f"{glyphs.about} Platform info",
0,
lambda: self.ui.draw_log(
text_line_1=f"Platform name: {self.status.platforms[self.platforms_selected_position].display_name}"
),
),
]
else:
self.contextual_menu_options = []
else:
# Reset position if list is empty to avoid out-of-bounds
if len(self.status.platforms) == 0:
self.platforms_selected_position = 0
else:
self.platforms_selected_position = self.input.handle_navigation(
self.platforms_selected_position,
self.max_n_platforms,
len(self.status.platforms),
)
def _render_collections_view(self):
if self.status.collections_ready.is_set():
self.ui.draw_collections_list(
self.collections_selected_position,
self.max_n_collections,
self.status.collections,
fill=self.controller_layout["b"]["color"],
)
if not self.status.collections_ready.is_set():
current_time = time.time()
if current_time - self.last_spinner_update >= self.spinner_speed:
self.last_spinner_update = current_time
self.current_spinner_status = next(glyphs.spinner)
self.ui.draw_log(
text_line_1=f"{self.current_spinner_status} Fetching collections"
)
elif not self.status.download_rom_ready.is_set():
if self.status.extracting_rom and self.status.downloading_rom:
self.ui.draw_loader(
self.status.extracted_percent,
color=self.controller_layout["b"]["color"],
)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.extracted_percent:.2f}% | Extracting {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif self.status.downloading_rom:
self.ui.draw_loader(self.status.downloaded_percent)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.downloaded_percent:.2f}% | {glyphs.download} {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif not self.status.valid_host:
self.ui.draw_log(
text_line_1=f"Error: Can't connect to host {self.api.host}",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_host = True
elif not self.status.valid_credentials:
self.ui.draw_log(
text_line_1="Error: Permission denied",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_credentials = True
else:
self.buttons_config = [
{
"key": self.controller_layout["a"]["btn"],
"label": "Select",
"color": self.controller_layout["a"]["color"],
},
{
"key": self.controller_layout["y"]["btn"],
"label": "Refresh",
"color": self.controller_layout["y"]["color"],
},
{
"key": self.controller_layout["x"]["btn"],
"label": "Platforms",
"color": self.controller_layout["x"]["color"],
},
]
self.draw_buttons()
def _update_collections_view(self):
if self.input.key(self.controller_layout["a"]["key"]):
if self.status.roms_ready.is_set() and len(self.status.collections) > 0:
self.status.roms_ready.clear()
self.status.roms = []
selected_collection = self.status.collections[
self.collections_selected_position
]
if selected_collection.virtual:
self.status.selected_virtual_collection = selected_collection
else:
self.status.selected_collection = selected_collection
self.status.current_view = View.ROMS
threading.Thread(target=self.api.fetch_roms).start()
elif self.input.key(self.controller_layout["y"]["key"]):
if self.status.collections_ready.is_set():
self.status.collections_ready.clear()
threading.Thread(target=self.api.fetch_collections).start()
elif self.input.key(self.controller_layout["x"]["key"]):
self.status.current_view = View.PLATFORMS
elif self.input.key("START"):
self.status.show_contextual_menu = not self.status.show_contextual_menu
if self.status.show_contextual_menu and len(self.status.collections) > 0:
self.contextual_menu_options = [
(
f"{glyphs.about} Collection info",
0,
lambda: self.ui.draw_log(
text_line_1=f"Collection name: {self.status.collections[self.collections_selected_position].name}"
),
),
]
else:
self.contextual_menu_options = []
else:
self.collections_selected_position = self.input.handle_navigation(
self.collections_selected_position,
self.max_n_collections,
len(self.status.collections),
)
def _render_roms_view(self):
if len(self.status.roms) == 0 and self.status.roms_ready.is_set():
header_text = "No ROMs available"
header_color = self.controller_layout["a"]["color"]
prepend_platform_slug = False
elif self.status.selected_platform:
header_text = self.status.platforms[
self.platforms_selected_position
].display_name
header_color = self.controller_layout["a"]["color"]
prepend_platform_slug = False
elif self.status.selected_collection or self.status.selected_virtual_collection:
header_text = self.status.collections[
self.collections_selected_position
].name
header_color = self.controller_layout["b"]["color"]
prepend_platform_slug = True
else:
header_text = "ROMs"
header_color = self.controller_layout["a"]["color"]
prepend_platform_slug = False
total_pages = (
len(self.status.roms_to_show) + self.max_n_roms - 1
) // self.max_n_roms
current_page = (self.roms_selected_position // self.max_n_roms) + 1
header_text += f" [{current_page if total_pages > 0 else 0}/{total_pages}]"
if len(self.status.multi_selected_roms) > 0:
header_text += f" ({len(self.status.multi_selected_roms)} selected)"
if self.status.current_filter == Filter.ALL:
self.status.roms_to_show = self.status.roms
elif self.status.current_filter == Filter.LOCAL:
self.status.roms_to_show = [
r for r in self.status.roms if self.fs.is_rom_in_device(r)
]
elif self.status.current_filter == Filter.REMOTE:
self.status.roms_to_show = [
r for r in self.status.roms if not self.fs.is_rom_in_device(r)
]
self.ui.draw_roms_list(
self.roms_selected_position,
self.max_n_roms,
self.status.roms_to_show,
header_text,
header_color,
self.status.multi_selected_roms,
prepend_platform_slug=prepend_platform_slug,
)
if not self.status.roms_ready.is_set():
current_time = time.time()
if current_time - self.last_spinner_update >= self.spinner_speed:
self.last_spinner_update = current_time
self.current_spinner_status = next(glyphs.spinner)
self.ui.draw_log(text_line_1=f"{self.current_spinner_status} Fetching roms")
elif not self.status.download_rom_ready.is_set():
if self.status.extracting_rom and self.status.downloading_rom:
self.ui.draw_loader(
self.status.extracted_percent,
color=self.controller_layout["b"]["color"],
)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.extracted_percent:.2f}% | Extracting {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif self.status.downloading_rom:
self.ui.draw_loader(self.status.downloaded_percent)
self.ui.draw_log(
text_line_1=f"{self.status.downloading_rom_position}/{len(self.status.download_queue)} | {self.status.downloaded_percent:.2f}% | {glyphs.download} {self.status.downloading_rom.name}",
text_line_2=f"({self.status.downloading_rom.fs_name})",
background=False,
)
elif not self.status.valid_host:
self.ui.draw_log(
text_line_1=f"Error: Can't connect to host {self.api.host}",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_host = True
elif not self.status.valid_credentials:
self.ui.draw_log(
text_line_1="Error: Permission denied",
text_color=self.controller_layout["a"]["color"],
)
self.status.valid_credentials = True
else:
self.buttons_config = [
{
"key": self.controller_layout["a"]["btn"],
"label": "Download",
"color": self.controller_layout["a"]["color"],
},
{
"key": self.controller_layout["b"]["btn"],
"label": "Back",
"color": self.controller_layout["b"]["color"],
},
{
"key": self.controller_layout["y"]["btn"],
"label": "Refresh",
"color": self.controller_layout["y"]["color"],
},
{
"key": self.controller_layout["x"]["btn"],
"label": f"Filter:{self.status.current_filter}",
"color": self.controller_layout["x"]["color"],
},
{
"key": self.controller_layout["l1"]["btn"],
"label": (
"Deselect rom"
if (
len(self.status.roms_to_show) > 0
and self.status.roms_to_show[self.roms_selected_position]
in self.status.multi_selected_roms
)
else "Select rom"
),
"color": self.controller_layout["l1"]["color"],
},
{
"key": self.controller_layout["r1"]["btn"],
"label": (
"Deselect all"
if len(self.status.multi_selected_roms)
== len(self.status.roms_to_show)
else "Select all"
),
"color": self.controller_layout["r1"]["color"],
},
]
self.draw_buttons()
def _update_roms_view(self):
if self.input.key(self.controller_layout["a"]["key"]):
if (
self.status.roms_ready.is_set()
and self.status.download_rom_ready.is_set()
and len(self.status.roms_to_show) > 0
):
self.status.download_rom_ready.clear()
if len(self.status.multi_selected_roms) == 0:
self.status.multi_selected_roms.append(
self.status.roms_to_show[self.roms_selected_position]
)
self.status.download_queue = self.status.multi_selected_roms
self.status.abort_download.clear()
threading.Thread(target=self.api.download_rom).start()
elif self.input.key(self.controller_layout["b"]["key"]):
if self.status.selected_platform:
self.status.current_view = View.PLATFORMS
self.status.selected_platform = None
elif self.status.selected_collection:
self.status.current_view = View.COLLECTIONS
self.status.selected_collection = None
elif self.status.selected_virtual_collection:
self.status.current_view = View.COLLECTIONS
self.status.selected_virtual_collection = None
else:
self.status.current_view = View.PLATFORMS
self.status.reset_roms_list()
self.roms_selected_position = 0
self.status.multi_selected_roms = []
elif self.input.key(self.controller_layout["y"]["key"]):
if self.status.roms_ready.is_set():
self.status.roms_ready.clear()
threading.Thread(target=self.api.fetch_roms).start()
self.status.multi_selected_roms = []
elif self.input.key(self.controller_layout["x"]["key"]):
self.status.current_filter = next(self.status.filters)
self.roms_selected_position = 0
elif self.input.key(self.controller_layout["r1"]["key"]):
if len(self.status.multi_selected_roms) == len(self.status.roms_to_show):
self.status.multi_selected_roms = []
else:
self.status.multi_selected_roms = self.status.roms_to_show.copy()
elif self.input.key(self.controller_layout["l1"]["key"]):
if (
self.status.download_rom_ready.is_set()
and len(self.status.roms_to_show) > 0
):
selected_rom = self.status.roms_to_show[self.roms_selected_position]
if selected_rom not in self.status.multi_selected_roms:
self.status.multi_selected_roms.append(selected_rom)
else:
self.status.multi_selected_roms.remove(selected_rom)
elif self.input.key("START"):
self.status.show_contextual_menu = not self.status.show_contextual_menu
if self.status.show_contextual_menu and len(self.status.roms_to_show) > 0:
selected_rom = self.status.roms_to_show[self.roms_selected_position]
self.contextual_menu_options = [
(
f"{glyphs.about} Rom info",
0,
lambda: self.ui.draw_log(
text_line_1=f"Rom name: {selected_rom.name}"
),
),
]
if self.fs.is_rom_in_device(selected_rom):
self.contextual_menu_options.append(
(
f"{glyphs.delete} Remove from device",
1,
lambda: self._remove_rom_files(selected_rom),
),
)
else:
self.contextual_menu_options = []
else:
self.roms_selected_position = self.input.handle_navigation(
self.roms_selected_position,
self.max_n_roms,
len(self.status.roms_to_show),
)
def _render_contextual_menu(self):
pos = [self.ui.screen_width / 3, self.ui.screen_height / 3]
padding = 5
width = 200
n_options = len(self.contextual_menu_options)
option_height = 32
gap = 3
self.ui.draw_menu_background(pos, width, n_options, option_height, gap, padding)
n_options = len(self.contextual_menu_options)
if n_options == 0: # Avoid division by zero when menu is empty
return
for i, option in enumerate(self.contextual_menu_options):
is_selected = i == (self.contextual_menu_selected_position % n_options)
self.ui.row_list(
option[0],
(pos[0] + padding, pos[1] + padding + (i * (option_height + gap))),
width,
option_height,
is_selected,
)
def _update_contextual_menu(self):
if self.input.key(self.controller_layout["a"]["key"]):
if len(self.contextual_menu_options) > 0:
self.contextual_menu_options[self.contextual_menu_selected_position][
2
]()
self.status.show_contextual_menu = False
elif self.input.key(self.controller_layout["b"]["key"]):
self.status.show_contextual_menu = False
self.contextual_menu_options = []
else:
self.contextual_menu_selected_position = self.input.handle_navigation(
self.contextual_menu_selected_position,
len(self.contextual_menu_options),
len(self.contextual_menu_options),
)
def _render_start_menu(self):
pos = [self.ui.screen_width / 3, self.ui.screen_height / 3]
padding = 6
width = 200
n_selectable_options = 4 if self.fs._sd2_roms_storage_path else 3
option_height = 28
gap = 4
title = "Main menu"
title_x_adjustment = 35
version_x_adjustment = 50 / 6 * (len(version) + 2)
version_height = 20
layouts = list(BUTTON_CONFIGS.keys())
current_idx = layouts.index(self.ui.layout_name)
next_layout = layouts[(current_idx + 1) % len(layouts)]
self.start_menu_options[2] = (
f"{glyphs.user} Layout: {next_layout.capitalize()}",
self.start_menu_options[2][1],
)
self.ui.draw_menu_background(
pos,
width,
len(self.start_menu_options),
option_height,
gap,
padding,
extra_top_offset=version_height,
extra_bottom_offset=version_height,
)
selected_position = self.start_menu_selected_position % n_selectable_options
for i, option in enumerate(self.start_menu_options):
self.ui.row_list(
text=option[0],
position=(
pos[0] + padding,
pos[1] + padding + (i * (option_height + gap)),
),
width=width,
height=option_height,
selected=selected_position == option[1],
color=color_text if option[1] > -1 else color_menu_bg,
)
self.ui.draw_text(
(
pos[0] + width - version_x_adjustment,
pos[1] + padding + len(self.start_menu_options) * (option_height + gap),
),
f"v{version}",
)
self.ui.draw_text(
(
pos[0] + width / 2 - title_x_adjustment,
pos[1] - option_height + version_height - padding,
),
title,
)
def _update_start_menu(self):
if self.input.key(self.controller_layout["a"]["key"]):
selected_pos = self.start_menu_selected_position
if selected_pos == self.start_menu_options[0][1]:
self.status.abort_download.set()
self.status.show_start_menu = False
elif selected_pos == self.start_menu_options[1][1]:
self.fs.switch_sd_storage()
self.status.show_start_menu = False
elif selected_pos == self.start_menu_options[2][1]:
layouts = list(BUTTON_CONFIGS.keys())
current_idx = layouts.index(self.ui.layout_name)
new_layout = layouts[(current_idx + 1) % len(layouts)]
set_controller_layout(new_layout)
self.ui.layout_name = new_layout
self.controller_layout = get_controller_layout()
save_controller_layout()
self.status.show_start_menu = False
self.ui.draw_clear()
if self.status.current_view == View.PLATFORMS:
self._render_platforms_view()
elif self.status.current_view == View.COLLECTIONS:
self._render_collections_view()
elif self.status.current_view == View.ROMS:
self._render_roms_view()
else:
self._render_platforms_view()
self.ui.render_to_screen()
elif selected_pos == self.start_menu_options[3][1]:
self.running = False
self.status.show_start_menu = False
elif self.input.key(self.controller_layout["b"]["key"]):
self.status.show_start_menu = not self.status.show_start_menu
else:
n_selectable_options = 4 if self.fs._sd2_roms_storage_path else 3
self.start_menu_selected_position = self.input.handle_navigation(
self.start_menu_selected_position,
n_selectable_options,
n_selectable_options,
)
def _update_common(self):
if (
self.input.key("MENUF") or self.input.key("SELECT")
) and not self.status.show_contextual_menu:
self.status.show_start_menu = not self.status.show_start_menu
if self.input.key("START") and not self.status.show_start_menu:
self.status.show_contextual_menu = not self.status.show_contextual_menu
def _monitor_input(self):
while self.running:
events = sdl2.ext.get_events()
for event in events:
self.input.check_event(event)
if event.type == sdl2.SDL_QUIT:
self.running = False
def start(self):
self._render_platforms_view()
threading.Thread(target=self._monitor_input, daemon=True).start()
threading.Thread(target=self._check_for_updates).start()
threading.Thread(target=self.api.fetch_platforms).start()
threading.Thread(target=self.api.fetch_collections).start()
threading.Thread(target=self.api.fetch_me).start()
def update(self):
self.ui.draw_clear()
if self.awaiting_input:
self._handle_update_confirmation()
return
if self.status.updating.is_set():
return
if self.status.me_ready.is_set():
self.ui.draw_header(self.api.host, self.api.username)
if not self.status.valid_host:
if self.input.key(self.controller_layout["y"]["key"]):
if self.status.platforms_ready.is_set():
self.status.platforms_ready.clear()
threading.Thread(target=self.api.fetch_platforms).start()
self.ui.button_circle(
(20, 460),
self.controller_layout["y"]["btn"],
"Refresh",
color=self.controller_layout["y"]["color"],
)
self.ui.draw_text(
(self.ui.screen_width / 2, self.ui.screen_height / 2),
f"Error: Can't connect to host\n{self.api.host}",
color=self.controller_layout["a"]["color"],
anchor="mm",
)
elif not self.status.valid_credentials:
if self.input.key(self.controller_layout["y"]["key"]):
if self.status.platforms_ready.is_set():
self.status.platforms_ready.clear()
threading.Thread(target=self.api.fetch_platforms).start()
self.ui.button_circle(
(20, 460),
self.controller_layout["y"]["btn"],
"Refresh",
color=self.controller_layout["y"]["color"],
)
self.ui.draw_text(
(self.ui.screen_width / 2, self.ui.screen_height / 2),
"Error: Permission denied",
color=self.controller_layout["a"]["color"],
anchor="mm",
)
else:
if self.status.current_view == View.PLATFORMS:
self._render_platforms_view()
if (
not self.status.show_start_menu
and not self.status.show_contextual_menu
):
self._update_platforms_view()
elif self.status.current_view == View.COLLECTIONS:
self._render_collections_view()
if (
not self.status.show_start_menu
and not self.status.show_contextual_menu
):
self._update_collections_view()
elif self.status.current_view == View.ROMS:
self._render_roms_view()
if (
not self.status.show_start_menu
and not self.status.show_contextual_menu
):
self._update_roms_view()
else:
self._render_platforms_view()
if (
not self.status.show_start_menu
and not self.status.show_contextual_menu
):
self._update_platforms_view()
# Render start menu
if self.status.show_start_menu:
self._render_start_menu()
self._update_start_menu()
elif self.status.show_contextual_menu:
self._render_contextual_menu()
self._update_contextual_menu()
self._update_common()
def _remove_rom_files(self, rom: Rom):
storage_path = self.fs.get_platforms_storage_path(rom.platform_slug)
if rom.has_multiple_files:
# Read the m3u file to get the list of ROMs under the .hidden folder
rom_list_path = os.path.join(storage_path, rom.fs_name + ".m3u")
if os.path.isfile(rom_list_path):
with open(rom_list_path, "r") as f:
for line in f:
filename = line.strip()
if filename:
full_path = os.path.join(storage_path, filename)
if os.path.commonpath(
[storage_path, full_path]
) == storage_path and os.path.isfile(full_path):
os.remove(full_path)
os.remove(rom_list_path)
else:
full_path = os.path.join(storage_path, rom.fs_name)
if os.path.commonpath(
[storage_path, full_path]
) == storage_path and os.path.isfile(full_path):
os.remove(full_path)