Start handling messages in InventoryManager

This commit is contained in:
Salad Dais
2024-01-14 07:04:28 +00:00
parent 01ea9d7879
commit 3500212da0
2 changed files with 90 additions and 6 deletions

View File

@@ -11,6 +11,7 @@ It's typically only used for object contents now.
from __future__ import annotations
import abc
import asyncio
import dataclasses
import datetime as dt
import inspect
@@ -202,6 +203,7 @@ class InventoryModel(InventoryBase):
def __init__(self):
self.nodes: Dict[UUID, InventoryNodeBase] = {}
self.root: Optional[InventoryContainerBase] = None
self.any_dirty = asyncio.Event()
@classmethod
def from_reader(cls, reader: StringIO, read_header=False) -> InventoryModel:
@@ -246,6 +248,12 @@ class InventoryModel(InventoryBase):
if isinstance(node, InventoryContainerBase):
yield node
@property
def dirty_categories(self) -> Iterable[InventoryCategory]:
for node in self.nodes:
if isinstance(node, InventoryCategory) and node.version == InventoryCategory.VERSION_NONE:
yield node
@property
def all_items(self) -> Iterable[InventoryItem]:
for node in self.nodes.values():
@@ -273,6 +281,29 @@ class InventoryModel(InventoryBase):
if node.parent_id == UUID.ZERO:
self.root = node
node.model = weakref.proxy(self)
return node
def update(self, node: InventoryNodeBase, update_fields: Optional[Iterable[str]] = None) -> InventoryNodeBase:
"""Update an existing node, optionally only updating specific fields"""
if node.node_id not in self.nodes:
raise KeyError(f"{node.node_id} not in the inventory model")
orig_node = self.nodes[node.node_id]
if node.__class__ != orig_node.__class__:
raise ValueError(f"Tried to update {orig_node!r} from non-matching {node!r}")
if not update_fields:
# Update everything but the model parameter
update_fields = set(node._get_fields_dict().keys()) - {"model"}
for field_name in update_fields:
setattr(orig_node, field_name, getattr(node, field_name))
return orig_node
def upsert(self, node: InventoryNodeBase, update_fields: Optional[Iterable[str]] = None) -> InventoryNodeBase:
"""Add or update a node"""
if node.node_id in self.nodes:
return self.update(node, update_fields)
return self.add(node)
def unlink(self, node: InventoryNodeBase, single_only: bool = False) -> Sequence[InventoryNodeBase]:
"""Unlink a node and its descendants from the tree, returning the removed nodes"""
@@ -313,6 +344,10 @@ class InventoryModel(InventoryBase):
removed=removed_in_other,
)
def flag_if_dirty(self):
if any(self.dirty_categories):
self.any_dirty.set()
def __getitem__(self, item: UUID) -> InventoryNodeBase:
return self.nodes[item]
@@ -562,7 +597,8 @@ class InventoryItem(InventoryNodeBase):
def from_inventory_data(cls, block: Block):
return cls(
item_id=block["ItemID"],
parent_id=block["ParentID"],
# Might be under one of two names
parent_id=block.get("ParentID", block["FolderID"]),
permissions=InventoryPermissions(
creator_id=block["CreatorID"],
owner_id=block["OwnerID"],
@@ -573,7 +609,8 @@ class InventoryItem(InventoryNodeBase):
everyone_mask=block["EveryoneMask"],
next_owner_mask=block["NextOwnerMask"],
),
asset_id=block["AssetID"],
# May be missing in UpdateInventoryItem
asset_id=block.get("AssetID"),
type=AssetType(block["Type"]),
inv_type=InventoryType(block["InvType"]),
flags=block["Flags"],

View File

@@ -8,6 +8,7 @@ from typing import Union, List, Tuple, Set
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.inventory import InventoryModel, InventoryCategory, InventoryItem
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.templates import AssetType, FolderType
from hippolyzer.lib.client.state import BaseClientSession
@@ -20,6 +21,10 @@ class InventoryManager:
self._session = session
self.model: InventoryModel = InventoryModel()
self._load_skeleton()
self._session.message_handler.subscribe("BulkUpdateInventory", self._handle_bulk_update_inventory)
self._session.message_handler.subscribe("UpdateCreateInventoryItem", self._handle_update_create_inventory_item)
self._session.message_handler.subscribe("UpdateInventoryItem", self._handle_update_inventory_item)
self._session.message_handler.subscribe("RemoveInventoryItem", self._handle_remove_inventory_item)
def _load_skeleton(self):
assert not self.model.nodes
@@ -68,10 +73,8 @@ class InventoryManager:
# Cached cat isn't the same as what the inv server says it should be, can't use it.
if cached_cat.version != skel_versions.get(cached_cat.cat_id):
continue
if existing_cat:
# Remove the category so that we can replace it, but leave any children in place
self.model.unlink(existing_cat, single_only=True)
self.model.add(cached_cat)
# Update any existing category in-place, or add if not present
self.model.upsert(cached_cat)
# Any items in this category in our cache file are usable and should be added
loaded_cat_ids.add(cached_cat.cat_id)
@@ -81,10 +84,13 @@ class InventoryManager:
if cached_item.item_id in self.model:
continue
# The parent category didn't have a cache hit against the inventory skeleton, can't add!
# We don't even know if this item would be in the current version of it's parent cat!
if cached_item.parent_id not in loaded_cat_ids:
continue
self.model.add(cached_item)
self.model.flag_if_dirty()
def _parse_cache(self, path: Union[str, Path]) -> Tuple[List[InventoryCategory], List[InventoryItem]]:
"""Warning, may be incredibly slow due to llsd.parse_notation() behavior"""
categories: List[InventoryCategory] = []
@@ -112,3 +118,44 @@ class InventoryManager:
else:
LOG.warning(f"Unknown node type in inv cache: {node_llsd!r}")
return categories, items
def _handle_bulk_update_inventory(self, msg: Message):
any_cats = False
for folder_block in msg["FolderData"]:
if folder_block["FolderID"] == UUID.ZERO:
continue
any_cats = True
self.model.upsert(
InventoryCategory.from_folder_data(folder_block),
# Don't clobber version, we only want to fetch the folder if it's new
# and hasn't just moved.
update_fields={"parent_id", "name", "pref_type"},
)
for item_block in msg["ItemData"]:
if item_block["ItemID"] == UUID.ZERO:
continue
self.model.upsert(InventoryItem.from_inventory_data(item_block))
if any_cats:
self.model.flag_if_dirty()
def _validate_recipient(self, recipient: UUID):
if self._session.agent_id != recipient:
raise ValueError(f"AgentID Mismatch {self._session.agent_id} != {recipient}")
def _handle_update_create_inventory_item(self, msg: Message):
self._validate_recipient(msg["AgentData"]["AgentID"])
for inventory_block in msg["InventoryData"]:
self.model.upsert(InventoryItem.from_inventory_data(inventory_block))
def _handle_update_inventory_item(self, msg: Message):
self._validate_recipient(msg["AgentData"]["AgentID"])
for inventory_block in msg["InventoryData"]:
self.model.update(InventoryItem.from_inventory_data(inventory_block))
def _handle_remove_inventory_item(self, msg: Message):
self._validate_recipient(msg["AgentData"]["AgentID"])
for inventory_block in msg["InventoryData"]:
node = self.model.get(inventory_block["ItemID"])
if node:
self.model.unlink(node)