diff --git a/libsecondlife/AssetManager.cs b/libsecondlife/AssetManager.cs
index 33c59b6b..8c2d550c 100644
--- a/libsecondlife/AssetManager.cs
+++ b/libsecondlife/AssetManager.cs
@@ -6,6 +6,8 @@ using libsecondlife.Packets;
namespace libsecondlife
{
+ #region Enums
+
///
/// The different types of assets in Second Life
///
@@ -145,17 +147,42 @@ namespace libsecondlife
Baked = 1
}
+ #endregion Enums
+
+ #region Transfer Classes
+
///
///
///
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); }
+ }
+ }
}
///
@@ -199,12 +226,15 @@ namespace libsecondlife
public uint PacketNum = 0;
}
+ #endregion Transfer Classes
///
///
///
public class AssetManager
{
+ #region Delegates
+
///
///
///
@@ -226,6 +256,10 @@ namespace libsecondlife
///
public delegate void UploadProgressCallback(AssetUpload upload);
+ #endregion Delegates
+
+ #region Events
+
///
///
///
@@ -243,6 +277,8 @@ namespace libsecondlife
///
public event UploadProgressCallback OnUploadProgress;
+ #endregion Events
+
private SecondLife Client;
private Dictionary Transfers = new Dictionary();
@@ -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 foundTransfer = new KeyValuePair();
@@ -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;
diff --git a/libsecondlife/Capabilities.cs b/libsecondlife/Capabilities.cs
index 26575afc..356dab47 100644
--- a/libsecondlife/Capabilities.cs
+++ b/libsecondlife/Capabilities.cs
@@ -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)
diff --git a/libsecondlife/CapsEventQueue.cs b/libsecondlife/CapsEventQueue.cs
index 9ac098a5..8f45e6e7 100644
--- a/libsecondlife/CapsEventQueue.cs
+++ b/libsecondlife/CapsEventQueue.cs
@@ -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
diff --git a/libsecondlife/CapsRequest.cs b/libsecondlife/CapsRequest.cs
index 3a8e2b55..8bf543f0 100644
--- a/libsecondlife/CapsRequest.cs
+++ b/libsecondlife/CapsRequest.cs
@@ -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;
}
diff --git a/libsecondlife/HttpBase.cs b/libsecondlife/HttpBase.cs
index 7104df45..bc2162c6 100644
--- a/libsecondlife/HttpBase.cs
+++ b/libsecondlife/HttpBase.cs
@@ -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(
diff --git a/libsecondlife/Inventory.cs b/libsecondlife/Inventory.cs
index 8342c8a7..b71fef20 100644
--- a/libsecondlife/Inventory.cs
+++ b/libsecondlife/Inventory.cs
@@ -29,6 +29,15 @@ using System.Collections.Generic;
namespace libsecondlife
{
+ ///
+ /// Exception class to identify inventory exceptions
+ ///
+ public class InventoryException : Exception
+ {
+ public InventoryException(string message)
+ : base(message) { }
+ }
+
///
/// 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
/// The state of the InventoryObject before the update occured.
/// The state of the InventoryObject after the update occured.
public delegate void InventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject);
-
- ///
- /// Called when an InventoryObject's state is changed.
- ///
- public event InventoryObjectUpdated OnInventoryObjectUpdated;
-
-
///
/// Delegate to use for the OnInventoryObjectRemoved event.
///
/// The InventoryObject that was removed.
public delegate void InventoryObjectRemoved(InventoryBase obj);
-
+ ///
+ /// Called when an InventoryObject's state is changed.
+ ///
+ public event InventoryObjectUpdated OnInventoryObjectUpdated;
///
/// Called when an item or folder is removed from inventory.
///
@@ -75,42 +80,7 @@ namespace libsecondlife
private SecondLife Client;
private InventoryManager Manager;
- private Dictionary Items;
-
- ///
- /// 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 RemoveNodeFor(this[uuid]).
- /// If the value is non-null, it is equivelant to a call to UpdateNodeFor(value),
- /// the uuid parameter is ignored.
- ///
- /// The UUID of the InventoryObject to get or set, ignored if set to non-null value.
- /// The InventoryObject corresponding to uuid.
- 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 Items = new Dictionary();
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();
Items[rootFolder.UUID] = RootNode;
}
-
public List GetContents(InventoryFolder folder)
{
return GetContents(folder.UUID);
}
///
- /// Returns the contents of the specified folder.
+ /// Returns the contents of the specified folder
///
- /// A folder's UUID.
- /// The contents of the folder corresponding to folder.
- /// When folder does not exist in the inventory.
+ /// A folder's UUID
+ /// The contents of the folder corresponding to folder
+ /// When folder does not exist in the inventory
public List GetContents(LLUUID folder)
{
InventoryNode folderNode;
@@ -150,17 +118,16 @@ namespace libsecondlife
}
}
-
///
/// 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 item.parentUUID does
- /// not match node.Parent.Data.UUID.
+ /// not match node.Parent.Data.UUID.
///
- /// You can not set the inventory root folder using this method.
+ /// You can not set the inventory root folder using this method
///
- /// The InventoryObject to store.
+ /// The InventoryObject to store
public void UpdateNodeFor(InventoryBase item)
{
lock (Items)
@@ -268,28 +235,68 @@ namespace libsecondlife
return Contains(obj.UUID);
}
+ #region Operators
+
+ ///
+ /// 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 RemoveNodeFor(this[uuid]).
+ /// If the value is non-null, it is equivelant to a call to UpdateNodeFor(value),
+ /// the uuid parameter is ignored.
+ ///
+ /// The UUID of the InventoryObject to get or set, ignored if set to non-null value.
+ /// The InventoryObject corresponding to uuid.
+ 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
-
- }
-
- ///
- /// A rudimentary Exception subclass, so exceptions thrown by the Inventory class
- /// can be easily identified and caught.
- ///
- public class InventoryException : Exception
- {
- public InventoryException(string message)
- : base(message) { }
}
}
diff --git a/libsecondlife/InventoryManager.cs b/libsecondlife/InventoryManager.cs
index 301610b5..106872b4 100644
--- a/libsecondlife/InventoryManager.cs
+++ b/libsecondlife/InventoryManager.cs
@@ -25,14 +25,18 @@
*/
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Text;
using libsecondlife;
using libsecondlife.Packets;
namespace libsecondlife
{
+ #region Enums
+
public enum InventoryType : sbyte
{
Unknown = -1,
@@ -74,6 +78,10 @@ namespace libsecondlife
SystemFoldersToTop = 4
}
+ #endregion Enums
+
+ #region Inventory Object Classes
+
public abstract class InventoryBase
{
public readonly LLUUID UUID;
@@ -227,86 +235,273 @@ namespace libsecondlife
}
}
+ #endregion Inventory Object Classes
+
public class InventoryManager
{
- public delegate void InventoryFolderUpdated(LLUUID folderID);
- public delegate bool InventoryObjectReceived(LLUUID fromAgentID, string fromAgentName, uint parentEstateID,
+ ///
+ /// Callback for inventory item creation finishing
+ ///
+ /// Whether the request to create an inventory
+ /// item succeeded or not
+ /// Inventory item being created. If success is
+ /// false this will be null
+ public delegate void ItemCreatedCallback(bool success, InventoryItem item);
+ ///
+ /// Callback for an inventory folder updating
+ ///
+ /// UUID of the folder that was updated
+ public delegate void FolderUpdatedCallback(LLUUID folderID);
+ ///
+ /// Callback when an inventory object is received from another avatar
+ /// or a primitive
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// True to accept the inventory offer, false to reject it
+ public delegate bool ObjectReceivedCallback(LLUUID fromAgentID, string fromAgentName, uint parentEstateID,
LLUUID regionID, LLVector3 position, DateTime timestamp, AssetType type, LLUUID objectID, bool fromTask);
- public event InventoryFolderUpdated OnInventoryFolderUpdated;
- public event InventoryObjectReceived OnInventoryObjectReceived;
+ public event FolderUpdatedCallback OnInventoryFolderUpdated;
+ public event ObjectReceivedCallback OnInventoryObjectReceived;
- private SecondLife Client;
- public Inventory Store
- {
- get { return store; }
- }
-
- private Inventory store;
+ private SecondLife _Client;
+ private Inventory _Store;
+ private Dictionary> _FolderRequests = new Dictionary>();
+ private Dictionary _ItemCreatedCallbacks = new Dictionary();
+ private uint _ItemCreatedCallbackPos = 0;
/// Partial mapping of AssetTypes to folder names
- private string[] NewFolderNames = new string[]
- {
- "Textures",
- "Sounds",
- "Calling Cards",
- "Landmarks",
- "Scripts",
- "Clothing",
- "Objects",
- "Notecards",
- "New Folder",
- "Inventory",
- "Scripts",
- "Scripts",
- "Uncompressed Images",
- "Body Parts",
- "Trash",
- "Photo Album",
- "Lost And Found",
- "Uncompressed Sounds",
- "Uncompressed Images",
- "Uncompressed Images",
- "Animations",
- "Gestures"
- };
+ private static readonly string[] _NewFolderNames = new string[]
+ {
+ "Textures",
+ "Sounds",
+ "Calling Cards",
+ "Landmarks",
+ "Scripts",
+ "Clothing",
+ "Objects",
+ "Notecards",
+ "New Folder",
+ "Inventory",
+ "Scripts",
+ "Scripts",
+ "Uncompressed Images",
+ "Body Parts",
+ "Trash",
+ "Photo Album",
+ "Lost And Found",
+ "Uncompressed Sounds",
+ "Uncompressed Images",
+ "Uncompressed Images",
+ "Animations",
+ "Gestures"
+ };
+
+ private static readonly string[] _AssetTypeNames = new string[]
+ {
+ "texture",
+ "sound",
+ "callcard",
+ "landmark",
+ "script",
+ "clothing",
+ "object",
+ "notecard",
+ "category",
+ "root",
+ "lsltext",
+ "lslbyte",
+ "txtr_tga",
+ "bodypart",
+ "trash",
+ "snapshot",
+ "lstndfnd",
+ "snd_wav",
+ "img_tga",
+ "jpeg",
+ "animatn",
+ "gesture",
+ "simstate"
+ };
+
+ private static readonly string[] _InventoryTypeNames = new string[]
+ {
+ "texture",
+ "sound",
+ "callcard",
+ "landmark",
+ String.Empty,
+ String.Empty,
+ "object",
+ "notecard",
+ "category",
+ "root",
+ "script",
+ String.Empty,
+ String.Empty,
+ String.Empty,
+ String.Empty,
+ "snapshot",
+ String.Empty,
+ "attach",
+ "wearable",
+ "animation",
+ "gesture",
+ };
+
+ #region Properties
+
+ public Inventory Store { get { return _Store; } }
+
+ #endregion Properties
public InventoryManager(SecondLife client)
{
- Client = client;
+ _Client = client;
- Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler));
- //Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler));
- Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler));
- //Client.Network.RegisterCallback(PacketType.MoveInventoryItem, new NetworkManager.PacketCallback(MoveInventoryItemHandler));
- //Client.Network.RegisterCallback(PacketType.MoveInventoryFolder, new NetworkManager.PacketCallback(MoveInventoryFolderHandler));
- Client.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler));
- Client.Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler));
+ _Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler));
+ _Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler));
+ _Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler));
+ _Client.Network.RegisterCallback(PacketType.MoveInventoryItem, new NetworkManager.PacketCallback(MoveInventoryItemHandler));
+ _Client.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler));
+ _Client.Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler));
// Watch for inventory given to us through instant message
- Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
+ _Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
}
- private InventoryItem CreateInventoryItem(InventoryType type, LLUUID id)
+ #region File & Folder Public Methods
+
+ ///
+ /// If you have a list of inventory item IDs (from a cached inventory, perhaps)
+ /// you can use this function to request an update from the server for those items.
+ ///
+ /// A list of LLUUIDs of the items to request.
+ public void FetchInventory(List itemIDs)
{
- switch (type)
+ FetchInventoryPacket fetch = new FetchInventoryPacket();
+ fetch.AgentData = new FetchInventoryPacket.AgentDataBlock();
+ fetch.AgentData.AgentID = _Client.Network.AgentID;
+ fetch.AgentData.SessionID = _Client.Network.SessionID;
+
+ fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count];
+ // TODO: Make sure the packet doesnt overflow.
+ for (int i = 0; i < itemIDs.Count; ++i)
{
- case InventoryType.Texture: return new InventoryTexture(id);
- case InventoryType.Sound: return new InventorySound(id);
- case InventoryType.CallingCard: return new InventoryCallingCard(id);
- case InventoryType.Landmark: return new InventoryLandmark(id);
- case InventoryType.Object: return new InventoryObject(id);
- case InventoryType.Notecard: return new InventoryNotecard(id);
- case InventoryType.Category: return new InventoryCategory(id);
- case InventoryType.LSL: return new InventoryLSL(id);
- case InventoryType.Snapshot: return new InventorySnapshot(id);
- case InventoryType.Attachment: return new InventoryAttachment(id);
- case InventoryType.Wearable: return new InventoryWearable(id);
- case InventoryType.Animation: return new InventoryAnimation(id);
- case InventoryType.Gesture: return new InventoryGesture(id);
- default: return new InventoryItem(type,id);
+ fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock();
+ fetch.InventoryData[i].ItemID = itemIDs[i];
+ fetch.InventoryData[i].OwnerID = _Client.Network.AgentID;
+ }
+
+ _Client.Network.SendPacket(fetch);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void FetchInventory(LLUUID itemID)
+ {
+ List list = new List(1);
+ list.Add(itemID);
+ FetchInventory(list);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void Remove(InventoryBase obj)
+ {
+ List temp = new List(1);
+ temp.Add(obj);
+ Remove(temp);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void Remove(List objects)
+ {
+ List items = new List(objects.Count);
+ List folders = new List(objects.Count);
+ foreach (InventoryBase obj in objects)
+ {
+ if (obj is InventoryFolder)
+ {
+ folders.Add(obj.UUID);
+ }
+ else
+ {
+ items.Add(obj.UUID);
+ }
+ }
+ Remove(items, folders);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Remove(List items, List folders)
+ {
+ if ((items == null && items.Count == 0) && (folders == null && folders.Count == 0))
+ return;
+
+ RemoveInventoryObjectsPacket rem = new RemoveInventoryObjectsPacket();
+ rem.AgentData.AgentID = _Client.Network.AgentID;
+ rem.AgentData.SessionID = _Client.Network.SessionID;
+
+ if (items == null || items.Count == 0)
+ {
+ // To indicate that we want no items removed:
+ rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[1];
+ rem.ItemData[0] = new RemoveInventoryObjectsPacket.ItemDataBlock();
+ rem.ItemData[0].ItemID = LLUUID.Zero;
+ }
+ else
+ {
+ rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[items.Count];
+ for (int i = 0; i < items.Count; ++i)
+ {
+ rem.ItemData[i] = new RemoveInventoryObjectsPacket.ItemDataBlock();
+ rem.ItemData[i].ItemID = items[i];
+ // Update local copy
+ _Store.RemoveNodeFor(_Store[items[i]]);
+ }
+ }
+
+ if (folders == null || folders.Count == 0)
+ {
+ // To indicate we want no folders removed:
+ rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[1];
+ rem.FolderData[0] = new RemoveInventoryObjectsPacket.FolderDataBlock();
+ rem.FolderData[0].FolderID = LLUUID.Zero;
+ }
+ else
+ {
+ rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[folders.Count];
+ for (int i = 0; i < folders.Count; ++i)
+ {
+ rem.FolderData[i] = new RemoveInventoryObjectsPacket.FolderDataBlock();
+ rem.FolderData[i].FolderID = folders[i];
+ // Update local copy:
+ _Store.RemoveNodeFor(_Store[folders[i]]);
+ }
}
}
+ #endregion File & Folder Public Methods
+
#region Searching
private Dictionary FindDescendantsMap = new Dictionary();
@@ -342,7 +537,7 @@ namespace libsecondlife
{
lock (FindDescendantsMap)
{
- IAsyncResult descendReq = BeginRequestFolderContents(baseFolder, Client.Network.AgentID, true, true, recurse && !firstOnly, InventorySortOrder.ByName, new AsyncCallback(SearchDescendantsCallback), baseFolder);
+ IAsyncResult descendReq = BeginRequestFolderContents(baseFolder, _Client.Network.AgentID, true, true, recurse && !firstOnly, InventorySortOrder.ByName, new AsyncCallback(SearchDescendantsCallback), baseFolder);
FindDescendantsMap.Add(descendReq, result);
}
}
@@ -382,7 +577,7 @@ namespace libsecondlife
return;
}
Interlocked.Decrement(ref find.FoldersWaiting);
- List folderContents = Store.GetContents(updatedFolder);
+ List folderContents = _Store.GetContents(updatedFolder);
foreach (InventoryBase obj in folderContents)
{
if (find.Regex.IsMatch(obj.Name))
@@ -401,7 +596,7 @@ namespace libsecondlife
{
IAsyncResult descendReq = BeginRequestFolderContents(
obj.UUID,
- Client.Network.AgentID,
+ _Client.Network.AgentID,
true,
true,
true,
@@ -425,7 +620,7 @@ namespace libsecondlife
List objects = new List();
List folders = new List();
- List contents = Store.GetContents(baseFolder);
+ List contents = _Store.GetContents(baseFolder);
foreach (InventoryBase inv in contents)
{
if (regexp.IsMatch(inv.Name))
@@ -486,7 +681,7 @@ namespace libsecondlife
result.FoldersWaiting = 1;
BeginRequestFolderContents(
baseFolder,
- Client.Network.AgentID,
+ _Client.Network.AgentID,
true,
true,
false,
@@ -510,7 +705,7 @@ namespace libsecondlife
FindObjectsByPathState state = (FindObjectsByPathState)result.AsyncState;
Interlocked.Decrement(ref state.Result.FoldersWaiting);
- List folderContents = Store.GetContents(state.Folder);
+ List folderContents = _Store.GetContents(state.Folder);
foreach (InventoryBase obj in folderContents)
{
@@ -531,7 +726,7 @@ namespace libsecondlife
Interlocked.Increment(ref state.Result.FoldersWaiting);
BeginRequestFolderContents(
obj.UUID,
- Client.Network.AgentID,
+ _Client.Network.AgentID,
true,
true,
false,
@@ -550,7 +745,7 @@ namespace libsecondlife
{
List objects = new List();
List folders = new List();
- List contents = Store.GetContents(baseFolder);
+ List contents = _Store.GetContents(baseFolder);
foreach (InventoryBase inv in contents)
{
@@ -573,8 +768,6 @@ namespace libsecondlife
#region Folder Actions
- private Dictionary> folderRequests = new Dictionary>();
-
public void RequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items, bool recurse,
InventorySortOrder order)
{
@@ -592,84 +785,6 @@ namespace libsecondlife
return InternalFolderContentsRequest(folder, owner, result);
}
- private void EndRequestFolderContents(IAsyncResult result)
- {
- result.AsyncWaitHandle.WaitOne();
- }
-
- private DescendantsResult InternalFolderContentsRequest(LLUUID folder, LLUUID owner, DescendantsResult parameters)
- {
- lock (folderRequests)
- {
- List requestsForFolder;
- if (!folderRequests.TryGetValue(folder, out requestsForFolder))
- {
- requestsForFolder = new List();
- folderRequests.Add(folder, requestsForFolder);
- }
- lock (requestsForFolder)
- requestsForFolder.Add(parameters);
- }
-
- FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket();
- fetch.AgentData.AgentID = Client.Network.AgentID;
- fetch.AgentData.SessionID = Client.Network.SessionID;
-
- fetch.InventoryData.FetchFolders = parameters.Folders;
- fetch.InventoryData.FetchItems = parameters.Items;
- fetch.InventoryData.FolderID = folder;
- fetch.InventoryData.OwnerID = owner;
- fetch.InventoryData.SortOrder = (int)parameters.SortOrder;
-
- Client.Network.SendPacket(fetch);
- return parameters;
- }
-
- public void HandleDescendantsRetrieved(LLUUID uuid)
- {
- List satisfiedResults = null;
- lock (folderRequests)
- {
- if (folderRequests.TryGetValue(uuid, out satisfiedResults))
- folderRequests.Remove(uuid);
- }
- if (satisfiedResults == null)
- return;
- lock (satisfiedResults)
- {
- List contents = Store.GetContents(uuid);
- foreach (DescendantsResult result in satisfiedResults)
- {
- if (result.Recurse)
- {
- bool done = true;
-
- foreach (InventoryBase obj in contents)
- {
- if (obj is InventoryFolder)
- {
- done = false;
- DescendantsResult child = new DescendantsResult(null);
- child.Folders = result.Folders;
- child.Items = result.Items;
- child.Recurse = result.Recurse;
- child.SortOrder = result.SortOrder;
- child.Parent = result;
- result.AddChild(child);
- InternalFolderContentsRequest(obj.UUID, obj.OwnerID, child);
- }
- }
- if (done)
- result.IsCompleted = true;
- }
- else
- {
- result.IsCompleted = true;
- }
- }
- }
- }
-
///
/// Returns the UUID of the folder (category) that defaults to
/// containing 'type'. The folder is not necessarily only for that
@@ -682,20 +797,20 @@ namespace libsecondlife
/// LLUUID.Zero on failure
public LLUUID FindFolderForType(AssetType type)
{
- if (Store == null)
+ if (_Store == null)
{
- Client.Log("Inventory is null, FindFolderForType() lookup cannot continue",
+ _Client.Log("Inventory is null, FindFolderForType() lookup cannot continue",
Helpers.LogLevel.Error);
return LLUUID.Zero;
}
// Folders go in the root
if (type == AssetType.Folder)
- return Store.RootFolder.UUID;
+ return _Store.RootFolder.UUID;
// Loop through each top-level directory and check if PreferredType
// matches the requested type
- List contents = Store.GetContents(Store.RootFolder.UUID);
+ List contents = _Store.GetContents(_Store.RootFolder.UUID);
foreach (InventoryBase inv in contents)
{
if (inv is InventoryFolder)
@@ -708,7 +823,7 @@ namespace libsecondlife
}
// No match found, create one
- return CreateFolder(Store.RootFolder.UUID, type, String.Empty);
+ return CreateFolder(_Store.RootFolder.UUID, type, String.Empty);
}
public LLUUID CreateFolder(LLUUID parentID, AssetType preferredType, string name)
@@ -720,7 +835,7 @@ namespace libsecondlife
{
if (preferredType >= AssetType.Texture && preferredType <= AssetType.Gesture)
{
- name = NewFolderNames[(int)preferredType];
+ name = _NewFolderNames[(int)preferredType];
}
else
{
@@ -735,27 +850,27 @@ namespace libsecondlife
newFolder.ParentUUID = parentID;
newFolder.PreferredType = preferredType;
newFolder.Name = name;
- newFolder.OwnerID = Client.Network.AgentID;
+ newFolder.OwnerID = _Client.Network.AgentID;
try
{
- Store[newFolder.UUID] = newFolder;
+ _Store[newFolder.UUID] = newFolder;
}
catch (InventoryException ie)
{
- Client.Log(ie.Message, Helpers.LogLevel.Warning);
+ _Client.Log(ie.Message, Helpers.LogLevel.Warning);
}
// Create the create folder packet and send it
CreateInventoryFolderPacket create = new CreateInventoryFolderPacket();
- create.AgentData.AgentID = Client.Network.AgentID;
- create.AgentData.SessionID = Client.Network.SessionID;
+ create.AgentData.AgentID = _Client.Network.AgentID;
+ create.AgentData.SessionID = _Client.Network.SessionID;
create.FolderData.FolderID = id;
create.FolderData.ParentID = parentID;
create.FolderData.Type = (sbyte)preferredType;
create.FolderData.Name = Helpers.StringToField(name);
- Client.Network.SendPacket(create);
+ _Client.Network.SendPacket(create);
return id;
}
@@ -763,17 +878,17 @@ namespace libsecondlife
public void RemoveDescendants(LLUUID folder)
{
PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket();
- purge.AgentData.AgentID = Client.Network.AgentID;
- purge.AgentData.SessionID = Client.Network.SessionID;
+ purge.AgentData.AgentID = _Client.Network.AgentID;
+ purge.AgentData.SessionID = _Client.Network.SessionID;
purge.InventoryData.FolderID = folder;
- Client.Network.SendPacket(purge);
+ _Client.Network.SendPacket(purge);
// Update our local copy:
- if (Store.Contains(folder))
+ if (_Store.Contains(folder))
{
- List contents = Store.GetContents(folder);
+ List contents = _Store.GetContents(folder);
foreach (InventoryBase obj in contents) {
- Store.RemoveNodeFor(obj);
+ _Store.RemoveNodeFor(obj);
}
}
}
@@ -784,129 +899,153 @@ namespace libsecondlife
folders.Add(folder);
Remove(null, folders);
}
+
#endregion Folder Actions
#region Item Actions
+
+ public void BeginCreateItem(LLUUID parentFolder, string name, string description, AssetType type, InventoryType invType,
+ PermissionMask nextOwnerMask, ItemCreatedCallback callback)
+ {
+ // Even though WearableType 0 is Shape, in this context it is treated as NOT_WEARABLE
+ BeginCreateItem(parentFolder, name, description, type, invType, (WearableType)0, nextOwnerMask, callback);
+ }
+
+ public void BeginCreateItem(LLUUID parentFolder, string name, string description, AssetType type, InventoryType invType,
+ WearableType wearableType, PermissionMask nextOwnerMask, ItemCreatedCallback callback)
+ {
+ CreateInventoryItemPacket create = new CreateInventoryItemPacket();
+ create.AgentData.AgentID = _Client.Network.AgentID;
+ create.AgentData.SessionID = _Client.Network.SessionID;
+
+ create.InventoryBlock.CallbackID = RegisterInventoryCallback(callback);
+ create.InventoryBlock.FolderID = parentFolder;
+ create.InventoryBlock.TransactionID = LLUUID.Random();
+ create.InventoryBlock.NextOwnerMask = (uint)nextOwnerMask;
+ create.InventoryBlock.Type = (sbyte)type;
+ create.InventoryBlock.InvType = (sbyte)invType;
+ create.InventoryBlock.WearableType = (byte)wearableType;
+ create.InventoryBlock.Name = Helpers.StringToField(name);
+ create.InventoryBlock.Description = Helpers.StringToField(description);
+
+ _Client.Network.SendPacket(create);
+ }
+
+ public void BeginCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType,
+ InventoryType invType, LLUUID folderID, ItemCreatedCallback callback)
+ {
+ string url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory");
+
+ if (url != String.Empty)
+ {
+ Hashtable query = new Hashtable();
+ query.Add("folder_id", folderID);
+ query.Add("asset_type", AssetTypeToString(assetType));
+ query.Add("inventory_type", InventoryTypeToString(invType));
+ query.Add("name", name);
+ query.Add("description", description);
+
+ byte[] postData = LLSD.LLSDSerialize(query);
+
+ // Make the request
+ CapsRequest request = new CapsRequest(url, _Client.Network.CurrentSim);
+ request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateItemFromAssetResponse);
+ request.MakeRequest(postData, "application/xml", _Client.Network.CurrentSim.udpPort,
+ new KeyValuePair(callback, data));
+ }
+ else
+ {
+ throw new Exception("NewFileAgentInventory capability is not currently available");
+ }
+ }
+
+ private void CreateItemFromAssetResponse(object response, HttpRequestState state)
+ {
+ Hashtable contents = (Hashtable)response;
+ KeyValuePair kvp = (KeyValuePair)state.State;
+ ItemCreatedCallback callback = kvp.Key;
+ byte[] itemData = (byte[])kvp.Value;
+
+ string status = (string)contents["state"];
+
+ if (status == "upload")
+ {
+ string uploadURL = (string)contents["uploader"];
+
+ // This makes the assumption that all uploads go to CurrentSim, to avoid
+ // the problem of HttpRequestState not knowing anything about simulators
+ CapsRequest upload = new CapsRequest(uploadURL, _Client.Network.CurrentSim);
+ upload.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateItemFromAssetResponse);
+ upload.MakeRequest(itemData, "application/octet-stream", _Client.Network.CurrentSim.udpPort, kvp);
+ }
+ else if (status == "complete")
+ {
+ //FIXME: Callback successfully
+ callback(true, null);
+ }
+ else
+ {
+ // Failure
+ try { callback(false, null); }
+ catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+
+ public void CopyItem(LLUUID currentOwner, LLUUID itemID, LLUUID parentID, string newName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void CopyItemFromNotecard(LLUUID objectID, LLUUID notecardID, LLUUID folderID, LLUUID itemID)
+ {
+ CopyInventoryFromNotecardPacket copy = new CopyInventoryFromNotecardPacket();
+ copy.AgentData.AgentID = _Client.Network.AgentID;
+ copy.AgentData.SessionID = _Client.Network.SessionID;
+
+ copy.NotecardData.ObjectID = objectID;
+ copy.NotecardData.NotecardItemID = notecardID;
+
+ copy.InventoryData = new CopyInventoryFromNotecardPacket.InventoryDataBlock[1];
+ copy.InventoryData[0] = new CopyInventoryFromNotecardPacket.InventoryDataBlock();
+ copy.InventoryData[0].FolderID = folderID;
+ copy.InventoryData[0].ItemID = itemID;
+
+ _Client.Network.SendPacket(copy);
+ }
+
+ public void MoveItem(LLUUID itemID, LLUUID parentID, string newName)
+ {
+ throw new NotImplementedException();
+ }
+
public void RemoveItem(LLUUID item)
{
List items = new List(1);
items.Add(item);
Remove(items, null);
}
- #endregion
- internal void InitializeRootNode(LLUUID rootFolderID)
+ public void GiveItem(LLUUID itemID, string itemName, AssetType assetType, LLUUID recipient, bool doEffect)
{
- InventoryFolder rootFolder = new InventoryFolder(rootFolderID);
- rootFolder.Name = String.Empty;
- rootFolder.ParentUUID = LLUUID.Zero;
+ byte[] bucket = new byte[17];
+ bucket[0] = (byte)assetType;
+ Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16);
- store = new Inventory(Client, this, rootFolder);
- }
+ _Client.Self.InstantMessage(
+ _Client.Self.Name,
+ recipient,
+ itemName,
+ LLUUID.Random(),
+ InstantMessageDialog.InventoryOffered,
+ InstantMessageOnline.Online,
+ _Client.Self.Position,
+ _Client.Network.CurrentSim.ID,
+ bucket);
- ///
- /// If you have a list of inventory item IDs (from a cached inventory, perhaps)
- /// you can use this function to request an update from the server for those items.
- ///
- /// A list of LLUUIDs of the items to request.
- public void FetchInventory(List itemIDs)
- {
- FetchInventoryPacket fetch = new FetchInventoryPacket();
- fetch.AgentData = new FetchInventoryPacket.AgentDataBlock();
- fetch.AgentData.AgentID = Client.Network.AgentID;
- fetch.AgentData.SessionID = Client.Network.SessionID;
-
- fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count];
- // TODO: Make sure the packet doesnt overflow.
- for (int i = 0; i < itemIDs.Count; ++i)
+ if (doEffect)
{
- fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock();
- fetch.InventoryData[i].ItemID = itemIDs[i];
- fetch.InventoryData[i].OwnerID = Client.Network.AgentID;
- }
-
- Client.Network.SendPacket(fetch);
- }
-
- public void FetchInventory(LLUUID itemID)
- {
- List list = new List(1);
- list.Add(itemID);
- FetchInventory(list);
- }
-
- public void Remove(InventoryBase obj)
- {
- List temp = new List(1);
- temp.Add(obj);
- Remove(temp);
- }
-
- public void Remove(List objects)
- {
- List items = new List(objects.Count);
- List folders = new List(objects.Count);
- foreach (InventoryBase obj in objects)
- {
- if (obj is InventoryFolder)
- {
- folders.Add(obj.UUID);
- }
- else
- {
- items.Add(obj.UUID);
- }
- }
- Remove(items, folders);
- }
-
- public void Remove(List items, List folders)
- {
- if ((items == null && items.Count == 0) && (folders == null && folders.Count == 0))
- return;
-
- RemoveInventoryObjectsPacket rem = new RemoveInventoryObjectsPacket();
- rem.AgentData.AgentID = Client.Network.AgentID;
- rem.AgentData.SessionID = Client.Network.SessionID;
-
- if (items == null || items.Count == 0)
- {
- // To indicate that we want no items removed:
- rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[1];
- rem.ItemData[0] = new RemoveInventoryObjectsPacket.ItemDataBlock();
- rem.ItemData[0].ItemID = LLUUID.Zero;
- }
- else
- {
- rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[items.Count];
- for (int i = 0; i < items.Count; ++i)
- {
- rem.ItemData[i] = new RemoveInventoryObjectsPacket.ItemDataBlock();
- rem.ItemData[i].ItemID = items[i];
- // Update local copy:
- if (Store.Contains(items[i]))
- Store.RemoveNodeFor(Store[items[i]]);
- }
- }
-
- if (folders == null || folders.Count == 0)
- {
- // To indicate we want no folders removed:
- rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[1];
- rem.FolderData[0] = new RemoveInventoryObjectsPacket.FolderDataBlock();
- rem.FolderData[0].FolderID = LLUUID.Zero;
- }
- else
- {
- rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[folders.Count];
- for (int i = 0; i < folders.Count; ++i)
- {
- rem.FolderData[i] = new RemoveInventoryObjectsPacket.FolderDataBlock();
- rem.FolderData[i].FolderID = folders[i];
- // Update local copy:
- if (Store.Contains(folders[i]))
- Store.RemoveNodeFor(Store[folders[i]]);
- }
+ _Client.Self.BeamEffect(_Client.Network.AgentID, recipient, LLVector3d.Zero,
+ _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, LLUUID.Random());
}
}
@@ -919,7 +1058,7 @@ namespace libsecondlife
/// InventoryObject object containing item details
public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item)
{
- return RezFromInventory(simulator, rotation, position, item, Client.Self.ActiveGroup, LLUUID.Random());
+ return RezFromInventory(simulator, rotation, position, item, _Client.Self.ActiveGroup, LLUUID.Random());
}
///
@@ -930,7 +1069,7 @@ namespace libsecondlife
/// Vector of where to place object
/// InventoryObject object containing item details
/// LLUUID of group to own the object
- public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
+ public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
LLUUID groupOwner)
{
return RezFromInventory(simulator, rotation, position, item, groupOwner, LLUUID.Random());
@@ -945,13 +1084,13 @@ namespace libsecondlife
/// InventoryObject object containing item details
/// LLUUID of group to own the object.
/// User defined queryID to correlate replies.
- public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
+ public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
LLUUID groupOwner, LLUUID queryID)
{
RezObjectPacket add = new RezObjectPacket();
- add.AgentData.AgentID = Client.Network.AgentID;
- add.AgentData.SessionID = Client.Network.SessionID;
+ add.AgentData.AgentID = _Client.Network.AgentID;
+ add.AgentData.SessionID = _Client.Network.SessionID;
add.AgentData.GroupID = groupOwner;
add.RezData.FromTaskID = LLUUID.Zero;
@@ -988,32 +1127,189 @@ namespace libsecondlife
add.InventoryData.Description = Helpers.StringToField(item.Description);
add.InventoryData.CreationDate = (int)Helpers.DateTimeToUnixTime(item.CreationDate);
- Client.Network.SendPacket(add, simulator);
+ _Client.Network.SendPacket(add, simulator);
return queryID;
}
+ #endregion
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public string AssetTypeToString(AssetType type)
+ {
+ return _AssetTypeNames[(int)type];
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public string InventoryTypeToString(InventoryType type)
+ {
+ return _InventoryTypeNames[(int)type];
+ }
+
+ internal void InitializeRootNode(LLUUID rootFolderID)
+ {
+ InventoryFolder rootFolder = new InventoryFolder(rootFolderID);
+ rootFolder.Name = String.Empty;
+ rootFolder.ParentUUID = LLUUID.Zero;
+
+ _Store = new Inventory(_Client, this, rootFolder);
+ }
+
+ #region Private Helper Functions
+
+ private uint RegisterInventoryCallback(ItemCreatedCallback callback)
+ {
+ if (_ItemCreatedCallbackPos == UInt32.MaxValue)
+ _ItemCreatedCallbackPos = 0;
+
+ _ItemCreatedCallbackPos++;
+
+ if (_ItemCreatedCallbacks.ContainsKey(_ItemCreatedCallbackPos))
+ _Client.Log("Overwriting an existing ItemCreatedCallback", Helpers.LogLevel.Warning);
+
+ _ItemCreatedCallbacks[_ItemCreatedCallbackPos] = callback;
+
+ return _ItemCreatedCallbackPos;
+ }
+
+ private InventoryItem CreateInventoryItem(InventoryType type, LLUUID id)
+ {
+ switch (type)
+ {
+ case InventoryType.Texture: return new InventoryTexture(id);
+ case InventoryType.Sound: return new InventorySound(id);
+ case InventoryType.CallingCard: return new InventoryCallingCard(id);
+ case InventoryType.Landmark: return new InventoryLandmark(id);
+ case InventoryType.Object: return new InventoryObject(id);
+ case InventoryType.Notecard: return new InventoryNotecard(id);
+ case InventoryType.Category: return new InventoryCategory(id);
+ case InventoryType.LSL: return new InventoryLSL(id);
+ case InventoryType.Snapshot: return new InventorySnapshot(id);
+ case InventoryType.Attachment: return new InventoryAttachment(id);
+ case InventoryType.Wearable: return new InventoryWearable(id);
+ case InventoryType.Animation: return new InventoryAnimation(id);
+ case InventoryType.Gesture: return new InventoryGesture(id);
+ default: return new InventoryItem(type, id);
+ }
+ }
+
+ private void EndRequestFolderContents(IAsyncResult result)
+ {
+ result.AsyncWaitHandle.WaitOne();
+ }
+
+ private DescendantsResult InternalFolderContentsRequest(LLUUID folder, LLUUID owner, DescendantsResult parameters)
+ {
+ lock (_FolderRequests)
+ {
+ List requestsForFolder;
+ if (!_FolderRequests.TryGetValue(folder, out requestsForFolder))
+ {
+ requestsForFolder = new List();
+ _FolderRequests.Add(folder, requestsForFolder);
+ }
+ lock (requestsForFolder)
+ requestsForFolder.Add(parameters);
+ }
+
+ FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket();
+ fetch.AgentData.AgentID = _Client.Network.AgentID;
+ fetch.AgentData.SessionID = _Client.Network.SessionID;
+
+ fetch.InventoryData.FetchFolders = parameters.Folders;
+ fetch.InventoryData.FetchItems = parameters.Items;
+ fetch.InventoryData.FolderID = folder;
+ fetch.InventoryData.OwnerID = owner;
+ fetch.InventoryData.SortOrder = (int)parameters.SortOrder;
+
+ _Client.Network.SendPacket(fetch);
+ return parameters;
+ }
+
+ private void HandleDescendantsRetrieved(LLUUID uuid)
+ {
+ List satisfiedResults = null;
+ lock (_FolderRequests)
+ {
+ if (_FolderRequests.TryGetValue(uuid, out satisfiedResults))
+ _FolderRequests.Remove(uuid);
+ }
+ if (satisfiedResults == null)
+ return;
+ lock (satisfiedResults)
+ {
+ List contents = _Store.GetContents(uuid);
+ foreach (DescendantsResult result in satisfiedResults)
+ {
+ if (result.Recurse)
+ {
+ bool done = true;
+
+ foreach (InventoryBase obj in contents)
+ {
+ if (obj is InventoryFolder)
+ {
+ done = false;
+ DescendantsResult child = new DescendantsResult(null);
+ child.Folders = result.Folders;
+ child.Items = result.Items;
+ child.Recurse = result.Recurse;
+ child.SortOrder = result.SortOrder;
+ child.Parent = result;
+ result.AddChild(child);
+ InternalFolderContentsRequest(obj.UUID, obj.OwnerID, child);
+ }
+ }
+ if (done)
+ result.IsCompleted = true;
+ }
+ else
+ {
+ result.IsCompleted = true;
+ }
+ }
+ }
+ }
+
+ #endregion Private Helper Functions
+
#region Callbacks
+ private void SaveAssetIntoInventoryHandler(Packet packet, Simulator simulator)
+ {
+ SaveAssetIntoInventoryPacket save = (SaveAssetIntoInventoryPacket)packet;
+
+ // FIXME: Find this item in the inventory structure and mark the parent as needing an update
+ //save.InventoryData.ItemID;
+ }
+
private void InventoryDescendentsHandler(Packet packet, Simulator simulator)
{
InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet;
InventoryFolder parentFolder = null;
- if (Store.Contains(reply.AgentData.FolderID) &&
- Store[reply.AgentData.FolderID] is InventoryFolder)
+ if (_Store.Contains(reply.AgentData.FolderID) &&
+ _Store[reply.AgentData.FolderID] is InventoryFolder)
{
- parentFolder = Store[reply.AgentData.FolderID] as InventoryFolder;
+ parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder;
}
else
{
- Client.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToStringHyphenated() +
+ _Client.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToStringHyphenated() +
" or it is not a folder", Helpers.LogLevel.Error);
return;
}
if (reply.AgentData.Version < parentFolder.Version)
{
- Client.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name +
+ _Client.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name +
", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version,
Helpers.LogLevel.Warning);
return;
@@ -1033,7 +1329,7 @@ namespace libsecondlife
folder.PreferredType = (AssetType)reply.FolderData[i].Type;
folder.OwnerID = reply.AgentData.OwnerID;
- Store[folder.UUID] = folder;
+ _Store[folder.UUID] = folder;
}
}
@@ -1066,7 +1362,7 @@ namespace libsecondlife
item.SaleType = (SaleType)reply.ItemData[i].SaleType;
item.OwnerID = reply.AgentData.OwnerID;
- Store[item.UUID] = item;
+ _Store[item.UUID] = item;
}
}
}
@@ -1078,11 +1374,11 @@ namespace libsecondlife
if (OnInventoryFolderUpdated != null)
{
try { OnInventoryFolderUpdated(parentFolder.UUID); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
// For RequestFolderContents - only call the handler if we've retrieved all the descendants.
- if (folderRequests.ContainsKey(parentFolder.UUID) && parentFolder.DescendentCount == Store.GetContents(parentFolder.UUID).Count)
+ if (_FolderRequests.ContainsKey(parentFolder.UUID) && parentFolder.DescendentCount == _Store.GetContents(parentFolder.UUID).Count)
HandleDescendantsRetrieved(parentFolder.UUID);
}
@@ -1099,7 +1395,7 @@ namespace libsecondlife
foreach (UpdateCreateInventoryItemPacket.InventoryDataBlock dataBlock in reply.InventoryData)
{
if (dataBlock.InvType == (sbyte)InventoryType.Folder) {
- Client.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet.", Helpers.LogLevel.Error);
+ _Client.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet.", Helpers.LogLevel.Error);
continue;
}
@@ -1123,7 +1419,31 @@ namespace libsecondlife
item.SalePrice = dataBlock.SalePrice;
item.SaleType = (SaleType)dataBlock.SaleType;
- Store[item.UUID] = item;
+ _Store[item.UUID] = item;
+
+ ItemCreatedCallback callback;
+ if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback))
+ {
+ _ItemCreatedCallbacks.Remove(dataBlock.CallbackID);
+
+ try { callback(true, item); }
+ catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+ }
+
+ private void MoveInventoryItemHandler(Packet packet, Simulator simulator)
+ {
+ MoveInventoryItemPacket move = (MoveInventoryItemPacket)packet;
+
+ for (int i = 0; i < move.InventoryData.Length; i++)
+ {
+ // FIXME: Do something here
+ string newName = Helpers.FieldToUTF8String(move.InventoryData[i].NewName);
+
+ _Client.DebugLog(String.Format("MoveInventoryItemHandler: Item {0} is moving to Folder {1} with new name \"{2}\"",
+ move.InventoryData[i].ItemID.ToStringHyphenated(), move.InventoryData[i].FolderID.ToStringHyphenated(),
+ newName));
}
}
@@ -1136,14 +1456,14 @@ namespace libsecondlife
foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData)
{
- if (Store.Contains(dataBlock.FolderID))
- Client.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning);
+ if (_Store.Contains(dataBlock.FolderID))
+ _Client.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning);
InventoryFolder folder = new InventoryFolder(dataBlock.FolderID);
folder.Name = Helpers.FieldToUTF8String(dataBlock.Name);
folder.OwnerID = update.AgentData.AgentID;
folder.ParentUUID = dataBlock.ParentID;
- Store[folder.UUID] = folder;
+ _Store[folder.UUID] = folder;
}
}
@@ -1151,12 +1471,15 @@ namespace libsecondlife
{
foreach (BulkUpdateInventoryPacket.ItemDataBlock dataBlock in update.ItemData)
{
- if (!Store.Contains(dataBlock.ItemID))
- Client.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning);
+ if (!_Store.Contains(dataBlock.ItemID))
+ _Client.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning);
+
+ // FIXME: Write a helper function that will either fetch an item out of the store or create it
+ // and use that here instead, to prevent overwriting already fetched AssetIDs on an update
+ InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType, dataBlock.ItemID);
- InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID);
item.AssetType = (AssetType)dataBlock.Type;
- item.AssetUUID = dataBlock.AssetID; // FIXME: Should we set this here? Isnt it always zero?
+ if (dataBlock.AssetID != LLUUID.Zero) item.AssetUUID = dataBlock.AssetID;
item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
item.Description = Helpers.FieldToUTF8String(dataBlock.Description);
item.Flags = dataBlock.Flags;
@@ -1174,7 +1497,16 @@ namespace libsecondlife
item.SalePrice = dataBlock.SalePrice;
item.SaleType = (SaleType)dataBlock.SaleType;
- Store[item.UUID] = item;
+ _Store[item.UUID] = item;
+
+ ItemCreatedCallback callback;
+ if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback))
+ {
+ _ItemCreatedCallbacks.Remove(dataBlock.CallbackID);
+
+ try { callback(true, item); }
+ catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
}
}
}
@@ -1187,7 +1519,7 @@ namespace libsecondlife
{
if (dataBlock.InvType == (sbyte)InventoryType.Folder)
{
- Client.Log("Received FetchInventoryReply for inventory folder!", Helpers.LogLevel.Error);
+ _Client.Log("Received FetchInventoryReply for inventory folder!", Helpers.LogLevel.Error);
continue;
}
@@ -1211,7 +1543,7 @@ namespace libsecondlife
item.SalePrice = dataBlock.SalePrice;
item.SaleType = (SaleType)dataBlock.SaleType;
- Store[item.UUID] = item;
+ _Store[item.UUID] = item;
}
}
private void Self_OnInstantMessage(InstantMessage im, Simulator simulator)
@@ -1236,7 +1568,7 @@ namespace libsecondlife
}
else
{
- Client.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning);
+ _Client.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning);
return;
}
}
@@ -1249,7 +1581,7 @@ namespace libsecondlife
}
else
{
- Client.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning);
+ _Client.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning);
return;
}
}
@@ -1261,18 +1593,18 @@ namespace libsecondlife
try
{
ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket();
- imp.AgentData.AgentID = Client.Network.AgentID;
- imp.AgentData.SessionID = Client.Network.SessionID;
+ imp.AgentData.AgentID = _Client.Network.AgentID;
+ imp.AgentData.SessionID = _Client.Network.SessionID;
imp.MessageBlock.FromGroup = false;
imp.MessageBlock.ToAgentID = im.FromAgentID;
imp.MessageBlock.Offline = 0;
imp.MessageBlock.ID = im.IMSessionID;
imp.MessageBlock.Timestamp = 0;
- imp.MessageBlock.FromAgentName = Helpers.StringToField(Client.Self.Name);
+ imp.MessageBlock.FromAgentName = Helpers.StringToField(_Client.Self.Name);
imp.MessageBlock.Message = new byte[0];
imp.MessageBlock.ParentEstateID = 0;
imp.MessageBlock.RegionID = LLUUID.Zero;
- imp.MessageBlock.Position = Client.Self.Position;
+ imp.MessageBlock.Position = _Client.Self.Position;
if (OnInventoryObjectReceived(im.FromAgentID, im.FromAgentName, im.ParentEstateID, im.RegionID, im.Position,
im.Timestamp, type, objectID, fromTask))
@@ -1312,11 +1644,11 @@ namespace libsecondlife
imp.MessageBlock.BinaryBucket = new byte[0];
}
- Client.Network.SendPacket(imp, simulator);
+ _Client.Network.SendPacket(imp, simulator);
}
catch (Exception e)
{
- Client.Log(e.ToString(), Helpers.LogLevel.Error);
+ _Client.Log(e.ToString(), Helpers.LogLevel.Error);
}
}
}
@@ -1324,61 +1656,31 @@ namespace libsecondlife
#endregion Callbacks
}
-
class FindResult : IAsyncResult
{
public List Result;
-
- public bool Recurse
- {
- get { return recurse; }
- }
-
- public Regex Regex
- {
- get { return regex; }
- }
-
- public string[] Path
- {
- get { return path; }
- }
-
- public AsyncCallback Callback
- {
- get { return callback; }
- }
public int FoldersWaiting;
public bool FirstOnly;
+
private AsyncCallback callback;
private Regex regex;
private string[] path;
private bool recurse;
- private ManualResetEvent waitHandle;
+ private AutoResetEvent waitHandle;
private bool complete;
private bool sync;
private object asyncstate;
- public FindResult(Regex regex, bool recurse, AsyncCallback callback)
- {
- this.waitHandle = new ManualResetEvent(false);
- this.callback = callback;
- this.recurse = recurse;
- this.regex = regex;
- this.Result = new List();
- }
+ #region Properties
- public FindResult(string[] path, AsyncCallback callback)
- {
- this.waitHandle = new ManualResetEvent(false);
- this.callback = callback;
- this.path = path;
- this.Result = new List();
- }
+ public bool Recurse { get { return recurse; } }
+ public Regex Regex { get { return regex; } }
+ public string[] Path { get { return path; } }
+ public AsyncCallback Callback { get { return callback; } }
#region IAsyncResult Members
-
- public object AsyncState
+
+ public object AsyncState
{
get { return asyncstate; }
set { asyncstate = value; }
@@ -1413,6 +1715,25 @@ namespace libsecondlife
}
#endregion
+
+ #endregion Properties
+
+ public FindResult(Regex regex, bool recurse, AsyncCallback callback)
+ {
+ this.waitHandle = new AutoResetEvent(false);
+ this.callback = callback;
+ this.recurse = recurse;
+ this.regex = regex;
+ this.Result = new List();
+ }
+
+ public FindResult(string[] path, AsyncCallback callback)
+ {
+ this.waitHandle = new AutoResetEvent(false);
+ this.callback = callback;
+ this.path = path;
+ this.Result = new List();
+ }
}
class DescendantsResult : IAsyncResult
@@ -1422,36 +1743,14 @@ namespace libsecondlife
public bool Recurse = false;
public InventorySortOrder SortOrder = InventorySortOrder.ByName;
public DescendantsResult Parent;
+
private AsyncCallback _Callback;
private ManualResetEvent _AsyncWaitHandle;
private object _AsyncState;
private bool _IsCompleted;
private List _ChildrenWaiting = new List();
- public DescendantsResult(AsyncCallback callback)
- {
- _Callback = callback;
- _AsyncWaitHandle = new ManualResetEvent(false);
- }
-
- public void AddChild(DescendantsResult child)
- {
- lock (_ChildrenWaiting)
- {
- if (!child.IsCompleted)
- _ChildrenWaiting.Add(child);
- }
- }
-
- public void ChildComplete(DescendantsResult child)
- {
- lock (_ChildrenWaiting)
- {
- _ChildrenWaiting.Remove(child);
- if (_ChildrenWaiting.Count == 0)
- IsCompleted = true;
- }
- }
+ #region Properties
#region IAsyncResult Members
@@ -1497,5 +1796,32 @@ namespace libsecondlife
}
#endregion
+
+ #endregion Properties
+
+ public DescendantsResult(AsyncCallback callback)
+ {
+ _Callback = callback;
+ _AsyncWaitHandle = new ManualResetEvent(false);
+ }
+
+ public void AddChild(DescendantsResult child)
+ {
+ lock (_ChildrenWaiting)
+ {
+ if (!child.IsCompleted)
+ _ChildrenWaiting.Add(child);
+ }
+ }
+
+ public void ChildComplete(DescendantsResult child)
+ {
+ lock (_ChildrenWaiting)
+ {
+ _ChildrenWaiting.Remove(child);
+ if (_ChildrenWaiting.Count == 0)
+ IsCompleted = true;
+ }
+ }
}
}
diff --git a/libsecondlife/Login.cs b/libsecondlife/Login.cs
index d2257ace..7177e600 100644
--- a/libsecondlife/Login.cs
+++ b/libsecondlife/Login.cs
@@ -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);
diff --git a/libsecondlife/NetworkManager.cs b/libsecondlife/NetworkManager.cs
index 43734f68..d44d8b52 100644
--- a/libsecondlife/NetworkManager.cs
+++ b/libsecondlife/NetworkManager.cs
@@ -102,7 +102,9 @@ namespace libsecondlife
/// The connection to the new simulator won't be established
/// until this callback returns
/// The simulator that is being connected to
- public delegate void SimConnectingCallback(Simulator simulator);
+ /// Whether to continue connecting to the simulator or abort
+ /// the connection
+ public delegate bool SimConnectingCallback(Simulator simulator);
///
/// Triggered when a new connection to a simulator is established
///
@@ -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;
+ }
}
///
diff --git a/libsecondlife/UDPBase.cs b/libsecondlife/UDPBase.cs
index 737dfcdd..07f1de69 100644
--- a/libsecondlife/UDPBase.cs
+++ b/libsecondlife/UDPBase.cs
@@ -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;
diff --git a/libsecondlife/examples/TestClient/TestClient.cs b/libsecondlife/examples/TestClient/TestClient.cs
index 123ec0fa..4ff18225 100644
--- a/libsecondlife/examples/TestClient/TestClient.cs
+++ b/libsecondlife/examples/TestClient/TestClient.cs
@@ -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));
diff --git a/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs b/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs
index 0e39e317..5f405210 100644
--- a/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs
+++ b/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs
@@ -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;