Files
Hippolyzer/hippolyzer/lib/client/asset_uploader.py
2023-12-22 02:40:53 +00:00

128 lines
4.8 KiB
Python

from typing import NamedTuple, Union, Optional, List
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.mesh import MeshAsset, LLMeshSerializer
from hippolyzer.lib.base.templates import AssetType
from hippolyzer.lib.client.state import BaseClientRegion
class UploadError(Exception):
pass
class UploadToken(NamedTuple):
linden_cost: int
uploader_url: str
payload: bytes
class MeshUploadDetails(NamedTuple):
mesh_bytes: bytes
num_faces: int
class AssetUploader:
def __init__(self, region: BaseClientRegion):
self._region = region
async def initiate_asset_upload(self, name: str, asset_type: AssetType,
body: bytes, flags: Optional[int] = None) -> UploadToken:
payload = {
"asset_type": asset_type.to_lookup_name(),
"description": "(No Description)",
"everyone_mask": 0,
"group_mask": 0,
"folder_id": UUID.ZERO, # Puts it in the default folder, I guess. Undocumented.
"inventory_type": asset_type.inventory_type.to_lookup_name(),
"name": name,
"next_owner_mask": 581632,
}
if flags is not None:
payload['flags'] = flags
resp_payload = await self._make_newfileagentinventory_req(payload)
return UploadToken(resp_payload["upload_price"], resp_payload["uploader"], body)
async def _make_newfileagentinventory_req(self, payload: dict):
async with self._region.caps_client.post("NewFileAgentInventory", llsd=payload) as resp:
resp.raise_for_status()
resp_payload = await resp.read_llsd()
# Need to sniff the resp payload for this because SL sends a 200 status code on error
if "error" in resp_payload:
raise UploadError(resp_payload)
return resp_payload
async def complete_upload(self, token: UploadToken) -> dict:
async with self._region.caps_client.post(token.uploader_url, data=token.payload) as resp:
resp.raise_for_status()
resp_payload = await resp.read_llsd()
# The actual upload endpoints return 200 on error, have to sniff the payload to figure
# out if it actually failed...
if "error" in resp_payload:
raise UploadError(resp_payload)
await self._handle_upload_complete(resp_payload)
return resp_payload
async def _handle_upload_complete(self, resp_payload: dict):
"""
Generic hook called when any asset upload completes.
Could trigger an AIS fetch to send the viewer details about the item we just created,
assuming we were in proxy context.
"""
pass
# The mesh upload flow is a little special, so it gets its own method
async def initiate_mesh_upload(self, name: str, mesh: Union[MeshUploadDetails, MeshAsset],
flags: Optional[int] = None) -> UploadToken:
if isinstance(mesh, MeshAsset):
writer = se.BufferWriter("!")
writer.write(LLMeshSerializer(), mesh)
mesh = MeshUploadDetails(writer.copy_buffer(), len(mesh.segments['high_lod']))
asset_resources = self._build_asset_resources(name, [mesh])
payload = {
'asset_resources': asset_resources,
'asset_type': 'mesh',
'description': '(No Description)',
'everyone_mask': 0,
'folder_id': UUID.ZERO,
'group_mask': 0,
'inventory_type': 'object',
'name': name,
'next_owner_mask': 581632,
'texture_folder_id': UUID.ZERO
}
if flags is not None:
payload['flags'] = flags
resp_payload = await self._make_newfileagentinventory_req(payload)
upload_body = llsd.format_xml(asset_resources)
return UploadToken(resp_payload["upload_price"], resp_payload["uploader"], upload_body)
def _build_asset_resources(self, name: str, meshes: List[MeshUploadDetails]) -> dict:
instances = []
for mesh in meshes:
instances.append({
'face_list': [{
'diffuse_color': [1.0, 1.0, 1.0, 1.0],
'fullbright': False
}] * mesh.num_faces,
'material': 3,
'mesh': 0,
'mesh_name': name,
'physics_shape_type': 2,
'position': [0.0, 0.0, 0.0],
'rotation': [0.7071067690849304, 0.0, 0.0, 0.7071067690849304],
'scale': [1.0, 1.0, 1.0]
})
return {
'instance_list': instances,
'mesh_list': [mesh.mesh_bytes for mesh in meshes],
'metric': 'MUT_Unspecified',
'texture_list': []
}