Skip to content

Commit

Permalink
Basic AIS response handling in proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
SaladDais committed Jan 19, 2024
1 parent c949576 commit 3bb4fb0
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 7 deletions.
25 changes: 21 additions & 4 deletions hippolyzer/lib/base/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def from_llsd(cls, llsd_val: List[Dict], flavor: str = "legacy") -> InventoryMod
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}")
LOG.warning(f"Unknown object type {obj_dict!r}")
return model

@property
Expand Down Expand Up @@ -498,6 +498,8 @@ class InventoryObject(InventoryContainerBase):
@dataclasses.dataclass
class InventoryCategory(InventoryContainerBase):
ID_ATTR: ClassVar[str] = "cat_id"
# AIS calls this something else...
ID_ATTR_AIS: ClassVar[str] = "category_id"
SCHEMA_NAME: ClassVar[str] = "inv_category"
VERSION_NONE: ClassVar[int] = -1

Expand Down Expand Up @@ -528,12 +530,24 @@ def from_folder_data(cls, block: Block):
type=AssetType.CATEGORY,
)

@classmethod
def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"):
if flavor == "ais" and "type" not in inv_dict:
inv_dict = inv_dict.copy()
inv_dict["type"] = AssetType.CATEGORY
return super().from_llsd(inv_dict, flavor)

def to_llsd(self, flavor: str = "legacy"):
payload = super().to_llsd(flavor)
if flavor == "ais":
# AIS already knows the inventory type is category
payload.pop("type", None)
return payload

@classmethod
def _get_fields_dict(cls, llsd_flavor: Optional[str] = None):
fields = super()._get_fields_dict(llsd_flavor)
if llsd_flavor == "ais":
# AIS is smart enough to know that all categories are asset type category...
fields.pop("type")
# These have different names though
fields["type_default"] = fields.pop("preferred_type")
fields["agent_id"] = fields.pop("owner_id")
Expand Down Expand Up @@ -644,7 +658,7 @@ def to_llsd(self, flavor: str = "legacy"):

@classmethod
def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"):
if flavor == "ais" and inv_dict["type"] == AssetType.LINK:
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.
inv_dict = inv_dict.copy()
Expand All @@ -666,6 +680,9 @@ def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"):
sale_type=SaleType.NOT,
sale_price=0,
).to_llsd("ais")
if "type" not in inv_dict:
inv_dict["type"] = AssetType.LINK

# In the context of symlinks, asset id means linked item ID.
# This is also how indra stores symlinks. Why the asymmetry in AIS if none of the
# consumers actually want it? Who knows.
Expand Down
34 changes: 33 additions & 1 deletion hippolyzer/lib/client/inventory_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import gzip
import itertools
import logging
from pathlib import Path
from typing import Union, List, Tuple, Set
Expand Down Expand Up @@ -173,4 +174,35 @@ def _handle_move_inventory_item(self, msg: Message):
node.parent_id = inventory_block['FolderID']

def process_aisv3_response(self, payload: dict):
pass
if "name" in payload:
# Just a rough guess. Assume this response is updating something if there's
# a "name" key.
if InventoryCategory.ID_ATTR_AIS in payload:
if (cat_node := InventoryCategory.from_llsd(payload, flavor="ais")) is not None:
self.model.upsert(cat_node)
elif InventoryItem.ID_ATTR in payload:
if (item_node := InventoryItem.from_llsd(payload, flavor="ais")) is not None:
self.model.upsert(item_node)
else:
LOG.warning(f"Unknown node type in AIS payload: {payload!r}")

# Parse the embedded stuff
embedded_dict = payload.get("_embedded", {})
for category_llsd in embedded_dict.get("categories", {}).values():
self.model.upsert(InventoryCategory.from_llsd(category_llsd, flavor="ais"))
for item_llsd in embedded_dict.get("items", {}).values():
self.model.upsert(InventoryItem.from_llsd(item_llsd, flavor="ais"))
for link_llsd in embedded_dict.get("links", {}).values():
self.model.upsert(InventoryItem.from_llsd(link_llsd, flavor="ais"))

# Get rid of anything we were asked to
for node_id in itertools.chain(
payload.get("_broken_links_removed", ()),
payload.get("_removed_items", ()),
payload.get("_category_items_removed", ()),
payload.get("_categories_removed", ()),
):
node = self.model.get(node_id)
if node:
# Presumably this list is exhaustive, so don't unlink children.
self.model.unlink(node, single_only=True)
23 changes: 21 additions & 2 deletions hippolyzer/lib/proxy/inventory_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@
import logging
from typing import *

from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.helpers import get_mtime, create_logged_task
from hippolyzer.lib.client.inventory_manager import InventoryManager
from hippolyzer.lib.client.state import BaseClientSession
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.viewer_settings import iter_viewer_cache_dirs

if TYPE_CHECKING:
from hippolyzer.lib.proxy.sessions import Session


LOG = logging.getLogger(__name__)


class ProxyInventoryManager(InventoryManager):
def __init__(self, session: BaseClientSession):
_session: "Session"

def __init__(self, session: "Session"):
# These handlers all need their processing deferred until the cache has been loaded.
# Since cache is loaded asynchronously, the viewer may get ahead of us due to parsing
# the cache faster and start requesting inventory details we can't do anything with yet.
Expand All @@ -41,6 +47,7 @@ def __init__(self, session: BaseClientSession):
# be wrapped before we call they're registered. Handlers are registered by method reference,
# not by name!
super().__init__(session)
session.http_message_handler.subscribe("InventoryAPIv3", self._handle_aisv3_flow)
newest_cache = None
newest_timestamp = dt.datetime(year=1970, month=1, day=1, tzinfo=dt.timezone.utc)
# So consumers know when the inventory should be complete
Expand Down Expand Up @@ -85,3 +92,15 @@ def wrapped(*inner_args):
else:
func(*inner_args)
return wrapped

def _handle_aisv3_flow(self, flow: HippoHTTPFlow):
if flow.response.status_code < 200 or flow.response.status_code > 300:
# Probably not a success
return
content_type = flow.response.headers.get("Content-Type", "")
if "llsd" not in content_type:
# Okay, probably still some kind of error...
return

# Try and add anything from the response into the model
self.process_aisv3_response(llsd.parse(flow.response.content))

0 comments on commit 3bb4fb0

Please sign in to comment.