Source code for bacommon.cloud
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to cloud functionality."""
from __future__ import annotations
from enum import Enum
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated, override
from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs
from bacommon.securedata import SecureDataChecker
from bacommon.transfer import DirectoryManifest
from bacommon.login import LoginType
if TYPE_CHECKING:
pass
[docs]
class WebLocation(Enum):
"""Set of places we can be directed on ballistica.net."""
ACCOUNT_EDITOR = 'e'
ACCOUNT_DELETE_SECTION = 'd'
[docs]
@ioprepped
@dataclass
class CloudVals:
"""Engine config values provided by the master server.
Used to convey things such as debug logging.
"""
#: Fully qualified type names we should emit extra debug logs for
#: when garbage-collected (for debugging ref loops).
gc_debug_types: list[str] = field(default_factory=list)
#: Max number of objects of a given type to emit debug logs for.
gc_debug_type_limit: int = 2
[docs]
@ioprepped
@dataclass
class LoginProxyRequestMessage(Message):
"""Request send to the cloud to ask for a login-proxy."""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyRequestResponse]
[docs]
@ioprepped
@dataclass
class LoginProxyRequestResponse(Response):
"""Response to a request for a login proxy."""
# URL to direct the user to for sign in.
url: str
# URL to use for overlay-web-browser sign ins.
url_overlay: str
# Proxy-Login id for querying results.
proxyid: str
# Proxy-Login key for querying results.
proxykey: str
[docs]
@ioprepped
@dataclass
class LoginProxyStateQueryMessage(Message):
"""Soo.. how is that login proxy going?"""
proxyid: str
proxykey: str
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyStateQueryResponse]
[docs]
@ioprepped
@dataclass
class LoginProxyStateQueryResponse(Response):
"""Here's the info on that login-proxy you asked about, boss."""
[docs]
class State(Enum):
"""States a login-proxy can be in."""
WAITING = 'waiting'
SUCCESS = 'success'
FAIL = 'fail'
state: State
# On success, these will be filled out.
credentials: str | None
[docs]
@ioprepped
@dataclass
class LoginProxyCompleteMessage(Message):
"""Just so you know, we're done with this proxy."""
proxyid: str
[docs]
@ioprepped
@dataclass
class PingMessage(Message):
"""Standard ping."""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [PingResponse]
[docs]
@ioprepped
@dataclass
class PingResponse(Response):
"""pong."""
[docs]
@ioprepped
@dataclass
class TestMessage(Message):
"""Can I get some of that workspace action?"""
testfoo: int
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [TestResponse]
[docs]
@ioprepped
@dataclass
class TestResponse(Response):
"""Here's that workspace you asked for, boss."""
testfoo: int
[docs]
@ioprepped
@dataclass
class WorkspaceFetchState:
"""Common state data for a workspace fetch."""
manifest: DirectoryManifest
iteration: int = 0
total_deletes: int = 0
total_downloads: int = 0
total_up_to_date: int | None = None
[docs]
@ioprepped
@dataclass
class WorkspaceFetchMessage(Message):
"""Can I get some of that workspace action?"""
workspaceid: str
state: WorkspaceFetchState
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [WorkspaceFetchResponse]
[docs]
@ioprepped
@dataclass
class WorkspaceFetchResponse(Response):
"""Here's that workspace you asked for, boss."""
state: WorkspaceFetchState
deletes: list[str] = field(
default_factory=list
)
downloads_inline: dict[str, bytes] = field(default_factory=dict)
done: bool = False
[docs]
@ioprepped
@dataclass
class MerchAvailabilityMessage(Message):
"""Can we show merch link?"""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [MerchAvailabilityResponse]
[docs]
@ioprepped
@dataclass
class MerchAvailabilityResponse(Response):
"""About that merch..."""
url: str | None
[docs]
@ioprepped
@dataclass
class SignInMessage(Message):
"""Can I sign in please?"""
login_type: LoginType
sign_in_token: str
# For debugging. Can remove soft_default once build 20988+ is ubiquitous.
description: str
apptime: float
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [SignInResponse]
[docs]
@ioprepped
@dataclass
class SignInResponse(Response):
"""Here's that sign-in result you asked for, boss."""
credentials: str | None
[docs]
@ioprepped
@dataclass
class ManageAccountMessage(Message):
"""Message asking for a manage-account url."""
weblocation: WebLocation = (
WebLocation.ACCOUNT_EDITOR
)
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [ManageAccountResponse]
[docs]
@ioprepped
@dataclass
class ManageAccountResponse(Response):
"""Here's that sign-in result you asked for, boss."""
url: str | None
[docs]
@ioprepped
@dataclass
class StoreQueryMessage(Message):
"""Message asking about purchasable stuff and store related state."""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [StoreQueryResponse]
[docs]
@ioprepped
@dataclass
class StoreQueryResponse(Response):
"""Here's that store info you asked for, boss."""
[docs]
class Result(Enum):
"""Our overall result."""
SUCCESS = 's'
ERROR = 'e'
[docs]
@dataclass
class Purchase:
"""Info about a purchasable thing."""
purchaseid: str
# Overall result; all data is undefined if not SUCCESS.
result: Result
tokens: int
gold_pass: bool
available_purchases: list[Purchase]
token_info_url: str
[docs]
@ioprepped
@dataclass
class SecureDataCheckMessage(Message):
"""Was this data signed by the master-server?."""
data: bytes
signature: bytes
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [SecureDataCheckResponse]
[docs]
@ioprepped
@dataclass
class SecureDataCheckResponse(Response):
"""Here's the result of that data check, boss."""
# Whether the data signature was valid.
result: bool
[docs]
@ioprepped
@dataclass
class SecureDataCheckerRequest(Message):
"""Can I get a checker over here?."""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [SecureDataCheckerResponse]
[docs]
@ioprepped
@dataclass
class SecureDataCheckerResponse(Response):
"""Here's that checker ya asked for, boss."""
checker: SecureDataChecker
[docs]
@ioprepped
@dataclass
class CloudValsRequest(Message):
"""Can a fella get some cloud vals around here?."""
[docs]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [CloudValsResponse]
[docs]
@ioprepped
@dataclass
class CloudValsResponse(Response):
"""Here's them cloud vals ya asked for, boss."""
vals: CloudVals
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum