Update inventory handling code
This commit is contained in:
@@ -229,12 +229,14 @@ class InventoryModel(InventoryBase):
|
||||
def from_llsd(cls, llsd_val: List[Dict], flavor: str = "legacy") -> InventoryModel:
|
||||
model = cls()
|
||||
for obj_dict in llsd_val:
|
||||
obj = None
|
||||
for inv_type in INVENTORY_TYPES:
|
||||
if inv_type.ID_ATTR in obj_dict:
|
||||
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}")
|
||||
if obj is None:
|
||||
LOG.warning(f"Unknown object type {obj_dict!r}")
|
||||
return model
|
||||
|
||||
@property
|
||||
@@ -258,7 +260,7 @@ class InventoryModel(InventoryBase):
|
||||
def all_items(self) -> Iterable[InventoryItem]:
|
||||
for node in self.nodes.values():
|
||||
if not isinstance(node, InventoryContainerBase):
|
||||
yield node
|
||||
yield node # type: ignore
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, InventoryModel):
|
||||
@@ -584,9 +586,9 @@ class InventoryItem(InventoryNodeBase):
|
||||
return self.asset_id
|
||||
return self.shadow_id ^ MAGIC_ID
|
||||
|
||||
def to_inventory_data(self) -> Block:
|
||||
def to_inventory_data(self, block_name: str = "InventoryData") -> Block:
|
||||
return Block(
|
||||
"InventoryData",
|
||||
block_name,
|
||||
ItemID=self.item_id,
|
||||
FolderID=self.parent_id,
|
||||
CallbackID=0,
|
||||
@@ -641,7 +643,7 @@ class InventoryItem(InventoryNodeBase):
|
||||
),
|
||||
name=block["Name"],
|
||||
desc=block["Description"],
|
||||
creation_date=block["CreationDate"],
|
||||
creation_date=SchemaDate.from_llsd(block["CreationDate"], "legacy"),
|
||||
)
|
||||
|
||||
def to_llsd(self, flavor: str = "legacy"):
|
||||
|
||||
@@ -18,6 +18,11 @@ from hippolyzer.lib.base.templates import WearableType
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CannotMoveError(Exception):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class InventoryManager:
|
||||
def __init__(self, session: BaseClientSession):
|
||||
self._session = session
|
||||
@@ -222,6 +227,28 @@ class InventoryManager:
|
||||
# Presumably this list is exhaustive, so don't unlink children.
|
||||
self.model.unlink(node, single_only=True)
|
||||
|
||||
async def make_ais_request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
params: dict,
|
||||
payload: dict,
|
||||
) -> dict:
|
||||
caps_client = self._session.main_region.caps_client
|
||||
async with caps_client.request(method, "InventoryAPIv3", path=path, params=params, llsd=payload) as resp:
|
||||
if resp.ok or resp.status == 400:
|
||||
data = await resp.read_llsd()
|
||||
if err_desc := data.get("error_description", ""):
|
||||
err_desc: str
|
||||
if err_desc.startswith("Cannot change parent_id."):
|
||||
raise CannotMoveError()
|
||||
resp.raise_for_status()
|
||||
self.process_aisv3_response(data)
|
||||
else:
|
||||
resp.raise_for_status()
|
||||
|
||||
return data
|
||||
|
||||
async def create_folder(
|
||||
self,
|
||||
parent: InventoryCategory | UUID,
|
||||
@@ -234,9 +261,6 @@ class InventoryManager:
|
||||
else:
|
||||
parent_id = parent
|
||||
|
||||
caps_client = self._session.main_region.caps_client
|
||||
transaction_id = UUID.random()
|
||||
params = {"tid": transaction_id}
|
||||
payload = {
|
||||
"categories": [
|
||||
{
|
||||
@@ -247,11 +271,8 @@ class InventoryManager:
|
||||
}
|
||||
]
|
||||
}
|
||||
async with caps_client.post("InventoryAPIv3", path=f"/category/{parent_id}", params=params, llsd=payload) as resp:
|
||||
resp.raise_for_status()
|
||||
self.process_aisv3_response(await resp.read_llsd())
|
||||
parent_cat: InventoryCategory = self.model.get(parent_id) # type: ignore
|
||||
return [x for x in parent_cat.children if x.name == name][0] # type: ignore
|
||||
data = await self.make_ais_request("POST", f"/category/{parent_id}", {"tid": UUID.random()}, payload)
|
||||
return self.model.get(data["_created_categories"][0]) # type: ignore
|
||||
|
||||
async def create_item(
|
||||
self,
|
||||
@@ -260,7 +281,8 @@ class InventoryManager:
|
||||
type: AssetType,
|
||||
inv_type: InventoryType,
|
||||
wearable_type: WearableType,
|
||||
perms: int,
|
||||
transaction_id: UUID,
|
||||
perms: int = 0x7FffFFff,
|
||||
description: str = '',
|
||||
) -> InventoryItem:
|
||||
if isinstance(parent, InventoryCategory):
|
||||
@@ -268,7 +290,6 @@ class InventoryManager:
|
||||
else:
|
||||
parent_id = parent
|
||||
|
||||
transaction_id = UUID.random()
|
||||
with self._session.main_region.message_handler.subscribe_async(
|
||||
("UpdateCreateInventoryItem",),
|
||||
predicate=lambda x: x["AgentData"]["TransactionID"] == transaction_id,
|
||||
@@ -293,5 +314,6 @@ class InventoryManager:
|
||||
)
|
||||
)
|
||||
msg = await asyncio.wait_for(get_msg(), 5.0)
|
||||
# Handle this synchronously.
|
||||
self._handle_update_create_inventory_item(msg)
|
||||
return self.model.get(msg["InventoryData"]["ItemID"]) # type: ignore
|
||||
|
||||
@@ -12,6 +12,9 @@ from hippolyzer.lib.proxy.viewer_settings import iter_viewer_cache_dirs
|
||||
from hippolyzer.lib.base.datatypes import UUID
|
||||
from hippolyzer.lib.base.inventory import InventoryCategory
|
||||
from hippolyzer.lib.base.message.message import Message, Block
|
||||
from hippolyzer.lib.base.inventory import InventoryItem
|
||||
from hippolyzer.lib.base.templates import AssetType, InventoryType, WearableType
|
||||
from hippolyzer.lib.base.network.transport import Direction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from hippolyzer.lib.proxy.sessions import Session
|
||||
@@ -121,9 +124,40 @@ class ProxyInventoryManager(InventoryManager):
|
||||
) -> InventoryCategory:
|
||||
cat = await super().create_folder(parent, name, type, cat_id)
|
||||
# We need to tell the client about the new folder via an injected eq event
|
||||
self._session.main_region.eq_manager.inject_message(Message(
|
||||
self._session.main_region.circuit.send(Message(
|
||||
"BulkUpdateInventory",
|
||||
Block("AgentData", AgentID=self._session.agent_id, TransactionID=UUID.random()),
|
||||
cat.to_folder_data()
|
||||
cat.to_folder_data(),
|
||||
direction=Direction.IN
|
||||
))
|
||||
return cat
|
||||
|
||||
async def create_item(
|
||||
self,
|
||||
parent: UUID | InventoryCategory,
|
||||
name: str,
|
||||
type: AssetType,
|
||||
inv_type: InventoryType,
|
||||
wearable_type: WearableType,
|
||||
transaction_id: UUID,
|
||||
perms: int = 0x7FffFFff,
|
||||
description: str = '',
|
||||
) -> InventoryItem:
|
||||
item = await super().create_item(
|
||||
parent=parent,
|
||||
name=name,
|
||||
type=type,
|
||||
inv_type=inv_type,
|
||||
wearable_type=wearable_type,
|
||||
transaction_id=transaction_id,
|
||||
perms=perms,
|
||||
description=description,
|
||||
)
|
||||
# We need to tell the client about the new folder via an injected eq event
|
||||
self._session.main_region.circuit.send(Message(
|
||||
"BulkUpdateInventory",
|
||||
Block("AgentData", AgentID=self._session.agent_id, TransactionID=UUID.random()),
|
||||
item.to_inventory_data("ItemData"),
|
||||
direction=Direction.IN
|
||||
))
|
||||
return item
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import copy
|
||||
import datetime as dt
|
||||
import unittest
|
||||
|
||||
from hippolyzer.lib.base.datatypes import *
|
||||
from hippolyzer.lib.base.inventory import InventoryModel, SaleType
|
||||
from hippolyzer.lib.base.inventory import InventoryModel, SaleType, InventoryItem
|
||||
from hippolyzer.lib.base.wearables import Wearable, VISUAL_PARAMS
|
||||
|
||||
SIMPLE_INV = """\tinv_object\t0
|
||||
@@ -47,6 +48,42 @@ SIMPLE_INV = """\tinv_object\t0
|
||||
\t}
|
||||
"""
|
||||
|
||||
SIMPLE_INV_PARSED = [
|
||||
{
|
||||
'name': 'Contents',
|
||||
'obj_id': UUID('f4d91477-def1-487a-b4f3-6fa201c17376'),
|
||||
'parent_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'type': 'category'
|
||||
},
|
||||
{
|
||||
'asset_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'created_at': 1587367239,
|
||||
'desc': '2020-04-20 04:20:39 lsl2 script',
|
||||
'flags': b'\x00\x00\x00\x00',
|
||||
'inv_type': 'script',
|
||||
'item_id': UUID('dd163122-946b-44df-99f6-a6030e2b9597'),
|
||||
'name': 'New Script',
|
||||
'metadata': {"experience": UUID("a2e76fcd-9360-4f6d-a924-000000000003")},
|
||||
'parent_id': UUID('f4d91477-def1-487a-b4f3-6fa201c17376'),
|
||||
'permissions': {
|
||||
'base_mask': 2147483647,
|
||||
'creator_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'everyone_mask': 0,
|
||||
'group_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'group_mask': 0,
|
||||
'last_owner_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'next_owner_mask': 581632,
|
||||
'owner_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'owner_mask': 2147483647,
|
||||
},
|
||||
'sale_info': {
|
||||
'sale_price': 10,
|
||||
'sale_type': 'not'
|
||||
},
|
||||
'type': 'lsltext'
|
||||
}
|
||||
]
|
||||
|
||||
INV_CATEGORY = """\tinv_category\t0
|
||||
\t{
|
||||
\t\tcat_id\tf4d91477-def1-487a-b4f3-6fa201c17376
|
||||
@@ -122,44 +159,12 @@ class TestLegacyInv(unittest.TestCase):
|
||||
self.assertEqual(item, item_copy)
|
||||
|
||||
def test_llsd_serialization(self):
|
||||
self.assertEqual(
|
||||
self.model.to_llsd(),
|
||||
[
|
||||
{
|
||||
'name': 'Contents',
|
||||
'obj_id': UUID('f4d91477-def1-487a-b4f3-6fa201c17376'),
|
||||
'parent_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'type': 'category'
|
||||
},
|
||||
{
|
||||
'asset_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'created_at': 1587367239,
|
||||
'desc': '2020-04-20 04:20:39 lsl2 script',
|
||||
'flags': b'\x00\x00\x00\x00',
|
||||
'inv_type': 'script',
|
||||
'item_id': UUID('dd163122-946b-44df-99f6-a6030e2b9597'),
|
||||
'name': 'New Script',
|
||||
'metadata': {"experience": UUID("a2e76fcd-9360-4f6d-a924-000000000003")},
|
||||
'parent_id': UUID('f4d91477-def1-487a-b4f3-6fa201c17376'),
|
||||
'permissions': {
|
||||
'base_mask': 2147483647,
|
||||
'creator_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'everyone_mask': 0,
|
||||
'group_id': UUID('00000000-0000-0000-0000-000000000000'),
|
||||
'group_mask': 0,
|
||||
'last_owner_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'next_owner_mask': 581632,
|
||||
'owner_id': UUID('a2e76fcd-9360-4f6d-a924-000000000003'),
|
||||
'owner_mask': 2147483647,
|
||||
},
|
||||
'sale_info': {
|
||||
'sale_price': 10,
|
||||
'sale_type': 'not'
|
||||
},
|
||||
'type': 'lsltext'
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertEqual(self.model.to_llsd(), SIMPLE_INV_PARSED)
|
||||
|
||||
def test_llsd_date_parsing(self):
|
||||
model = InventoryModel.from_llsd(SIMPLE_INV_PARSED)
|
||||
item: InventoryItem = model.nodes.get(UUID("dd163122-946b-44df-99f6-a6030e2b9597")) # type: ignore
|
||||
self.assertEqual(item.creation_date, dt.datetime(2020, 4, 20, 7, 20, 39, tzinfo=dt.timezone.utc))
|
||||
|
||||
def test_llsd_serialization_ais(self):
|
||||
model = InventoryModel.from_str(INV_CATEGORY)
|
||||
|
||||
Reference in New Issue
Block a user