Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Launcher: handle apworld installation #3472

Merged
merged 7 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions inno_setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Arc
Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: "";

Root: HKCR; Subkey: ".apworld"; ValueData: "{#MyAppName}worlddata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}worlddata"; ValueData: "Archipelago World Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}worlddata\DefaultIcon"; ValueData: "{app}\ArchipelagoLauncher.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1""";

Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey;
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: "";
Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoTextClient.exe,0";
Expand Down
64 changes: 62 additions & 2 deletions worlds/LauncherComponents.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import logging
import pathlib
import weakref
from enum import Enum, auto
from typing import Optional, Callable, List, Iterable
from typing import Optional, Callable, List, Iterable, Tuple

from Utils import local_path
from Utils import local_path, open_filename


class Type(Enum):
Expand Down Expand Up @@ -49,15 +51,18 @@ def handles_file(self, path: str):
def __repr__(self):
return f"{self.__class__.__name__}({self.display_name})"


processes = weakref.WeakSet()


def launch_subprocess(func: Callable, name: str = None):
global processes
import multiprocessing
process = multiprocessing.Process(target=func, name=name)
process.start()
processes.add(process)


class SuffixIdentifier:
suffixes: Iterable[str]

Expand All @@ -77,13 +82,68 @@ def launch_textclient():
launch_subprocess(CommonClient.run_as_textclient, name="TextClient")


def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
if not apworld_src:
apworld_src = open_filename('Select APWorld file to install', (('APWorld', ('.apworld',)),))
if not apworld_src:
# user closed menu
return

if not apworld_src.endswith(".apworld"):
raise Exception(f"Wrong file format, looking for .apworld. File identified: {apworld_src}")

apworld_path = pathlib.Path(apworld_src)

try:
import zipfile
zipfile.ZipFile(apworld_path).open(pathlib.Path(apworld_path.name).stem + "/__init__.py")
except ValueError as e:
raise Exception("Archive appears invalid or damaged.") from e
except KeyError as e:
raise Exception("Archive appears to not be an apworld. (missing __init__.py)") from e

import worlds
if worlds.user_folder is None:
raise Exception("Custom Worlds directory appears to not be writable.")
for world_source in worlds.world_sources:
if apworld_path.samefile(world_source.resolved_path):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing - do you want to compare file content, or just make sure it doesn't try to have the same path in src and dst of copyfile? (because this doesn't compare content)

Copy link
Member Author

@Berserker66 Berserker66 Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention was preventing users from double clicking already installed apworlds and that mucking something up.

raise Exception(f"APWorld is already installed at {world_source.resolved_path}.")

# TODO: run generic test suite over the apworld.
# TODO: have some kind of version system to tell from metadata if the apworld should be compatible.

target = pathlib.Path(worlds.user_folder) / apworld_path.name
import shutil
shutil.copyfile(apworld_path, target)

return apworld_path, target


def install_apworld(apworld_path: str = "") -> None:
try:
res = _install_apworld(apworld_path)
if res is None:
logging.info("Aborting APWorld installation.")
return
source, target = res
except Exception as e:
import Utils
Utils.messagebox(e.__class__.__name__, str(e), error=True)
logging.exception(e)
else:
import Utils
logging.info(f"Installed APWorld successfully, copied {source} to {target}.")
Utils.messagebox("Install complete.", f"Installed APWorld from {source}.")


components: List[Component] = [
# Launcher
Component('Launcher', 'Launcher', component_type=Type.HIDDEN),
# Core
Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
Component('Generate', 'Generate', cli=True),
Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld")),
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient),
Component('Links Awakening DX Client', 'LinksAwakeningClient',
file_identifier=SuffixIdentifier('.apladx')),
Expand Down
6 changes: 5 additions & 1 deletion worlds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
from Utils import local_path, user_path

local_folder = os.path.dirname(__file__)
user_folder = user_path("worlds") if user_path() != local_path() else None
user_folder = user_path("worlds") if user_path() != local_path() else user_path("custom_worlds")
try:
os.makedirs(user_folder, exist_ok=True)
except OSError: # can't access/write?
user_folder = None

__all__ = {
"network_data_package",
Expand Down
Loading