Bombsquad-Ballistica-Modded.../dist/ba_data/python/bacommon/bacloud.py

178 lines
6.5 KiB
Python
Raw Normal View History

2022-06-09 01:26:46 +05:30
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to the bacloud tool."""
from __future__ import annotations
2022-06-30 00:31:52 +05:30
2022-06-09 01:26:46 +05:30
from dataclasses import dataclass
2022-06-30 00:31:52 +05:30
from typing import TYPE_CHECKING, Annotated
2022-06-09 01:26:46 +05:30
2022-06-30 00:31:52 +05:30
from efro.dataclassio import ioprepped, IOAttrs
2022-06-09 01:26:46 +05:30
if TYPE_CHECKING:
pass
2022-06-30 00:31:52 +05:30
# Version is sent to the master-server with all commands. Can be incremented
# if we need to change behavior server-side to go along with client changes.
2024-11-28 00:23:35 +05:30
BACLOUD_VERSION = 13
def asset_file_cache_path(filehash: str) -> str:
"""Given a sha256 hex file hash, return a storage path."""
# We expect a 64 byte hex str with only lowercase letters and
# numbers. Note to self: I considered base64 hashes to save space
# but then remembered that lots of filesystems out there ignore case
# so that would not end well.
assert len(filehash) == 64
assert filehash.islower()
assert filehash.isalnum()
# Split into a few levels of directories to keep directory listings
# and operations reasonable. This will give 256 top level dirs, each
# with 256 subdirs. So if we have 65,536 files in our cache then
# dirs will average 1 file each. That seems like a reasonable spread
# I think.
return f'{filehash[:2]}/{filehash[2:4]}/{filehash[4:]}'
2022-06-30 00:31:52 +05:30
@ioprepped
@dataclass
class RequestData:
"""Request sent to bacloud server."""
2022-06-30 00:31:52 +05:30
command: Annotated[str, IOAttrs('c')]
token: Annotated[str | None, IOAttrs('t')]
payload: Annotated[dict, IOAttrs('p')]
tzoffset: Annotated[float, IOAttrs('z')]
isatty: Annotated[bool, IOAttrs('y')]
2022-06-09 01:26:46 +05:30
@ioprepped
@dataclass
2022-06-30 00:31:52 +05:30
class ResponseData:
2025-04-06 17:17:13 +05:30
"""Response sent from the bacloud server to the client."""
2024-11-28 00:23:35 +05:30
@ioprepped
@dataclass
class Downloads:
"""Info about downloads included in a response."""
@ioprepped
@dataclass
class Entry:
"""Individual download."""
path: Annotated[str, IOAttrs('p')]
2025-04-06 17:17:13 +05:30
#: Args include with this particular request (combined with
#: baseargs).
2024-11-28 00:23:35 +05:30
args: Annotated[dict[str, str], IOAttrs('a')]
2025-04-06 17:17:13 +05:30
2024-11-28 00:23:35 +05:30
# TODO: could add a hash here if we want the client to
# verify hashes.
2025-04-06 17:17:13 +05:30
#: If present, will be prepended to all entry paths via os.path.join.
2024-11-28 00:23:35 +05:30
basepath: Annotated[str | None, IOAttrs('p')]
2025-04-06 17:17:13 +05:30
#: Server command that should be called for each download. The
#: server command is expected to respond with a downloads_inline
#: containing a single 'default' entry. In the future this may
#: be expanded to a more streaming-friendly process.
2024-11-28 00:23:35 +05:30
cmd: Annotated[str, IOAttrs('c')]
2025-04-06 17:17:13 +05:30
#: Args that should be included with all download requests.
2024-11-28 00:23:35 +05:30
baseargs: Annotated[dict[str, str], IOAttrs('a')]
2025-04-06 17:17:13 +05:30
#: Everything that should be downloaded.
2024-11-28 00:23:35 +05:30
entries: Annotated[list[Entry], IOAttrs('e')]
2025-04-06 17:17:13 +05:30
#: If present, client should print this message before any other
#: response processing (including error handling) occurs.
2022-06-30 00:31:52 +05:30
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
2025-04-06 17:17:13 +05:30
#: End arg for message print() call.
2022-06-30 00:31:52 +05:30
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
2025-04-06 17:17:13 +05:30
#: If present, client should abort with this error message.
2022-06-30 00:31:52 +05:30
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
2025-04-06 17:17:13 +05:30
#: How long to wait before proceeding with remaining response (can
#: be useful when waiting for server progress in a loop).
2022-06-30 00:31:52 +05:30
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
2025-04-06 17:17:13 +05:30
#: If present, a token that should be stored client-side and passed
#: with subsequent commands.
2022-06-30 00:31:52 +05:30
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
2025-04-06 17:17:13 +05:30
#: If True, any existing client-side token should be discarded.
2022-06-30 00:31:52 +05:30
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
2025-04-06 17:17:13 +05:30
#: If present, client should generate a manifest of this dir.
#: It should be added to end_command args as 'manifest'.
2024-03-10 15:37:50 +05:30
dir_manifest: Annotated[str | None, IOAttrs('man', store_default=False)] = (
None
)
2025-04-06 17:17:13 +05:30
#: If present, client should upload the requested files (arg1)
#: individually to a server command (arg2) with provided args (arg3).
uploads: Annotated[
tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, a list of pathnames that should be gzipped
#: and uploaded to an 'uploads_inline' bytes dict in end_command args.
#: This should be limited to relatively small files.
uploads_inline: Annotated[
list[str] | None, IOAttrs('uinl', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, file paths that should be deleted on the client.
deletes: Annotated[
list[str] | None, IOAttrs('dlt', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, describes files the client should individually
#: request from the server if not already present on the client.
2024-11-28 00:23:35 +05:30
downloads: Annotated[
Downloads | None, IOAttrs('dl', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, pathnames mapped to gzipped data to
#: be written to the client. This should only be used for relatively
#: small files as they are all included inline as part of the response.
downloads_inline: Annotated[
2024-11-28 00:23:35 +05:30
dict[str, bytes] | None, IOAttrs('dinl', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, all empty dirs under this one should be removed.
dir_prune_empty: Annotated[
str | None, IOAttrs('dpe', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, url to display to the user.
2022-06-30 00:31:52 +05:30
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
2025-04-06 17:17:13 +05:30
#: If present, a line of input is read and placed into
#: end_command args as 'input'. The first value is the prompt printed
#: before reading and the second is whether it should be read as a
#: password (without echoing to the terminal).
input_prompt: Annotated[
tuple[str, bool] | None, IOAttrs('inp', store_default=False)
] = None
2025-04-06 17:17:13 +05:30
#: If present, a message that should be printed after all other
#: response processing is done.
2024-03-10 15:37:50 +05:30
end_message: Annotated[str | None, IOAttrs('em', store_default=False)] = (
None
)
2025-04-06 17:17:13 +05:30
#: End arg for end_message print() call.
2022-06-30 00:31:52 +05:30
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
2025-04-06 17:17:13 +05:30
#: If present, this command is run with these args at the end
#: of response processing.
end_command: Annotated[
tuple[str, dict] | None, IOAttrs('ec', store_default=False)
] = None