Improve AssetUploader API, make uploader example addon use it

This commit is contained in:
Salad Dais
2022-07-26 00:11:37 +00:00
parent 3c6a917550
commit e34927a996
3 changed files with 45 additions and 98 deletions

View File

@@ -183,7 +183,7 @@ class DeformerAddon(BaseAddon):
# Do the actual upload
try:
await region.asset_uploader.complete_mesh_upload(upload_token)
await region.asset_uploader.complete_upload(upload_token)
except Exception as e:
show_message(e)
raise

View File

@@ -2,21 +2,15 @@
Example of how to upload assets, assumes assets are already encoded
in the appropriate format.
/524 upload <asset type>
/524 upload_asset <asset type>
"""
import pprint
from pathlib import Path
from typing import *
import aiohttp
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.templates import AssetType
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import ais_item_to_inventory_data, show_message, BaseAddon
from hippolyzer.lib.proxy.addon_utils import show_message, BaseAddon
from hippolyzer.lib.proxy.commands import handle_command, Parameter
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -29,7 +23,6 @@ class UploaderAddon(BaseAddon):
async def upload_asset(self, _session: Session, region: ProxiedRegion,
asset_type: AssetType, flags: Optional[int] = None):
"""Upload a raw asset with optional flags"""
inv_type = asset_type.inventory_type
file = await AddonManager.UI.open_file()
if not file:
return
@@ -42,67 +35,29 @@ class UploaderAddon(BaseAddon):
with open(file, "rb") as f:
file_body = f.read()
params = {
"asset_type": asset_type.human_name,
"description": "(No Description)",
"everyone_mask": 0,
"group_mask": 0,
"folder_id": UUID(), # Puts it in the default folder, I guess. Undocumented.
"inventory_type": inv_type.human_name,
"name": name,
"next_owner_mask": 581632,
}
if flags is not None:
params['flags'] = flags
try:
if asset_type == AssetType.MESH:
# Kicking off a mesh upload works a little differently internally
upload_token = await region.asset_uploader.initiate_mesh_upload(
name, file_body, flags=flags
)
else:
upload_token = await region.asset_uploader.initiate_asset_upload(
name, asset_type, file_body, flags=flags,
)
except Exception as e:
show_message(e)
raise
caps = region.caps_client
async with aiohttp.ClientSession() as sess:
async with caps.post('NewFileAgentInventory', llsd=params, session=sess) as resp:
parsed = await resp.read_llsd()
if "uploader" not in parsed:
show_message(f"Upload error!: {parsed!r}")
return
print("Got upload URL, uploading...")
if not await AddonManager.UI.confirm("Upload", f"Spend {upload_token.linden_cost}L on upload?"):
return
async with caps.post(parsed["uploader"], data=file_body, session=sess) as resp:
upload_parsed = await resp.read_llsd()
if "new_inventory_item" not in upload_parsed:
show_message(f"Got weird upload resp: {pprint.pformat(upload_parsed)}")
return
await self._force_inv_update(region, upload_parsed['new_inventory_item'])
@handle_command(item_id=UUID)
async def force_inv_update(self, _session: Session, region: ProxiedRegion, item_id: UUID):
"""Force an inventory update for a given item id"""
await self._force_inv_update(region, item_id)
async def _force_inv_update(self, region: ProxiedRegion, item_id: UUID):
session = region.session()
ais_req_data = {
"items": [
{
"owner_id": session.agent_id,
"item_id": item_id,
}
]
}
async with region.caps_client.post('FetchInventory2', llsd=ais_req_data) as resp:
ais_item = (await resp.read_llsd())["items"][0]
message = Message(
"UpdateCreateInventoryItem",
Block(
"AgentData",
AgentID=session.agent_id,
SimApproved=1,
TransactionID=UUID.random(),
),
ais_item_to_inventory_data(ais_item),
direction=Direction.IN
)
region.circuit.send(message)
# Do the actual upload
try:
await region.asset_uploader.complete_upload(upload_token)
except Exception as e:
show_message(e)
raise
addons = [UploaderAddon()]

View File

@@ -1,6 +1,7 @@
from typing import NamedTuple, Union, Any, Optional
from typing import NamedTuple, Union, Optional
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
@@ -11,14 +12,7 @@ class UploadError(Exception):
pass
class MeshUploadToken(NamedTuple):
linden_cost: int
uploader_url: str
name: str
mesh: bytes
class AssetUploadToken(NamedTuple):
class UploadToken(NamedTuple):
linden_cost: int
uploader_url: str
payload: bytes
@@ -29,7 +23,7 @@ class AssetUploader:
self._region = region
async def initiate_asset_upload(self, name: str, asset_type: AssetType,
body: bytes, flags: Optional[int] = None) -> AssetUploadToken:
body: bytes, flags: Optional[int] = None) -> UploadToken:
payload = {
"asset_type": asset_type.human_name,
"description": "(No Description)",
@@ -44,7 +38,7 @@ class AssetUploader:
payload['flags'] = flags
resp_payload = await self._make_newfileagentinventory_req(payload)
return AssetUploadToken(resp_payload["upload_price"], resp_payload["uploader"], body)
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:
@@ -55,12 +49,16 @@ class AssetUploader:
raise UploadError(resp_payload)
return resp_payload
async def complete_asset_upload(self, token: AssetUploadToken) -> dict:
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()
await self._handle_upload_complete(resp_payload)
return resp_payload
# 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):
"""
@@ -69,13 +67,11 @@ class AssetUploader:
Could trigger an AIS fetch to send the viewer details about the item we just created,
assuming we were in proxy context.
"""
# 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)
pass
# The mesh upload flow is a little special, so it gets its own methods
async def initiate_mesh_upload(self, name: str, mesh: Union[bytes, MeshAsset]) -> MeshUploadToken:
async def initiate_mesh_upload(self, name: str, mesh: Union[bytes, MeshAsset],
flags: Optional[int] = None) -> UploadToken:
"""
Very basic LL-serialized mesh uploader
@@ -86,8 +82,9 @@ class AssetUploader:
writer.write(LLMeshSerializer(), mesh)
mesh = writer.copy_buffer()
asset_resources = self._build_asset_resources(name, mesh)
payload = {
'asset_resources': self._build_asset_resources(name, mesh),
'asset_resources': asset_resources,
'asset_type': 'mesh',
'description': '(No Description)',
'everyone_mask': 0,
@@ -98,17 +95,12 @@ class AssetUploader:
'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)
return MeshUploadToken(resp_payload["upload_price"], resp_payload["uploader"], name, mesh)
async def complete_mesh_upload(self, token: MeshUploadToken) -> Any:
payload = self._build_asset_resources(token.name, token.mesh)
async with self._region.caps_client.post(token.uploader_url, llsd=payload) as resp:
resp.raise_for_status()
resp_payload = await resp.read_llsd()
await self._handle_upload_complete(resp_payload)
return resp_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, mesh: bytes) -> dict:
return {