From 62dddabd7c1dbb5c4a43afa6cbc24df2fe71dc1a Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Mon, 10 Sep 2007 10:20:30 +0000 Subject: [PATCH] * 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 --- libsecondlife/AssetManager.cs | 200 ++- libsecondlife/Capabilities.cs | 4 +- libsecondlife/CapsEventQueue.cs | 8 +- libsecondlife/CapsRequest.cs | 4 +- libsecondlife/HttpBase.cs | 16 +- libsecondlife/Inventory.cs | 141 ++- libsecondlife/InventoryManager.cs | 1074 +++++++++++------ libsecondlife/Login.cs | 17 +- libsecondlife/NetworkManager.cs | 31 +- libsecondlife/UDPBase.cs | 2 +- .../examples/TestClient/TestClient.cs | 2 +- .../RegistrationApi.cs | 16 +- 12 files changed, 986 insertions(+), 529 deletions(-) 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;