From a652779cc590f3ea5d41692d87a0411a2ed626c0 Mon Sep 17 00:00:00 2001 From: Salad Dais Date: Sun, 15 Jun 2025 17:44:03 +0000 Subject: [PATCH] Add object inventory helpers to region object manager --- hippolyzer/lib/base/inventory.py | 6 ++--- hippolyzer/lib/base/legacy_schema.py | 8 +++--- hippolyzer/lib/client/object_manager.py | 35 ++++++++++++++++++++++++- hippolyzer/lib/client/state.py | 2 ++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/hippolyzer/lib/base/inventory.py b/hippolyzer/lib/base/inventory.py index 4855c63..65cac83 100644 --- a/hippolyzer/lib/base/inventory.py +++ b/hippolyzer/lib/base/inventory.py @@ -226,7 +226,7 @@ class InventoryModel(InventoryBase): return model @classmethod - def from_llsd(cls, llsd_val: List[Dict], flavor: str = "legacy") -> InventoryModel: + def from_llsd(cls, llsd_val: List[Dict], flavor: str = "legacy") -> Self: model = cls() for obj_dict in llsd_val: obj = None @@ -565,7 +565,7 @@ class InventoryCategory(InventoryContainerBase): ) @classmethod - def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"): + def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy") -> Self: if flavor == "ais" and "type" not in inv_dict: inv_dict = inv_dict.copy() inv_dict["type"] = AssetType.CATEGORY @@ -691,7 +691,7 @@ class InventoryItem(InventoryNodeBase): return val @classmethod - def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"): + def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy") -> Self: if flavor == "ais" and "linked_id" in inv_dict: # Links get represented differently than other items for whatever reason. # This is incredibly annoying, under *NIX there's nothing really special about symlinks. diff --git a/hippolyzer/lib/base/legacy_schema.py b/hippolyzer/lib/base/legacy_schema.py index 55132ae..28fe1b2 100644 --- a/hippolyzer/lib/base/legacy_schema.py +++ b/hippolyzer/lib/base/legacy_schema.py @@ -174,7 +174,7 @@ class SchemaBase(abc.ABC): return fields_dict @classmethod - def from_str(cls, text: str): + def from_str(cls, text: str) -> Self: return cls.from_reader(StringIO(text)) @classmethod @@ -183,11 +183,11 @@ class SchemaBase(abc.ABC): pass @classmethod - def from_bytes(cls, data: bytes): + def from_bytes(cls, data: bytes) -> Self: return cls.from_str(data.decode("utf8")) @classmethod - def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"): + def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy") -> Self: fields = cls._get_fields_dict(llsd_flavor=flavor) obj_dict = {} try: @@ -262,5 +262,5 @@ class SchemaBase(abc.ABC): pass @classmethod - def _obj_from_dict(cls, obj_dict: Dict): + def _obj_from_dict(cls, obj_dict: Dict) -> Self: return cls(**obj_dict) # type: ignore diff --git a/hippolyzer/lib/client/object_manager.py b/hippolyzer/lib/client/object_manager.py index dab2e69..b223730 100644 --- a/hippolyzer/lib/client/object_manager.py +++ b/hippolyzer/lib/client/object_manager.py @@ -15,6 +15,7 @@ from typing import * from hippolyzer.lib.base.datatypes import UUID, Vector3 from hippolyzer.lib.base.helpers import proxify +from hippolyzer.lib.base.inventory import InventoryItem, InventoryModel from hippolyzer.lib.base.message.message import Block, Message from hippolyzer.lib.base.message.message_handler import MessageHandler from hippolyzer.lib.base.message.msgtypes import PacketFlags @@ -27,7 +28,7 @@ from hippolyzer.lib.base.objects import ( ) from hippolyzer.lib.base.settings import Settings from hippolyzer.lib.client.namecache import NameCache, NameCacheEntry -from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer +from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer, XferFilePath from hippolyzer.lib.base import llsd if TYPE_CHECKING: @@ -217,6 +218,38 @@ class ClientObjectManager: for entry in entries: self.state.materials[UUID(bytes=entry["ID"])] = entry["Material"] + async def request_object_inv(self, obj: Object) -> List[InventoryItem]: + if "RequestTaskInventory" in self._region.cap_urls: + return await self.request_object_inv_via_cap(obj) + else: + return await self.request_object_inv_via_xfer(obj) + + async def request_object_inv_via_cap(self, obj: Object) -> List[InventoryItem]: + async with self._region.caps_client.get("RequestTaskInventory", params={"task_id": obj.FullID}) as resp: + resp.raise_for_status() + return [InventoryItem.from_llsd(x) for x in (await resp.read_llsd())["contents"]] + + async def request_object_inv_via_xfer(self, obj: Object) -> List[InventoryItem]: + session = self._region.session() + with self._region.message_handler.subscribe_async( + ('ReplyTaskInventory',), predicate=lambda x: x["InventoryData"]["TaskID"] == obj.FullID + ) as get_msg: + await self._region.circuit.send_reliable(Message( + 'RequestTaskInventory', + # If no session is passed in we'll use the active session when the coro was created + Block('AgentData', AgentID=session.agent_id, SessionID=session.id), + Block('InventoryData', LocalID=obj.LocalID), + )) + + inv_message = await asyncio.wait_for(get_msg(), timeout=5.0) + + # Xfer doesn't need to be immediately awaited, multiple signals can be waited on. + xfer = await self._region.xfer_manager.request( + file_name=inv_message["InventoryData"]["Filename"], file_path=XferFilePath.CACHE) + + inv_model = InventoryModel.from_bytes(xfer.reassemble_chunks()) + return list(inv_model.all_items) + class ObjectEvent: __slots__ = ("object", "updated", "update_type") diff --git a/hippolyzer/lib/client/state.py b/hippolyzer/lib/client/state.py index c112753..a34e8c4 100644 --- a/hippolyzer/lib/client/state.py +++ b/hippolyzer/lib/client/state.py @@ -17,6 +17,7 @@ from hippolyzer.lib.base.message.message_handler import MessageHandler from hippolyzer.lib.base.network.caps_client import CapsClient from hippolyzer.lib.base.network.transport import ADDR_TUPLE from hippolyzer.lib.base.objects import handle_to_global_pos +from hippolyzer.lib.base.xfer_manager import XferManager from hippolyzer.lib.client.object_manager import ClientObjectManager, ClientWorldObjectManager @@ -27,6 +28,7 @@ class BaseClientRegion(ConnectionHolder, abc.ABC): # Actually a weakref session: Callable[[], BaseClientSession] objects: ClientObjectManager + xfer_manager: XferManager caps_client: CapsClient cap_urls: multidict.MultiDict[str] circuit_addr: ADDR_TUPLE