More enumification in inventory code
This commit is contained in:
@@ -8,7 +8,6 @@ from __future__ import annotations
|
||||
import abc
|
||||
import dataclasses
|
||||
import datetime as dt
|
||||
import enum
|
||||
import inspect
|
||||
import logging
|
||||
import struct
|
||||
@@ -32,6 +31,7 @@ from hippolyzer.lib.base.legacy_schema import (
|
||||
SchemaUUID,
|
||||
schema_field,
|
||||
)
|
||||
from hippolyzer.lib.base.templates import SaleType, InventoryType, LegacyIntEnum, AssetType, FolderType
|
||||
|
||||
MAGIC_ID = UUID("3c115e51-04f4-523c-9fa6-98aff1034730")
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -41,29 +41,14 @@ _T = TypeVar("_T")
|
||||
class SchemaFlagField(SchemaHexInt):
|
||||
"""Like a hex int, but must be serialized as bytes in LLSD due to being a U32"""
|
||||
@classmethod
|
||||
def from_llsd(cls, val: Any) -> int:
|
||||
def from_llsd(cls, val: Any, flavor: str) -> int:
|
||||
return struct.unpack("!I", val)[0]
|
||||
|
||||
@classmethod
|
||||
def to_llsd(cls, val: int) -> Any:
|
||||
def to_llsd(cls, val: int, flavor: str) -> Any:
|
||||
return struct.pack("!I", val)
|
||||
|
||||
|
||||
class LegacyIntEnum(enum.IntEnum):
|
||||
_ignore_ = ['_LEGACY_NAMES']
|
||||
|
||||
@classmethod
|
||||
def _get_legacy_names(cls) -> Tuple[str, ...]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, name: str):
|
||||
return cls(cls._get_legacy_names().index(name))
|
||||
|
||||
def to_legacy_name(self) -> str:
|
||||
return self._get_legacy_names()[self.value]
|
||||
|
||||
|
||||
class SchemaEnumField(SchemaStr, Generic[_T]):
|
||||
def __init__(self, enum_cls: Type[LegacyIntEnum]):
|
||||
super().__init__()
|
||||
@@ -73,12 +58,12 @@ class SchemaEnumField(SchemaStr, Generic[_T]):
|
||||
return self._enum_cls.from_legacy_name(val)
|
||||
|
||||
def serialize(self, val: _T) -> str:
|
||||
return self._enum_cls.to_legacy_name(val)
|
||||
return self._enum_cls(val).to_legacy_name()
|
||||
|
||||
def from_llsd(self, val: str) -> _T:
|
||||
def from_llsd(self, val: str, flavor: str) -> _T:
|
||||
return self.deserialize(val)
|
||||
|
||||
def to_llsd(self, val: _T) -> str:
|
||||
def to_llsd(self, val: _T, flavor: str) -> str:
|
||||
return self.serialize(val)
|
||||
|
||||
|
||||
@@ -221,12 +206,12 @@ class InventoryModel(InventoryBase):
|
||||
return model
|
||||
|
||||
@classmethod
|
||||
def from_llsd(cls, llsd_val: List[Dict]) -> InventoryModel:
|
||||
def from_llsd(cls, llsd_val: List[Dict], flavor: str = "legacy") -> InventoryModel:
|
||||
model = cls()
|
||||
for obj_dict in llsd_val:
|
||||
for inv_type in INVENTORY_TYPES:
|
||||
if inv_type.ID_ATTR in obj_dict:
|
||||
if (obj := inv_type.from_llsd(obj_dict)) is not None:
|
||||
if (obj := inv_type.from_llsd(obj_dict, flavor)) is not None:
|
||||
model.add(obj)
|
||||
break
|
||||
LOG.warning(f"Unknown object type {obj_dict!r}")
|
||||
@@ -258,8 +243,8 @@ class InventoryModel(InventoryBase):
|
||||
for node in self.ordered_nodes:
|
||||
node.to_writer(writer)
|
||||
|
||||
def to_llsd(self):
|
||||
return list(node.to_llsd() for node in self.ordered_nodes)
|
||||
def to_llsd(self, flavor: str = "legacy"):
|
||||
return list(node.to_llsd(flavor) for node in self.ordered_nodes)
|
||||
|
||||
def add(self, node: InventoryNodeBase):
|
||||
if node.node_id in self.nodes:
|
||||
@@ -338,17 +323,6 @@ class InventoryPermissions(InventoryBase):
|
||||
is_owner_group: Optional[int] = schema_field(SchemaInt, default=None, llsd_only=True)
|
||||
|
||||
|
||||
class SaleType(LegacyIntEnum):
|
||||
NOT = 0
|
||||
ORIGINAL = 1
|
||||
COPY = 2
|
||||
CONTENTS = 3
|
||||
|
||||
@classmethod
|
||||
def _get_legacy_names(cls) -> Tuple[str, ...]:
|
||||
return "not", "orig", "copy", "cntn"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class InventorySaleInfo(InventoryBase):
|
||||
SCHEMA_NAME: ClassVar[str] = "sale_info"
|
||||
@@ -413,7 +387,7 @@ class InventoryNodeBase(InventoryBase, _HasName):
|
||||
@dataclasses.dataclass
|
||||
class InventoryContainerBase(InventoryNodeBase):
|
||||
# TODO: Not a string in AIS
|
||||
type: str = schema_field(SchemaStr)
|
||||
type: AssetType = schema_field(SchemaEnumField(AssetType))
|
||||
|
||||
@property
|
||||
def children(self) -> Sequence[InventoryNodeBase]:
|
||||
@@ -442,8 +416,8 @@ class InventoryContainerBase(InventoryNodeBase):
|
||||
name=name,
|
||||
cat_id=UUID.random(),
|
||||
parent_id=self.node_id,
|
||||
type="category",
|
||||
pref_type="-1",
|
||||
type=AssetType.CATEGORY,
|
||||
pref_type=FolderType.NONE,
|
||||
owner_id=getattr(self, 'owner_id', UUID.ZERO),
|
||||
version=1,
|
||||
)
|
||||
@@ -473,7 +447,8 @@ class InventoryCategory(InventoryContainerBase):
|
||||
VERSION_NONE: ClassVar[int] = -1
|
||||
|
||||
cat_id: UUID = schema_field(SchemaUUID)
|
||||
pref_type: str = schema_field(SchemaStr, llsd_name="preferred_type")
|
||||
# TODO: not a string in AIS
|
||||
pref_type: FolderType = schema_field(SchemaEnumField(FolderType), llsd_name="preferred_type")
|
||||
name: str = schema_field(SchemaMultilineStr)
|
||||
owner_id: UUID = schema_field(SchemaUUID)
|
||||
version: int = schema_field(SchemaInt)
|
||||
@@ -492,9 +467,9 @@ class InventoryItem(InventoryNodeBase):
|
||||
asset_id: Optional[UUID] = schema_field(SchemaUUID, default=None)
|
||||
shadow_id: Optional[UUID] = schema_field(SchemaUUID, default=None)
|
||||
# TODO: Not a string in AIS
|
||||
type: Optional[str] = schema_field(SchemaStr, default=None)
|
||||
type: Optional[AssetType] = schema_field(SchemaEnumField(AssetType), default=None)
|
||||
# TODO: Not a string in AIS
|
||||
inv_type: Optional[str] = schema_field(SchemaStr, default=None)
|
||||
inv_type: Optional[InventoryType] = schema_field(SchemaEnumField(InventoryType), default=None)
|
||||
flags: Optional[int] = schema_field(SchemaFlagField, default=None)
|
||||
sale_info: Optional[InventorySaleInfo] = schema_field(InventorySaleInfo, default=None)
|
||||
name: Optional[str] = schema_field(SchemaMultilineStr, default=None)
|
||||
|
||||
@@ -35,11 +35,11 @@ class SchemaFieldSerializer(abc.ABC, Generic[_T]):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def from_llsd(cls, val: Any) -> _T:
|
||||
def from_llsd(cls, val: Any, flavor: str) -> _T:
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
def to_llsd(cls, val: _T) -> Any:
|
||||
def to_llsd(cls, val: _T, flavor: str) -> Any:
|
||||
return val
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ class SchemaDate(SchemaFieldSerializer[dt.datetime]):
|
||||
return str(calendar.timegm(val.utctimetuple()))
|
||||
|
||||
@classmethod
|
||||
def from_llsd(cls, val: Any) -> dt.datetime:
|
||||
def from_llsd(cls, val: Any, flavor: str) -> dt.datetime:
|
||||
return dt.datetime.utcfromtimestamp(val)
|
||||
|
||||
@classmethod
|
||||
def to_llsd(cls, val: dt.datetime):
|
||||
def to_llsd(cls, val: dt.datetime, flavor: str):
|
||||
return calendar.timegm(val.utctimetuple())
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ class SchemaBase(abc.ABC):
|
||||
return cls.from_str(data.decode("utf8"))
|
||||
|
||||
@classmethod
|
||||
def from_llsd(cls, inv_dict: Dict):
|
||||
def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"):
|
||||
fields = cls._get_fields_dict(llsd=True)
|
||||
obj_dict = {}
|
||||
for key, val in inv_dict.items():
|
||||
@@ -199,9 +199,9 @@ class SchemaBase(abc.ABC):
|
||||
|
||||
# some kind of nested structure like sale_info
|
||||
if issubclass(spec_cls, SchemaBase):
|
||||
obj_dict[key] = spec.from_llsd(val)
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
elif issubclass(spec_cls, SchemaFieldSerializer):
|
||||
obj_dict[key] = spec.from_llsd(val)
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
else:
|
||||
raise ValueError(f"Unsupported spec for {key!r}, {spec!r}")
|
||||
else:
|
||||
@@ -217,7 +217,7 @@ class SchemaBase(abc.ABC):
|
||||
writer.seek(0)
|
||||
return writer.read()
|
||||
|
||||
def to_llsd(self):
|
||||
def to_llsd(self, flavor: str = "legacy"):
|
||||
obj_dict = {}
|
||||
for field_name, field in self._get_fields_dict(llsd=True).items():
|
||||
spec = field.metadata.get("spec")
|
||||
@@ -235,9 +235,9 @@ class SchemaBase(abc.ABC):
|
||||
|
||||
# Some kind of nested structure like sale_info
|
||||
if isinstance(val, SchemaBase):
|
||||
val = val.to_llsd()
|
||||
val = val.to_llsd(flavor)
|
||||
elif issubclass(spec_cls, SchemaFieldSerializer):
|
||||
val = spec.to_llsd(val)
|
||||
val = spec.to_llsd(val, flavor)
|
||||
else:
|
||||
raise ValueError(f"Bad inventory spec {spec!r}")
|
||||
obj_dict[field_name] = val
|
||||
|
||||
@@ -18,6 +18,17 @@ from hippolyzer.lib.base.datatypes import UUID, IntEnum, IntFlag, Vector3, Quate
|
||||
from hippolyzer.lib.base.namevalue import NameValuesSerializer
|
||||
|
||||
|
||||
class LegacyIntEnum(IntEnum):
|
||||
"""Used for enums that have legacy string names, may be used in the legacy schema"""
|
||||
@abc.abstractmethod
|
||||
def to_legacy_name(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, legacy_name: str):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@se.enum_field_serializer("RequestXfer", "XferID", "VFileType")
|
||||
@se.enum_field_serializer("AssetUploadRequest", "AssetBlock", "Type")
|
||||
@se.enum_field_serializer("AssetUploadComplete", "AssetBlock", "Type")
|
||||
@@ -26,7 +37,7 @@ from hippolyzer.lib.base.namevalue import NameValuesSerializer
|
||||
@se.enum_field_serializer("RezObject", "InventoryData", "Type")
|
||||
@se.enum_field_serializer("RezScript", "InventoryBlock", "Type")
|
||||
@se.enum_field_serializer("UpdateTaskInventory", "InventoryData", "Type")
|
||||
class AssetType(IntEnum):
|
||||
class AssetType(LegacyIntEnum):
|
||||
TEXTURE = 0
|
||||
SOUND = 1
|
||||
CALLINGCARD = 2
|
||||
@@ -47,7 +58,7 @@ class AssetType(IntEnum):
|
||||
GESTURE = 21
|
||||
SIMSTATE = 22
|
||||
LINK = 24
|
||||
LINK_FOLDER = 25
|
||||
FOLDER_LINK = 25
|
||||
MARKETPLACE_FOLDER = 26
|
||||
WIDGET = 40
|
||||
PERSON = 45
|
||||
@@ -62,8 +73,7 @@ class AssetType(IntEnum):
|
||||
UNKNOWN = 255
|
||||
NONE = -1
|
||||
|
||||
@property
|
||||
def human_name(self):
|
||||
def to_legacy_name(self) -> str:
|
||||
lower = self.name.lower()
|
||||
return {
|
||||
"animation": "animatn",
|
||||
@@ -71,8 +81,27 @@ class AssetType(IntEnum):
|
||||
"texture_tga": "txtr_tga",
|
||||
"image_tga": "img_tga",
|
||||
"sound_wav": "snd_wav",
|
||||
"lsl_text": "lsltext",
|
||||
"lsl_bytecode": "lslbyte",
|
||||
"folder_link": "link_f",
|
||||
"unknown": "invalid",
|
||||
}.get(lower, lower)
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, legacy_name: str):
|
||||
reg_name = {
|
||||
"animatn": "animation",
|
||||
"callcard": "callingcard",
|
||||
"txtr_tga": "texture_tga",
|
||||
"img_tga": "image_tga",
|
||||
"snd_wav": "sound_wav",
|
||||
"lsltext": "lsl_text",
|
||||
"lslbyte": "lsl_bytecode",
|
||||
"invalid": "unknown",
|
||||
"link_f": "folder_link",
|
||||
}.get(legacy_name, legacy_name).upper()
|
||||
return cls[reg_name]
|
||||
|
||||
@property
|
||||
def inventory_type(self):
|
||||
return {
|
||||
@@ -104,7 +133,7 @@ class AssetType(IntEnum):
|
||||
@se.enum_field_serializer("RezObject", "InventoryData", "InvType")
|
||||
@se.enum_field_serializer("RezScript", "InventoryBlock", "InvType")
|
||||
@se.enum_field_serializer("UpdateTaskInventory", "InventoryData", "InvType")
|
||||
class InventoryType(IntEnum):
|
||||
class InventoryType(LegacyIntEnum):
|
||||
TEXTURE = 0
|
||||
SOUND = 1
|
||||
CALLINGCARD = 2
|
||||
@@ -133,16 +162,23 @@ class InventoryType(IntEnum):
|
||||
UNKNOWN = 255
|
||||
NONE = -1
|
||||
|
||||
@property
|
||||
def human_name(self):
|
||||
def to_legacy_name(self) -> str:
|
||||
lower = self.name.lower()
|
||||
return {
|
||||
"callingcard": "callcard",
|
||||
"none": "-1",
|
||||
}.get(lower, lower)
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, legacy_name: str):
|
||||
reg_name = {
|
||||
"callcard": "callingcard",
|
||||
"-1": "none",
|
||||
}.get(legacy_name, legacy_name).upper()
|
||||
return cls[reg_name]
|
||||
|
||||
class FolderType(IntEnum):
|
||||
|
||||
class FolderType(LegacyIntEnum):
|
||||
TEXTURE = 0
|
||||
SOUND = 1
|
||||
CALLINGCARD = 2
|
||||
@@ -161,6 +197,7 @@ class FolderType(IntEnum):
|
||||
ANIMATION = 20
|
||||
GESTURE = 21
|
||||
FAVORITE = 23
|
||||
# The "ensemble" values aren't used, no idea what they were for.
|
||||
ENSEMBLE_START = 26
|
||||
ENSEMBLE_END = 45
|
||||
# This range is reserved for special clothing folder types.
|
||||
@@ -177,7 +214,7 @@ class FolderType(IntEnum):
|
||||
# Note: We actually *never* create folders with that type. This is used for icon override only.
|
||||
MARKETPLACE_VERSION = 55
|
||||
SETTINGS = 56
|
||||
# Firestorm folders, may not actually exist
|
||||
# Firestorm folders, may not actually exist in legacy schema
|
||||
FIRESTORM = 57
|
||||
PHOENIX = 58
|
||||
RLV = 59
|
||||
@@ -185,6 +222,46 @@ class FolderType(IntEnum):
|
||||
MY_SUITCASE = 100
|
||||
NONE = -1
|
||||
|
||||
def to_legacy_name(self) -> str:
|
||||
lower = self.name.lower()
|
||||
return {
|
||||
"callingcard": "callcard",
|
||||
"lsl_text": "lsltext",
|
||||
"animation": "animatn",
|
||||
"snapshot_category": "snapshot",
|
||||
"lost_and_found": "lstndfnd",
|
||||
"ensemble_start": "ensemble",
|
||||
"ensemble_end": "ensemble",
|
||||
"current_outfit": "current",
|
||||
"my_outfits": "my_otfts",
|
||||
"basic_root": "basic_rt",
|
||||
"marketplace_listings": "merchant",
|
||||
"marketplace_stock": "stock",
|
||||
"marketplace_version": "version",
|
||||
"my_suitcase": "suitcase",
|
||||
"none": "-1",
|
||||
}.get(lower, lower)
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, legacy_name: str):
|
||||
reg_name = {
|
||||
"callcard": "callingcard",
|
||||
"lsltext": "lsl_text",
|
||||
"animatn": "animation",
|
||||
"snapshot": "snapshot_category",
|
||||
"lstndfnd": "lost_and_found",
|
||||
"ensemble": "ensemble_start",
|
||||
"current": "current_outfit",
|
||||
"my_otfts": "my_outfits",
|
||||
"basic_rt": "basic_root",
|
||||
"merchant": "marketplace_listings",
|
||||
"stock": "marketplace_stock",
|
||||
"version": "marketplace_version",
|
||||
"suitcase": "my_suitcase",
|
||||
"-1": "none",
|
||||
}.get(legacy_name, legacy_name).upper()
|
||||
return cls[reg_name]
|
||||
|
||||
|
||||
@se.enum_field_serializer("AgentIsNowWearing", "WearableData", "WearableType")
|
||||
@se.enum_field_serializer("AgentWearablesUpdate", "WearableData", "WearableType")
|
||||
@@ -244,6 +321,9 @@ class Permissions(IntFlag):
|
||||
RESERVED = 1 << 31
|
||||
|
||||
|
||||
_SALE_TYPE_LEGACY_NAMES = ("not", "orig", "copy", "cntn")
|
||||
|
||||
|
||||
@se.enum_field_serializer("ObjectSaleInfo", "ObjectData", "SaleType")
|
||||
@se.enum_field_serializer("ObjectProperties", "ObjectData", "SaleType")
|
||||
@se.enum_field_serializer("ObjectPropertiesFamily", "ObjectData", "SaleType")
|
||||
@@ -252,12 +332,19 @@ class Permissions(IntFlag):
|
||||
@se.enum_field_serializer("RezObject", "InventoryData", "SaleType")
|
||||
@se.enum_field_serializer("UpdateTaskInventory", "InventoryData", "SaleType")
|
||||
@se.enum_field_serializer("UpdateCreateInventoryItem", "InventoryData", "SaleType")
|
||||
class SaleInfo(IntEnum):
|
||||
class SaleType(LegacyIntEnum):
|
||||
NOT = 0
|
||||
ORIGINAL = 1
|
||||
COPY = 2
|
||||
CONTENTS = 3
|
||||
|
||||
@classmethod
|
||||
def from_legacy_name(cls, legacy_name: str):
|
||||
return cls(_SALE_TYPE_LEGACY_NAMES.index(legacy_name))
|
||||
|
||||
def to_legacy_name(self) -> str:
|
||||
return _SALE_TYPE_LEGACY_NAMES[int(self.value)]
|
||||
|
||||
|
||||
@se.flag_field_serializer("ParcelInfoReply", "Data", "Flags")
|
||||
class ParcelInfoFlags(IntFlag):
|
||||
|
||||
@@ -30,12 +30,12 @@ class AssetUploader:
|
||||
async def initiate_asset_upload(self, name: str, asset_type: AssetType,
|
||||
body: bytes, flags: Optional[int] = None) -> UploadToken:
|
||||
payload = {
|
||||
"asset_type": asset_type.human_name,
|
||||
"asset_type": asset_type.to_legacy_name(),
|
||||
"description": "(No Description)",
|
||||
"everyone_mask": 0,
|
||||
"group_mask": 0,
|
||||
"folder_id": UUID.ZERO, # Puts it in the default folder, I guess. Undocumented.
|
||||
"inventory_type": asset_type.inventory_type.human_name,
|
||||
"inventory_type": asset_type.inventory_type.to_legacy_name(),
|
||||
"name": name,
|
||||
"next_owner_mask": 581632,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user