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:
parent
880326c9a5
commit
d2e9bfb196
10
Utils.py
10
Utils.py
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
for entry in os.scandir(folder):
|
||||||
# prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "."
|
# prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "."
|
||||||
if not file.name.startswith(("_", ".")):
|
if not entry.name.startswith(("_", ".")):
|
||||||
if file.is_dir():
|
file_name = entry.name if relative else os.path.join(folder, entry.name)
|
||||||
world_sources.append(WorldSource(file.name))
|
if entry.is_dir():
|
||||||
elif file.is_file() and file.name.endswith(".apworld"):
|
world_sources.append(WorldSource(file_name, relative=relative))
|
||||||
world_sources.append(WorldSource(file.name, is_zip=True))
|
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():
|
||||||
|
|
Loading…
Reference in New Issue