/* * Copyright (c) 2006-2008, openmetaverse.org * 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 openmetaverse.org 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.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Text; using OpenMetaverse.Capabilities; using OpenMetaverse.StructuredData; using OpenMetaverse.Packets; namespace OpenMetaverse { #region Enums /// /// Inventory Item Types, eg Script, Notecard, Folder, etc /// public enum InventoryType : sbyte { /// Unknown Unknown = -1, /// Texture Texture = 0, /// Sound Sound = 1, /// Calling Card CallingCard = 2, /// Landmark Landmark = 3, /// Script //[Obsolete("See LSL")] Script = 4, /// Clothing //[Obsolete("See Wearable")] Clothing = 5, /// Object, both single and coalesced Object = 6, /// Notecard Notecard = 7, /// Category = 8, /// Folder Folder = 8, /// RootCategory = 9, /// an LSL Script LSL = 10, /// //[Obsolete("See LSL")] LSLBytecode = 11, /// //[Obsolete("See Texture")] TextureTGA = 12, /// //[Obsolete] Bodypart = 13, /// //[Obsolete] Trash = 14, /// Snapshot = 15, /// //[Obsolete] LostAndFound = 16, /// Attachment = 17, /// Wearable = 18, /// Animation = 19, /// Gesture = 20 } /// /// Item Sale Status /// public enum SaleType : byte { /// Not for sale Not = 0, /// The original is for sale Original = 1, /// Copies are for sale Copy = 2, /// The contents of the object are for sale Contents = 3 } [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 } /// /// Possible destinations for DeRezObject request /// public enum DeRezDestination : byte { /// Derez to TaskInventory TaskInventory = 2, /// Take Object ObjectsFolder = 4, /// Delete Object TrashFolder = 6 } #endregion Enums #region Inventory Object Classes /// /// Base Class for Inventory Items /// public abstract class InventoryBase { /// of item/folder public readonly UUID UUID; /// of parent folder public UUID ParentUUID; /// Name of item/folder public string Name; /// Item/Folder Owners public UUID OwnerID; /// /// Constructor, takes an itemID as a parameter /// /// The of the item public InventoryBase(UUID itemID) { if (itemID == UUID.Zero) Logger.Log("Initializing an InventoryBase with UUID.Zero", Helpers.LogLevel.Warning); UUID = itemID; } /// /// Generates a number corresponding to the value of the object to support the use of a hash table, /// suitable for use in hashing algorithms and data structures such as a hash table /// /// A Hashcode of all the combined InventoryBase fields public override int GetHashCode() { return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode(); } /// /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same public override bool Equals(object o) { InventoryBase inv = o as InventoryBase; return inv != null && Equals(inv); } /// /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same public virtual bool Equals(InventoryBase o) { return o.UUID == UUID && o.ParentUUID == ParentUUID && o.Name == Name && o.OwnerID == OwnerID; } } /// /// An Item in Inventory /// public class InventoryItem : InventoryBase { /// The of this item public UUID AssetUUID; /// The combined of this item public Permissions Permissions; /// The type of item from public AssetType AssetType; /// The type of item from the enum public InventoryType InventoryType; /// The of the creator of this item public UUID CreatorID; /// A Description of this item public string Description; /// The s this item is set to or owned by public UUID GroupID; /// If true, item is owned by a group public bool GroupOwned; /// The price this item can be purchased for public int SalePrice; /// The type of sale from the enum public SaleType SaleType; /// Combined flags from public uint Flags; /// Time and date this inventory item was created, stored as /// UTC (Coordinated Universal Time) public DateTime CreationDate; /// /// Construct a new InventoryItem object /// /// The of the item public InventoryItem(UUID itemID) : base(itemID) { } /// /// Construct a new InventoryItem object of a specific Type /// /// The type of item from /// of the item public InventoryItem(InventoryType type, UUID itemID) : base(itemID) { InventoryType = type; } /// /// Generates a number corresponding to the value of the object to support the use of a hash table. /// Suitable for use in hashing algorithms and data structures such as a hash table /// /// A Hashcode of all the combined InventoryItem fields 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(); } /// /// Compares an object /// /// The object to compare /// true if comparison object matches public override bool Equals(object o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same public override bool Equals(InventoryBase o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same 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; } } /// /// InventoryTexture Class representing a graphical image /// /// public class InventoryTexture : InventoryItem { /// /// Construct an InventoryTexture object /// /// A which becomes the /// objects AssetUUID public InventoryTexture(UUID itemID) : base(itemID) { InventoryType = InventoryType.Texture; } } /// /// InventorySound Class representing a playable sound /// public class InventorySound : InventoryItem { /// /// Construct an InventorySound object /// /// A which becomes the /// objects AssetUUID public InventorySound(UUID itemID) : base(itemID) { InventoryType = InventoryType.Sound; } } /// /// InventoryCallingCard Class, contains information on another avatar /// public class InventoryCallingCard : InventoryItem { /// /// Construct an InventoryCallingCard object /// /// A which becomes the /// objects AssetUUID public InventoryCallingCard(UUID itemID) : base(itemID) { InventoryType = InventoryType.CallingCard; } } /// /// InventoryLandmark Class, contains details on a specific location /// public class InventoryLandmark : InventoryItem { /// /// Construct an InventoryLandmark object /// /// A which becomes the /// objects AssetUUID public InventoryLandmark(UUID itemID) : base(itemID) { InventoryType = InventoryType.Landmark; } /// /// Landmarks use the ObjectType struct and will have a flag of 1 set if they have been visited /// public ObjectType LandmarkType { get { return (ObjectType)Flags; } set { Flags = (uint)value; } } } /// /// InventoryObject Class contains details on a primitive or coalesced set of primitives /// public class InventoryObject : InventoryItem { /// /// Construct an InventoryObject object /// /// A which becomes the /// objects AssetUUID public InventoryObject(UUID itemID) : base(itemID) { InventoryType = InventoryType.Object; } /// /// Get the Objects permission override settings /// /// These will indicate the which permissions that /// will be overwritten when the object is rezzed in-world /// public ObjectType ObjectType { get { return (ObjectType)Flags; } set { Flags = (uint)value; } } } /// /// InventoryNotecard Class, contains details on an encoded text document /// public class InventoryNotecard : InventoryItem { /// /// Construct an InventoryNotecard object /// /// A which becomes the /// objects AssetUUID public InventoryNotecard(UUID itemID) : base(itemID) { InventoryType = InventoryType.Notecard; } } /// /// InventoryCategory Class /// /// TODO: Is this even used for anything? public class InventoryCategory : InventoryItem { /// /// Construct an InventoryCategory object /// /// A which becomes the /// objects AssetUUID public InventoryCategory(UUID itemID) : base(itemID) { InventoryType = InventoryType.Category; } } /// /// InventoryLSL Class, represents a Linden Scripting Language object /// public class InventoryLSL : InventoryItem { /// /// Construct an InventoryLSL object /// /// A which becomes the /// objects AssetUUID public InventoryLSL(UUID itemID) : base(itemID) { InventoryType = InventoryType.LSL; } } /// /// InventorySnapshot Class, an image taken with the viewer /// public class InventorySnapshot : InventoryItem { /// /// Construct an InventorySnapshot object /// /// A which becomes the /// objects AssetUUID public InventorySnapshot(UUID itemID) : base(itemID) { InventoryType = InventoryType.Snapshot; } } /// /// InventoryAttachment Class, contains details on an attachable object /// public class InventoryAttachment : InventoryItem { /// /// Construct an InventoryAttachment object /// /// A which becomes the /// objects AssetUUID public InventoryAttachment(UUID itemID) : base(itemID) { InventoryType = InventoryType.Attachment; } /// /// Get the last AttachmentPoint this object was attached to /// public AttachmentPoint AttachmentPoint { get { return (AttachmentPoint)Flags; } set { Flags = (uint)value; } } } /// /// InventoryWearable Class, details on a clothing item or body part /// public class InventoryWearable : InventoryItem { /// /// Construct an InventoryWearable object /// /// A which becomes the /// objects AssetUUID public InventoryWearable(UUID itemID) : base(itemID) { InventoryType = InventoryType.Wearable; } /// /// The , Skin, Shape, Skirt, Etc /// public WearableType WearableType { get { return (WearableType)Flags; } set { Flags = (uint)value; } } } /// /// InventoryAnimation Class, A bvh encoded object which animates an avatar /// public class InventoryAnimation : InventoryItem { /// /// Construct an InventoryAnimation object /// /// A which becomes the /// objects AssetUUID public InventoryAnimation(UUID itemID) : base(itemID) { InventoryType = InventoryType.Animation; } } /// /// InventoryGesture Class, details on a series of animations, sounds, and actions /// public class InventoryGesture : InventoryItem { /// /// Construct an InventoryGesture object /// /// A which becomes the /// objects AssetUUID public InventoryGesture(UUID itemID) : base(itemID) { InventoryType = InventoryType.Gesture; } } /// /// A folder contains s and has certain attributes specific /// to itself /// public class InventoryFolder : InventoryBase { /// The Preferred for a folder. public AssetType PreferredType; /// The Version of this folder public int Version; /// Number of child items this folder contains. public int DescendentCount; /// /// Constructor /// /// UUID of the folder public InventoryFolder(UUID itemID) : base(itemID) { } /// /// /// /// public override string ToString() { return Name; } /// /// /// /// 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 /// /// Tools for dealing with agents inventory /// public class InventoryManager { protected struct InventorySearch { public UUID Folder; public UUID Owner; public string[] Path; public int Level; } #region Delegates /// /// 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 item being create from an uploaded asset /// /// true if inventory item creation was successful /// /// /// public delegate void ItemCreatedFromAssetCallback(bool success, string status, UUID itemID, UUID assetID); /// /// /// /// public delegate void ItemCopiedCallback(InventoryBase item); /// /// /// /// public delegate void ItemReceivedCallback(InventoryItem item); /// /// Callback for an inventory folder updating /// /// UUID of the folder that was updated public delegate void FolderUpdatedCallback(UUID folderID); /// /// Callback for when an inventory item is offered to us by another avatar or an object /// /// A object containing specific /// details on the item being offered, eg who its from /// The AssetType being offered /// Will be null if item is offered from an object /// will be true of item is offered from an object /// Return true to accept the offer, or false to decline it public delegate bool ObjectOfferedCallback(InstantMessage offerDetails, AssetType type, UUID objectID, bool fromTask); /// /// Callback when an inventory object is accepted and received from a /// task inventory. This is the callback in which you actually get /// the ItemID, as in ObjectOfferedCallback it is null when received /// from a task. /// /// /// /// /// public delegate void TaskItemReceivedCallback(UUID itemID, UUID folderID, UUID creatorID, UUID assetID, InventoryType type); /// /// /// /// /// public delegate void FindObjectByPathCallback(string path, UUID inventoryObjectID); /// /// Reply received after calling RequestTaskInventory, /// contains a filename that can be used in an asset download request /// /// UUID of the inventory item /// Version number of the task inventory asset /// Filename of the task inventory asset public delegate void TaskInventoryReplyCallback(UUID itemID, short serial, string assetFilename); /// /// /// /// /// /// /// public delegate void NotecardUploadedAssetCallback(bool success, string status, UUID itemID, UUID assetID); #endregion Delegates #region Events /// /// Fired when a reply to a RequestFetchInventory() is received /// /// public event ItemReceivedCallback OnItemReceived; /// /// Fired when a response to a RequestFolderContents() is received /// /// public event FolderUpdatedCallback OnFolderUpdated; /// /// Fired when an object or another avatar offers us an inventory item /// public event ObjectOfferedCallback OnObjectOffered; /// /// Fired when a response to FindObjectByPath() is received /// /// public event FindObjectByPathCallback OnFindObjectByPath; /// /// Fired when a task inventory item is received /// /// 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 /// public event TaskItemReceivedCallback OnTaskItemReceived; /// /// Fired in response to a request for a tasks (primitive) inventory /// /// /// public event TaskInventoryReplyCallback OnTaskInventoryReply; #endregion Events private GridClient _Client; private Inventory _Store; private Random _RandNumbers = new Random(); private object _CallbacksLock = new object(); private uint _CallbackPos; private Dictionary _ItemCreatedCallbacks = new Dictionary(); private Dictionary _ItemCopiedCallbacks = new Dictionary(); private List _Searches = new List(); #region String Arrays /// 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", }; private static readonly string[] _SaleTypeNames = new string[] { "not", "orig", "copy", "cntn" }; #endregion String Arrays #region Properties /// /// Get this agents Inventory data /// public Inventory Store { get { return _Store; } } #endregion Properties /// /// Default constructor /// /// Reference to the GridClient object public InventoryManager(GridClient 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)); _Client.Network.RegisterCallback(PacketType.ReplyTaskInventory, new NetworkManager.PacketCallback(ReplyTaskInventoryHandler)); // Watch for inventory given to us through instant message _Client.Self.OnInstantMessage += new AgentManager.InstantMessageCallback(Self_OnInstantMessage); // Register extra parameters with login and parse the inventory data that comes back _Client.Network.RegisterLoginResponseCallback( new NetworkManager.LoginResponseCallback(Network_OnLoginResponse), new string[] { "inventory-root", "inventory-skeleton", "inventory-lib-root", "inventory-lib-owner", "inventory-skel-lib"}); } #region Fetch /// /// Fetch an inventory item from the dataserver /// /// The items /// The item Owners /// a integer representing the number of milliseconds to wait for results /// An object on success, or null if no item was found /// Items will also be sent to the event public InventoryItem FetchItem(UUID itemID, UUID ownerID, int timeoutMS) { AutoResetEvent fetchEvent = new AutoResetEvent(false); InventoryItem fetchedItem = null; ItemReceivedCallback callback = delegate(InventoryItem item) { if (item.UUID == itemID) { fetchedItem = item; fetchEvent.Set(); } }; OnItemReceived += callback; RequestFetchInventory(itemID, ownerID); fetchEvent.WaitOne(timeoutMS, false); OnItemReceived -= callback; return fetchedItem; } /// /// Request A single inventory item /// /// The items /// The item Owners /// public void RequestFetchInventory(UUID itemID, UUID ownerID) { FetchInventoryPacket fetch = new FetchInventoryPacket(); fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[1]; fetch.InventoryData[0] = new FetchInventoryPacket.InventoryDataBlock(); fetch.InventoryData[0].ItemID = itemID; fetch.InventoryData[0].OwnerID = ownerID; _Client.Network.SendPacket(fetch); } /// /// Request inventory items /// /// Inventory items to request /// Owners of the inventory items /// public void RequestFetchInventory(List itemIDs, List ownerIDs) { if (itemIDs.Count != ownerIDs.Count) throw new ArgumentException("itemIDs and ownerIDs must contain the same number of entries"); FetchInventoryPacket fetch = new FetchInventoryPacket(); fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count]; for (int i = 0; i < itemIDs.Count; i++) { fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock(); fetch.InventoryData[i].ItemID = itemIDs[i]; fetch.InventoryData[i].OwnerID = ownerIDs[i]; } _Client.Network.SendPacket(fetch); } /// /// Get contents of a folder /// /// The of the folder to search /// The of the folders owner /// true to retrieve folders /// true to retrieve items /// sort order to return results in /// a integer representing the number of milliseconds to wait for results /// A list of inventory items matching search criteria within folder /// /// InventoryFolder.DescendentCount will only be accurate if both folders and items are /// requested public List FolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, int timeoutMS) { List objects = null; AutoResetEvent fetchEvent = new AutoResetEvent(false); FolderUpdatedCallback callback = delegate(UUID folderID) { if (folderID == folder && _Store[folder] is InventoryFolder) { // InventoryDescendentsHandler only stores DescendendCount if both folders and items are fetched. if (_Store.GetContents(folder).Count >= ((InventoryFolder)_Store[folder]).DescendentCount) { fetchEvent.Set(); } } else { fetchEvent.Set(); } }; OnFolderUpdated += callback; RequestFolderContents(folder, owner, folders, items, order); if (fetchEvent.WaitOne(timeoutMS, false)) objects = _Store.GetContents(folder); OnFolderUpdated -= callback; return objects; } /// /// Request the contents of an inventory folder /// /// The folder to search /// The folder owners /// true to return s contained in folder /// true to return s containd in folder /// the sort order to return items in /// public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order) { FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData.FetchFolders = folders; fetch.InventoryData.FetchItems = items; fetch.InventoryData.FolderID = folder; fetch.InventoryData.OwnerID = owner; fetch.InventoryData.SortOrder = (int)order; _Client.Network.SendPacket(fetch); } #endregion Fetch #region Find /// /// Returns the UUID of the folder (category) that defaults to /// containing 'type'. The folder is not necessarily only for that /// type /// /// This will return the root folder if one does not exist /// /// The UUID of the desired folder if found, the UUID of the RootFolder /// if not found, or UUID.Zero on failure public UUID FindFolderForType(AssetType type) { if (_Store == null) { Logger.Log("Inventory is null, FindFolderForType() lookup cannot continue", Helpers.LogLevel.Error, _Client); return UUID.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, return Root Folder ID return _Store.RootFolder.UUID; } /// /// Find an object in inventory using a specific path to search /// /// The folder to begin the search in /// The object owners /// A string path to search /// milliseconds to wait for a reply /// Found items or if /// timeout occurs or item is not found public UUID FindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path, int timeoutMS) { AutoResetEvent findEvent = new AutoResetEvent(false); UUID foundItem = UUID.Zero; FindObjectByPathCallback callback = delegate(string thisPath, UUID inventoryObjectID) { if (thisPath == path) { foundItem = inventoryObjectID; findEvent.Set(); } }; OnFindObjectByPath += callback; RequestFindObjectByPath(baseFolder, inventoryOwner, path); findEvent.WaitOne(timeoutMS, false); OnFindObjectByPath -= callback; return foundItem; } /// /// Find inventory items by path /// /// The folder to begin the search in /// The object owners /// A string path to search, folders/objects separated by a '/' /// Results are sent to the event public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path) { if (path == null || path.Length == 0) throw new ArgumentException("Empty path is not supported"); // Store this search InventorySearch search; search.Folder = baseFolder; search.Owner = inventoryOwner; search.Path = path.Split('/'); search.Level = 0; lock (_Searches) _Searches.Add(search); // Start the search RequestFolderContents(baseFolder, inventoryOwner, true, true, InventorySortOrder.ByName); } /// /// Search inventory Store object for an item or folder /// /// The folder to begin the search in /// An array which creates a path to search /// Number of levels below baseFolder to conduct searches /// if True, will stop searching after first match is found /// A list of inventory items found public List LocalFind(UUID 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 Find #region Move/Rename /// /// Move an inventory item or folder to a new location /// /// The item or folder to move /// The to move item or folder to public void Move(InventoryBase item, InventoryFolder newParent) { if (item is InventoryFolder) MoveFolder(item.UUID, newParent.UUID); else MoveItem(item.UUID, newParent.UUID); } /// /// Move an inventory item or folder to a new location and change its name /// /// The item or folder to move /// The to move item or folder to /// The name to change the item or folder to public void Move(InventoryBase item, InventoryFolder newParent, string newName) { if (item is InventoryFolder) MoveFolder(item.UUID, newParent.UUID, newName); else MoveItem(item.UUID, newParent.UUID, newName); } /// /// Move and rename a folder /// /// The source folders /// The destination folders /// The name to change the folder to public void MoveFolder(UUID folderID, UUID newparentID, string newName) { lock (Store) { if (_Store.Contains(folderID)) { InventoryBase inv = Store[folderID]; inv.Name = newName; _Store.UpdateNodeFor(inv); } } UpdateInventoryFolderPacket move = new UpdateInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.FolderData = new UpdateInventoryFolderPacket.FolderDataBlock[1]; move.FolderData[0] = new UpdateInventoryFolderPacket.FolderDataBlock(); move.FolderData[0].FolderID = folderID; move.FolderData[0].ParentID = newparentID; move.FolderData[0].Name = Utils.StringToBytes(newName); move.FolderData[0].Type = -1; _Client.Network.SendPacket(move); } /// /// Move a folder /// /// The source folders /// The destination folders public void MoveFolder(UUID folderID, UUID newParentID) { lock (Store) { if (_Store.Contains(folderID)) { InventoryBase inv = Store[folderID]; inv.ParentUUID = newParentID; _Store.UpdateNodeFor(inv); } } MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[1]; move.InventoryData[0] = new MoveInventoryFolderPacket.InventoryDataBlock(); move.InventoryData[0].FolderID = folderID; move.InventoryData[0].ParentID = newParentID; _Client.Network.SendPacket(move); } /// /// Move multiple folders, the keys in the Dictionary parameter, /// to a new parents, the value of that folder's key. /// /// A Dictionary containing the /// of the source as the key, and the /// of the destination as the value public void MoveFolders(Dictionary foldersNewParents) { // FIXME: Use two List to stay consistent lock (Store) { foreach (KeyValuePair entry in foldersNewParents) { if (_Store.Contains(entry.Key)) { InventoryBase inv = _Store[entry.Key]; inv.ParentUUID = entry.Value; _Store.UpdateNodeFor(inv); } } } //TODO: Test if this truly supports multiple-folder move MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[foldersNewParents.Count]; int index = 0; foreach (KeyValuePair folder in foldersNewParents) { MoveInventoryFolderPacket.InventoryDataBlock block = new MoveInventoryFolderPacket.InventoryDataBlock(); block.FolderID = folder.Key; block.ParentID = folder.Value; move.InventoryData[index++] = block; } _Client.Network.SendPacket(move); } /// /// Move an inventory item to a new folder /// /// The of the source item to move /// The of the destination folder public void MoveItem(UUID itemID, UUID folderID) { MoveItem(itemID, folderID, String.Empty); } /// /// Move and rename an inventory item /// /// The of the source item to move /// The of the destination folder /// The name to change the folder to public void MoveItem(UUID itemID, UUID folderID, string newName) { lock (_Store) { if (_Store.Contains(itemID)) { InventoryBase inv = _Store[itemID]; inv.ParentUUID = folderID; _Store.UpdateNodeFor(inv); } } MoveInventoryItemPacket move = new MoveInventoryItemPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[1]; move.InventoryData[0] = new MoveInventoryItemPacket.InventoryDataBlock(); move.InventoryData[0].ItemID = itemID; move.InventoryData[0].FolderID = folderID; move.InventoryData[0].NewName = Utils.StringToBytes(newName); _Client.Network.SendPacket(move); } /// /// Move multiple inventory items to new locations /// /// A Dictionary containing the /// of the source item as the key, and the /// of the destination folder as the value public void MoveItems(Dictionary itemsNewParents) { lock (_Store) { foreach (KeyValuePair entry in itemsNewParents) { if (_Store.Contains(entry.Key)) { InventoryBase inv = _Store[entry.Key]; inv.ParentUUID = entry.Value; _Store.UpdateNodeFor(inv); } } } MoveInventoryItemPacket move = new MoveInventoryItemPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[itemsNewParents.Count]; int index = 0; foreach (KeyValuePair entry in itemsNewParents) { MoveInventoryItemPacket.InventoryDataBlock block = new MoveInventoryItemPacket.InventoryDataBlock(); block.ItemID = entry.Key; block.FolderID = entry.Value; block.NewName = new byte[0]; move.InventoryData[index++] = block; } _Client.Network.SendPacket(move); } #endregion Move #region Remove /// /// Remove descendants of a folder /// /// The of the folder public void RemoveDescendants(UUID folder) { PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket(); purge.AgentData.AgentID = _Client.Self.AgentID; purge.AgentData.SessionID = _Client.Self.SessionID; purge.InventoryData.FolderID = folder; _Client.Network.SendPacket(purge); // Update our local copy lock (_Store) { if (_Store.Contains(folder)) { List contents = _Store.GetContents(folder); foreach (InventoryBase obj in contents) { _Store.RemoveNodeFor(obj); } } } } /// /// Remove a single item from inventory /// /// The of the inventory item to remove public void RemoveItem(UUID item) { List items = new List(1); items.Add(item); Remove(items, null); } /// /// Remove a folder from inventory /// /// The of the folder to remove public void RemoveFolder(UUID folder) { List folders = new List(1); folders.Add(folder); Remove(null, folders); } /// /// Remove multiple items or folders from inventory /// /// A List containing the s of items to remove /// A List containing the s of the folders to remove 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.Self.AgentID; rem.AgentData.SessionID = _Client.Self.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 = UUID.Zero; } else { lock (_Store) { 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 = UUID.Zero; } else { lock (_Store) { 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.Network.SendPacket(rem); } /// /// Empty the Lost and Found folder /// public void EmptyLostAndFound() { EmptySystemFolder(AssetType.LostAndFoundFolder); } /// /// Empty the Trash folder /// public void EmptyTrash() { EmptySystemFolder(AssetType.TrashFolder); } private void EmptySystemFolder(AssetType folderType) { List items = _Store.GetContents(_Store.RootFolder); UUID folderKey = UUID.Zero; foreach (InventoryBase item in items) { if ((item as InventoryFolder) != null) { InventoryFolder folder = item as InventoryFolder; if (folder.PreferredType == folderType) { folderKey = folder.UUID; break; } } } items = _Store.GetContents(folderKey); List remItems = new List(); List remFolders = new List(); foreach (InventoryBase item in items) { if ((item as InventoryFolder) != null) { remFolders.Add(item.UUID); } else { remItems.Add(item.UUID); } } Remove(remItems, remFolders); } #endregion Remove #region Create /// /// /// /// Proper use is to upload the inventory's asset first, then provide the Asset's TransactionID here. public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, UUID assetTransactionID, InventoryType invType, PermissionMask nextOwnerMask, ItemCreatedCallback callback) { // Even though WearableType 0 is Shape, in this context it is treated as NOT_WEARABLE RequestCreateItem(parentFolder, name, description, type, assetTransactionID, invType, (WearableType)0, nextOwnerMask, callback); } /// /// /// /// Proper use is to upload the inventory's asset first, then provide the Asset's TransactionID here. public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, UUID assetTransactionID, InventoryType invType, WearableType wearableType, PermissionMask nextOwnerMask, ItemCreatedCallback callback) { CreateInventoryItemPacket create = new CreateInventoryItemPacket(); create.AgentData.AgentID = _Client.Self.AgentID; create.AgentData.SessionID = _Client.Self.SessionID; create.InventoryBlock.CallbackID = RegisterItemCreatedCallback(callback); create.InventoryBlock.FolderID = parentFolder; create.InventoryBlock.TransactionID = assetTransactionID; create.InventoryBlock.NextOwnerMask = (uint)nextOwnerMask; create.InventoryBlock.Type = (sbyte)type; create.InventoryBlock.InvType = (sbyte)invType; create.InventoryBlock.WearableType = (byte)wearableType; create.InventoryBlock.Name = Utils.StringToBytes(name); create.InventoryBlock.Description = Utils.StringToBytes(description); _Client.Network.SendPacket(create); } /// /// Creates a new inventory folder /// /// ID of the folder to put this folder in /// Name of the folder to create /// The UUID of the newly created folder public UUID CreateFolder(UUID parentID, string name) { return CreateFolder(parentID, name, AssetType.Unknown); } /// /// Creates a new inventory folder /// /// ID of the folder to put this folder in /// Name of the folder to create /// Sets this folder as the default folder /// for new assets of the specified type. Use AssetType.Unknown /// to create a normal folder, otherwise it will likely create a /// duplicate of an existing folder type /// The UUID of the newly created folder /// If you specify a preferred type of AsseType.Folder /// it will create a new root folder which may likely cause all sorts /// of strange problems public UUID CreateFolder(UUID parentID, string name, AssetType preferredType) { UUID id = UUID.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.Self.AgentID; // Update the local store try { _Store[newFolder.UUID] = newFolder; } catch (InventoryException ie) { Logger.Log(ie.Message, Helpers.LogLevel.Warning, _Client, ie); } // Create the create folder packet and send it CreateInventoryFolderPacket create = new CreateInventoryFolderPacket(); create.AgentData.AgentID = _Client.Self.AgentID; create.AgentData.SessionID = _Client.Self.SessionID; create.FolderData.FolderID = id; create.FolderData.ParentID = parentID; create.FolderData.Type = (sbyte)preferredType; create.FolderData.Name = Utils.StringToBytes(name); _Client.Network.SendPacket(create); return id; } public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, InventoryType invType, UUID folderID, CapsClient.ProgressCallback progCallback, ItemCreatedFromAssetCallback callback) { if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("NewFileAgentInventory capability is not currently available"); Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); if (url != null) { LLSDMap query = new LLSDMap(); query.Add("folder_id", LLSD.FromUUID(folderID)); query.Add("asset_type", LLSD.FromString(AssetTypeToString(assetType))); query.Add("inventory_type", LLSD.FromString(InventoryTypeToString(invType))); query.Add("name", LLSD.FromString(name)); query.Add("description", LLSD.FromString(description)); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); request.UserData = new object[] { progCallback, callback, data }; request.StartRequest(query); } else { throw new Exception("NewFileAgentInventory capability is not currently available"); } } #endregion Create #region Copy /// /// /// /// /// /// /// public void RequestCopyItem(UUID item, UUID newParent, string newName, ItemCopiedCallback callback) { RequestCopyItem(item, newParent, newName, _Client.Self.AgentID, callback); } /// /// /// /// /// /// /// /// public void RequestCopyItem(UUID item, UUID newParent, string newName, UUID oldOwnerID, ItemCopiedCallback callback) { List items = new List(1); items.Add(item); List folders = new List(1); folders.Add(newParent); List names = new List(1); names.Add(newName); RequestCopyItems(items, folders, names, oldOwnerID, callback); } /// /// /// /// /// /// /// /// public void RequestCopyItems(List items, List targetFolders, List newNames, UUID oldOwnerID, ItemCopiedCallback callback) { if (items.Count != targetFolders.Count || (newNames != null && items.Count != newNames.Count)) throw new ArgumentException("All list arguments must have an equal number of entries"); uint callbackID = RegisterItemsCopiedCallback(callback); CopyInventoryItemPacket copy = new CopyInventoryItemPacket(); copy.AgentData.AgentID = _Client.Self.AgentID; copy.AgentData.SessionID = _Client.Self.SessionID; copy.InventoryData = new CopyInventoryItemPacket.InventoryDataBlock[items.Count]; for (int i = 0; i < items.Count; ++i) { copy.InventoryData[i] = new CopyInventoryItemPacket.InventoryDataBlock(); copy.InventoryData[i].CallbackID = callbackID; copy.InventoryData[i].NewFolderID = targetFolders[i]; copy.InventoryData[i].OldAgentID = oldOwnerID; copy.InventoryData[i].OldItemID = items[i]; if (newNames != null && !String.IsNullOrEmpty(newNames[i])) copy.InventoryData[i].NewName = Utils.StringToBytes(newNames[i]); else copy.InventoryData[i].NewName = new byte[0]; } _Client.Network.SendPacket(copy); } /// /// /// /// /// /// /// public void RequestCopyItemFromNotecard(UUID objectID, UUID notecardID, UUID folderID, UUID itemID) { CopyInventoryFromNotecardPacket copy = new CopyInventoryFromNotecardPacket(); copy.AgentData.AgentID = _Client.Self.AgentID; copy.AgentData.SessionID = _Client.Self.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); } #endregion Copy #region Update /// /// /// /// public void RequestUpdateItem(InventoryItem item) { List items = new List(1); items.Add(item); RequestUpdateItems(items, UUID.Random()); } /// /// /// /// public void RequestUpdateItems(List items) { RequestUpdateItems(items, UUID.Random()); } /// /// /// /// /// public void RequestUpdateItems(List items, UUID transactionID) { UpdateInventoryItemPacket update = new UpdateInventoryItemPacket(); update.AgentData.AgentID = _Client.Self.AgentID; update.AgentData.SessionID = _Client.Self.SessionID; update.AgentData.TransactionID = transactionID; update.InventoryData = new UpdateInventoryItemPacket.InventoryDataBlock[items.Count]; for (int i = 0; i < items.Count; i++) { InventoryItem item = items[i]; UpdateInventoryItemPacket.InventoryDataBlock block = new UpdateInventoryItemPacket.InventoryDataBlock(); block.BaseMask = (uint)item.Permissions.BaseMask; block.CRC = ItemCRC(item); block.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); block.CreatorID = item.CreatorID; block.Description = Utils.StringToBytes(item.Description); block.EveryoneMask = (uint)item.Permissions.EveryoneMask; block.Flags = (uint)item.Flags; block.FolderID = item.ParentUUID; block.GroupID = item.GroupID; block.GroupMask = (uint)item.Permissions.GroupMask; block.GroupOwned = item.GroupOwned; block.InvType = (sbyte)item.InventoryType; block.ItemID = item.UUID; block.Name = Utils.StringToBytes(item.Name); block.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; block.OwnerID = item.OwnerID; block.OwnerMask = (uint)item.Permissions.OwnerMask; block.SalePrice = item.SalePrice; block.SaleType = (byte)item.SaleType; block.TransactionID = UUID.Zero; block.Type = (sbyte)item.AssetType; update.InventoryData[i] = block; } _Client.Network.SendPacket(update); } /// /// /// /// /// /// public void RequestUploadNotecardAsset(byte[] data, UUID notecardID, NotecardUploadedAssetCallback callback) { if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("UpdateNotecardAgentInventory"); if (url != null) { LLSDMap query = new LLSDMap(); query.Add("item_id", LLSD.FromUUID(notecardID)); byte[] postData = StructuredData.LLSDParser.SerializeXmlBytes(query); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); request.UserData = new object[2] { new KeyValuePair(callback, data), notecardID }; request.StartRequest(postData); } else { throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); } } #endregion Update #region Rez/Give /// /// 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 UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryObject item) { return RequestRezFromInventory(simulator, rotation, position, item, _Client.Self.ActiveGroup, UUID.Random(), false); } /// /// 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 /// UUID of group to own the object public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryObject item, UUID groupOwner) { return RequestRezFromInventory(simulator, rotation, position, item, groupOwner, UUID.Random(), false); } /// /// 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 /// UUID of group to own the object /// User defined queryID to correlate replies /// if set to true the simulator /// will automatically send object detail packet(s) back to the client public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryObject item, UUID groupOwner, UUID queryID, bool requestObjectDetails) { RezObjectPacket add = new RezObjectPacket(); add.AgentData.AgentID = _Client.Self.AgentID; add.AgentData.SessionID = _Client.Self.SessionID; add.AgentData.GroupID = groupOwner; add.RezData.FromTaskID = UUID.Zero; add.RezData.BypassRaycast = 1; add.RezData.RayStart = position; add.RezData.RayEnd = position; add.RezData.RayTargetID = UUID.Zero; add.RezData.RayEndIsIntersection = false; add.RezData.RezSelected = requestObjectDetails; add.RezData.RemoveItem = false; add.RezData.ItemFlags = (uint)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 = (uint)item.Flags; add.InventoryData.SaleType = (byte)item.SaleType; add.InventoryData.SalePrice = item.SalePrice; add.InventoryData.Name = Utils.StringToBytes(item.Name); add.InventoryData.Description = Utils.StringToBytes(item.Description); add.InventoryData.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); _Client.Network.SendPacket(add, simulator); return queryID; } /// /// DeRez an object from the simulator to the agents Objects folder in the agents Inventory /// /// The simulator Local ID of the object public void RequestDeRezToInventory(uint objectLocalID) { RequestDeRezToInventory(objectLocalID, DeRezDestination.ObjectsFolder, _Client.Inventory.FindFolderForType(AssetType.Object), UUID.Random()); } /// /// DeRez an object from the simulator and return to inventory /// /// The simulator Local ID of the object /// The type of destination from the enum /// The destination inventory folders -or- /// if DeRezzing object to a tasks Inventory, the Tasks /// The transaction ID for this request which /// can be used to correlate this request with other packets public void RequestDeRezToInventory(uint objectLocalID, DeRezDestination destType, UUID destFolder, UUID transactionID) { DeRezObjectPacket take = new DeRezObjectPacket(); take.AgentData.AgentID = _Client.Self.AgentID; take.AgentData.SessionID = _Client.Self.SessionID; take.AgentBlock = new DeRezObjectPacket.AgentBlockBlock(); take.AgentBlock.GroupID = UUID.Zero; take.AgentBlock.Destination = (byte)destType; take.AgentBlock.DestinationID = destFolder; take.AgentBlock.PacketCount = 1; take.AgentBlock.PacketNumber = 1; take.AgentBlock.TransactionID = transactionID; take.ObjectData = new DeRezObjectPacket.ObjectDataBlock[1]; take.ObjectData[0] = new DeRezObjectPacket.ObjectDataBlock(); take.ObjectData[0].ObjectLocalID = objectLocalID; _Client.Network.SendPacket(take); } /// /// Give an inventory item to another avatar /// /// The of the item to give /// The name of the item /// The type of the item from the enum /// The of the recipient /// true to generate a beameffect during transfer public void GiveItem(UUID itemID, string itemName, AssetType assetType, UUID recipient, bool doEffect) { byte[] bucket; bucket = new byte[17]; bucket[0] = (byte)assetType; Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16); _Client.Self.InstantMessage( _Client.Self.Name, recipient, itemName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, _Client.Self.SimPosition, _Client.Network.CurrentSim.ID, bucket); if (doEffect) { _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } /// /// Give an inventory Folder with contents to another avatar /// /// The of the Folder to give /// The name of the folder /// The type of the item from the enum /// The of the recipient /// true to generate a beameffect during transfer public void GiveFolder(UUID folderID, string folderName, AssetType assetType, UUID recipient, bool doEffect) { byte[] bucket; List folderContents = new List(); _Client.Inventory.FolderContents(folderID, _Client.Self.AgentID, false, true, InventorySortOrder.ByDate, 1000 * 15).ForEach( delegate(InventoryBase ib) { folderContents.Add(_Client.Inventory.FetchItem(ib.UUID, _Client.Self.AgentID, 1000 * 10)); }); bucket = new byte[17 * (folderContents.Count + 1)]; //Add parent folder (first item in bucket) bucket[0] = (byte)assetType; Buffer.BlockCopy(folderID.GetBytes(), 0, bucket, 1, 16); //Add contents to bucket after folder for (int i = 1; i <= folderContents.Count; ++i) { bucket[i * 17] = (byte)folderContents[i - 1].AssetType; Buffer.BlockCopy(folderContents[i - 1].UUID.GetBytes(), 0, bucket, i * 17 + 1, 16); } _Client.Self.InstantMessage( _Client.Self.Name, recipient, folderName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, _Client.Self.SimPosition, _Client.Network.CurrentSim.ID, bucket); if (doEffect) { _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } #endregion Rez/Give #region Task /// /// /// /// /// /// public UUID UpdateTaskInventory(uint objectLocalID, InventoryItem item) { UUID transactionID = UUID.Random(); UpdateTaskInventoryPacket update = new UpdateTaskInventoryPacket(); update.AgentData.AgentID = _Client.Self.AgentID; update.AgentData.SessionID = _Client.Self.SessionID; update.UpdateData.Key = 0; update.UpdateData.LocalID = objectLocalID; update.InventoryData.ItemID = item.UUID; update.InventoryData.FolderID = item.ParentUUID; update.InventoryData.CreatorID = item.CreatorID; update.InventoryData.OwnerID = item.OwnerID; update.InventoryData.GroupID = item.GroupID; update.InventoryData.BaseMask = (uint)item.Permissions.BaseMask; update.InventoryData.OwnerMask = (uint)item.Permissions.OwnerMask; update.InventoryData.GroupMask = (uint)item.Permissions.GroupMask; update.InventoryData.EveryoneMask = (uint)item.Permissions.EveryoneMask; update.InventoryData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; update.InventoryData.GroupOwned = item.GroupOwned; update.InventoryData.TransactionID = transactionID; update.InventoryData.Type = (sbyte)item.AssetType; update.InventoryData.InvType = (sbyte)item.InventoryType; update.InventoryData.Flags = (uint)item.Flags; update.InventoryData.SaleType = (byte)item.SaleType; update.InventoryData.SalePrice = item.SalePrice; update.InventoryData.Name = Utils.StringToBytes(item.Name); update.InventoryData.Description = Utils.StringToBytes(item.Description); update.InventoryData.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); update.InventoryData.CRC = ItemCRC(item); _Client.Network.SendPacket(update); return transactionID; } /// /// Get the inventory of a Task (Primitive) /// /// The tasks /// The tasks simulator local ID /// milliseconds to wait for reply from simulator /// A List containing the inventory items inside the task public List GetTaskInventory(UUID objectID, uint objectLocalID, int timeoutMS) { string filename = null; AutoResetEvent taskReplyEvent = new AutoResetEvent(false); TaskInventoryReplyCallback callback = delegate(UUID itemID, short serial, string assetFilename) { if (itemID == objectID) { filename = assetFilename; taskReplyEvent.Set(); } }; OnTaskInventoryReply += callback; RequestTaskInventory(objectLocalID); if (taskReplyEvent.WaitOne(timeoutMS, false)) { OnTaskInventoryReply -= callback; if (!String.IsNullOrEmpty(filename)) { byte[] assetData = null; ulong xferID = 0; AutoResetEvent taskDownloadEvent = new AutoResetEvent(false); AssetManager.XferReceivedCallback xferCallback = delegate(XferDownload xfer) { if (xfer.XferID == xferID) { assetData = xfer.AssetData; taskDownloadEvent.Set(); } }; _Client.Assets.OnXferReceived += xferCallback; // Start the actual asset xfer xferID = _Client.Assets.RequestAssetXfer(filename, true, false, UUID.Zero, AssetType.Unknown); if (taskDownloadEvent.WaitOne(timeoutMS, false)) { _Client.Assets.OnXferReceived -= xferCallback; string taskList = Utils.BytesToString(assetData); return ParseTaskInventory(taskList); } else { Logger.Log("Timed out waiting for task inventory download for " + filename, Helpers.LogLevel.Warning, _Client); _Client.Assets.OnXferReceived -= xferCallback; return null; } } else { Logger.DebugLog("Task is empty for " + objectLocalID, _Client); return null; } } else { Logger.Log("Timed out waiting for task inventory reply for " + objectLocalID, Helpers.LogLevel.Warning, _Client); OnTaskInventoryReply -= callback; return null; } } /// /// /// /// public void RequestTaskInventory(uint objectLocalID) { RequestTaskInventory(objectLocalID, _Client.Network.CurrentSim); } /// /// Request the contents of a tasks (primitives) inventory /// /// The simulator Local ID of the object /// A reference to the simulator object that contains the object public void RequestTaskInventory(uint objectLocalID, Simulator simulator) { RequestTaskInventoryPacket request = new RequestTaskInventoryPacket(); request.AgentData.AgentID = _Client.Self.AgentID; request.AgentData.SessionID = _Client.Self.SessionID; request.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(request, simulator); } /// /// Moves an Item from an objects (Prim) Inventory to the specified folder in the avatars inventory /// /// LocalID of the object in the simulator /// UUID of the task item to move /// UUID of the folder to move the item to /// Simulator Object public void MoveTaskInventory(uint objectLocalID, UUID taskItemID, UUID inventoryFolderID, Simulator simulator) { MoveTaskInventoryPacket request = new MoveTaskInventoryPacket(); request.AgentData.AgentID = _Client.Self.AgentID; request.AgentData.SessionID = _Client.Self.SessionID; request.AgentData.FolderID = inventoryFolderID; request.InventoryData.ItemID = taskItemID; request.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(request, simulator); } /// /// Remove an item from an objects (Prim) Inventory /// /// LocalID of the object in the simulator /// UUID of the task item to remove /// Simulator Object public void RemoveTaskInventory(uint objectLocalID, UUID taskItemID, Simulator simulator) { RemoveTaskInventoryPacket remove = new RemoveTaskInventoryPacket(); remove.AgentData.AgentID = _Client.Self.AgentID; remove.AgentData.SessionID = _Client.Self.SessionID; remove.InventoryData.ItemID = taskItemID; remove.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(remove, simulator); } #endregion Task #region Helper Functions /// /// Takes an AssetType and returns the string representation /// /// The source /// The string version of the AssetType public static string AssetTypeToString(AssetType type) { return _AssetTypeNames[(int)type]; } /// /// Translate a string name of an AssetType into the proper Type /// /// A string containing the AssetType name /// The AssetType which matches the string name, or AssetType.Unknown if no match was found public static AssetType StringToAssetType(string type) { for (int i = 0; i < _AssetTypeNames.Length; i++) { if (_AssetTypeNames[i] == type) return (AssetType)i; } return AssetType.Unknown; } /// /// Convert an InventoryType to a string /// /// The to convert /// A string representation of the source public static string InventoryTypeToString(InventoryType type) { return _InventoryTypeNames[(int)type]; } /// /// Convert a string into a valid InventoryType /// /// A string representation of the InventoryType to convert /// A InventoryType object which matched the type public static InventoryType StringToInventoryType(string type) { for (int i = 0; i < _InventoryTypeNames.Length; i++) { if (_InventoryTypeNames[i] == type) return (InventoryType)i; } return InventoryType.Unknown; } public static string SaleTypeToString(SaleType type) { return _SaleTypeNames[(int)type]; } public static SaleType StringToSaleType(string value) { for (int i = 0; i < _SaleTypeNames.Length; i++) { if (value == _SaleTypeNames[i]) return (SaleType)i; } return SaleType.Not; } private uint RegisterItemCreatedCallback(ItemCreatedCallback callback) { lock (_CallbacksLock) { if (_CallbackPos == UInt32.MaxValue) _CallbackPos = 0; _CallbackPos++; if (_ItemCreatedCallbacks.ContainsKey(_CallbackPos)) Logger.Log("Overwriting an existing ItemCreatedCallback", Helpers.LogLevel.Warning, _Client); _ItemCreatedCallbacks[_CallbackPos] = callback; return _CallbackPos; } } private uint RegisterItemsCopiedCallback(ItemCopiedCallback callback) { lock (_CallbacksLock) { if (_CallbackPos == UInt32.MaxValue) _CallbackPos = 0; _CallbackPos++; if (_ItemCopiedCallbacks.ContainsKey(_CallbackPos)) Logger.Log("Overwriting an existing ItemsCopiedCallback", Helpers.LogLevel.Warning, _Client); _ItemCopiedCallbacks[_CallbackPos] = callback; return _CallbackPos; } } /// /// Create a CRC from an InventoryItem /// /// The source InventoryItem /// A uint representing the source InventoryItem as a CRC public static uint ItemCRC(InventoryItem iitem) { uint CRC = 0; // IDs CRC += iitem.AssetUUID.CRC(); // AssetID CRC += iitem.ParentUUID.CRC(); // FolderID CRC += iitem.UUID.CRC(); // ItemID // Permission stuff CRC += iitem.CreatorID.CRC(); // CreatorID CRC += iitem.OwnerID.CRC(); // OwnerID CRC += iitem.GroupID.CRC(); // GroupID // CRC += another 4 words which always seem to be zero -- unclear if this is a UUID or what CRC += (uint)iitem.Permissions.OwnerMask; //owner_mask; // Either owner_mask or next_owner_mask may need to be CRC += (uint)iitem.Permissions.NextOwnerMask; //next_owner_mask; // switched with base_mask -- 2 values go here and in my CRC += (uint)iitem.Permissions.EveryoneMask; //everyone_mask; // study item, the three were identical. CRC += (uint)iitem.Permissions.GroupMask; //group_mask; // The rest of the CRC fields CRC += (uint)iitem.Flags; // Flags CRC += (uint)iitem.InventoryType; // InvType CRC += (uint)iitem.AssetType; // Type CRC += (uint)Utils.DateTimeToUnixTime(iitem.CreationDate); // CreationDate CRC += (uint)iitem.SalePrice; // SalePrice CRC += (uint)((uint)iitem.SaleType * 0x07073096); // SaleType return CRC; } /// /// Wrapper for creating a new object /// /// The type of item from the enum /// The of the newly created object /// An object with the type and id passed public static InventoryItem CreateInventoryItem(InventoryType type, UUID 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 InventoryItem SafeCreateInventoryItem(InventoryType InvType, UUID ItemID) { InventoryItem ret = null; if (_Store.Contains(ItemID)) ret = _Store[ItemID] as InventoryItem; if (ret == null) ret = CreateInventoryItem(InvType, ItemID); return ret; } private static bool ParseLine(string line, out string key, out string value) { string origLine = line; // Clean up and convert tabs to spaces line = line.Trim(); line = line.Replace('\t', ' '); // Shrink all whitespace down to single spaces while (line.IndexOf(" ") > 0) line = line.Replace(" ", " "); if (line.Length > 2) { int sep = line.IndexOf(' '); if (sep > 0) { key = line.Substring(0, sep); value = line.Substring(sep + 1); return true; } } else if (line.Length == 1) { key = line; value = String.Empty; return true; } key = null; value = null; return false; } /// /// Parse the results of a RequestTaskInventory() response /// /// A string which contains the data from the task reply /// A List containing the items contained within the tasks inventory public static List ParseTaskInventory(string taskData) { List items = new List(); int lineNum = 0; string[] lines = taskData.Replace("\r\n", "\n").Split('\n'); while (lineNum < lines.Length) { string key, value; if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "inv_object") { #region inv_object // In practice this appears to only be used for folders UUID itemID = UUID.Zero; UUID parentID = UUID.Zero; string name = String.Empty; AssetType assetType = AssetType.Unknown; while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "obj_id") { UUID.TryParse(value, out itemID); } else if (key == "parent_id") { UUID.TryParse(value, out parentID); } else if (key == "type") { assetType = StringToAssetType(value); } else if (key == "name") { name = value.Substring(0, value.IndexOf('|')); } } } if (assetType == AssetType.Folder) { InventoryFolder folder = new InventoryFolder(itemID); folder.Name = name; folder.ParentUUID = parentID; items.Add(folder); } else { InventoryItem item = new InventoryItem(itemID); item.Name = name; item.ParentUUID = parentID; item.AssetType = assetType; items.Add(item); } #endregion inv_object } else if (key == "inv_item") { #region inv_item // Any inventory item that links to an assetID, has permissions, etc UUID itemID = UUID.Zero; UUID assetID = UUID.Zero; UUID parentID = UUID.Zero; UUID creatorID = UUID.Zero; UUID ownerID = UUID.Zero; UUID lastOwnerID = UUID.Zero; UUID groupID = UUID.Zero; bool groupOwned = false; string name = String.Empty; string desc = String.Empty; AssetType assetType = AssetType.Unknown; InventoryType inventoryType = InventoryType.Unknown; DateTime creationDate = Utils.Epoch; uint flags = 0; Permissions perms = Permissions.NoPermissions; SaleType saleType = SaleType.Not; int salePrice = 0; while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "item_id") { UUID.TryParse(value, out itemID); } else if (key == "parent_id") { UUID.TryParse(value, out parentID); } else if (key == "permissions") { #region permissions while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "creator_mask") { // Deprecated uint val; if (Utils.TryParseHex(value, out val)) perms.BaseMask = (PermissionMask)val; } else if (key == "base_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.BaseMask = (PermissionMask)val; } else if (key == "owner_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.OwnerMask = (PermissionMask)val; } else if (key == "group_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.GroupMask = (PermissionMask)val; } else if (key == "everyone_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.EveryoneMask = (PermissionMask)val; } else if (key == "next_owner_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.NextOwnerMask = (PermissionMask)val; } else if (key == "creator_id") { UUID.TryParse(value, out creatorID); } else if (key == "owner_id") { UUID.TryParse(value, out ownerID); } else if (key == "last_owner_id") { UUID.TryParse(value, out lastOwnerID); } else if (key == "group_id") { UUID.TryParse(value, out groupID); } else if (key == "group_owned") { uint val; if (UInt32.TryParse(value, out val)) groupOwned = (val != 0); } } } #endregion permissions } else if (key == "sale_info") { #region sale_info while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "sale_type") { saleType = StringToSaleType(value); } else if (key == "sale_price") { Int32.TryParse(value, out salePrice); } } } #endregion sale_info } else if (key == "shadow_id") { //FIXME: } else if (key == "asset_id") { UUID.TryParse(value, out assetID); } else if (key == "type") { assetType = StringToAssetType(value); } else if (key == "inv_type") { inventoryType = StringToInventoryType(value); } else if (key == "flags") { UInt32.TryParse(value, out flags); } else if (key == "name") { name = value.Substring(0, value.IndexOf('|')); } else if (key == "desc") { desc = value.Substring(0, value.IndexOf('|')); } else if (key == "creation_date") { uint timestamp; if (UInt32.TryParse(value, out timestamp)) creationDate = Utils.UnixTimeToDateTime(timestamp); else Logger.Log("Failed to parse creation_date " + value, Helpers.LogLevel.Warning); } } } InventoryItem item = CreateInventoryItem(inventoryType, itemID); item.AssetUUID = assetID; item.AssetType = assetType; item.CreationDate = creationDate; item.CreatorID = creatorID; item.Description = desc; item.Flags = flags; item.GroupID = groupID; item.GroupOwned = groupOwned; item.Name = name; item.OwnerID = ownerID; item.ParentUUID = parentID; item.Permissions = perms; item.SalePrice = salePrice; item.SaleType = saleType; items.Add(item); #endregion inv_item } else { Logger.Log("Unrecognized token " + key + " in: " + Helpers.NewLine + taskData, Helpers.LogLevel.Error); } } } return items; } #endregion Helper Functions #region Callbacks private void CreateItemFromAssetResponse(CapsClient client, LLSD result, Exception error) { object[] args = (object[])client.UserData; CapsClient.ProgressCallback progCallback = (CapsClient.ProgressCallback)args[0]; ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[1]; byte[] itemData = (byte[])args[2]; if (result == null) { try { callback(false, error.Message, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } return; } LLSDMap contents = (LLSDMap)result; string status = contents["state"].AsString().ToLower(); if (status == "upload") { string uploadURL = contents["uploader"].AsString(); Logger.DebugLog("CreateItemFromAsset: uploading to " + uploadURL); // This makes the assumption that all uploads go to CurrentSim, to avoid // the problem of HttpRequestState not knowing anything about simulators CapsClient upload = new CapsClient(new Uri(uploadURL)); upload.OnProgress += progCallback; upload.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); upload.UserData = new object[] { null, callback, itemData }; upload.StartRequest(itemData, "application/octet-stream"); } else if (status == "complete") { Logger.DebugLog("CreateItemFromAsset: completed"); if (contents.ContainsKey("new_inventory_item") && contents.ContainsKey("new_asset")) { try { callback(true, String.Empty, contents["new_inventory_item"].AsUUID(), contents["new_asset"].AsUUID()); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } else { try { callback(false, "Failed to parse asset and item UUIDs", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } else { // Failure try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } 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; Logger.Log("SaveAssetIntoInventory packet received, someone write this function!", Helpers.LogLevel.Error, _Client); } private void InventoryDescendentsHandler(Packet packet, Simulator simulator) { InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet; if (reply.AgentData.Descendents > 0) { // InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders if (reply.FolderData[0].FolderID != UUID.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 = Utils.BytesToString(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 != UUID.Zero) { // Iterate items in this packet for (int i = 0; i < reply.ItemData.Length; i++) { if (reply.ItemData[i].ItemID != UUID.Zero) { InventoryItem item; /* * Objects that have been attached in-world prior to being stored on the * asset server are stored with the InventoryType of 0 (Texture) * instead of 17 (Attachment) * * This corrects that behavior by forcing Object Asset types that have an * invalid InventoryType with the proper InventoryType of Attachment. */ if ((AssetType)reply.ItemData[i].Type == AssetType.Object && (InventoryType)reply.ItemData[i].InvType == InventoryType.Texture) { item = CreateInventoryItem(InventoryType.Attachment, reply.ItemData[i].ItemID); item.InventoryType = InventoryType.Attachment; } else { item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID); item.InventoryType = (InventoryType)reply.ItemData[i].InvType; } 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 = Utils.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate); item.Description = Utils.BytesToString(reply.ItemData[i].Description); item.Flags = reply.ItemData[i].Flags; item.Name = Utils.BytesToString(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; } } } } InventoryFolder parentFolder = null; if (_Store.Contains(reply.AgentData.FolderID) && _Store[reply.AgentData.FolderID] is InventoryFolder) { parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder; } else { Logger.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToString() + " or it is not a folder", Helpers.LogLevel.Error, _Client); return; } if (reply.AgentData.Version < parentFolder.Version) { Logger.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name + ", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version, Helpers.LogLevel.Warning, _Client); return; } parentFolder.Version = reply.AgentData.Version; // FIXME: reply.AgentData.Descendants is not parentFolder.DescendentCount if we didn't // request items and folders parentFolder.DescendentCount = reply.AgentData.Descendents; #region FindObjectsByPath Handling if (_Searches.Count > 0) { lock (_Searches) { StartSearch: // Iterate over all of the outstanding searches for (int i = 0; i < _Searches.Count; i++) { InventorySearch search = _Searches[i]; List folderContents = _Store.GetContents(search.Folder); // Iterate over all of the inventory objects in the base search folder for (int j = 0; j < folderContents.Count; j++) { // Check if this inventory object matches the current path node if (folderContents[j].Name == search.Path[search.Level]) { if (search.Level == search.Path.Length - 1) { Logger.DebugLog("Finished path search of " + String.Join("/", search.Path), _Client); // This is the last node in the path, fire the callback and clean up if (OnFindObjectByPath != null) { try { OnFindObjectByPath(String.Join("/", search.Path), folderContents[j].UUID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // Remove this entry and restart the loop since we are changing the collection size _Searches.RemoveAt(i); goto StartSearch; } else { // We found a match but it is not the end of the path, request the next level Logger.DebugLog(String.Format("Matched level {0}/{1} in a path search of {2}", search.Level, search.Path.Length - 1, String.Join("/", search.Path)), _Client); search.Folder = folderContents[j].UUID; search.Level++; _Searches[i] = search; RequestFolderContents(search.Folder, search.Owner, true, true, InventorySortOrder.ByName); } } } } } } #endregion FindObjectsByPath Handling // Callback for inventory folder contents being updated if (OnFolderUpdated != null) { try { OnFolderUpdated(parentFolder.UUID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } /// /// 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) { Logger.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet, this should not happen!", Helpers.LogLevel.Error, _Client); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(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; // Update the local copy _Store[item.UUID] = item; // Look for an "item created" callback ItemCreatedCallback createdCallback; if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out createdCallback)) { _ItemCreatedCallbacks.Remove(dataBlock.CallbackID); try { createdCallback(true, item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // TODO: Is this callback even triggered when items are copied? // Look for an "item copied" callback ItemCopiedCallback copyCallback; if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) { _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); try { copyCallback(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } //This is triggered when an item is received from a task if (OnTaskItemReceived != null) { try { OnTaskItemReceived(item.UUID, dataBlock.FolderID, item.CreatorID, item.AssetUUID, item.InventoryType); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } 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 = Utils.BytesToString(move.InventoryData[i].NewName); Logger.Log(String.Format( "MoveInventoryItemHandler: Item {0} is moving to Folder {1} with new name \"{2}\". Someone write this function!", move.InventoryData[i].ItemID.ToString(), move.InventoryData[i].FolderID.ToString(), newName), Helpers.LogLevel.Warning, _Client); } } private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator) { BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket; if (update.FolderData.Length > 0 && update.FolderData[0].FolderID != UUID.Zero) { foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData) { if (!_Store.Contains(dataBlock.FolderID)) Logger.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning, _Client); InventoryFolder folder = new InventoryFolder(dataBlock.FolderID); folder.Name = Utils.BytesToString(dataBlock.Name); folder.OwnerID = update.AgentData.AgentID; folder.ParentUUID = dataBlock.ParentID; _Store[folder.UUID] = folder; } } if (update.ItemData.Length > 0 && update.ItemData[0].ItemID != UUID.Zero) { for (int i = 0; i < update.ItemData.Length; i++) { BulkUpdateInventoryPacket.ItemDataBlock dataBlock = update.ItemData[i]; // If we are given a folder of items, the item information might arrive before the folder // (parent) is in the store if (!_Store.Contains(dataBlock.ItemID)) Logger.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning, _Client); InventoryItem item = SafeCreateInventoryItem((InventoryType)dataBlock.InvType, dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; if (dataBlock.AssetID != UUID.Zero) item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(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; // Look for an "item created" callback ItemCreatedCallback callback; if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback)) { _ItemCreatedCallbacks.Remove(dataBlock.CallbackID); try { callback(true, item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // Look for an "item copied" callback ItemCopiedCallback copyCallback; if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) { _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); try { copyCallback(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } } 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) { Logger.Log("Received FetchInventoryReply for an inventory folder, this should not happen!", Helpers.LogLevel.Error, _Client); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(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; // Fire the callback for an item being fetched if (OnItemReceived != null) { try { OnItemReceived(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } private void ReplyTaskInventoryHandler(Packet packet, Simulator simulator) { if (OnTaskInventoryReply != null) { ReplyTaskInventoryPacket reply = (ReplyTaskInventoryPacket)packet; try { OnTaskInventoryReply(reply.InventoryData.TaskID, reply.InventoryData.Serial, Utils.BytesToString(reply.InventoryData.Filename)); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } private void Self_OnInstantMessage(InstantMessage im, Simulator simulator) { // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we // handle it here? if (OnObjectOffered != null && (im.Dialog == InstantMessageDialog.InventoryOffered || im.Dialog == InstantMessageDialog.TaskInventoryOffered)) { AssetType type = AssetType.Unknown; UUID objectID = UUID.Zero; bool fromTask = false; if (im.Dialog == InstantMessageDialog.InventoryOffered) { if (im.BinaryBucket.Length == 17) { type = (AssetType)im.BinaryBucket[0]; objectID = new UUID(im.BinaryBucket, 1); fromTask = false; } else { Logger.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning, _Client); return; } } else if (im.Dialog == InstantMessageDialog.TaskInventoryOffered) { if (im.BinaryBucket.Length == 1) { type = (AssetType)im.BinaryBucket[0]; fromTask = true; } else { Logger.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning, _Client); return; } } // Find the folder where this is going to go UUID destinationFolderID = FindFolderForType(type); // Fire the callback try { ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket(); imp.AgentData.AgentID = _Client.Self.AgentID; imp.AgentData.SessionID = _Client.Self.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 = Utils.StringToBytes(_Client.Self.Name); imp.MessageBlock.Message = new byte[0]; imp.MessageBlock.ParentEstateID = 0; imp.MessageBlock.RegionID = UUID.Zero; imp.MessageBlock.Position = _Client.Self.SimPosition; if (OnObjectOffered(im, 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.TaskInventoryAccepted; 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) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData replyData) { if (loginSuccess) { // Initialize the store here so we know who owns it: _Store = new Inventory(_Client, this, _Client.Self.AgentID); Logger.DebugLog("Setting InventoryRoot to " + replyData.InventoryRoot.ToString(), _Client); InventoryFolder rootFolder = new InventoryFolder(replyData.InventoryRoot); rootFolder.Name = String.Empty; rootFolder.ParentUUID = UUID.Zero; _Store.RootFolder = rootFolder; for (int i = 0; i < replyData.InventorySkeleton.Length; i++) _Store.UpdateNodeFor(replyData.InventorySkeleton[i]); InventoryFolder libraryRootFolder = new InventoryFolder(replyData.LibraryRoot); libraryRootFolder.Name = String.Empty; libraryRootFolder.ParentUUID = UUID.Zero; _Store.LibraryFolder = libraryRootFolder; for(int i = 0; i < replyData.LibrarySkeleton.Length; i++) _Store.UpdateNodeFor(replyData.LibrarySkeleton[i]); } } private void UploadNotecardAssetResponse(CapsClient client, LLSD result, Exception error) { LLSDMap contents = (LLSDMap)result; KeyValuePair kvp = (KeyValuePair)(((object[])client.UserData)[0]); NotecardUploadedAssetCallback callback = kvp.Key; byte[] itemData = (byte[])kvp.Value; string status = contents["state"].AsString(); if (status == "upload") { string uploadURL = contents["uploader"].AsString(); // This makes the assumption that all uploads go to CurrentSim, to avoid // the problem of HttpRequestState not knowing anything about simulators CapsClient upload = new CapsClient(new Uri(uploadURL)); upload.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; upload.StartRequest(itemData, "application/octet-stream"); } else if (status == "complete") { if (contents.ContainsKey("new_asset")) { try { callback(true, String.Empty, (UUID)(((object[])client.UserData)[1]), contents["new_asset"].AsUUID()); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } else { try { callback(false, "Failed to parse asset and item UUIDs", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } else { // Failure try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } #endregion Callbacks } }