From 19896e1fae8beb1825077fc4f9cdc9731ea333c6 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 14 May 2021 15:25:57 +0200 Subject: [PATCH] prepare webhost for multi-game per-slot downloads --- Main.py | 5 ++-- MultiMystery.py | 1 - Patch.py | 15 +++++++----- WebHostLib/autolauncher.py | 5 +++- WebHostLib/downloads.py | 9 ++++--- WebHostLib/generate.py | 12 ++++----- WebHostLib/models.py | 7 +++--- WebHostLib/templates/macros.html | 4 +-- WebHostLib/templates/userContent.html | 4 +-- WebHostLib/templates/viewSeed.html | 21 ++++++---------- WebHostLib/upload.py | 35 +++++++++++++++------------ 11 files changed, 61 insertions(+), 57 deletions(-) diff --git a/Main.py b/Main.py index 5405ec79..c1026e87 100644 --- a/Main.py +++ b/Main.py @@ -366,8 +366,7 @@ def main(args, seed=None): 'U' if world.keyshuffle[player] == "universal" else 'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '') - outfilepname = f'_T{team + 1}' if world.teams > 1 else '' - outfilepname += f'_P{player}' + outfilepname = f'_P{player}' outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \ if world.player_names[player][team] != 'Player%d' % player else '' outfilestuffs = { @@ -410,7 +409,7 @@ def main(args, seed=None): rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc') rom.write_to_file(rompath, hide_enemizer=True) if args.create_diff: - Patch.create_patch_file(rompath) + Patch.create_patch_file(rompath, player=player, player_name = world.player_names[player][team]) return player, team, bytes(rom.name) pool = concurrent.futures.ThreadPoolExecutor() diff --git a/MultiMystery.py b/MultiMystery.py index 4f231702..72e76560 100644 --- a/MultiMystery.py +++ b/MultiMystery.py @@ -26,7 +26,6 @@ if __name__ == "__main__": from Utils import get_public_ipv4, get_options from Mystery import get_seed_name - from Patch import create_patch_file options = get_options() diff --git a/Patch.py b/Patch.py index 0c0f08dd..962bde44 100644 --- a/Patch.py +++ b/Patch.py @@ -12,7 +12,7 @@ from typing import Tuple, Optional import Utils from worlds.alttp.Rom import JAP10HASH -current_patch_version = 1 +current_patch_version = 2 def get_base_rom_path(file_name: str = "") -> str: @@ -43,9 +43,9 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes: patch = yaml.dump({"meta": metadata, "patch": patch, - "game": "alttp", - "compatible_version": 1, + "game": "A Link to the Past", # minimum version of patch system expected for patching to be successful + "compatible_version": 1, "version": current_patch_version, "base_checksum": JAP10HASH}) return patch.encode(encoding="utf-8-sig") @@ -58,10 +58,13 @@ def generate_patch(rom: bytes, metadata: Optional[dict] = None) -> bytes: return generate_yaml(patch, metadata) -def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None) -> str: +def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None, + player: int = 0, player_name: str = "") -> str: + meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise + "player_id": player, + "player_name": player_name} bytes = generate_patch(load_bytes(rom_file_to_patch), - { - "server": server}) # allow immediate connection to server in multiworld. Empty string otherwise + meta) target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".apbp" write_lzma(bytes, target) return target diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 2f365127..839529b9 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -28,6 +28,7 @@ class AlreadyRunningException(Exception): if sys.platform == 'win32': import os + class Locker(CommonLocker): def __enter__(self): try: @@ -46,6 +47,7 @@ if sys.platform == 'win32': else: # unix import fcntl + class Locker(CommonLocker): def __enter__(self): try: @@ -148,6 +150,7 @@ multiworlds = {} guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian") + class MultiworldInstance(): def __init__(self, room: Room, config: dict): self.room_id = room.id @@ -172,7 +175,7 @@ class MultiworldInstance(): self.process = None def _collect(self): - self.process.join() # wait for process to finish + self.process.join() # wait for process to finish self.process = None self.guardian = None diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index ceb464ac..6a3204a1 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -2,12 +2,12 @@ from flask import send_file, Response from pony.orm import select from Patch import update_patch_data -from WebHostLib import app, Patch, Room, Seed +from WebHostLib import app, Slot, Room, Seed @app.route("/dl_patch//") def download_patch(room_id, patch_id): - patch = Patch.get(id=patch_id) + patch = Slot.get(id=patch_id) if not patch: return "Patch not found" else: @@ -30,8 +30,9 @@ def download_spoiler(seed_id): @app.route("/dl_raw_patch//") def download_raw_patch(seed_id, player_id: int): - patch = select(patch for patch in Patch if - patch.player_id == player_id and patch.seed.id == seed_id).first() + seed = Seed.get(id=seed_id) + patch = select(patch for patch in seed.slots if + patch.player_id == player_id).first() if not patch: return "Patch not found" diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index d15af19c..718fe3b9 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -128,7 +128,7 @@ def wait_seed(seed: UUID): def upload_to_db(folder, owner, sid, race:bool): - patches = set() + slots = set() spoiler = "" multidata = None @@ -138,8 +138,8 @@ def upload_to_db(folder, owner, sid, race:bool): player_text = file.split("_P", 1)[1] player_name = player_text.split("_", 1)[1].split(".", 1)[0] player_id = int(player_text.split(".", 1)[0].split("_", 1)[0]) - patches.add(Patch(data=open(file, "rb").read(), - player_id=player_id, player_name = player_name)) + slots.add(Slot(data=open(file, "rb").read(), + player_id=player_id, player_name = player_name, game = "A Link to the Past")) elif file.endswith(".txt"): spoiler = open(file, "rt", encoding="utf-8-sig").read() elif file.endswith(".archipelago"): @@ -147,12 +147,12 @@ def upload_to_db(folder, owner, sid, race:bool): if multidata: with db_session: if sid: - seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner, + seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, id=sid, meta=json.dumps({"tags": ["generated"]})) else: - seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner, + seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, meta=json.dumps({"tags": ["generated"]})) - for patch in patches: + for patch in slots: patch.seed = seed if sid: gen = Generation.get(id=sid) diff --git a/WebHostLib/models.py b/WebHostLib/models.py index 7c910cb8..e03d7666 100644 --- a/WebHostLib/models.py +++ b/WebHostLib/models.py @@ -9,12 +9,13 @@ STATE_STARTED = 1 STATE_ERROR = -1 -class Patch(db.Entity): +class Slot(db.Entity): id = PrimaryKey(int, auto=True) player_id = Required(int) player_name = Required(str, 16) - data = Required(bytes, lazy=True) + data = Optional(bytes, lazy=True) seed = Optional('Seed') + game = Required(str) class Room(db.Entity): @@ -37,7 +38,7 @@ class Seed(db.Entity): multidata = Required(bytes, lazy=True) owner = Required(UUID, index=True) creation_time = Required(datetime, default=lambda: datetime.utcnow()) - patches = Set(Patch) + slots = Set(Slot) spoiler = Optional(LongStr, lazy=True) meta = Required(str, default=lambda: "{\"race\": false}") # additional meta information/tags diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html index 4e80cb20..0ad67085 100644 --- a/WebHostLib/templates/macros.html +++ b/WebHostLib/templates/macros.html @@ -7,9 +7,9 @@ {%- endmacro %} {% macro list_patches_room(room) %} - {% if room.seed.patches %} + {% if room.seed.slots %}
    - {% for patch in room.seed.patches|list|sort(attribute="player_id") %} + {% for patch in room.seed.slots|list|sort(attribute="player_id") %}
  • Patch for player {{ patch.player_id }} - {{ patch.player_name }}
  • {% endfor %} diff --git a/WebHostLib/templates/userContent.html b/WebHostLib/templates/userContent.html index ea3bad20..c7754bed 100644 --- a/WebHostLib/templates/userContent.html +++ b/WebHostLib/templates/userContent.html @@ -31,7 +31,7 @@ {{ room.seed.id|suuid }} {{ room.id|suuid }} - >={{ room.seed.patches|length }} + >={{ room.seed.slots|length }} {{ room.creation_time.strftime("%Y-%m-%d %H:%M") }} {{ room.last_activity.strftime("%Y-%m-%d %H:%M") }} @@ -56,7 +56,7 @@ {% for seed in seeds %} {{ seed.id|suuid }} - {% if seed.multidata %}>={{ seed.patches|length }}{% else %}1{% endif %} + {% if seed.multidata %}>={{ seed.slots|length }}{% else %}1{% endif %} {{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }} diff --git a/WebHostLib/templates/viewSeed.html b/WebHostLib/templates/viewSeed.html index 89968909..d8ba3d1c 100644 --- a/WebHostLib/templates/viewSeed.html +++ b/WebHostLib/templates/viewSeed.html @@ -37,17 +37,10 @@ Players: 
      - {% for team in seed.multidata["names"] %} - {% set outer_loop = loop %} -
    • Team #{{ loop.index }} - {{ team | length }} -
        - {% for player in team %} -
      • - {{ player }} -
      • - {% endfor %} -
      -
    • + {% for patch in seed.slots|sort(attribute='player_id') %} +
    • + {{ patch.player_name }} +
    • {% endfor %}
    @@ -64,13 +57,13 @@ {% else %} - Patches:  + Files: 
      - {% for patch in seed.patches %} + {% for slot in seed.slots %}
    • - Player {{ patch.player }} + Player {{ slot.player_name }}
    • diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 95735955..fd54eb80 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -1,13 +1,12 @@ -import json -import zlib import zipfile -import logging +import lzma import MultiServer from flask import request, flash, redirect, url_for, session, render_template -from pony.orm import commit, select +from pony.orm import flush, select -from WebHostLib import app, Seed, Room, Patch +from WebHostLib import app, Seed, Room, Slot +from Utils import parse_yaml accepted_zip_contents = {"patches": ".apbp", "spoiler": ".txt", @@ -30,7 +29,7 @@ def uploads(): flash('No selected file') elif file and allowed_file(file.filename): if file.filename.endswith(".zip"): - patches = set() + slots = set() spoiler = "" multidata = None with zipfile.ZipFile(file, 'r') as zfile: @@ -40,9 +39,15 @@ def uploads(): if file.filename.endswith(banned_zip_contents): return "Uploaded data contained a rom file, which is likely to contain copyrighted material. Your file was deleted." elif file.filename.endswith(".apbp"): - splitted = file.filename.split("/")[-1][3:].split("P", 1) - player_id, player_name = splitted[1].split(".")[0].split("_") - patches.add(Patch(data=zfile.open(file, "r").read(), player_name=player_name, player_id=player_id)) + data = zfile.open(file, "r").read() + yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig")) + if yaml_data["version"] < 2: + return "Old format cannot be uploaded (outdated .apbp)", 500 + + metadata = yaml_data["meta"] + slots.add(Slot(data=data, player_name=metadata["player_name"], + player_id=metadata["player_id"], + game="A Link to the Past")) elif file.filename.endswith(".txt"): spoiler = zfile.open(file, "r").read().decode("utf-8-sig") elif file.filename.endswith(".archipelago"): @@ -54,11 +59,11 @@ def uploads(): else: multidata = zfile.open(file).read() if multidata: - commit() # commit patches - seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=session["_id"]) - commit() # create seed - for patch in patches: - patch.seed = seed + flush() # commit slots + seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=session["_id"]) + flush() # create seed + for slot in slots: + slot.seed = seed return redirect(url_for("viewSeed", seed=seed.id)) else: @@ -72,7 +77,7 @@ def uploads(): raise else: seed = Seed(multidata=multidata, owner=session["_id"]) - commit() # place into DB and generate ids + flush() # place into DB and generate ids return redirect(url_for("viewSeed", seed=seed.id)) else: flash("Not recognized file format. Awaiting a .multidata file.")