mirror of
https://github.com/opensim/opensim.git
synced 2026-06-27 00:55:48 +08:00
* Linden client 1.19.1.4 appears to be happy using UDP for this request again, and Linden's own servers (1.21) don't appear to be supplying the CAPS version currently * This should for now fix the bug where texture selection in the edit window both shows only already open textures, and later stops subsequent inventory requests from working.
997 lines
42 KiB
C#
997 lines
42 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSim Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using libsecondlife;
|
|
using log4net;
|
|
using OpenSim.Framework.Communications.Cache;
|
|
using OpenSim.Framework.Servers;
|
|
|
|
namespace OpenSim.Framework.Communications.Capabilities
|
|
{
|
|
public delegate void UpLoadedAsset(
|
|
string assetName, string description, LLUUID assetID, LLUUID inventoryItem, LLUUID parentFolder,
|
|
byte[] data, string inventoryType, string assetType);
|
|
|
|
public delegate LLUUID UpdateItem(LLUUID itemID, byte[] data);
|
|
|
|
public delegate void UpdateTaskScript(LLUUID itemID, LLUUID primID, bool isScriptRunning, byte[] data);
|
|
|
|
public delegate void NewInventoryItem(LLUUID userID, InventoryItemBase item);
|
|
|
|
public delegate LLUUID ItemUpdatedCallback(LLUUID userID, LLUUID itemID, byte[] data);
|
|
|
|
public delegate void TaskScriptUpdatedCallback(LLUUID userID, LLUUID itemID, LLUUID primID,
|
|
bool isScriptRunning, byte[] data);
|
|
|
|
public delegate List<InventoryItemBase> FetchInventoryDescendentsCAPS(LLUUID agentID, LLUUID folderID, LLUUID ownerID,
|
|
bool fetchFolders, bool fetchItems, int sortOrder);
|
|
|
|
/// <summary>
|
|
/// XXX Probably not a particularly nice way of allow us to get the scene presence from the scene (chiefly so that
|
|
/// we can popup a message on the user's client if the inventory service has permanently failed). But I didn't want
|
|
/// to just pass the whole Scene into CAPS.
|
|
/// </summary>
|
|
public delegate IClientAPI GetClientDelegate(LLUUID agentID);
|
|
|
|
public class Caps
|
|
{
|
|
private static readonly ILog m_log =
|
|
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private string m_httpListenerHostName;
|
|
private uint m_httpListenPort;
|
|
|
|
/// <summary>
|
|
/// This is the uuid portion of every CAPS path. It is used to make capability urls private to the requester.
|
|
/// </summary>
|
|
private string m_capsObjectPath;
|
|
public string CapsObjectPath { get { return m_capsObjectPath; } }
|
|
|
|
private CapsHandlers m_capsHandlers;
|
|
|
|
private static readonly string m_requestPath = "0000/";
|
|
private static readonly string m_mapLayerPath = "0001/";
|
|
private static readonly string m_newInventory = "0002/";
|
|
//private static readonly string m_requestTexture = "0003/";
|
|
private static readonly string m_notecardUpdatePath = "0004/";
|
|
private static readonly string m_notecardTaskUpdatePath = "0005/";
|
|
private static readonly string m_fetchInventoryPath = "0006/";
|
|
|
|
// The following two entries are in a module, however, there also here so that we don't re-assign
|
|
// the path to another cap by mistake.
|
|
private static readonly string m_parcelVoiceInfoRequestPath = "0007/"; // This is in a module.
|
|
private static readonly string m_provisionVoiceAccountRequestPath = "0008/";// This is in a module.
|
|
|
|
//private string eventQueue = "0100/";
|
|
private BaseHttpServer m_httpListener;
|
|
private LLUUID m_agentID;
|
|
private AssetCache m_assetCache;
|
|
private int m_eventQueueCount = 1;
|
|
private Queue<string> m_capsEventQueue = new Queue<string>();
|
|
private bool m_dumpAssetsToFile;
|
|
private string m_regionName;
|
|
|
|
// These are callbacks which will be setup by the scene so that we can update scene data when we
|
|
// receive capability calls
|
|
public NewInventoryItem AddNewInventoryItem = null;
|
|
public ItemUpdatedCallback ItemUpdatedCall = null;
|
|
public TaskScriptUpdatedCallback TaskScriptUpdatedCall = null;
|
|
public FetchInventoryDescendentsCAPS CAPSFetchInventoryDescendents = null;
|
|
public GetClientDelegate GetClient = null;
|
|
|
|
public Caps(AssetCache assetCache, BaseHttpServer httpServer, string httpListen, uint httpPort, string capsPath,
|
|
LLUUID agent, bool dumpAssetsToFile, string regionName)
|
|
{
|
|
m_assetCache = assetCache;
|
|
m_capsObjectPath = capsPath;
|
|
m_httpListener = httpServer;
|
|
m_httpListenerHostName = httpListen;
|
|
m_httpListenPort = httpPort;
|
|
m_agentID = agent;
|
|
m_dumpAssetsToFile = dumpAssetsToFile;
|
|
m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort);
|
|
m_regionName = regionName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register all CAPS http service handlers
|
|
/// </summary>
|
|
public void RegisterHandlers()
|
|
{
|
|
DeregisterHandlers();
|
|
|
|
string capsBase = "/CAPS/" + m_capsObjectPath;
|
|
|
|
try
|
|
{
|
|
// the root of all evil
|
|
m_capsHandlers["SEED"] = new RestStreamHandler("POST", capsBase + m_requestPath, CapsRequest);
|
|
m_capsHandlers["MapLayer"] =
|
|
new LLSDStreamhandler<LLSDMapRequest, LLSDMapLayerResponse>("POST",
|
|
capsBase + m_mapLayerPath,
|
|
GetMapLayer);
|
|
m_capsHandlers["NewFileAgentInventory"] =
|
|
new LLSDStreamhandler<LLSDAssetUploadRequest, LLSDAssetUploadResponse>("POST",
|
|
capsBase + m_newInventory,
|
|
NewAgentInventoryRequest);
|
|
m_capsHandlers["UpdateNotecardAgentInventory"] =
|
|
new RestStreamHandler("POST", capsBase + m_notecardUpdatePath, NoteCardAgentInventory);
|
|
m_capsHandlers["UpdateScriptAgentInventory"] = m_capsHandlers["UpdateNotecardAgentInventory"];
|
|
m_capsHandlers["UpdateScriptTaskInventory"] =
|
|
new RestStreamHandler("POST", capsBase + m_notecardTaskUpdatePath, ScriptTaskInventory);
|
|
|
|
// justincc: I've disabled the CAPS service for now to fix problems with selecting textures, and
|
|
// subsequent inventory breakage, in the edit object pane (such as mantis 1085). This requires
|
|
// enhancements (probably filling out the folder part of the LLSD reply) to our CAPS service,
|
|
// but when I went on the Linden grid, the
|
|
// simulators I visited (version 1.21) were, surprisingly, no longer supplying this capability. Instead,
|
|
// the 1.19.1.4 client appeared to be happily flowing inventory data over UDP
|
|
//
|
|
// This is very probably just a temporary measure - once the CAPS service appears again on the Linden grid
|
|
// we will be
|
|
// able to get the data we need to implement the necessary part of the protocol to fix the issue above.
|
|
// m_capsHandlers["FetchInventoryDescendents"] =
|
|
// new RestStreamHandler("POST", capsBase + m_fetchInventoryPath, FetchInventoryRequest);
|
|
|
|
// m_capsHandlers["FetchInventoryDescendents"] =
|
|
// new LLSDStreamhandler<LLSDFetchInventoryDescendents, LLSDInventoryDescendents>("POST",
|
|
// capsBase + m_fetchInventory,
|
|
// FetchInventory));
|
|
// m_capsHandlers["RequestTextureDownload"] = new RestStreamHandler("POST",
|
|
// capsBase + m_requestTexture,
|
|
// RequestTexture);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.Error("[CAPS]: " + e.ToString());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a handler. This allows modules to register handlers.
|
|
/// </summary>
|
|
/// <param name="capName"></param>
|
|
/// <param name="handler"></param>
|
|
public void RegisterHandler(string capName, IRequestHandler handler)
|
|
{
|
|
m_capsHandlers[capName] = handler;
|
|
m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all CAPS service handlers.
|
|
///
|
|
/// </summary>
|
|
/// <param name="httpListener"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="restMethod"></param>
|
|
public void DeregisterHandlers()
|
|
{
|
|
foreach(string capsName in m_capsHandlers.Caps)
|
|
{
|
|
m_capsHandlers.Remove(capsName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct a client response detailing all the capabilities this server can provide.
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string CapsRequest(string request, string path, string param)
|
|
{
|
|
m_log.Debug("[CAPS]: Seed Caps Request in region: " + m_regionName);
|
|
//Console.WriteLine("caps request " + request);
|
|
string result = LLSDHelpers.SerialiseLLSDReply(m_capsHandlers.CapsDetails);
|
|
//m_log.DebugFormat("[CAPS] CapsRequest {0}", result);
|
|
return result;
|
|
}
|
|
|
|
// FIXME: these all should probably go into the respective region
|
|
// modules
|
|
|
|
/// <summary>
|
|
/// Processes a fetch inventory request and sends the reply
|
|
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
// Request is like:
|
|
//<llsd>
|
|
// <map><key>folders</key>
|
|
// <array>
|
|
// <map>
|
|
// <key>fetch-folders</key><boolean>1</boolean><key>fetch-items</key><boolean>1</boolean><key>folder-id</key><uuid>8e1e3a30-b9bf-11dc-95ff-0800200c9a66</uuid><key>owner-id</key><uuid>11111111-1111-0000-0000-000100bba000</uuid><key>sort-order</key><integer>1</integer>
|
|
// </map>
|
|
// </array>
|
|
// </map>
|
|
//</llsd>
|
|
//
|
|
// multiple fetch-folder maps are allowed within the larger folders map.
|
|
public string FetchInventoryRequest(string request, string path, string param)
|
|
{
|
|
string unmodifiedRequest = request.ToString();
|
|
|
|
//m_log.DebugFormat("[AGENT INVENTORY]: Received CAPS fetch inventory request {0}", unmodifiedRequest);
|
|
m_log.Debug("[CAPS]: Inventory Request in region: " + m_regionName);
|
|
|
|
Hashtable hash = new Hashtable();
|
|
try
|
|
{
|
|
hash = (Hashtable)LLSD.LLSDDeserialize(Helpers.StringToField(request));
|
|
}
|
|
catch (LLSD.LLSDParseException pe)
|
|
{
|
|
m_log.Error("[AGENT INVENTORY]: Fetch error: " + pe.Message);
|
|
m_log.Error("Request: " + request.ToString());
|
|
}
|
|
|
|
ArrayList foldersrequested = (ArrayList)hash["folders"];
|
|
|
|
string response = "";
|
|
|
|
for (int i = 0; i < foldersrequested.Count; i++)
|
|
{
|
|
string inventoryitemstr = "";
|
|
Hashtable inventoryhash = (Hashtable)foldersrequested[i];
|
|
|
|
LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents();
|
|
LLSDHelpers.DeserialiseLLSDMap(inventoryhash, llsdRequest);
|
|
LLSDInventoryDescendents reply = FetchInventoryReply(llsdRequest);
|
|
|
|
inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply);
|
|
inventoryitemstr = inventoryitemstr.Replace("<llsd><map><key>folders</key><array>", "");
|
|
inventoryitemstr = inventoryitemstr.Replace("</array></map></llsd>", "");
|
|
|
|
response += inventoryitemstr;
|
|
}
|
|
|
|
if (response.Length == 0)
|
|
{
|
|
// Ter-guess: If requests fail a lot, the client seems to stop requesting descendants.
|
|
// Therefore, I'm concluding that the client only has so many threads available to do requests
|
|
// and when a thread stalls.. is stays stalled.
|
|
// Therefore we need to return something valid
|
|
response = "<llsd><map><key>folders</key><array /></map></llsd>";
|
|
}
|
|
else
|
|
{
|
|
response = "<llsd><map><key>folders</key><array>" + response + "</array></map></llsd>";
|
|
}
|
|
|
|
//m_log.DebugFormat("[AGENT INVENTORY]: Replying to CAPS fetch inventory request with following xml");
|
|
//m_log.Debug(Util.GetFormattedXml(response));
|
|
|
|
return response;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct an LLSD reply packet to a CAPS inventory request
|
|
/// </summary>
|
|
/// <param name="invFetch"></param>
|
|
/// <returns></returns>
|
|
private LLSDInventoryDescendents FetchInventoryReply(LLSDFetchInventoryDescendents invFetch)
|
|
{
|
|
LLSDInventoryDescendents reply = new LLSDInventoryDescendents();
|
|
LLSDInventoryFolderContents contents = new LLSDInventoryFolderContents();
|
|
contents.agent___id = m_agentID;
|
|
contents.owner___id = invFetch.owner_id;
|
|
contents.folder___id = invFetch.folder_id;
|
|
|
|
// The version number being sent back was originally 1.
|
|
// Unfortunately, on 1.19.1.4, this means that we see a problem where on subsequent logins
|
|
// without clearing client cache, objects in the root folder disappear until the cache is cleared,
|
|
// at which point they reappear.
|
|
//
|
|
// Seeing the version to something other than 0 may be the right thing to do, but there is
|
|
// a greater subtlety of the second life protocol that needs to be understood first.
|
|
contents.version = 0;
|
|
|
|
contents.descendents = 0;
|
|
reply.folders.Array.Add(contents);
|
|
List<InventoryItemBase> itemList = null;
|
|
if (CAPSFetchInventoryDescendents != null)
|
|
{
|
|
itemList = CAPSFetchInventoryDescendents(m_agentID, invFetch.folder_id, invFetch.owner_id, invFetch.fetch_folders, invFetch.fetch_items, invFetch.sort_order);
|
|
}
|
|
|
|
if (itemList != null)
|
|
{
|
|
foreach (InventoryItemBase invItem in itemList)
|
|
{
|
|
contents.items.Array.Add(ConvertInventoryItem(invItem));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IClientAPI client = GetClient(m_agentID);
|
|
|
|
// We're going to both notify the client of inventory service failure and send back a 'no folder contents' response.
|
|
// If we don't send back the response,
|
|
// the client becomes unhappy (see Teravus' comment in FetchInventoryRequest())
|
|
if (client != null)
|
|
{
|
|
client.SendAgentAlertMessage(
|
|
"AGIN0001E: The inventory service has either failed or is not responding. Your inventory will not function properly for the rest of this session. Please clear your cache and relog.",
|
|
true);
|
|
}
|
|
else
|
|
{
|
|
m_log.ErrorFormat(
|
|
"[AGENT INVENTORY]: Could not lookup controlling client for {0} in order to notify them of the inventory service failure",
|
|
m_agentID);
|
|
}
|
|
}
|
|
|
|
contents.descendents = contents.items.Array.Count;
|
|
return reply;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an internal inventory item object into an LLSD object.
|
|
/// </summary>
|
|
/// <param name="invItem"></param>
|
|
/// <returns></returns>
|
|
private LLSDInventoryItem ConvertInventoryItem(InventoryItemBase invItem)
|
|
{
|
|
LLSDInventoryItem llsdItem = new LLSDInventoryItem();
|
|
llsdItem.asset_id = invItem.AssetID;
|
|
llsdItem.created_at = 1000;
|
|
llsdItem.desc = invItem.Description;
|
|
llsdItem.flags = 0;
|
|
llsdItem.item_id = invItem.ID;
|
|
llsdItem.name = invItem.Name;
|
|
llsdItem.parent_id = invItem.Folder;
|
|
llsdItem.type = Enum.GetName(typeof(AssetType), invItem.AssetType).ToLower();
|
|
llsdItem.inv_type = Enum.GetName(typeof(InventoryType), invItem.InvType).ToLower();
|
|
llsdItem.permissions = new LLSDPermissions();
|
|
llsdItem.permissions.creator_id = invItem.Creator;
|
|
llsdItem.permissions.base_mask = (int)invItem.CurrentPermissions;
|
|
llsdItem.permissions.everyone_mask = (int)invItem.EveryOnePermissions;
|
|
llsdItem.permissions.group_id = LLUUID.Zero;
|
|
llsdItem.permissions.group_mask = 0;
|
|
llsdItem.permissions.is_owner_group = false;
|
|
llsdItem.permissions.next_owner_mask = (int)invItem.NextPermissions;
|
|
llsdItem.permissions.owner_id = m_agentID; // FixMe
|
|
llsdItem.permissions.owner_mask = (int)invItem.CurrentPermissions;
|
|
llsdItem.sale_info = new LLSDSaleInfo();
|
|
llsdItem.sale_info.sale_price = 10;
|
|
llsdItem.sale_info.sale_type = "not";
|
|
|
|
return llsdItem;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="mapReq"></param>
|
|
/// <returns></returns>
|
|
public LLSDMapLayerResponse GetMapLayer(LLSDMapRequest mapReq)
|
|
{
|
|
m_log.Debug("[CAPS]: MapLayer Request in region: " + m_regionName);
|
|
LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse();
|
|
mapResponse.LayerData.Array.Add(GetLLSDMapLayerResponse());
|
|
return mapResponse;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected static LLSDMapLayer GetLLSDMapLayerResponse()
|
|
{
|
|
LLSDMapLayer mapLayer = new LLSDMapLayer();
|
|
mapLayer.Right = 5000;
|
|
mapLayer.Top = 5000;
|
|
mapLayer.ImageID = new LLUUID("00000000-0000-1111-9999-000000000006");
|
|
return mapLayer;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string RequestTexture(string request, string path, string param)
|
|
{
|
|
System.Console.WriteLine("texture request " + request);
|
|
// Needs implementing (added to remove compiler warning)
|
|
return String.Empty;
|
|
}
|
|
|
|
#region EventQueue (Currently not enabled)
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string ProcessEventQueue(string request, string path, string param)
|
|
{
|
|
string res = String.Empty;
|
|
|
|
if (m_capsEventQueue.Count > 0)
|
|
{
|
|
lock (m_capsEventQueue)
|
|
{
|
|
string item = m_capsEventQueue.Dequeue();
|
|
res = item;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = CreateEmptyEventResponse();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="caps"></param>
|
|
/// <param name="ipAddressPort"></param>
|
|
/// <returns></returns>
|
|
public string CreateEstablishAgentComms(string caps, string ipAddressPort)
|
|
{
|
|
LLSDCapEvent eventItem = new LLSDCapEvent();
|
|
eventItem.id = m_eventQueueCount;
|
|
//should be creating a EstablishAgentComms item, but there isn't a class for it yet
|
|
eventItem.events.Array.Add(new LLSDEmpty());
|
|
string res = LLSDHelpers.SerialiseLLSDReply(eventItem);
|
|
m_eventQueueCount++;
|
|
|
|
m_capsEventQueue.Enqueue(res);
|
|
return res;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string CreateEmptyEventResponse()
|
|
{
|
|
LLSDCapEvent eventItem = new LLSDCapEvent();
|
|
eventItem.id = m_eventQueueCount;
|
|
eventItem.events.Array.Add(new LLSDEmpty());
|
|
string res = LLSDHelpers.SerialiseLLSDReply(eventItem);
|
|
m_eventQueueCount++;
|
|
return res;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Called by the script task update handler. Provides a URL to which the client can upload a new asset.
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string ScriptTaskInventory(string request, string path, string param)
|
|
{
|
|
try
|
|
{
|
|
m_log.Debug("[CAPS]: ScriptTaskInventory Request in region: " + m_regionName);
|
|
//m_log.DebugFormat("[CAPS]: request: {0}, path: {1}, param: {2}", request, path, param);
|
|
|
|
Hashtable hash = (Hashtable) LLSD.LLSDDeserialize(Helpers.StringToField(request));
|
|
LLSDTaskScriptUpdate llsdUpdateRequest = new LLSDTaskScriptUpdate();
|
|
LLSDHelpers.DeserialiseLLSDMap(hash, llsdUpdateRequest);
|
|
|
|
string capsBase = "/CAPS/" + m_capsObjectPath;
|
|
string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000");
|
|
|
|
TaskInventoryScriptUpdater uploader =
|
|
new TaskInventoryScriptUpdater(
|
|
llsdUpdateRequest.item_id,
|
|
llsdUpdateRequest.task_id,
|
|
llsdUpdateRequest.is_script_running,
|
|
capsBase + uploaderPath,
|
|
m_httpListener,
|
|
m_dumpAssetsToFile);
|
|
uploader.OnUpLoad += TaskScriptUpdated;
|
|
|
|
m_httpListener.AddStreamHandler(
|
|
new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps));
|
|
string uploaderURL = "http://" + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase +
|
|
uploaderPath;
|
|
|
|
LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse();
|
|
uploadResponse.uploader = uploaderURL;
|
|
uploadResponse.state = "upload";
|
|
|
|
// m_log.InfoFormat("[CAPS]: " +
|
|
// "ScriptTaskInventory response: {0}",
|
|
// LLSDHelpers.SerialiseLLSDReply(uploadResponse)));
|
|
|
|
return LLSDHelpers.SerialiseLLSDReply(uploadResponse);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.Error("[CAPS]: " + e.ToString());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by the notecard update handler. Provides a URL to which the client can upload a new asset.
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string NoteCardAgentInventory(string request, string path, string param)
|
|
{
|
|
m_log.Debug("[CAPS]: NoteCardAgentInventory Request in region: " + m_regionName);
|
|
//libsecondlife.StructuredData.LLSDMap hash = (libsecondlife.StructuredData.LLSDMap)libsecondlife.StructuredData.LLSDParser.DeserializeBinary(Helpers.StringToField(request));
|
|
Hashtable hash = (Hashtable) LLSD.LLSDDeserialize(Helpers.StringToField(request));
|
|
LLSDItemUpdate llsdRequest = new LLSDItemUpdate();
|
|
LLSDHelpers.DeserialiseLLSDMap(hash, llsdRequest);
|
|
|
|
string capsBase = "/CAPS/" + m_capsObjectPath;
|
|
string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000");
|
|
|
|
ItemUpdater uploader =
|
|
new ItemUpdater(llsdRequest.item_id, capsBase + uploaderPath, m_httpListener, m_dumpAssetsToFile);
|
|
uploader.OnUpLoad += ItemUpdated;
|
|
|
|
m_httpListener.AddStreamHandler(
|
|
new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps));
|
|
string uploaderURL = "http://" + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase +
|
|
uploaderPath;
|
|
|
|
LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse();
|
|
uploadResponse.uploader = uploaderURL;
|
|
uploadResponse.state = "upload";
|
|
|
|
// m_log.InfoFormat("[CAPS]: " +
|
|
// "NoteCardAgentInventory response: {0}",
|
|
// LLSDHelpers.SerialiseLLSDReply(uploadResponse)));
|
|
|
|
return LLSDHelpers.SerialiseLLSDReply(uploadResponse);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="llsdRequest"></param>
|
|
/// <returns></returns>
|
|
public LLSDAssetUploadResponse NewAgentInventoryRequest(LLSDAssetUploadRequest llsdRequest)
|
|
{
|
|
//Console.WriteLine("asset upload request via CAPS" + llsdRequest.inventory_type +" , "+ llsdRequest.asset_type);
|
|
|
|
string assetName = llsdRequest.name;
|
|
string assetDes = llsdRequest.description;
|
|
string capsBase = "/CAPS/" + m_capsObjectPath;
|
|
LLUUID newAsset = LLUUID.Random();
|
|
LLUUID newInvItem = LLUUID.Random();
|
|
LLUUID parentFolder = llsdRequest.folder_id;
|
|
string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000");
|
|
|
|
AssetUploader uploader =
|
|
new AssetUploader(assetName, assetDes, newAsset, newInvItem, parentFolder, llsdRequest.inventory_type,
|
|
llsdRequest.asset_type, capsBase + uploaderPath, m_httpListener, m_dumpAssetsToFile);
|
|
m_httpListener.AddStreamHandler(
|
|
new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps));
|
|
string uploaderURL = "http://" + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase +
|
|
uploaderPath;
|
|
|
|
LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse();
|
|
uploadResponse.uploader = uploaderURL;
|
|
uploadResponse.state = "upload";
|
|
uploader.OnUpLoad += UploadCompleteHandler;
|
|
return uploadResponse;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="assetID"></param>
|
|
/// <param name="inventoryItem"></param>
|
|
/// <param name="data"></param>
|
|
public void UploadCompleteHandler(string assetName, string assetDescription, LLUUID assetID,
|
|
LLUUID inventoryItem, LLUUID parentFolder, byte[] data, string inventoryType,
|
|
string assetType)
|
|
{
|
|
sbyte assType = 0;
|
|
sbyte inType = 0;
|
|
|
|
if (inventoryType == "sound")
|
|
{
|
|
inType = 1;
|
|
assType = 1;
|
|
}
|
|
else if (inventoryType == "animation")
|
|
{
|
|
inType = 19;
|
|
assType = 20;
|
|
}
|
|
else if (inventoryType == "wearable")
|
|
{
|
|
inType = 18;
|
|
switch (assetType)
|
|
{
|
|
case "bodypart":
|
|
assType = 13;
|
|
break;
|
|
case "clothing":
|
|
assType = 5;
|
|
break;
|
|
}
|
|
}
|
|
|
|
AssetBase asset;
|
|
asset = new AssetBase();
|
|
asset.FullID = assetID;
|
|
asset.Type = assType;
|
|
asset.InvType = inType;
|
|
asset.Name = assetName;
|
|
asset.Data = data;
|
|
m_assetCache.AddAsset(asset);
|
|
|
|
InventoryItemBase item = new InventoryItemBase();
|
|
item.Owner = m_agentID;
|
|
item.Creator = m_agentID;
|
|
item.ID = inventoryItem;
|
|
item.AssetID = asset.FullID;
|
|
item.Description = assetDescription;
|
|
item.Name = assetName;
|
|
item.AssetType = assType;
|
|
item.InvType = inType;
|
|
item.Folder = parentFolder;
|
|
item.CurrentPermissions = 2147483647;
|
|
item.NextPermissions = 2147483647;
|
|
|
|
if (AddNewInventoryItem != null)
|
|
{
|
|
AddNewInventoryItem(m_agentID, item);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when new asset data for an agent inventory item update has been uploaded.
|
|
/// </summary>
|
|
/// <param name="itemID">Item to update</param>
|
|
/// <param name="data">New asset data</param>
|
|
/// <returns></returns>
|
|
public LLUUID ItemUpdated(LLUUID itemID, byte[] data)
|
|
{
|
|
if (ItemUpdatedCall != null)
|
|
{
|
|
return ItemUpdatedCall(m_agentID, itemID, data);
|
|
}
|
|
|
|
return LLUUID.Zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when new asset data for an agent inventory item update has been uploaded.
|
|
/// </summary>
|
|
/// <param name="itemID">Item to update</param>
|
|
/// <param name="primID">Prim containing item to update</param>
|
|
/// <param name="isScriptRunning">Signals whether the script to update is currently running</param>
|
|
/// <param name="data">New asset data</param>
|
|
public void TaskScriptUpdated(LLUUID itemID, LLUUID primID, bool isScriptRunning, byte[] data)
|
|
{
|
|
if (TaskScriptUpdatedCall != null)
|
|
{
|
|
TaskScriptUpdatedCall(m_agentID, itemID, primID, isScriptRunning, data);
|
|
}
|
|
}
|
|
|
|
public class AssetUploader
|
|
{
|
|
public event UpLoadedAsset OnUpLoad;
|
|
private UpLoadedAsset handlerUpLoad = null;
|
|
|
|
private string uploaderPath = String.Empty;
|
|
private LLUUID newAssetID;
|
|
private LLUUID inventoryItemID;
|
|
private LLUUID parentFolder;
|
|
private BaseHttpServer httpListener;
|
|
private bool m_dumpAssetsToFile;
|
|
private string m_assetName = String.Empty;
|
|
private string m_assetDes = String.Empty;
|
|
|
|
private string m_invType = String.Empty;
|
|
private string m_assetType = String.Empty;
|
|
|
|
public AssetUploader(string assetName, string description, LLUUID assetID, LLUUID inventoryItem,
|
|
LLUUID parentFolderID, string invType, string assetType, string path,
|
|
BaseHttpServer httpServer, bool dumpAssetsToFile)
|
|
{
|
|
m_assetName = assetName;
|
|
m_assetDes = description;
|
|
newAssetID = assetID;
|
|
inventoryItemID = inventoryItem;
|
|
uploaderPath = path;
|
|
httpListener = httpServer;
|
|
parentFolder = parentFolderID;
|
|
m_assetType = assetType;
|
|
m_invType = invType;
|
|
m_dumpAssetsToFile = dumpAssetsToFile;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string uploaderCaps(byte[] data, string path, string param)
|
|
{
|
|
LLUUID inv = inventoryItemID;
|
|
string res = String.Empty;
|
|
LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete();
|
|
uploadComplete.new_asset = newAssetID.ToString();
|
|
uploadComplete.new_inventory_item = inv;
|
|
uploadComplete.state = "complete";
|
|
|
|
res = LLSDHelpers.SerialiseLLSDReply(uploadComplete);
|
|
|
|
httpListener.RemoveStreamHandler("POST", uploaderPath);
|
|
|
|
if (m_dumpAssetsToFile)
|
|
{
|
|
SaveAssetToFile(m_assetName + ".jp2", data);
|
|
}
|
|
handlerUpLoad = OnUpLoad;
|
|
if (handlerUpLoad != null)
|
|
{
|
|
handlerUpLoad(m_assetName, m_assetDes, newAssetID, inv, parentFolder, data, m_invType, m_assetType);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
///Left this in and commented in case there are unforseen issues
|
|
//private void SaveAssetToFile(string filename, byte[] data)
|
|
//{
|
|
// FileStream fs = File.Create(filename);
|
|
// BinaryWriter bw = new BinaryWriter(fs);
|
|
// bw.Write(data);
|
|
// bw.Close();
|
|
// fs.Close();
|
|
//}
|
|
private static void SaveAssetToFile(string filename, byte[] data)
|
|
{
|
|
string assetPath = "UserAssets";
|
|
if (!Directory.Exists(assetPath))
|
|
{
|
|
Directory.CreateDirectory(assetPath);
|
|
}
|
|
FileStream fs = File.Create(Path.Combine(assetPath, Util.safeFileName(filename)));
|
|
BinaryWriter bw = new BinaryWriter(fs);
|
|
bw.Write(data);
|
|
bw.Close();
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class is a callback invoked when a client sends asset data to
|
|
/// an agent inventory notecard update url
|
|
/// </summary>
|
|
public class ItemUpdater
|
|
{
|
|
public event UpdateItem OnUpLoad;
|
|
|
|
private UpdateItem handlerUpdateItem = null;
|
|
|
|
private string uploaderPath = String.Empty;
|
|
private LLUUID inventoryItemID;
|
|
private BaseHttpServer httpListener;
|
|
private bool m_dumpAssetToFile;
|
|
|
|
public ItemUpdater(LLUUID inventoryItem, string path, BaseHttpServer httpServer, bool dumpAssetToFile)
|
|
{
|
|
m_dumpAssetToFile = dumpAssetToFile;
|
|
|
|
inventoryItemID = inventoryItem;
|
|
uploaderPath = path;
|
|
httpListener = httpServer;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string uploaderCaps(byte[] data, string path, string param)
|
|
{
|
|
LLUUID inv = inventoryItemID;
|
|
string res = String.Empty;
|
|
LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete();
|
|
LLUUID assetID = LLUUID.Zero;
|
|
handlerUpdateItem = OnUpLoad;
|
|
if (handlerUpdateItem != null)
|
|
{
|
|
assetID = handlerUpdateItem(inv, data);
|
|
}
|
|
|
|
uploadComplete.new_asset = assetID.ToString();
|
|
uploadComplete.new_inventory_item = inv;
|
|
uploadComplete.state = "complete";
|
|
|
|
res = LLSDHelpers.SerialiseLLSDReply(uploadComplete);
|
|
|
|
httpListener.RemoveStreamHandler("POST", uploaderPath);
|
|
|
|
if (m_dumpAssetToFile)
|
|
{
|
|
SaveAssetToFile("updateditem" + Util.RandomClass.Next(1, 1000) + ".dat", data);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
///Left this in and commented in case there are unforseen issues
|
|
//private void SaveAssetToFile(string filename, byte[] data)
|
|
//{
|
|
// FileStream fs = File.Create(filename);
|
|
// BinaryWriter bw = new BinaryWriter(fs);
|
|
// bw.Write(data);
|
|
// bw.Close();
|
|
// fs.Close();
|
|
//}
|
|
private static void SaveAssetToFile(string filename, byte[] data)
|
|
{
|
|
string assetPath = "UserAssets";
|
|
if (!Directory.Exists(assetPath))
|
|
{
|
|
Directory.CreateDirectory(assetPath);
|
|
}
|
|
FileStream fs = File.Create(Path.Combine(assetPath, filename));
|
|
BinaryWriter bw = new BinaryWriter(fs);
|
|
bw.Write(data);
|
|
bw.Close();
|
|
fs.Close();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class is a callback invoked when a client sends asset data to
|
|
/// a task inventory script update url
|
|
/// </summary>
|
|
public class TaskInventoryScriptUpdater
|
|
{
|
|
public event UpdateTaskScript OnUpLoad;
|
|
|
|
private UpdateTaskScript handlerUpdateTaskScript = null;
|
|
|
|
private string uploaderPath = String.Empty;
|
|
private LLUUID inventoryItemID;
|
|
private LLUUID primID;
|
|
private bool isScriptRunning;
|
|
private BaseHttpServer httpListener;
|
|
private bool m_dumpAssetToFile;
|
|
|
|
public TaskInventoryScriptUpdater(LLUUID inventoryItemID, LLUUID primID, int isScriptRunning,
|
|
string path, BaseHttpServer httpServer, bool dumpAssetToFile)
|
|
{
|
|
m_dumpAssetToFile = dumpAssetToFile;
|
|
|
|
this.inventoryItemID = inventoryItemID;
|
|
this.primID = primID;
|
|
|
|
// This comes in over the packet as an integer, but actually appears to be treated as a bool
|
|
this.isScriptRunning = (0 == isScriptRunning ? false : true);
|
|
|
|
uploaderPath = path;
|
|
httpListener = httpServer;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <returns></returns>
|
|
public string uploaderCaps(byte[] data, string path, string param)
|
|
{
|
|
try
|
|
{
|
|
// m_log.InfoFormat("[CAPS]: " +
|
|
// "TaskInventoryScriptUpdater received data: {0}, path: {1}, param: {2}",
|
|
// data, path, param));
|
|
|
|
string res = String.Empty;
|
|
LLSDTaskInventoryUploadComplete uploadComplete = new LLSDTaskInventoryUploadComplete();
|
|
|
|
handlerUpdateTaskScript = OnUpLoad;
|
|
if (handlerUpdateTaskScript != null)
|
|
{
|
|
handlerUpdateTaskScript(inventoryItemID, primID, isScriptRunning, data);
|
|
}
|
|
|
|
uploadComplete.item_id = inventoryItemID;
|
|
uploadComplete.task_id = primID;
|
|
uploadComplete.state = "complete";
|
|
|
|
res = LLSDHelpers.SerialiseLLSDReply(uploadComplete);
|
|
|
|
httpListener.RemoveStreamHandler("POST", uploaderPath);
|
|
|
|
if (m_dumpAssetToFile)
|
|
{
|
|
SaveAssetToFile("updatedtaskscript" + Util.RandomClass.Next(1, 1000) + ".dat", data);
|
|
}
|
|
|
|
// m_log.InfoFormat("[CAPS]: TaskInventoryScriptUpdater.uploaderCaps res: {0}", res);
|
|
|
|
return res;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.Error("[CAPS]: " + e.ToString());
|
|
}
|
|
|
|
// XXX Maybe this should be some meaningful error packet
|
|
return null;
|
|
}
|
|
///Left this in and commented in case there are unforseen issues
|
|
//private void SaveAssetToFile(string filename, byte[] data)
|
|
//{
|
|
// FileStream fs = File.Create(filename);
|
|
// BinaryWriter bw = new BinaryWriter(fs);
|
|
// bw.Write(data);
|
|
// bw.Close();
|
|
// fs.Close();
|
|
//}
|
|
private static void SaveAssetToFile(string filename, byte[] data)
|
|
{
|
|
string assetPath = "UserAssets";
|
|
if (!Directory.Exists(assetPath))
|
|
{
|
|
Directory.CreateDirectory(assetPath);
|
|
}
|
|
FileStream fs = File.Create(Path.Combine(assetPath, filename));
|
|
BinaryWriter bw = new BinaryWriter(fs);
|
|
bw.Write(data);
|
|
bw.Close();
|
|
fs.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|