/* * Copyright (c) 2006-2007, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Text; using libsecondlife; using libsecondlife.Packets; namespace libsecondlife { #region Enums public enum InventoryType : sbyte { Unknown = -1, Texture = 0, Sound = 1, CallingCard = 2, Landmark = 3, [Obsolete] Script = 4, [Obsolete] Clothing = 5, Object = 6, Notecard = 7, Category = 8, Folder = 8, RootCategory = 0, LSL = 10, [Obsolete] LSLBytecode = 11, [Obsolete] TextureTGA = 12, [Obsolete] Bodypart = 13, [Obsolete] Trash = 14, Snapshot = 15, [Obsolete] LostAndFound = 16, Attachment = 17, Wearable = 18, Animation = 19, Gesture = 20 } [Flags] public enum InventorySortOrder : int { /// Sort by name ByName = 0, /// Sort by date ByDate = 1, /// Sort folders by name, regardless of whether items are /// sorted by name or date FoldersByName = 2, /// Place system folders at the top SystemFoldersToTop = 4 } #endregion Enums #region Inventory Object Classes public abstract class InventoryBase { public readonly LLUUID UUID; public LLUUID ParentUUID; public string Name; public LLUUID OwnerID; public InventoryBase(LLUUID itemID) { if (itemID == LLUUID.Zero) throw new ArgumentException("Inventory item ID cannot be NULL_KEY (LLUUID.Zero)"); UUID = itemID; } public override int GetHashCode() { return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode(); } public override bool Equals(object o) { InventoryBase inv = o as InventoryBase; return inv != null && Equals(inv); } public virtual bool Equals(InventoryBase o) { return o.UUID == UUID && o.ParentUUID == ParentUUID && o.Name == Name && o.OwnerID == OwnerID; } } public class InventoryItem : InventoryBase { public LLUUID AssetUUID; public Permissions Permissions; public AssetType AssetType; public InventoryType InventoryType; public LLUUID CreatorID; public string Description; public LLUUID GroupID; public bool GroupOwned; public int SalePrice; public SaleType SaleType; public uint Flags; /// Time and date this inventory item was created, stored as /// UTC (Coordinated Universal Time) public DateTime CreationDate; public InventoryItem(LLUUID itemID) : base(itemID) { } public InventoryItem(InventoryType type, LLUUID itemID) : base(itemID) { InventoryType = type; } public override int GetHashCode() { return AssetUUID.GetHashCode() ^ Permissions.GetHashCode() ^ AssetType.GetHashCode() ^ InventoryType.GetHashCode() ^ Description.GetHashCode() ^ GroupID.GetHashCode() ^ GroupOwned.GetHashCode() ^ SalePrice.GetHashCode() ^ SaleType.GetHashCode() ^ Flags.GetHashCode() ^ CreationDate.GetHashCode(); } public override bool Equals(object o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } public override bool Equals(InventoryBase o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } public bool Equals(InventoryItem o) { return base.Equals(o as InventoryBase) && o.AssetType == AssetType && o.AssetUUID == AssetUUID && o.CreationDate == CreationDate && o.Description == Description && o.Flags == Flags && o.GroupID == GroupID && o.GroupOwned == GroupOwned && o.InventoryType == InventoryType && o.Permissions.Equals(Permissions) && o.SalePrice == SalePrice && o.SaleType == SaleType; } } public class InventoryTexture : InventoryItem { public InventoryTexture(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Texture; } } public class InventorySound : InventoryItem { public InventorySound(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Sound; } } public class InventoryCallingCard : InventoryItem { public InventoryCallingCard(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.CallingCard; } } public class InventoryLandmark : InventoryItem { public InventoryLandmark(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Landmark; } } public class InventoryObject : InventoryItem { public InventoryObject(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Object; } } public class InventoryNotecard : InventoryItem { public InventoryNotecard(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Notecard; } } public class InventoryCategory : InventoryItem { public InventoryCategory(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Category; } } public class InventoryLSL : InventoryItem { public InventoryLSL(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.LSL; } } public class InventorySnapshot : InventoryItem { public InventorySnapshot(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Snapshot; } } public class InventoryAttachment : InventoryItem { public InventoryAttachment(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Attachment; } } public class InventoryWearable : InventoryItem { public InventoryWearable(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Wearable; } public WearableType WearableType { get { return (WearableType)Flags; } set { Flags = (uint)value; } } } public class InventoryAnimation : InventoryItem { public InventoryAnimation(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Animation; } } public class InventoryGesture : InventoryItem { public InventoryGesture(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Gesture; } } public class InventoryFolder : InventoryBase { public AssetType PreferredType; public int Version; public int DescendentCount; public InventoryFolder(LLUUID itemID) : base(itemID) { } public override int GetHashCode() { return PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode(); } public override bool Equals(object o) { InventoryFolder folder = o as InventoryFolder; return folder != null && Equals(folder); } public override bool Equals(InventoryBase o) { InventoryFolder folder = o as InventoryFolder; return folder != null && Equals(folder); } public bool Equals(InventoryFolder o) { return base.Equals(o as InventoryBase) && o.DescendentCount == DescendentCount && o.PreferredType == PreferredType && o.Version == Version; } } #endregion Inventory Object Classes public class InventoryManager { /// /// 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 FolderUpdatedCallback OnInventoryFolderUpdated; public event ObjectReceivedCallback OnInventoryObjectReceived; 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 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.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); } #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) { 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) { 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(); /// /// Starts a search for any items whose names match the regex within /// the spacified folder. /// /// Use the AsyncWaitHandle of the returned value to run the search synchronously. /// The UUID of the folder to look in. /// The regex that results match. /// Recurse into and search inside subfolders of baseFolder. /// Re-download the contents of baseFolder (and its subdirectories, if recursing) /// The AsyncCallback to call when the search is complete. /// An object that will be passed back to the caller. /// An IAsyncResult that represents this find operation, and can be passed to EndFindObjects. public IAsyncResult BeginFindObjects(LLUUID baseFolder, string regex, bool recurse, bool refresh, AsyncCallback callback, object asyncState) { return BeginFindObjects(baseFolder, new Regex(regex), recurse, refresh, false, callback, asyncState); } public IAsyncResult BeginFindObjects(LLUUID baseFolder, string regex, bool recurse, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState) { return BeginFindObjects(baseFolder, new Regex(regex), recurse, refresh, firstOnly, callback, asyncState); } public IAsyncResult BeginFindObjects(LLUUID baseFolder, Regex regexp, bool recurse, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState) { FindResult result = new FindResult(regexp, recurse, callback); result.FirstOnly = firstOnly; result.AsyncState = asyncState; result.FoldersWaiting = 1; if (refresh) { lock (FindDescendantsMap) { IAsyncResult descendReq = BeginRequestFolderContents(baseFolder, _Client.Network.AgentID, true, true, recurse && !firstOnly, InventorySortOrder.ByName, new AsyncCallback(SearchDescendantsCallback), baseFolder); FindDescendantsMap.Add(descendReq, result); } } else { result.Result = LocalFind(baseFolder, regexp, recurse, firstOnly); result.CompletedSynchronously = true; result.IsCompleted = true; } return result; } public List EndFindObjects(IAsyncResult result) { if (result is FindResult) { FindResult fr = result as FindResult; if (!fr.IsCompleted) fr.AsyncWaitHandle.WaitOne(); return fr.Result; } else { throw new Exception("EndFindObjects must be passed the return value of BeginFindObjects."); } } public void SearchDescendantsCallback(IAsyncResult result) { EndRequestFolderContents(result); LLUUID updatedFolder = (LLUUID)result.AsyncState; FindResult find = null; lock (FindDescendantsMap) { if (FindDescendantsMap.TryGetValue(result, out find)) FindDescendantsMap.Remove(result); else return; } Interlocked.Decrement(ref find.FoldersWaiting); List folderContents = _Store.GetContents(updatedFolder); foreach (InventoryBase obj in folderContents) { if (find.Regex.IsMatch(obj.Name)) { find.Result.Add(obj); if (find.FirstOnly) { find.IsCompleted = true; return; } } if (find.Recurse && obj is InventoryFolder) { Interlocked.Increment(ref find.FoldersWaiting); lock (FindDescendantsMap) { IAsyncResult descendReq = BeginRequestFolderContents( obj.UUID, _Client.Network.AgentID, true, true, true, InventorySortOrder.ByName, new AsyncCallback(SearchDescendantsCallback), obj.UUID); FindDescendantsMap.Add(descendReq, find); } } } if (Interlocked.Equals(find.FoldersWaiting, 0)) { find.IsCompleted = true; } } private List LocalFind(LLUUID baseFolder, Regex regexp, bool recurse, bool firstOnly) { List objects = new List(); List folders = new List(); List contents = _Store.GetContents(baseFolder); foreach (InventoryBase inv in contents) { if (regexp.IsMatch(inv.Name)) { objects.Add(inv); if (firstOnly) return objects; } if (inv is InventoryFolder) { folders.Add(inv as InventoryFolder); } } // Recurse outside of the loop because subsequent calls to FindObjects may // modify the baseNode.Nodes collection. // FIXME: I'm pretty sure this is not necessary if (recurse) { foreach (InventoryFolder folder in folders) { objects.AddRange(LocalFind(folder.UUID, regexp, true, firstOnly)); } } return objects; } private class FindObjectsByPathState { public FindResult Result; public LLUUID Folder; public int Level; public FindObjectsByPathState(FindResult result, LLUUID folder, int level) { Result = result; Folder = folder; Level = level; } } public List FindObjectsByPath(LLUUID baseFolder, string[] path, bool refresh, bool firstOnly) { IAsyncResult r = BeginFindObjectsByPath(baseFolder, path, refresh, firstOnly, null, null); return EndFindObjects(r); } public IAsyncResult BeginFindObjectsByPath(LLUUID baseFolder, string[] path, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState) { if (path.Length == 0) throw new ArgumentException("Empty path is not supported"); FindResult result = new FindResult(path, callback); result.FirstOnly = firstOnly; result.AsyncState = asyncState; if (refresh) { result.FoldersWaiting = 1; BeginRequestFolderContents( baseFolder, _Client.Network.AgentID, true, true, false, InventorySortOrder.ByName, new AsyncCallback(FindObjectsByPathCallback), new FindObjectsByPathState(result, baseFolder, 0)); } else { result.Result = LocalFind(baseFolder, path, 0, firstOnly); result.CompletedSynchronously = true; result.IsCompleted = true; } return result; } private void FindObjectsByPathCallback(IAsyncResult result) { EndRequestFolderContents(result); FindObjectsByPathState state = (FindObjectsByPathState)result.AsyncState; Interlocked.Decrement(ref state.Result.FoldersWaiting); List folderContents = _Store.GetContents(state.Folder); foreach (InventoryBase obj in folderContents) { if (obj.Name.CompareTo(state.Result.Path[state.Level]) == 0) { if (state.Level == state.Result.Path.Length - 1) { state.Result.Result.Add(obj); if (state.Result.FirstOnly) { state.Result.IsCompleted = true; return; } } else if (obj is InventoryFolder) { Interlocked.Increment(ref state.Result.FoldersWaiting); BeginRequestFolderContents( obj.UUID, _Client.Network.AgentID, true, true, false, InventorySortOrder.ByName, new AsyncCallback(FindObjectsByPathCallback), new FindObjectsByPathState(state.Result, obj.UUID, state.Level + 1)); } } } if (Interlocked.Equals(state.Result.FoldersWaiting, 0)) state.Result.IsCompleted = true; } private List LocalFind(LLUUID baseFolder, string[] path, int level, bool firstOnly) { List objects = new List(); List folders = new List(); List contents = _Store.GetContents(baseFolder); foreach (InventoryBase inv in contents) { if (inv.Name.CompareTo(path[level]) == 0) { if (level == path.Length - 1) { objects.Add(inv); if (firstOnly) return objects; } else if (inv is InventoryFolder) objects.AddRange(LocalFind(inv.UUID, path, level + 1, firstOnly)); } } return objects; } #endregion #region Folder Actions public void RequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items, bool recurse, InventorySortOrder order) { EndRequestFolderContents(BeginRequestFolderContents(folder, owner, folders, items, recurse, order, null, null)); } public IAsyncResult BeginRequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items, bool recurse, InventorySortOrder order, AsyncCallback callback, object asyncState) { DescendantsResult result = new DescendantsResult(callback); result.AsyncState = asyncState; result.Folders = folders; result.Items = items; result.Recurse = recurse; result.SortOrder = order; return InternalFolderContentsRequest(folder, owner, result); } /// /// Returns the UUID of the folder (category) that defaults to /// containing 'type'. The folder is not necessarily only for that /// type /// /// This will create a new inventory folder on the fly if /// one does not exist /// /// The UUID of the desired or newly created folder, or /// LLUUID.Zero on failure public LLUUID FindFolderForType(AssetType type) { if (_Store == null) { _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; // Loop through each top-level directory and check if PreferredType // matches the requested type List contents = _Store.GetContents(_Store.RootFolder.UUID); foreach (InventoryBase inv in contents) { if (inv is InventoryFolder) { InventoryFolder folder = inv as InventoryFolder; if (folder.PreferredType == type) return folder.UUID; } } // No match found, create one return CreateFolder(_Store.RootFolder.UUID, type, String.Empty); } public LLUUID CreateFolder(LLUUID parentID, AssetType preferredType, string name) { LLUUID id = LLUUID.Random(); // Assign a folder name if one is not already set if (String.IsNullOrEmpty(name)) { if (preferredType >= AssetType.Texture && preferredType <= AssetType.Gesture) { name = _NewFolderNames[(int)preferredType]; } else { name = "New Folder"; } } // Create the new folder locally InventoryFolder newFolder = new InventoryFolder(id); newFolder.Version = 1; newFolder.DescendentCount = 0; newFolder.ParentUUID = parentID; newFolder.PreferredType = preferredType; newFolder.Name = name; newFolder.OwnerID = _Client.Network.AgentID; try { _Store[newFolder.UUID] = newFolder; } catch (InventoryException ie) { _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.FolderData.FolderID = id; create.FolderData.ParentID = parentID; create.FolderData.Type = (sbyte)preferredType; create.FolderData.Name = Helpers.StringToField(name); _Client.Network.SendPacket(create); return id; } public void RemoveDescendants(LLUUID folder) { PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket(); purge.AgentData.AgentID = _Client.Network.AgentID; purge.AgentData.SessionID = _Client.Network.SessionID; purge.InventoryData.FolderID = folder; _Client.Network.SendPacket(purge); // Update our local copy: if (_Store.Contains(folder)) { List contents = _Store.GetContents(folder); foreach (InventoryBase obj in contents) { _Store.RemoveNodeFor(obj); } } } public void RemoveFolder(LLUUID folder) { List folders = new List(1); 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); } public void GiveItem(LLUUID itemID, string itemName, AssetType assetType, LLUUID recipient, bool doEffect) { byte[] bucket = new byte[17]; bucket[0] = (byte)assetType; Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16); _Client.Self.InstantMessage( _Client.Self.Name, recipient, itemName, LLUUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, _Client.Self.Position, _Client.Network.CurrentSim.ID, bucket); if (doEffect) { _Client.Self.BeamEffect(_Client.Network.AgentID, recipient, LLVector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, LLUUID.Random()); } } /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// Vector of where to place object /// 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()); } /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// 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, LLUUID groupOwner) { return RezFromInventory(simulator, rotation, position, item, groupOwner, LLUUID.Random()); } /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// Vector of where to place object /// 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, LLUUID groupOwner, LLUUID queryID) { RezObjectPacket add = new RezObjectPacket(); add.AgentData.AgentID = _Client.Network.AgentID; add.AgentData.SessionID = _Client.Network.SessionID; add.AgentData.GroupID = groupOwner; add.RezData.FromTaskID = LLUUID.Zero; add.RezData.BypassRaycast = 1; add.RezData.RayStart = position; add.RezData.RayEnd = position; add.RezData.RayTargetID = LLUUID.Zero; add.RezData.RayEndIsIntersection = false; add.RezData.RezSelected = false; add.RezData.RemoveItem = false; add.RezData.ItemFlags = item.Flags; add.RezData.GroupMask = (uint)item.Permissions.GroupMask; add.RezData.EveryoneMask = (uint)item.Permissions.EveryoneMask; add.RezData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; add.InventoryData.ItemID = item.UUID; add.InventoryData.FolderID = item.ParentUUID; add.InventoryData.CreatorID = item.CreatorID; add.InventoryData.OwnerID = item.OwnerID; add.InventoryData.GroupID = item.GroupID; add.InventoryData.BaseMask = (uint)item.Permissions.BaseMask; add.InventoryData.OwnerMask = (uint)item.Permissions.OwnerMask; add.InventoryData.GroupMask = (uint)item.Permissions.GroupMask; add.InventoryData.EveryoneMask = (uint)item.Permissions.EveryoneMask; add.InventoryData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; add.InventoryData.GroupOwned = item.GroupOwned; add.InventoryData.TransactionID = queryID; add.InventoryData.Type = (sbyte)item.InventoryType; add.InventoryData.InvType = (sbyte)item.InventoryType; add.InventoryData.Flags = item.Flags; add.InventoryData.SaleType = (byte)item.SaleType; add.InventoryData.SalePrice = item.SalePrice; add.InventoryData.Name = Helpers.StringToField(item.Name); add.InventoryData.Description = Helpers.StringToField(item.Description); add.InventoryData.CreationDate = (int)Helpers.DateTimeToUnixTime(item.CreationDate); _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) { parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder; } else { _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 + ", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version, Helpers.LogLevel.Warning); return; } if (reply.AgentData.Descendents > 0) { // InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders. if (reply.FolderData[0].FolderID != LLUUID.Zero) { // Iterate folders in this packet for (int i = 0; i < reply.FolderData.Length; i++) { InventoryFolder folder = new InventoryFolder(reply.FolderData[i].FolderID); folder.ParentUUID = reply.FolderData[i].ParentID; folder.Name = Helpers.FieldToUTF8String(reply.FolderData[i].Name); folder.PreferredType = (AssetType)reply.FolderData[i].Type; folder.OwnerID = reply.AgentData.OwnerID; _Store[folder.UUID] = folder; } } // InventoryDescendantsReply sends a null item if the parent doesnt contain any items. if (reply.ItemData[0].ItemID != LLUUID.Zero) { // Iterate items in this packet for (int i = 0; i < reply.ItemData.Length; i++) { if (reply.ItemData[i].ItemID != LLUUID.Zero) { InventoryItem item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType,reply.ItemData[i].ItemID); item.ParentUUID = reply.ItemData[i].FolderID; item.CreatorID = reply.ItemData[i].CreatorID; item.AssetType = (AssetType)reply.ItemData[i].Type; item.AssetUUID = reply.ItemData[i].AssetID; item.CreationDate = Helpers.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate); item.Description = Helpers.FieldToUTF8String(reply.ItemData[i].Description); item.Flags = reply.ItemData[i].Flags; item.Name = Helpers.FieldToUTF8String(reply.ItemData[i].Name); item.GroupID = reply.ItemData[i].GroupID; item.GroupOwned = reply.ItemData[i].GroupOwned; item.Permissions = new Permissions( reply.ItemData[i].BaseMask, reply.ItemData[i].EveryoneMask, reply.ItemData[i].GroupMask, reply.ItemData[i].NextOwnerMask, reply.ItemData[i].OwnerMask); item.SalePrice = reply.ItemData[i].SalePrice; item.SaleType = (SaleType)reply.ItemData[i].SaleType; item.OwnerID = reply.AgentData.OwnerID; _Store[item.UUID] = item; } } } } parentFolder.Version = reply.AgentData.Version; parentFolder.DescendentCount = reply.AgentData.Descendents; if (OnInventoryFolderUpdated != null) { try { OnInventoryFolderUpdated(parentFolder.UUID); } 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) HandleDescendantsRetrieved(parentFolder.UUID); } /// /// UpdateCreateInventoryItem packets are received when a new inventory item /// is created. This may occur when an object that's rezzed in world is /// taken into inventory, when an item is created using the CreateInventoryItem /// packet, or when an object is purchased. /// private void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator) { UpdateCreateInventoryItemPacket reply = packet as UpdateCreateInventoryItemPacket; foreach (UpdateCreateInventoryItemPacket.InventoryDataBlock dataBlock in reply.InventoryData) { if (dataBlock.InvType == (sbyte)InventoryType.Folder) { _Client.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet.", Helpers.LogLevel.Error); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate); item.Description = Helpers.FieldToUTF8String(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Helpers.FieldToUTF8String(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; _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)); } } private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator) { BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket; if (update.FolderData.Length > 0 && update.FolderData[0].FolderID != LLUUID.Zero) { foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData) { 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; } } if (update.ItemData.Length > 0 && update.ItemData[0].ItemID != LLUUID.Zero) { foreach (BulkUpdateInventoryPacket.ItemDataBlock dataBlock in update.ItemData) { 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); item.AssetType = (AssetType)dataBlock.Type; 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; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Helpers.FieldToUTF8String(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; _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 FetchInventoryReplyHandler(Packet packet, Simulator simulator) { FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket; foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData) { if (dataBlock.InvType == (sbyte)InventoryType.Folder) { _Client.Log("Received FetchInventoryReply for inventory folder!", Helpers.LogLevel.Error); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate); item.Description = Helpers.FieldToUTF8String(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Helpers.FieldToUTF8String(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; _Store[item.UUID] = item; } } private void Self_OnInstantMessage(InstantMessage im, Simulator simulator) { // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we // handle it here? if (OnInventoryObjectReceived != null && (im.Dialog == InstantMessageDialog.InventoryOffered || im.Dialog == InstantMessageDialog.TaskInventoryOffered)) { AssetType type = AssetType.Unknown; LLUUID objectID = LLUUID.Zero; bool fromTask = false; if (im.Dialog == InstantMessageDialog.InventoryOffered) { if (im.BinaryBucket.Length == 17) { type = (AssetType)im.BinaryBucket[0]; objectID = new LLUUID(im.BinaryBucket, 1); fromTask = false; } else { _Client.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning); return; } } else if (im.Dialog == InstantMessageDialog.TaskInventoryOffered) { if (im.BinaryBucket.Length == 1) { type = (AssetType)im.BinaryBucket[0]; fromTask = true; } else { _Client.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning); return; } } // Find the folder where this is going to go LLUUID destinationFolderID = FindFolderForType(type); // Fire the callback try { ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket(); 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.Message = new byte[0]; imp.MessageBlock.ParentEstateID = 0; imp.MessageBlock.RegionID = LLUUID.Zero; imp.MessageBlock.Position = _Client.Self.Position; if (OnInventoryObjectReceived(im.FromAgentID, im.FromAgentName, im.ParentEstateID, im.RegionID, im.Position, im.Timestamp, type, objectID, fromTask)) { // Accept the inventory offer switch (im.Dialog) { case InstantMessageDialog.InventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryAccepted; break; case InstantMessageDialog.TaskInventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryOffered; break; case InstantMessageDialog.GroupNotice: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryAccepted; break; } imp.MessageBlock.BinaryBucket = destinationFolderID.GetBytes(); } else { // Decline the inventory offer switch (im.Dialog) { case InstantMessageDialog.InventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryDeclined; break; case InstantMessageDialog.TaskInventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryDeclined; break; case InstantMessageDialog.GroupNotice: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryDeclined; break; } imp.MessageBlock.BinaryBucket = new byte[0]; } _Client.Network.SendPacket(imp, simulator); } catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } #endregion Callbacks } class FindResult : IAsyncResult { public List Result; public int FoldersWaiting; public bool FirstOnly; private AsyncCallback callback; private Regex regex; private string[] path; private bool recurse; private AutoResetEvent waitHandle; private bool complete; private bool sync; private object asyncstate; #region Properties 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 { get { return asyncstate; } set { asyncstate = value; } } public WaitHandle AsyncWaitHandle { get { return waitHandle; } } public bool CompletedSynchronously { get { return sync; } set { sync = value; } } public bool IsCompleted { get { return complete; } set { if (value) { waitHandle.Set(); if (callback != null) { callback(this); } } complete = value; } } #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 { public bool Folders = true; public bool Items = true; 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(); #region Properties #region IAsyncResult Members public object AsyncState { get { return _AsyncState; } set { _AsyncState = value; } } public WaitHandle AsyncWaitHandle { get { return _AsyncWaitHandle; } } public bool CompletedSynchronously { get { return false; } } public bool IsCompleted { get { return _IsCompleted; } set { _IsCompleted = value; if (value) { if (_ChildrenWaiting.Count == 0) { if (Parent != null) { Parent.ChildComplete(this); } else { _AsyncWaitHandle.Set(); if (_Callback != null) _Callback(this); } } } } } #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; } } } }