Improve AssetUploader API, make uploader example addon use it
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user