diff --git a/addon_examples/deformer_helper.py b/addon_examples/deformer_helper.py index 571a13e..e379839 100644 --- a/addon_examples/deformer_helper.py +++ b/addon_examples/deformer_helper.py @@ -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 diff --git a/addon_examples/uploader.py b/addon_examples/uploader.py index b5e9d6c..4e33a9f 100644 --- a/addon_examples/uploader.py +++ b/addon_examples/uploader.py @@ -2,21 +2,15 @@ Example of how to upload assets, assumes assets are already encoded in the appropriate format. -/524 upload +/524 upload_asset """ -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()] diff --git a/hippolyzer/lib/client/asset_uploader.py b/hippolyzer/lib/client/asset_uploader.py index ef5104f..cfb2e33 100644 --- a/hippolyzer/lib/client/asset_uploader.py +++ b/hippolyzer/lib/client/asset_uploader.py @@ -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 {