* Transfer timeout support for uploads. This code will be deprecated soon though as CAPS uploading is almost finished
* More parameters to HTTPBase and Capabilities for making special requests * Renamed InventoryManager callbacks to match the rest of libsecondlife * Several new InventoryManager functions, not complete yet! * Fix for null buddy list on login * OnSimConnecting returns a bool to allow canceling sim connections * NetworkManager.Connect() properly returns null on a failure git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1393 52acb1d6-8a22-11de-b505-999d5b087335
This commit is contained in:
@@ -6,6 +6,8 @@ using libsecondlife.Packets;
|
||||
|
||||
namespace libsecondlife
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// The different types of assets in Second Life
|
||||
/// </summary>
|
||||
@@ -145,17 +147,42 @@ namespace libsecondlife
|
||||
Baked = 1
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Transfer Classes
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Transfer
|
||||
{
|
||||
public delegate void Timeout(Transfer transfer);
|
||||
|
||||
public event Timeout OnTimeout;
|
||||
|
||||
public LLUUID ID = LLUUID.Zero;
|
||||
public int Size = 0;
|
||||
public byte[] AssetData = new byte[0];
|
||||
public int Transferred = 0;
|
||||
public bool Success = false;
|
||||
public AssetType AssetType = AssetType.Unknown;
|
||||
|
||||
internal System.Timers.Timer TransferTimer = new System.Timers.Timer(Settings.TRANSFER_TIMEOUT);
|
||||
|
||||
public Transfer()
|
||||
{
|
||||
TransferTimer.AutoReset = false;
|
||||
TransferTimer.Elapsed += new System.Timers.ElapsedEventHandler(TransferTimer_Elapsed);
|
||||
}
|
||||
|
||||
private void TransferTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (OnTimeout != null)
|
||||
{
|
||||
try { OnTimeout(this); }
|
||||
catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,12 +226,15 @@ namespace libsecondlife
|
||||
public uint PacketNum = 0;
|
||||
}
|
||||
|
||||
#endregion Transfer Classes
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class AssetManager
|
||||
{
|
||||
#region Delegates
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -226,6 +256,10 @@ namespace libsecondlife
|
||||
/// <param name="upload"></param>
|
||||
public delegate void UploadProgressCallback(AssetUpload upload);
|
||||
|
||||
#endregion Delegates
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -243,6 +277,8 @@ namespace libsecondlife
|
||||
/// </summary>
|
||||
public event UploadProgressCallback OnUploadProgress;
|
||||
|
||||
#endregion Events
|
||||
|
||||
private SecondLife Client;
|
||||
private Dictionary<LLUUID, Transfer> Transfers = new Dictionary<LLUUID, Transfer>();
|
||||
|
||||
@@ -441,6 +477,8 @@ namespace libsecondlife
|
||||
upload.AssetID = assetID;
|
||||
upload.Size = data.Length;
|
||||
upload.XferID = 0;
|
||||
upload.TransferTimer.Interval = 10 * 1000; // 10 second timeout for no upload packet confirmation
|
||||
upload.OnTimeout += new Transfer.Timeout(Transfer_OnTimeout);
|
||||
|
||||
// Build and send the upload packet
|
||||
AssetUploadRequestPacket request = new AssetUploadRequestPacket();
|
||||
@@ -498,6 +536,12 @@ namespace libsecondlife
|
||||
Buffer.BlockCopy(Helpers.IntToBytes(upload.Size), 0, send.DataPacket.Data, 0, 4);
|
||||
Buffer.BlockCopy(upload.AssetData, 0, send.DataPacket.Data, 4, 1000);
|
||||
upload.Transferred += 1000;
|
||||
|
||||
lock (Transfers)
|
||||
{
|
||||
Transfers.Remove(upload.AssetID);
|
||||
Transfers[upload.ID] = upload;
|
||||
}
|
||||
}
|
||||
else if ((send.XferID.Packet + 1) * 1000 < upload.Size)
|
||||
{
|
||||
@@ -520,49 +564,86 @@ namespace libsecondlife
|
||||
Client.Network.SendPacket(send);
|
||||
}
|
||||
|
||||
private void Transfer_OnTimeout(Transfer transfer)
|
||||
{
|
||||
if (transfer is AssetUpload)
|
||||
{
|
||||
AssetUpload upload = (AssetUpload)transfer;
|
||||
LLUUID transferID = new LLUUID(upload.XferID);
|
||||
|
||||
if (Transfers.ContainsKey(transferID))
|
||||
{
|
||||
Client.Log(String.Format(
|
||||
"Timed out waiting for an ACK during asset upload {0}, rolling back to packet number {1}",
|
||||
upload.AssetID.ToStringHyphenated(), (upload.PacketNum - 1)), Helpers.LogLevel.Info);
|
||||
|
||||
// Resend the last block of data and reset the timeout timer
|
||||
upload.PacketNum--;
|
||||
upload.Transferred -= 1000;
|
||||
upload.TransferTimer.Start();
|
||||
|
||||
SendNextUploadPacket(upload);
|
||||
}
|
||||
else
|
||||
{
|
||||
Client.Log(String.Format("Upload {0} (Type: {1}, Success: {2}) timed out but is not being tracked",
|
||||
upload.ID.ToStringHyphenated(), upload.AssetType, upload.Success), Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Transfers.ContainsKey(transfer.ID))
|
||||
{
|
||||
// TODO: Implement something here when timeouts for downloads are turned on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TransferInfoHandler(Packet packet, Simulator simulator)
|
||||
{
|
||||
if (OnAssetReceived != null)
|
||||
{
|
||||
TransferInfoPacket info = (TransferInfoPacket)packet;
|
||||
Transfer transfer;
|
||||
AssetDownload download;
|
||||
|
||||
if (Transfers.ContainsKey(info.TransferInfo.TransferID))
|
||||
if (Transfers.TryGetValue(info.TransferInfo.TransferID, out transfer))
|
||||
{
|
||||
AssetDownload transfer = (AssetDownload)Transfers[info.TransferInfo.TransferID];
|
||||
download = (AssetDownload)transfer;
|
||||
|
||||
transfer.Channel = (ChannelType)info.TransferInfo.ChannelType;
|
||||
transfer.Status = (StatusCode)info.TransferInfo.Status;
|
||||
transfer.Target = (TargetType)info.TransferInfo.TargetType;
|
||||
transfer.Size = info.TransferInfo.Size;
|
||||
download.Channel = (ChannelType)info.TransferInfo.ChannelType;
|
||||
download.Status = (StatusCode)info.TransferInfo.Status;
|
||||
download.Target = (TargetType)info.TransferInfo.TargetType;
|
||||
download.Size = info.TransferInfo.Size;
|
||||
|
||||
// TODO: Once we support mid-transfer status checking and aborting this
|
||||
// will need to become smarter
|
||||
if (transfer.Status != StatusCode.OK)
|
||||
if (download.Status != StatusCode.OK)
|
||||
{
|
||||
Client.Log("Transfer failed with status code " + transfer.Status, Helpers.LogLevel.Warning);
|
||||
Client.Log("Transfer failed with status code " + download.Status, Helpers.LogLevel.Warning);
|
||||
|
||||
lock (Transfers) Transfers.Remove(transfer.ID);
|
||||
lock (Transfers) Transfers.Remove(download.ID);
|
||||
|
||||
// No data could have been received before the TransferInfo packet
|
||||
transfer.AssetData = null;
|
||||
download.AssetData = null;
|
||||
|
||||
// Fire the event with our transfer that contains Success = false;
|
||||
try { OnAssetReceived(transfer, null); }
|
||||
try { OnAssetReceived(download, null); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
else
|
||||
{
|
||||
transfer.AssetData = new byte[transfer.Size];
|
||||
download.AssetData = new byte[download.Size];
|
||||
|
||||
if (transfer.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
|
||||
if (download.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
|
||||
{
|
||||
transfer.AssetID = new LLUUID(info.TransferInfo.Params, 0);
|
||||
transfer.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[16];
|
||||
download.AssetID = new LLUUID(info.TransferInfo.Params, 0);
|
||||
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[16];
|
||||
|
||||
//Client.DebugLog(String.Format("TransferInfo packet received. AssetID: {0} Type: {1}",
|
||||
// transfer.AssetID, type));
|
||||
}
|
||||
else if (transfer.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
|
||||
else if (download.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
|
||||
{
|
||||
// TODO: Can we use these?
|
||||
LLUUID agentID = new LLUUID(info.TransferInfo.Params, 0);
|
||||
@@ -570,8 +651,8 @@ namespace libsecondlife
|
||||
LLUUID ownerID = new LLUUID(info.TransferInfo.Params, 32);
|
||||
LLUUID taskID = new LLUUID(info.TransferInfo.Params, 48);
|
||||
LLUUID itemID = new LLUUID(info.TransferInfo.Params, 64);
|
||||
transfer.AssetID = new LLUUID(info.TransferInfo.Params, 80);
|
||||
transfer.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[96];
|
||||
download.AssetID = new LLUUID(info.TransferInfo.Params, 80);
|
||||
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[96];
|
||||
|
||||
//Client.DebugLog(String.Format("TransferInfo packet received. AgentID: {0} SessionID: {1} " +
|
||||
// "OwnerID: {2} TaskID: {3} ItemID: {4} AssetID: {5} Type: {6}", agentID, sessionID,
|
||||
@@ -579,7 +660,7 @@ namespace libsecondlife
|
||||
}
|
||||
else
|
||||
{
|
||||
Client.Log("Received a TransferInfo packet with a SourceType of " + transfer.Source.ToString() +
|
||||
Client.Log("Received a TransferInfo packet with a SourceType of " + download.Source.ToString() +
|
||||
" and a Params field length of " + info.TransferInfo.Params.Length,
|
||||
Helpers.LogLevel.Warning);
|
||||
}
|
||||
@@ -596,36 +677,42 @@ namespace libsecondlife
|
||||
private void TransferPacketHandler(Packet packet, Simulator simulator)
|
||||
{
|
||||
TransferPacketPacket asset = (TransferPacketPacket)packet;
|
||||
Transfer transfer;
|
||||
AssetDownload download;
|
||||
|
||||
if (Transfers.ContainsKey(asset.TransferData.TransferID))
|
||||
if (Transfers.TryGetValue(asset.TransferData.TransferID, out transfer))
|
||||
{
|
||||
AssetDownload transfer = (AssetDownload)Transfers[asset.TransferData.TransferID];
|
||||
download = (AssetDownload)transfer;
|
||||
|
||||
if (transfer.Size == 0)
|
||||
// Reset the transfer timer
|
||||
download.TransferTimer.Stop();
|
||||
download.TransferTimer.Start();
|
||||
|
||||
if (download.Size == 0)
|
||||
{
|
||||
Client.DebugLog("TransferPacket received ahead of the transfer header, blocking...");
|
||||
|
||||
// We haven't received the header yet, block until it's received or times out
|
||||
transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
|
||||
download.HeaderReceivedEvent.WaitOne(1000 * 20, false);
|
||||
|
||||
if (transfer.Size == 0)
|
||||
if (download.Size == 0)
|
||||
{
|
||||
Client.Log("Timed out while waiting for the asset header to download for " +
|
||||
transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
|
||||
download.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
|
||||
|
||||
// Abort the transfer
|
||||
TransferAbortPacket abort = new TransferAbortPacket();
|
||||
abort.TransferInfo.ChannelType = (int)transfer.Channel;
|
||||
abort.TransferInfo.TransferID = transfer.ID;
|
||||
Client.Network.SendPacket(abort, transfer.Simulator);
|
||||
abort.TransferInfo.ChannelType = (int)download.Channel;
|
||||
abort.TransferInfo.TransferID = download.ID;
|
||||
Client.Network.SendPacket(abort, download.Simulator);
|
||||
|
||||
transfer.Success = false;
|
||||
lock (Transfers) Transfers.Remove(transfer.ID);
|
||||
download.Success = false;
|
||||
lock (Transfers) Transfers.Remove(download.ID);
|
||||
|
||||
// Fire the event with our transfer that contains Success = false
|
||||
if (OnAssetReceived != null)
|
||||
{
|
||||
try { OnAssetReceived(transfer, null); }
|
||||
try { OnAssetReceived(download, null); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
|
||||
@@ -635,25 +722,25 @@ namespace libsecondlife
|
||||
|
||||
// This assumes that every transfer packet except the last one is exactly 1000 bytes,
|
||||
// hopefully that is a safe assumption to make
|
||||
Buffer.BlockCopy(asset.TransferData.Data, 0, transfer.AssetData, 1000 * asset.TransferData.Packet,
|
||||
Buffer.BlockCopy(asset.TransferData.Data, 0, download.AssetData, 1000 * asset.TransferData.Packet,
|
||||
asset.TransferData.Data.Length);
|
||||
transfer.Transferred += asset.TransferData.Data.Length;
|
||||
download.Transferred += asset.TransferData.Data.Length;
|
||||
|
||||
//Client.DebugLog(String.Format("Transfer packet {0}, received {1}/{2}/{3} bytes for asset {4}",
|
||||
// asset.TransferData.Packet, asset.TransferData.Data.Length, transfer.Transferred, transfer.Size,
|
||||
// transfer.AssetID.ToStringHyphenated()));
|
||||
|
||||
// Check if we downloaded the full asset
|
||||
if (transfer.Transferred >= transfer.Size)
|
||||
if (download.Transferred >= download.Size)
|
||||
{
|
||||
Client.DebugLog("Transfer for asset " + transfer.AssetID.ToStringHyphenated() + " completed");
|
||||
Client.DebugLog("Transfer for asset " + download.AssetID.ToStringHyphenated() + " completed");
|
||||
|
||||
transfer.Success = true;
|
||||
lock (Transfers) Transfers.Remove(transfer.ID);
|
||||
download.Success = true;
|
||||
lock (Transfers) Transfers.Remove(download.ID);
|
||||
|
||||
if (OnAssetReceived != null)
|
||||
{
|
||||
try { OnAssetReceived(transfer, WrapAsset(transfer)); }
|
||||
try { OnAssetReceived(download, WrapAsset(download)); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
}
|
||||
@@ -750,26 +837,28 @@ namespace libsecondlife
|
||||
// Building a new UUID every time an ACK is received for an upload is a horrible
|
||||
// thing, but this whole Xfer system is horrible
|
||||
LLUUID transferID = new LLUUID(confirm.XferID.ID);
|
||||
Transfer transfer;
|
||||
AssetUpload upload = null;
|
||||
|
||||
lock (Transfers)
|
||||
if (Transfers.TryGetValue(transferID, out transfer))
|
||||
{
|
||||
if (Transfers.ContainsKey(transferID))
|
||||
upload = (AssetUpload)transfer;
|
||||
|
||||
//Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
|
||||
// upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
|
||||
|
||||
// Reset the transfer timer
|
||||
upload.TransferTimer.Stop();
|
||||
upload.TransferTimer.Start();
|
||||
|
||||
if (OnUploadProgress != null)
|
||||
{
|
||||
upload = (AssetUpload)Transfers[transferID];
|
||||
|
||||
//Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
|
||||
// upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
|
||||
|
||||
if (OnUploadProgress != null)
|
||||
{
|
||||
try { OnUploadProgress(upload); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
|
||||
if (upload.Transferred < upload.Size)
|
||||
SendNextUploadPacket((AssetUpload)Transfers[transferID]);
|
||||
try { OnUploadProgress(upload); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
|
||||
if (upload.Transferred < upload.Size)
|
||||
SendNextUploadPacket(upload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,8 +868,6 @@ namespace libsecondlife
|
||||
|
||||
if (OnAssetUploaded != null)
|
||||
{
|
||||
//Client.DebugLog(complete.ToString());
|
||||
|
||||
bool found = false;
|
||||
KeyValuePair<LLUUID, Transfer> foundTransfer = new KeyValuePair<LLUUID, Transfer>();
|
||||
|
||||
@@ -795,6 +882,9 @@ namespace libsecondlife
|
||||
|
||||
if ((upload).AssetID == complete.AssetBlock.UUID)
|
||||
{
|
||||
// Stop the resend timer for this transfer
|
||||
upload.TransferTimer.Stop();
|
||||
|
||||
found = true;
|
||||
foundTransfer = transfer;
|
||||
upload.Success = complete.AssetBlock.Success;
|
||||
|
||||
@@ -154,9 +154,9 @@ namespace libsecondlife
|
||||
|
||||
Simulator.Client.DebugLog("Making initial capabilities connection for " + Simulator.ToString());
|
||||
|
||||
_SeedRequest = new CapsRequest(_SeedCapsURI);
|
||||
_SeedRequest = new CapsRequest(_SeedCapsURI, String.Empty, null);
|
||||
_SeedRequest.OnCapsResponse += new CapsRequest.CapsResponseCallback(seedRequest_OnCapsResponse);
|
||||
_SeedRequest.MakeRequest(postData);
|
||||
_SeedRequest.MakeRequest(postData, "application/xml", Simulator.udpPort, null);
|
||||
}
|
||||
|
||||
private void seedRequest_OnCapsResponse(object response, HttpRequestState state)
|
||||
|
||||
@@ -91,13 +91,15 @@ namespace libsecondlife
|
||||
// POST request
|
||||
_RequestState.WebRequest.Method = "POST";
|
||||
_RequestState.WebRequest.ContentLength = postData.Length;
|
||||
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", Simulator.udpPort.ToString());
|
||||
_RequestState.WebRequest.ContentType = "application/xml";
|
||||
_RequestState.RequestData = postData;
|
||||
|
||||
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(
|
||||
new AsyncCallback(EventRequestStreamCallback), _RequestState);
|
||||
}
|
||||
|
||||
public new void MakeRequest(byte[] postData)
|
||||
public new void MakeRequest(byte[] postData, string contentType, int udpListeningPort, object state)
|
||||
{
|
||||
// Create a new HttpWebRequest
|
||||
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL);
|
||||
@@ -127,6 +129,8 @@ namespace libsecondlife
|
||||
// POST request
|
||||
_RequestState.WebRequest.Method = "POST";
|
||||
_RequestState.WebRequest.ContentLength = postData.Length;
|
||||
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", Simulator.udpPort.ToString());
|
||||
_RequestState.WebRequest.ContentType = "application/xml";
|
||||
_RequestState.RequestData = postData;
|
||||
|
||||
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(
|
||||
@@ -278,7 +282,7 @@ namespace libsecondlife
|
||||
|
||||
byte[] postData = LLSD.LLSDSerialize(request);
|
||||
|
||||
MakeRequest(postData);
|
||||
MakeRequest(postData, "application/xml", Simulator.udpPort, null);
|
||||
|
||||
// If the event queue is dead at this point, turn it off since
|
||||
// that was the last thing we want to do
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace libsecondlife
|
||||
|
||||
public new void MakeRequest()
|
||||
{
|
||||
base.MakeRequest(new byte[0]);
|
||||
base.MakeRequest(new byte[0], null, Simulator.udpPort, null);
|
||||
}
|
||||
|
||||
protected override void Log(string message, Helpers.LogLevel level)
|
||||
@@ -89,7 +89,7 @@ namespace libsecondlife
|
||||
else if (exception != null && exception.Message.Contains("502"))
|
||||
{
|
||||
// These are normal, retry the request automatically
|
||||
MakeRequest(state.RequestData);
|
||||
MakeRequest(state.RequestData, "application/xml", Simulator.udpPort, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,17 +44,14 @@ namespace libsecondlife
|
||||
public HttpWebRequest WebRequest;
|
||||
public HttpWebResponse WebResponse;
|
||||
public Stream ResponseStream;
|
||||
public object State;
|
||||
|
||||
internal int ResponseDataPos = 0;
|
||||
|
||||
public HttpRequestState(HttpWebRequest webRequest)
|
||||
{
|
||||
WebRequest = webRequest;
|
||||
|
||||
BufferRead = new byte[BUFFER_SIZE];
|
||||
RequestData = null;
|
||||
ResponseData = null;
|
||||
ResponseStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +82,15 @@ namespace libsecondlife
|
||||
|
||||
public void MakeRequest()
|
||||
{
|
||||
MakeRequest(null);
|
||||
MakeRequest(null, null, 0, null);
|
||||
}
|
||||
|
||||
public void MakeRequest(byte[] postData)
|
||||
public void MakeRequest(byte[] postData, string contentType, int udpListeningPort, object state)
|
||||
{
|
||||
// Create a new HttpWebRequest
|
||||
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL);
|
||||
_RequestState = new HttpRequestState(httpRequest);
|
||||
_RequestState.State = state;
|
||||
|
||||
if (_ProxyURL != String.Empty)
|
||||
{
|
||||
@@ -118,6 +116,12 @@ namespace libsecondlife
|
||||
// POST request
|
||||
_RequestState.WebRequest.Method = "POST";
|
||||
_RequestState.WebRequest.ContentLength = postData.Length;
|
||||
if (udpListeningPort > 0)
|
||||
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", udpListeningPort.ToString());
|
||||
if (String.IsNullOrEmpty(contentType))
|
||||
_RequestState.WebRequest.ContentType = "application/xml";
|
||||
else
|
||||
_RequestState.WebRequest.ContentType = contentType;
|
||||
_RequestState.RequestData = postData;
|
||||
|
||||
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(
|
||||
|
||||
@@ -29,6 +29,15 @@ using System.Collections.Generic;
|
||||
|
||||
namespace libsecondlife
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception class to identify inventory exceptions
|
||||
/// </summary>
|
||||
public class InventoryException : Exception
|
||||
{
|
||||
public InventoryException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for maintaining inventory structure. Inventory constructs nodes
|
||||
/// and manages node children as is necessary to maintain a coherant hirarchy.
|
||||
@@ -45,20 +54,16 @@ namespace libsecondlife
|
||||
/// <param name="oldObject">The state of the InventoryObject before the update occured.</param>
|
||||
/// <param name="newObject">The state of the InventoryObject after the update occured.</param>
|
||||
public delegate void InventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject);
|
||||
|
||||
/// <summary>
|
||||
/// Called when an InventoryObject's state is changed.
|
||||
/// </summary>
|
||||
public event InventoryObjectUpdated OnInventoryObjectUpdated;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to use for the OnInventoryObjectRemoved event.
|
||||
/// </summary>
|
||||
/// <param name="obj">The InventoryObject that was removed.</param>
|
||||
public delegate void InventoryObjectRemoved(InventoryBase obj);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when an InventoryObject's state is changed.
|
||||
/// </summary>
|
||||
public event InventoryObjectUpdated OnInventoryObjectUpdated;
|
||||
/// <summary>
|
||||
/// Called when an item or folder is removed from inventory.
|
||||
/// </summary>
|
||||
@@ -75,42 +80,7 @@ namespace libsecondlife
|
||||
|
||||
private SecondLife Client;
|
||||
private InventoryManager Manager;
|
||||
private Dictionary<LLUUID, InventoryNode> Items;
|
||||
|
||||
/// <summary>
|
||||
/// By using the bracket operator on this class, the program can get the
|
||||
/// InventoryObject designated by the specified uuid. If the value for the corresponding
|
||||
/// UUID is null, the call is equivelant to a call to <code>RemoveNodeFor(this[uuid])</code>.
|
||||
/// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
|
||||
/// the uuid parameter is ignored.
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
|
||||
/// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
|
||||
public InventoryBase this[LLUUID uuid]
|
||||
{
|
||||
get
|
||||
{
|
||||
InventoryNode node = Items[uuid];
|
||||
return node.Data;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
// what if value.UUID != uuid? :-O
|
||||
// should we check for this?
|
||||
UpdateNodeFor(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
InventoryNode node;
|
||||
if (Items.TryGetValue(uuid, out node))
|
||||
{
|
||||
RemoveNodeFor(node.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private Dictionary<LLUUID, InventoryNode> Items = new Dictionary<LLUUID, InventoryNode>();
|
||||
|
||||
public Inventory(SecondLife client, InventoryManager manager, InventoryFolder rootFolder)
|
||||
{
|
||||
@@ -118,22 +88,20 @@ namespace libsecondlife
|
||||
Manager = manager;
|
||||
RootFolder = rootFolder;
|
||||
RootNode = new InventoryNode(rootFolder);
|
||||
Items = new Dictionary<LLUUID, InventoryNode>();
|
||||
Items[rootFolder.UUID] = RootNode;
|
||||
}
|
||||
|
||||
|
||||
public List<InventoryBase> GetContents(InventoryFolder folder)
|
||||
{
|
||||
return GetContents(folder.UUID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the contents of the specified folder.
|
||||
/// Returns the contents of the specified folder
|
||||
/// </summary>
|
||||
/// <param name="folder">A folder's UUID.</param>
|
||||
/// <returns>The contents of the folder corresponding to <code>folder</code>.</returns>
|
||||
/// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory.</exception>
|
||||
/// <param name="folder">A folder's UUID</param>
|
||||
/// <returns>The contents of the folder corresponding to <code>folder</code></returns>
|
||||
/// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory</exception>
|
||||
public List<InventoryBase> GetContents(LLUUID folder)
|
||||
{
|
||||
InventoryNode folderNode;
|
||||
@@ -150,17 +118,16 @@ namespace libsecondlife
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the InventoryNode and inventory data structure that
|
||||
/// is responsible for the InventoryObject. If the item was previously not added to inventory,
|
||||
/// it adds the item, and updates structure accordingly. If it was, it updates the
|
||||
/// InventoryNode, changing the parent node if <code>item.parentUUID</code> does
|
||||
/// not match <code>node.Parent.Data.UUID</code>.
|
||||
/// not match <code>node.Parent.Data.UUID</code>.
|
||||
///
|
||||
/// You can not set the inventory root folder using this method.
|
||||
/// You can not set the inventory root folder using this method
|
||||
/// </summary>
|
||||
/// <param name="item">The InventoryObject to store.</param>
|
||||
/// <param name="item">The InventoryObject to store</param>
|
||||
public void UpdateNodeFor(InventoryBase item)
|
||||
{
|
||||
lock (Items)
|
||||
@@ -268,28 +235,68 @@ namespace libsecondlife
|
||||
return Contains(obj.UUID);
|
||||
}
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <summary>
|
||||
/// By using the bracket operator on this class, the program can get the
|
||||
/// InventoryObject designated by the specified uuid. If the value for the corresponding
|
||||
/// UUID is null, the call is equivelant to a call to <code>RemoveNodeFor(this[uuid])</code>.
|
||||
/// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
|
||||
/// the uuid parameter is ignored.
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
|
||||
/// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
|
||||
public InventoryBase this[LLUUID uuid]
|
||||
{
|
||||
get
|
||||
{
|
||||
InventoryNode node = Items[uuid];
|
||||
return node.Data;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
// Log a warning if there is a UUID mismatch, this will cause problems
|
||||
if (value.UUID != uuid)
|
||||
Client.Log("Inventory[uuid]: uuid " + uuid.ToStringHyphenated() + " is not equal to value.UUID " +
|
||||
value.UUID.ToStringHyphenated(), Helpers.LogLevel.Warning);
|
||||
|
||||
UpdateNodeFor(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
InventoryNode node;
|
||||
if (Items.TryGetValue(uuid, out node))
|
||||
{
|
||||
RemoveNodeFor(node.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Operators
|
||||
|
||||
#region Event Firing
|
||||
|
||||
protected void FireOnInventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject)
|
||||
{
|
||||
if (OnInventoryObjectUpdated != null)
|
||||
OnInventoryObjectUpdated(oldObject, newObject);
|
||||
{
|
||||
try { OnInventoryObjectUpdated(oldObject, newObject); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
}
|
||||
|
||||
protected void FireOnInventoryObjectRemoved(InventoryBase obj)
|
||||
{
|
||||
if (OnInventoryObjectRemoved != null)
|
||||
OnInventoryObjectRemoved(obj);
|
||||
{
|
||||
try { OnInventoryObjectRemoved(obj); }
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A rudimentary Exception subclass, so exceptions thrown by the Inventory class
|
||||
/// can be easily identified and caught.
|
||||
/// </summary>
|
||||
public class InventoryException : Exception
|
||||
{
|
||||
public InventoryException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -183,12 +183,19 @@ namespace libsecondlife
|
||||
SimIP = IPAddress.Parse(reply.sim_ip);
|
||||
SeedCapability = reply.seed_capability;
|
||||
|
||||
BuddyList = new FriendsManager.FriendInfo[reply.buddy_list.Length];
|
||||
for (int i = 0; i < BuddyList.Length; ++i)
|
||||
if (reply.buddy_list != null)
|
||||
{
|
||||
BuddyListEntry buddy = reply.buddy_list[i];
|
||||
BuddyList[i] = new FriendsManager.FriendInfo(buddy.buddy_id, (FriendsManager.RightsFlags)buddy.buddy_rights_given,
|
||||
(FriendsManager.RightsFlags)buddy.buddy_rights_has);
|
||||
BuddyList = new FriendsManager.FriendInfo[reply.buddy_list.Length];
|
||||
for (int i = 0; i < BuddyList.Length; ++i)
|
||||
{
|
||||
BuddyListEntry buddy = reply.buddy_list[i];
|
||||
BuddyList[i] = new FriendsManager.FriendInfo(buddy.buddy_id, (FriendsManager.RightsFlags)buddy.buddy_rights_given,
|
||||
(FriendsManager.RightsFlags)buddy.buddy_rights_has);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BuddyList = new FriendsManager.FriendInfo[0];
|
||||
}
|
||||
|
||||
InventoryRoot = LLUUID.Parse(reply.inventory_root[0].folder_id);
|
||||
|
||||
@@ -102,7 +102,9 @@ namespace libsecondlife
|
||||
/// <remarks>The connection to the new simulator won't be established
|
||||
/// until this callback returns</remarks>
|
||||
/// <param name="simulator">The simulator that is being connected to</param>
|
||||
public delegate void SimConnectingCallback(Simulator simulator);
|
||||
/// <returns>Whether to continue connecting to the simulator or abort
|
||||
/// the connection</returns>
|
||||
public delegate bool SimConnectingCallback(Simulator simulator);
|
||||
/// <summary>
|
||||
/// Triggered when a new connection to a simulator is established
|
||||
/// </summary>
|
||||
@@ -370,11 +372,19 @@ namespace libsecondlife
|
||||
// Fire the OnSimConnecting event
|
||||
if (OnSimConnecting != null)
|
||||
{
|
||||
try { OnSimConnecting(simulator); }
|
||||
try
|
||||
{
|
||||
if (!OnSimConnecting(simulator))
|
||||
{
|
||||
// Callback is requesting that we abort this connection
|
||||
lock (Simulators) Simulators.Remove(simulator);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
||||
}
|
||||
|
||||
// We're not connected to this simulator, attempt to establish a connection
|
||||
// Attempt to establish a connection to the simulator
|
||||
if (simulator.Connect(setDefault))
|
||||
{
|
||||
// Start a timer that checks if we've been disconnected
|
||||
@@ -391,11 +401,14 @@ namespace libsecondlife
|
||||
|
||||
// If enabled, send an AgentThrottle packet to the server to increase our bandwidth
|
||||
if (Client.Settings.SEND_AGENT_THROTTLE) Client.Throttle.Set(simulator);
|
||||
|
||||
return simulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connection failed, so remove this simulator from our list and destroy it
|
||||
// Connection failed, remove this simulator from our list and destroy it
|
||||
lock (Simulators) Simulators.Remove(simulator);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (setDefault)
|
||||
@@ -409,9 +422,15 @@ namespace libsecondlife
|
||||
// Send an initial AgentUpdate to complete our movement in to the sim
|
||||
if (Client.Settings.SEND_AGENT_UPDATES)
|
||||
Client.Self.Status.SendUpdate(true, simulator);
|
||||
}
|
||||
|
||||
return simulator;
|
||||
return simulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already connected to this simulator and wasn't asked to set it as the default,
|
||||
// just return a reference to the existing object
|
||||
return simulator;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace libsecondlife
|
||||
protected abstract void PacketSent(UDPPacketBuffer buffer, int bytesSent);
|
||||
|
||||
// the port to listen on
|
||||
protected int udpPort;
|
||||
internal int udpPort;
|
||||
|
||||
// the UDP socket
|
||||
private Socket udpSocket;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace libsecondlife.TestClient
|
||||
|
||||
Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
|
||||
Groups.OnGroupMembers += new GroupManager.GroupMembersCallback(GroupMembersHandler);
|
||||
Inventory.OnInventoryObjectReceived += new InventoryManager.InventoryObjectReceived(Inventory_OnInventoryObjectReceived);
|
||||
Inventory.OnInventoryObjectReceived += new InventoryManager.ObjectReceivedCallback(Inventory_OnInventoryObjectReceived);
|
||||
|
||||
Network.RegisterCallback(PacketType.AvatarAppearance, new NetworkManager.PacketCallback(AvatarAppearanceHandler));
|
||||
Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(AlertMessageHandler));
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace libsecondlife
|
||||
|
||||
private void GatherCaps()
|
||||
{
|
||||
CapsRequest request = new CapsRequest(RegistrationApiCaps.AbsoluteUri);
|
||||
CapsRequest request = new CapsRequest(RegistrationApiCaps.AbsoluteUri, String.Empty, null);
|
||||
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherCapsResponse);
|
||||
|
||||
// build post data
|
||||
@@ -112,7 +112,7 @@ namespace libsecondlife
|
||||
_userInfo.Password));
|
||||
|
||||
// send
|
||||
request.MakeRequest(postData);
|
||||
request.MakeRequest(postData, "application/xml", 0, null);
|
||||
}
|
||||
|
||||
private void GatherCapsResponse(object response, HttpRequestState state)
|
||||
@@ -141,7 +141,7 @@ namespace libsecondlife
|
||||
if (_caps.GetErrorCodes == null)
|
||||
throw new InvalidOperationException("access denied"); // this should work even for not-approved users
|
||||
|
||||
CapsRequest request = new CapsRequest(_caps.GetErrorCodes.AbsoluteUri);
|
||||
CapsRequest request = new CapsRequest(_caps.GetErrorCodes.AbsoluteUri, String.Empty, null);
|
||||
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherErrorMessagesResponse);
|
||||
request.MakeRequest();
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace libsecondlife
|
||||
if (_caps.GetLastNames == null)
|
||||
throw new InvalidOperationException("access denied: only approved developers have access to the registration api");
|
||||
|
||||
CapsRequest request = new CapsRequest(_caps.GetLastNames.AbsoluteUri);
|
||||
CapsRequest request = new CapsRequest(_caps.GetLastNames.AbsoluteUri, String.Empty, null);
|
||||
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherLastNamesResponse);
|
||||
request.MakeRequest();
|
||||
|
||||
@@ -220,9 +220,9 @@ namespace libsecondlife
|
||||
query.Add("last_name_id", lastName.ID);
|
||||
byte[] postData = LLSD.LLSDSerialize(query);
|
||||
|
||||
CapsRequest request = new CapsRequest(_caps.CheckName.AbsoluteUri);
|
||||
CapsRequest request = new CapsRequest(_caps.CheckName.AbsoluteUri, String.Empty, null);
|
||||
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CheckNameResponse);
|
||||
request.MakeRequest(postData);
|
||||
request.MakeRequest(postData, "application/xml", 0, null);
|
||||
|
||||
// FIXME:
|
||||
return false;
|
||||
@@ -286,9 +286,9 @@ namespace libsecondlife
|
||||
byte[] postData = LLSD.LLSDSerialize(query);
|
||||
|
||||
// Make the request
|
||||
CapsRequest request = new CapsRequest(_caps.CreateUser.AbsoluteUri);
|
||||
CapsRequest request = new CapsRequest(_caps.CreateUser.AbsoluteUri, String.Empty, null);
|
||||
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateUserResponse);
|
||||
request.MakeRequest(postData);
|
||||
request.MakeRequest(postData, "application/xml", 0, null);
|
||||
|
||||
// FIXME: Block
|
||||
return LLUUID.Zero;
|
||||
|
||||
Reference in New Issue
Block a user