From 056e142347cc186f9b800703a114f715632bef6c Mon Sep 17 00:00:00 2001 From: Salad Dais Date: Mon, 7 Jul 2025 22:52:38 +0000 Subject: [PATCH] Add API for duplicating inventory folders / items --- hippolyzer/lib/base/inventory.py | 19 +++++++++ hippolyzer/lib/client/inventory_manager.py | 48 +++++++++++++++++++++- hippolyzer/lib/proxy/inventory_manager.py | 6 +++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/hippolyzer/lib/base/inventory.py b/hippolyzer/lib/base/inventory.py index 65cac83..49ed4d1 100644 --- a/hippolyzer/lib/base/inventory.py +++ b/hippolyzer/lib/base/inventory.py @@ -485,6 +485,25 @@ class InventoryContainerBase(InventoryNodeBase): if x.parent_id == self.node_id ) + @property + def descendents(self) -> List[InventoryNodeBase]: + new_children: List[InventoryNodeBase] = [self] + descendents = [] + while new_children: + to_check = new_children[:] + new_children.clear() + for obj in to_check: + if isinstance(obj, InventoryContainerBase): + for child in obj.children: + if child in descendents: + continue + new_children.append(child) + descendents.append(child) + else: + if obj not in descendents: + descendents.append(obj) + return descendents + def __getitem__(self, item: Union[int, str]) -> InventoryNodeBase: if isinstance(item, int): return self.children[item] diff --git a/hippolyzer/lib/client/inventory_manager.py b/hippolyzer/lib/client/inventory_manager.py index 0ec6878..ac7642b 100644 --- a/hippolyzer/lib/client/inventory_manager.py +++ b/hippolyzer/lib/client/inventory_manager.py @@ -6,7 +6,7 @@ import gzip import itertools import logging from pathlib import Path -from typing import Union, List, Tuple, Set, Sequence +from typing import Union, List, Tuple, Set, Sequence, Dict from hippolyzer.lib.base import llsd from hippolyzer.lib.base.datatypes import UUID @@ -341,6 +341,52 @@ class InventoryManager: await self._session.main_region.circuit.send_reliable(msg) node.parent_id = new_parent + async def copy(self, node: InventoryNodeBase, destination: UUID | InventoryCategory, contents: bool = True)\ + -> InventoryItem | InventoryCategory: + destination = _get_node_id(destination) + if isinstance(node, InventoryItem): + with self._session.main_region.message_handler.subscribe_async( + ("BulkUpdateInventory",), + # Not ideal, but there doesn't seem to be an easy way to determine the transaction ID, + # and using the callback ID seems a bit crap. + predicate=lambda x: x["ItemData"]["Name"] == node.name, + take=False, + ) as get_msg: + await self._session.main_region.circuit.send_reliable(Message( + 'CopyInventoryItem', + Block('AgentData', AgentID=self._session.agent_id, SessionID=self._session.id), + Block( + 'InventoryData', + CallbackID=0, + OldAgentID=self._session.agent_id, + OldItemID=node.item_id, + NewFolderID=destination, + NewName=b'' + ) + )) + msg = await asyncio.wait_for(get_msg(), 5.0) + return self.model.get(msg["ItemData"]["ItemID"]) # type: ignore + elif isinstance(node, InventoryCategory): + # Keep a list of the original descendents in case we're copy a folder within itself + to_copy = list(node.descendents) + # There's not really any way to "copy" a category, we just create a new one with the same properties. + new_cat = await self.create_folder(destination, node.name, node.pref_type) + if contents: + cat_lookup: Dict[UUID, UUID] = {node.node_id: new_cat.node_id} + # Recreate the category hierarchy first, keeping note of the new category IDs. + for node in to_copy: + if isinstance(node, InventoryCategory): + new_parent = cat_lookup[node.parent_id] + cat_lookup[node.node_id] = (await self.copy(node, new_parent, contents=False)).node_id + # Items have to be explicitly copied individually + for node in to_copy: + if isinstance(node, InventoryItem): + new_parent = cat_lookup[node.parent_id] + await self.copy(node, new_parent, contents=False) + return new_cat + else: + assert False + async def update(self, node: InventoryNodeBase, data: dict) -> None: path = f"/category/{node.node_id}" if isinstance(node, InventoryItem): diff --git a/hippolyzer/lib/proxy/inventory_manager.py b/hippolyzer/lib/proxy/inventory_manager.py index 79acdf3..198fe41 100644 --- a/hippolyzer/lib/proxy/inventory_manager.py +++ b/hippolyzer/lib/proxy/inventory_manager.py @@ -158,6 +158,12 @@ class ProxyInventoryManager(InventoryManager): await super().move(node, new_parent) await self._session.main_region.circuit.send_reliable(self._craft_update_message(node)) + async def copy(self, node: InventoryNodeBase, destination: UUID | InventoryCategory, contents: bool = True)\ + -> InventoryCategory | InventoryItem: + ret_node = await super().copy(node, destination, contents) + await self._session.main_region.circuit.send_reliable(self._craft_update_message(node)) + return ret_node + def _craft_removal_message(self, node: InventoryNodeBase) -> Message: is_folder = True if isinstance(node, InventoryItem):