Basic AIS response handling in proxy
This commit is contained in:
@@ -234,7 +234,7 @@ class InventoryModel(InventoryBase):
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -528,12 +530,24 @@ class InventoryCategory(InventoryContainerBase):
|
||||
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")
|
||||
@@ -644,7 +658,7 @@ class InventoryItem(InventoryNodeBase):
|
||||
|
||||
@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()
|
||||
@@ -666,6 +680,9 @@ class InventoryItem(InventoryNodeBase):
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
@@ -173,4 +174,35 @@ class InventoryManager:
|
||||
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)
|
||||
|
||||
@@ -4,17 +4,23 @@ import functools
|
||||
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.
|
||||
@@ -41,6 +47,7 @@ class ProxyInventoryManager(InventoryManager):
|
||||
# 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
|
||||
@@ -85,3 +92,15 @@ class ProxyInventoryManager(InventoryManager):
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user