prepare webhost for multi-game per-slot downloads
This commit is contained in:
parent
23678b814d
commit
19896e1fae
5
Main.py
5
Main.py
|
@ -366,8 +366,7 @@ def main(args, seed=None):
|
||||||
'U' if world.keyshuffle[player] == "universal" else 'S' if world.keyshuffle[player] else '',
|
'U' if world.keyshuffle[player] == "universal" else 'S' if world.keyshuffle[player] else '',
|
||||||
'B' if world.bigkeyshuffle[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(' ', '_')}" \
|
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
|
||||||
if world.player_names[player][team] != 'Player%d' % player else ''
|
if world.player_names[player][team] != 'Player%d' % player else ''
|
||||||
outfilestuffs = {
|
outfilestuffs = {
|
||||||
|
@ -410,7 +409,7 @@ def main(args, seed=None):
|
||||||
rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
||||||
rom.write_to_file(rompath, hide_enemizer=True)
|
rom.write_to_file(rompath, hide_enemizer=True)
|
||||||
if args.create_diff:
|
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)
|
return player, team, bytes(rom.name)
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor()
|
pool = concurrent.futures.ThreadPoolExecutor()
|
||||||
|
|
|
@ -26,7 +26,6 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
from Utils import get_public_ipv4, get_options
|
from Utils import get_public_ipv4, get_options
|
||||||
from Mystery import get_seed_name
|
from Mystery import get_seed_name
|
||||||
from Patch import create_patch_file
|
|
||||||
|
|
||||||
options = get_options()
|
options = get_options()
|
||||||
|
|
||||||
|
|
15
Patch.py
15
Patch.py
|
@ -12,7 +12,7 @@ from typing import Tuple, Optional
|
||||||
import Utils
|
import Utils
|
||||||
from worlds.alttp.Rom import JAP10HASH
|
from worlds.alttp.Rom import JAP10HASH
|
||||||
|
|
||||||
current_patch_version = 1
|
current_patch_version = 2
|
||||||
|
|
||||||
|
|
||||||
def get_base_rom_path(file_name: str = "") -> str:
|
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:
|
def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
|
||||||
patch = yaml.dump({"meta": metadata,
|
patch = yaml.dump({"meta": metadata,
|
||||||
"patch": patch,
|
"patch": patch,
|
||||||
"game": "alttp",
|
"game": "A Link to the Past",
|
||||||
"compatible_version": 1,
|
|
||||||
# minimum version of patch system expected for patching to be successful
|
# minimum version of patch system expected for patching to be successful
|
||||||
|
"compatible_version": 1,
|
||||||
"version": current_patch_version,
|
"version": current_patch_version,
|
||||||
"base_checksum": JAP10HASH})
|
"base_checksum": JAP10HASH})
|
||||||
return patch.encode(encoding="utf-8-sig")
|
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)
|
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),
|
bytes = generate_patch(load_bytes(rom_file_to_patch),
|
||||||
{
|
meta)
|
||||||
"server": server}) # allow immediate connection to server in multiworld. Empty string otherwise
|
|
||||||
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".apbp"
|
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".apbp"
|
||||||
write_lzma(bytes, target)
|
write_lzma(bytes, target)
|
||||||
return target
|
return target
|
||||||
|
|
|
@ -28,6 +28,7 @@ class AlreadyRunningException(Exception):
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class Locker(CommonLocker):
|
class Locker(CommonLocker):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
|
@ -46,6 +47,7 @@ if sys.platform == 'win32':
|
||||||
else: # unix
|
else: # unix
|
||||||
import fcntl
|
import fcntl
|
||||||
|
|
||||||
|
|
||||||
class Locker(CommonLocker):
|
class Locker(CommonLocker):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
|
@ -148,6 +150,7 @@ multiworlds = {}
|
||||||
|
|
||||||
guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian")
|
guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian")
|
||||||
|
|
||||||
|
|
||||||
class MultiworldInstance():
|
class MultiworldInstance():
|
||||||
def __init__(self, room: Room, config: dict):
|
def __init__(self, room: Room, config: dict):
|
||||||
self.room_id = room.id
|
self.room_id = room.id
|
||||||
|
|
|
@ -2,12 +2,12 @@ from flask import send_file, Response
|
||||||
from pony.orm import select
|
from pony.orm import select
|
||||||
|
|
||||||
from Patch import update_patch_data
|
from Patch import update_patch_data
|
||||||
from WebHostLib import app, Patch, Room, Seed
|
from WebHostLib import app, Slot, Room, Seed
|
||||||
|
|
||||||
|
|
||||||
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
||||||
def download_patch(room_id, patch_id):
|
def download_patch(room_id, patch_id):
|
||||||
patch = Patch.get(id=patch_id)
|
patch = Slot.get(id=patch_id)
|
||||||
if not patch:
|
if not patch:
|
||||||
return "Patch not found"
|
return "Patch not found"
|
||||||
else:
|
else:
|
||||||
|
@ -30,8 +30,9 @@ def download_spoiler(seed_id):
|
||||||
|
|
||||||
@app.route("/dl_raw_patch/<suuid:seed_id>/<int:player_id>")
|
@app.route("/dl_raw_patch/<suuid:seed_id>/<int:player_id>")
|
||||||
def download_raw_patch(seed_id, player_id: int):
|
def download_raw_patch(seed_id, player_id: int):
|
||||||
patch = select(patch for patch in Patch if
|
seed = Seed.get(id=seed_id)
|
||||||
patch.player_id == player_id and patch.seed.id == seed_id).first()
|
patch = select(patch for patch in seed.slots if
|
||||||
|
patch.player_id == player_id).first()
|
||||||
|
|
||||||
if not patch:
|
if not patch:
|
||||||
return "Patch not found"
|
return "Patch not found"
|
||||||
|
|
|
@ -128,7 +128,7 @@ def wait_seed(seed: UUID):
|
||||||
|
|
||||||
|
|
||||||
def upload_to_db(folder, owner, sid, race:bool):
|
def upload_to_db(folder, owner, sid, race:bool):
|
||||||
patches = set()
|
slots = set()
|
||||||
spoiler = ""
|
spoiler = ""
|
||||||
|
|
||||||
multidata = None
|
multidata = None
|
||||||
|
@ -138,8 +138,8 @@ def upload_to_db(folder, owner, sid, race:bool):
|
||||||
player_text = file.split("_P", 1)[1]
|
player_text = file.split("_P", 1)[1]
|
||||||
player_name = player_text.split("_", 1)[1].split(".", 1)[0]
|
player_name = player_text.split("_", 1)[1].split(".", 1)[0]
|
||||||
player_id = int(player_text.split(".", 1)[0].split("_", 1)[0])
|
player_id = int(player_text.split(".", 1)[0].split("_", 1)[0])
|
||||||
patches.add(Patch(data=open(file, "rb").read(),
|
slots.add(Slot(data=open(file, "rb").read(),
|
||||||
player_id=player_id, player_name = player_name))
|
player_id=player_id, player_name = player_name, game = "A Link to the Past"))
|
||||||
elif file.endswith(".txt"):
|
elif file.endswith(".txt"):
|
||||||
spoiler = open(file, "rt", encoding="utf-8-sig").read()
|
spoiler = open(file, "rt", encoding="utf-8-sig").read()
|
||||||
elif file.endswith(".archipelago"):
|
elif file.endswith(".archipelago"):
|
||||||
|
@ -147,12 +147,12 @@ def upload_to_db(folder, owner, sid, race:bool):
|
||||||
if multidata:
|
if multidata:
|
||||||
with db_session:
|
with db_session:
|
||||||
if sid:
|
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"]}))
|
id=sid, meta=json.dumps({"tags": ["generated"]}))
|
||||||
else:
|
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"]}))
|
meta=json.dumps({"tags": ["generated"]}))
|
||||||
for patch in patches:
|
for patch in slots:
|
||||||
patch.seed = seed
|
patch.seed = seed
|
||||||
if sid:
|
if sid:
|
||||||
gen = Generation.get(id=sid)
|
gen = Generation.get(id=sid)
|
||||||
|
|
|
@ -9,12 +9,13 @@ STATE_STARTED = 1
|
||||||
STATE_ERROR = -1
|
STATE_ERROR = -1
|
||||||
|
|
||||||
|
|
||||||
class Patch(db.Entity):
|
class Slot(db.Entity):
|
||||||
id = PrimaryKey(int, auto=True)
|
id = PrimaryKey(int, auto=True)
|
||||||
player_id = Required(int)
|
player_id = Required(int)
|
||||||
player_name = Required(str, 16)
|
player_name = Required(str, 16)
|
||||||
data = Required(bytes, lazy=True)
|
data = Optional(bytes, lazy=True)
|
||||||
seed = Optional('Seed')
|
seed = Optional('Seed')
|
||||||
|
game = Required(str)
|
||||||
|
|
||||||
|
|
||||||
class Room(db.Entity):
|
class Room(db.Entity):
|
||||||
|
@ -37,7 +38,7 @@ class Seed(db.Entity):
|
||||||
multidata = Required(bytes, lazy=True)
|
multidata = Required(bytes, lazy=True)
|
||||||
owner = Required(UUID, index=True)
|
owner = Required(UUID, index=True)
|
||||||
creation_time = Required(datetime, default=lambda: datetime.utcnow())
|
creation_time = Required(datetime, default=lambda: datetime.utcnow())
|
||||||
patches = Set(Patch)
|
slots = Set(Slot)
|
||||||
spoiler = Optional(LongStr, lazy=True)
|
spoiler = Optional(LongStr, lazy=True)
|
||||||
meta = Required(str, default=lambda: "{\"race\": false}") # additional meta information/tags
|
meta = Required(str, default=lambda: "{\"race\": false}") # additional meta information/tags
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
</ul>
|
</ul>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
{% macro list_patches_room(room) %}
|
{% macro list_patches_room(room) %}
|
||||||
{% if room.seed.patches %}
|
{% if room.seed.slots %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for patch in room.seed.patches|list|sort(attribute="player_id") %}
|
{% for patch in room.seed.slots|list|sort(attribute="player_id") %}
|
||||||
<li><a href="{{ url_for("download_patch", patch_id=patch.id, room_id=room.id) }}">
|
<li><a href="{{ url_for("download_patch", patch_id=patch.id, room_id=room.id) }}">
|
||||||
Patch for player {{ patch.player_id }} - {{ patch.player_name }}</a></li>
|
Patch for player {{ patch.player_id }} - {{ patch.player_name }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for("viewSeed", seed=room.seed.id) }}">{{ room.seed.id|suuid }}</a></td>
|
<td><a href="{{ url_for("viewSeed", seed=room.seed.id) }}">{{ room.seed.id|suuid }}</a></td>
|
||||||
<td><a href="{{ url_for("hostRoom", room=room.id) }}">{{ room.id|suuid }}</a></td>
|
<td><a href="{{ url_for("hostRoom", room=room.id) }}">{{ room.id|suuid }}</a></td>
|
||||||
<td>>={{ room.seed.patches|length }}</td>
|
<td>>={{ room.seed.slots|length }}</td>
|
||||||
<td>{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
<td>{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||||
<td>{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}</td>
|
<td>{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
{% for seed in seeds %}
|
{% for seed in seeds %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for("viewSeed", seed=seed.id) }}">{{ seed.id|suuid }}</a></td>
|
<td><a href="{{ url_for("viewSeed", seed=seed.id) }}">{{ seed.id|suuid }}</a></td>
|
||||||
<td>{% if seed.multidata %}>={{ seed.patches|length }}{% else %}1{% endif %}
|
<td>{% if seed.multidata %}>={{ seed.slots|length }}{% else %}1{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
<td>{{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -37,16 +37,9 @@
|
||||||
<td>Players: </td>
|
<td>Players: </td>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
{% for team in seed.multidata["names"] %}
|
{% for patch in seed.slots|sort(attribute='player_id') %}
|
||||||
{% set outer_loop = loop %}
|
|
||||||
<li>Team #{{ loop.index }} - {{ team | length }}
|
|
||||||
<ul>
|
|
||||||
{% for player in team %}
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=loop.index, team_id=outer_loop.index0) }}">{{ player }}</a>
|
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=patch.player_id) }}">{{ patch.player_name }}</a>
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -64,13 +57,13 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Patches: </td>
|
<td>Files: </td>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
{% for patch in seed.patches %}
|
{% for slot in seed.slots %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=patch.player, team_id=0) }}">Player {{ patch.player }}</a>
|
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=slot.player_id) }}">Player {{ slot.player_name }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import json
|
|
||||||
import zlib
|
|
||||||
import zipfile
|
import zipfile
|
||||||
import logging
|
import lzma
|
||||||
import MultiServer
|
import MultiServer
|
||||||
|
|
||||||
from flask import request, flash, redirect, url_for, session, render_template
|
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",
|
accepted_zip_contents = {"patches": ".apbp",
|
||||||
"spoiler": ".txt",
|
"spoiler": ".txt",
|
||||||
|
@ -30,7 +29,7 @@ def uploads():
|
||||||
flash('No selected file')
|
flash('No selected file')
|
||||||
elif file and allowed_file(file.filename):
|
elif file and allowed_file(file.filename):
|
||||||
if file.filename.endswith(".zip"):
|
if file.filename.endswith(".zip"):
|
||||||
patches = set()
|
slots = set()
|
||||||
spoiler = ""
|
spoiler = ""
|
||||||
multidata = None
|
multidata = None
|
||||||
with zipfile.ZipFile(file, 'r') as zfile:
|
with zipfile.ZipFile(file, 'r') as zfile:
|
||||||
|
@ -40,9 +39,15 @@ def uploads():
|
||||||
if file.filename.endswith(banned_zip_contents):
|
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."
|
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. Your file was deleted."
|
||||||
elif file.filename.endswith(".apbp"):
|
elif file.filename.endswith(".apbp"):
|
||||||
splitted = file.filename.split("/")[-1][3:].split("P", 1)
|
data = zfile.open(file, "r").read()
|
||||||
player_id, player_name = splitted[1].split(".")[0].split("_")
|
yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig"))
|
||||||
patches.add(Patch(data=zfile.open(file, "r").read(), player_name=player_name, player_id=player_id))
|
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"):
|
elif file.filename.endswith(".txt"):
|
||||||
spoiler = zfile.open(file, "r").read().decode("utf-8-sig")
|
spoiler = zfile.open(file, "r").read().decode("utf-8-sig")
|
||||||
elif file.filename.endswith(".archipelago"):
|
elif file.filename.endswith(".archipelago"):
|
||||||
|
@ -54,11 +59,11 @@ def uploads():
|
||||||
else:
|
else:
|
||||||
multidata = zfile.open(file).read()
|
multidata = zfile.open(file).read()
|
||||||
if multidata:
|
if multidata:
|
||||||
commit() # commit patches
|
flush() # commit slots
|
||||||
seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=session["_id"])
|
seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=session["_id"])
|
||||||
commit() # create seed
|
flush() # create seed
|
||||||
for patch in patches:
|
for slot in slots:
|
||||||
patch.seed = seed
|
slot.seed = seed
|
||||||
|
|
||||||
return redirect(url_for("viewSeed", seed=seed.id))
|
return redirect(url_for("viewSeed", seed=seed.id))
|
||||||
else:
|
else:
|
||||||
|
@ -72,7 +77,7 @@ def uploads():
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
seed = Seed(multidata=multidata, owner=session["_id"])
|
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))
|
return redirect(url_for("viewSeed", seed=seed.id))
|
||||||
else:
|
else:
|
||||||
flash("Not recognized file format. Awaiting a .multidata file.")
|
flash("Not recognized file format. Awaiting a .multidata file.")
|
||||||
|
|
Loading…
Reference in New Issue