Skip to content

Commit

Permalink
Start handling messages in InventoryManager
Browse files Browse the repository at this point in the history
  • Loading branch information
SaladDais committed Jan 14, 2024
1 parent 01ea9d7 commit 3500212
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 6 deletions.
41 changes: 39 additions & 2 deletions hippolyzer/lib/base/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from __future__ import annotations

import abc
import asyncio
import dataclasses
import datetime as dt
import inspect
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -246,6 +248,12 @@ def all_containers(self) -> Iterable[InventoryContainerBase]:
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():
Expand Down Expand Up @@ -273,6 +281,29 @@ def add(self, node: InventoryNodeBase):
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"""
Expand Down Expand Up @@ -313,6 +344,10 @@ def get_differences(self, other: InventoryModel) -> InventoryDifferences:
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]

Expand Down Expand Up @@ -562,7 +597,8 @@ def to_inventory_data(self) -> Block:
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"],
Expand All @@ -573,7 +609,8 @@ def from_inventory_data(cls, block: Block):
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"],
Expand Down
55 changes: 51 additions & 4 deletions hippolyzer/lib/client/inventory_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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

Expand All @@ -20,6 +21,10 @@ def __init__(self, session: BaseClientSession):
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
Expand Down Expand Up @@ -68,10 +73,8 @@ def load_cache(self, path: Union[str, Path]):
# 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)

Expand All @@ -81,10 +84,13 @@ def load_cache(self, path: Union[str, Path]):
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] = []
Expand Down Expand Up @@ -112,3 +118,44 @@ def _parse_cache(self, path: Union[str, Path]) -> Tuple[List[InventoryCategory],
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)

0 comments on commit 3500212

Please sign in to comment.