AppImage: allow loading apworlds from ~/Archipelago and copy scripts (#2358)

also fixes some mypy and flake8 violations in worlds/__init__.py
This commit is contained in:
black-sliver 2023-11-04 10:26:51 +01:00 committed by GitHub
parent 880326c9a5
commit d2e9bfb196
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 30 additions and 22 deletions

View File

@ -174,12 +174,16 @@ def user_path(*path: str) -> str:
if user_path.cached_path != local_path(): if user_path.cached_path != local_path():
import filecmp import filecmp
if not os.path.exists(user_path("manifest.json")) or \ if not os.path.exists(user_path("manifest.json")) or \
not os.path.exists(local_path("manifest.json")) or \
not filecmp.cmp(local_path("manifest.json"), user_path("manifest.json"), shallow=True): not filecmp.cmp(local_path("manifest.json"), user_path("manifest.json"), shallow=True):
import shutil import shutil
for dn in ("Players", "data/sprites"): for dn in ("Players", "data/sprites", "data/lua"):
shutil.copytree(local_path(dn), user_path(dn), dirs_exist_ok=True) shutil.copytree(local_path(dn), user_path(dn), dirs_exist_ok=True)
for fn in ("manifest.json",): if not os.path.exists(local_path("manifest.json")):
shutil.copy2(local_path(fn), user_path(fn)) warnings.warn(f"Upgrading {user_path()} from something that is not a proper install")
else:
shutil.copy2(local_path("manifest.json"), user_path("manifest.json"))
os.makedirs(user_path("worlds"), exist_ok=True)
return os.path.join(user_path.cached_path, *path) return os.path.join(user_path.cached_path, *path)

View File

@ -5,19 +5,20 @@ import typing
import warnings import warnings
import zipimport import zipimport
folder = os.path.dirname(__file__) from Utils import user_path, local_path
__all__ = { local_folder = os.path.dirname(__file__)
user_folder = user_path("worlds") if user_path() != local_path() else None
__all__ = (
"lookup_any_item_id_to_name", "lookup_any_item_id_to_name",
"lookup_any_location_id_to_name", "lookup_any_location_id_to_name",
"network_data_package", "network_data_package",
"AutoWorldRegister", "AutoWorldRegister",
"world_sources", "world_sources",
"folder", "local_folder",
} "user_folder",
)
if typing.TYPE_CHECKING:
from .AutoWorld import World
class GamesData(typing.TypedDict): class GamesData(typing.TypedDict):
@ -41,13 +42,13 @@ class WorldSource(typing.NamedTuple):
is_zip: bool = False is_zip: bool = False
relative: bool = True # relative to regular world import folder relative: bool = True # relative to regular world import folder
def __repr__(self): def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})" return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
@property @property
def resolved_path(self) -> str: def resolved_path(self) -> str:
if self.relative: if self.relative:
return os.path.join(folder, self.path) return os.path.join(local_folder, self.path)
return self.path return self.path
def load(self) -> bool: def load(self) -> bool:
@ -56,6 +57,7 @@ class WorldSource(typing.NamedTuple):
importer = zipimport.zipimporter(self.resolved_path) importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10 if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0]) spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
assert spec, f"{self.path} is not a loadable module"
mod = importlib.util.module_from_spec(spec) mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0]) mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])
@ -72,7 +74,7 @@ class WorldSource(typing.NamedTuple):
importlib.import_module(f".{self.path}", "worlds") importlib.import_module(f".{self.path}", "worlds")
return True return True
except Exception as e: except Exception:
# A single world failing can still mean enough is working for the user, log and carry on # A single world failing can still mean enough is working for the user, log and carry on
import traceback import traceback
import io import io
@ -87,14 +89,16 @@ class WorldSource(typing.NamedTuple):
# find potential world containers, currently folders and zip-importable .apworld's # find potential world containers, currently folders and zip-importable .apworld's
world_sources: typing.List[WorldSource] = [] world_sources: typing.List[WorldSource] = []
file: os.DirEntry # for me (Berserker) at least, PyCharm doesn't seem to infer the type correctly for folder in (folder for folder in (user_folder, local_folder) if folder):
for file in os.scandir(folder): relative = folder == local_folder
# prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "." for entry in os.scandir(folder):
if not file.name.startswith(("_", ".")): # prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "."
if file.is_dir(): if not entry.name.startswith(("_", ".")):
world_sources.append(WorldSource(file.name)) file_name = entry.name if relative else os.path.join(folder, entry.name)
elif file.is_file() and file.name.endswith(".apworld"): if entry.is_dir():
world_sources.append(WorldSource(file.name, is_zip=True)) world_sources.append(WorldSource(file_name, relative=relative))
elif entry.is_file() and entry.name.endswith(".apworld"):
world_sources.append(WorldSource(file_name, is_zip=True, relative=relative))
# import all submodules to trigger AutoWorldRegister # import all submodules to trigger AutoWorldRegister
world_sources.sort() world_sources.sort()
@ -105,7 +109,7 @@ lookup_any_item_id_to_name = {}
lookup_any_location_id_to_name = {} lookup_any_location_id_to_name = {}
games: typing.Dict[str, GamesPackage] = {} games: typing.Dict[str, GamesPackage] = {}
from .AutoWorld import AutoWorldRegister from .AutoWorld import AutoWorldRegister # noqa: E402
# Build the data package for each game. # Build the data package for each game.
for world_name, world in AutoWorldRegister.world_types.items(): for world_name, world in AutoWorldRegister.world_types.items():