import collections
from attr import attrib, dataclass
from gd.typing import Any, Dict, Iterable, LevelCollection, List, Optional, Sequence, Tuple, Union
from gd.utils import search_utils as search
from gd.utils.text_tools import dumps, make_repr
from gd.utils.xml_parser import XMLParser
from gd.api.struct import LevelAPI
from gd.api.utils import get_default
__all__ = ("Part", "Database", "LevelCollection")
@dataclass
class Batch:
completed: List[int] = attrib()
stars: List[int] = attrib()
demons: List[int] = attrib()
@classmethod
def create_empty(cls) -> Any:
return cls(completed=[], stars=[], demons=[])
@dataclass
class Values:
official: List[int] = attrib()
normal: Batch = attrib()
timely: Batch = attrib()
gauntlet: Batch = attrib()
packs: List[int] = attrib()
def get_prefixes(self) -> Dict[str, List[int]]:
values, normal, timely, gauntlet = self, self.normal, self.timely, self.gauntlet
return {
"n_": values.official,
"c_": normal.completed,
"d_": timely.completed,
"g_": gauntlet.completed,
"star_": normal.stars,
"dstar_": timely.stars,
"gstar_": gauntlet.stars,
"demon_": normal.demons,
"ddemon_": timely.demons,
"gdemon_": gauntlet.demons,
"pack_": values.packs,
}
@classmethod
def create_empty(cls) -> Any:
return cls(
official=[],
normal=Batch.create_empty(),
timely=Batch.create_empty(),
gauntlet=Batch.create_empty(),
packs=[],
)
def remove_prefix(string: str, prefix: str) -> str:
if string.startswith(prefix):
return string[len(prefix) :]
return string
[docs]class Part(dict):
def __init__(
self, stream: str = Union[bytes, str], default: Optional[Dict[str, Any]] = None
) -> None:
self.parser = XMLParser()
if isinstance(stream, str):
stream = stream.encode()
try:
loaded = self.parser.load(stream)
except Exception:
if default is None:
default = {}
loaded = default
super().__init__(loaded)
def __str__(self) -> str:
return dumps(self, indent=4)
def __repr__(self) -> str:
info = {"outer_len": len(self)}
return make_repr(self, info)
[docs] def copy(self) -> Any:
return self.__class__(super().copy())
[docs] def set(self, key: Any, value: Any) -> None:
"""Same as self[key] = value."""
self[key] = value
[docs] def dump(self) -> str:
"""Dump the part and return an xml string."""
return self.parser.dump(self)
[docs]class Database:
def __init__(self, main: str = "", levels: str = "") -> None:
self.main = Part(main, get_default("main"))
self.levels = Part(levels, get_default("levels"))
def __repr__(self) -> str:
info = {"main": repr(self.main), "levels": repr(self.levels)}
return make_repr(self, info)
def __json__(self) -> Dict[str, Any]:
return {"main": self.main, "levels": self.levels}
def get_user_name(self) -> str:
return self.main.get("GJA_001", "unknown")
def set_user_name(self, user_name: str) -> None:
self.main.set("GJA_001", user_name)
user_name = property(get_user_name, set_user_name)
def get_password(self) -> str:
return self.main.get("GJA_002", "unknown")
def set_password(self, password: str) -> None:
self.main.set("GJA_002", password)
password = property(get_password, set_password)
def get_account_id(self) -> int:
return self.main.get("GJA_003", 0)
def set_account_id(self, account_id: int) -> None:
self.main.set("GJA_003", account_id)
account_id = property(get_account_id, set_account_id)
def get_user_id(self) -> int:
return self.main.get("playerUserID", 0)
def set_user_id(self, user_id: int) -> None:
self.main.set("playerUserID", user_id)
user_id = property(get_user_id, set_user_id)
def get_udid(self) -> str:
return self.main.get("playerUDID", "S0")
def set_udid(self, udid: str) -> None:
self.main.set("playerUDID", udid)
udid = property(get_udid, set_udid)
def get_bootups(self) -> int:
return self.main.get("bootups", 0)
def set_bootups(self, bootups: int) -> None:
self.main.set("bootups", bootups)
bootups = property(get_bootups, set_bootups)
def get_followed(self) -> List[int]:
return list(map(int, self.main.get("GLM_06", {}).keys()))
def set_followed(self, followed: Sequence[int]) -> None:
self.main.set("GLM_06", {str(account_id): 1 for account_id in followed})
followed = property(get_followed, set_followed)
def get_values(self) -> Values: # O(nm), thanks rob
values = Values.create_empty()
prefixes = values.get_prefixes()
for string in self.main.get("GS_completed", {}).keys():
for prefix, array in prefixes.items():
id_string = remove_prefix(string, prefix)
if id_string != string:
array.append(int(id_string))
break
return values
def set_values(self, values: Values) -> None:
mapping = {}
prefixes = values.get_prefixes()
for prefix, array in prefixes.items():
mapping.update({f"{prefix}{value_id}": 1 for value_id in array})
self.main.set("GS_completed", mapping)
values = property(get_values, set_values)
def _to_levels(self, level_dicts: List[Dict[str, Any]], dump_name: str) -> LevelCollection:
return LevelCollection.launch(
self,
dump_name,
map(LevelAPI.from_mapping, filter(lambda thing: isinstance(thing, dict), level_dicts)),
)
[docs] def load_saved_levels(self) -> LevelCollection:
"""Load "Saved Levels" into :class:`.api.LevelCollection`."""
return self._to_levels(self.main.get("GLM_03", {}).values(), "dump_saved_levels")
[docs] def dump_saved_levels(self, levels: LevelCollection) -> None:
"""Dump "Saved Levels" from :class:`.api.LevelCollection`."""
self.main.set("GLM_03", {str(level.id): level.to_map() for level in levels})
[docs] def load_my_levels(self) -> LevelCollection:
"""Load "My Levels" into :class:`.api.LevelCollection`."""
return self._to_levels(self.levels.get("LLM_01", {}).values(), "dump_my_levels")
[docs] def dump_my_levels(self, levels: LevelCollection, *, prefix: str = "k_") -> None:
"""Dump "My Levels" from :class:`.api.LevelCollection`."""
stuff = {"_isArr": True}
stuff.update({f"{prefix}{n}": level.to_map() for n, level in enumerate(levels)})
self.levels.set("LLM_01", stuff)
def dump(self) -> None:
from gd.api.loader import save # I hate circular imports. - nekit
save.dump(self)
def as_tuple(self) -> Tuple[Part, Part]:
return (self.main, self.levels)
class LevelCollection(collections.UserList):
def __init__(self, *args) -> None:
if len(args) == 1:
new_args = args[0]
if iterable(new_args):
args = new_args
super().__init__(args)
self._callback = None
self._dump_name = None
def get_by_name(self, name: str) -> Optional[LevelAPI]:
return search.get(self, name=name)
@classmethod
def launch(cls, caller: Any, dump_name: str, iterable: Iterable) -> LevelCollection:
self = cls(iterable)
self._callback = caller
self._dump_name = dump_name
return self
def dump(self, api: Optional[Database] = None) -> None:
if api is None:
api = self._callback
getattr(api, self._dump_name)(self)
def iterable(maybe_iterable: Any) -> bool:
try:
iter(maybe_iterable)
return True
except TypeError:
return False