/* * 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.Net; using System.Text.RegularExpressions; using System.Threading; using System.Text; using System.Runtime.Serialization; using OpenMetaverse.Http; using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; using OpenMetaverse.Packets; namespace OpenMetaverse { #region Enums [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 { /// AgentInventorySave = 0, /// Copy from in-world to agent inventory AgentInventoryCopy = 1, /// Derez to TaskInventory TaskInventory = 2, /// Attachment = 3, /// Take Object AgentInventoryTake = 4, /// ForceToGodInventory = 5, /// Delete Object TrashFolder = 6, /// Put an avatar attachment into agent inventory AttachmentToInventory = 7, /// AttachmentExists = 8, /// Return an object back to the owner's inventory ReturnToOwner = 9, /// Return a deeded object back to the last owner's inventory ReturnToLastOwner = 10 } /// /// Upper half of the Flags field for inventory items /// [Flags] public enum InventoryItemFlags : uint { None = 0, /// Indicates that the NextOwner permission will be set to the /// most restrictive set of permissions found in the object set /// (including linkset items and object inventory items) on next rez ObjectSlamPerm = 0x100, /// Indicates that the object sale information has been /// changed ObjectSlamSale = 0x1000, /// If set, and a slam bit is set, indicates BaseMask will be overwritten on Rez ObjectOverwriteBase = 0x010000, /// If set, and a slam bit is set, indicates OwnerMask will be overwritten on Rez ObjectOverwriteOwner = 0x020000, /// If set, and a slam bit is set, indicates GroupMask will be overwritten on Rez ObjectOverwriteGroup = 0x040000, /// If set, and a slam bit is set, indicates EveryoneMask will be overwritten on Rez ObjectOverwriteEveryone = 0x080000, /// If set, and a slam bit is set, indicates NextOwnerMask will be overwritten on Rez ObjectOverwriteNextOwner = 0x100000, /// Indicates whether this object is composed of multiple /// items or not ObjectHasMultipleItems = 0x200000, /// Indicates that the asset is only referenced by this /// inventory item. If this item is deleted or updated to reference a /// new assetID, the asset can be deleted SharedSingleReference = 0x40000000, } #endregion Enums #region Inventory Object Classes /// /// Base Class for Inventory Items /// [Serializable()] public abstract class InventoryBase : ISerializable { /// of item/folder public 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; } /// /// /// /// public virtual void GetObjectData(SerializationInfo info, StreamingContext ctxt) { info.AddValue("UUID", UUID); info.AddValue("ParentUUID", ParentUUID); info.AddValue("Name", Name); info.AddValue("OwnerID", OwnerID); } /// /// /// /// public InventoryBase(SerializationInfo info, StreamingContext ctxt) { UUID = (UUID)info.GetValue("UUID", typeof(UUID)); ParentUUID = (UUID)info.GetValue("ParentUUID", typeof(UUID)); Name = (string)info.GetValue("Name", typeof(string)); OwnerID = (UUID)info.GetValue("OwnerID", typeof(UUID)); } /// /// 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; /// Used to update the AssetID in requests sent to the server public UUID TransactionID; /// The of the previous owner of the item public UUID LastOwnerID; /// /// 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; } /// /// /// /// override public void GetObjectData(SerializationInfo info, StreamingContext ctxt) { base.GetObjectData(info, ctxt); info.AddValue("AssetUUID", AssetUUID, typeof(UUID)); info.AddValue("Permissions", Permissions, typeof(Permissions)); info.AddValue("AssetType", AssetType); info.AddValue("InventoryType", InventoryType); info.AddValue("CreatorID", CreatorID); info.AddValue("Description", Description); info.AddValue("GroupID", GroupID); info.AddValue("GroupOwned", GroupOwned); info.AddValue("SalePrice", SalePrice); info.AddValue("SaleType", SaleType); info.AddValue("Flags", Flags); info.AddValue("CreationDate", CreationDate); info.AddValue("LastOwnerID", LastOwnerID); } /// /// /// /// public InventoryItem(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { AssetUUID = (UUID)info.GetValue("AssetUUID", typeof(UUID)); Permissions = (Permissions)info.GetValue("Permissions", typeof(Permissions)); AssetType = (AssetType)info.GetValue("AssetType", typeof(AssetType)); InventoryType = (InventoryType)info.GetValue("InventoryType", typeof(InventoryType)); CreatorID = (UUID)info.GetValue("CreatorID", typeof(UUID)); Description = (string)info.GetValue("Description", typeof(string)); GroupID = (UUID)info.GetValue("GroupID", typeof(UUID)); GroupOwned = (bool)info.GetValue("GroupOwned", typeof(bool)); SalePrice = (int)info.GetValue("SalePrice", typeof(int)); SaleType = (SaleType)info.GetValue("SaleType", typeof(SaleType)); Flags = (uint)info.GetValue("Flags", typeof(uint)); CreationDate = (DateTime)info.GetValue("CreationDate", typeof(DateTime)); LastOwnerID = (UUID)info.GetValue("LastOwnerID", typeof(UUID)); } /// /// 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() ^ LastOwnerID.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 && o.LastOwnerID == LastOwnerID; } } /// /// 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; } /// /// Construct an InventoryTexture object from a serialization stream /// public InventoryTexture(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventorySound object from a serialization stream /// public InventorySound(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryCallingCard object from a serialization stream /// public InventoryCallingCard(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryLandmark object from a serialization stream /// public InventoryLandmark(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { InventoryType = InventoryType.Landmark; } /// /// Landmarks use the InventoryItemFlags struct and will have a flag of 1 set if they have been visited /// public bool LandmarkVisited { get { return (Flags & 1) != 0; } set { if (value) Flags |= 1; else Flags &= ~1u; } } } /// /// 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; } /// /// Construct an InventoryObject object from a serialization stream /// public InventoryObject(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { InventoryType = InventoryType.Object; } /// /// Gets or sets the upper byte of the Flags value /// public InventoryItemFlags ItemFlags { get { return (InventoryItemFlags)(Flags & ~0xFF); } set { Flags = (uint)value | (Flags & 0xFF); } } /// /// Gets or sets the object attachment point, the lower byte of the Flags value /// public AttachmentPoint AttachPoint { get { return (AttachmentPoint)(Flags & 0xFF); } set { Flags = (uint)value | (Flags & 0xFFFFFF00); } } } /// /// 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; } /// /// Construct an InventoryNotecard object from a serialization stream /// public InventoryNotecard(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryCategory object from a serialization stream /// public InventoryCategory(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryLSL object from a serialization stream /// public InventoryLSL(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventorySnapshot object from a serialization stream /// public InventorySnapshot(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryAttachment object from a serialization stream /// public InventoryAttachment(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryWearable object from a serialization stream /// public InventoryWearable(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryAnimation object from a serialization stream /// public InventoryAnimation(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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; } /// /// Construct an InventoryGesture object from a serialization stream /// public InventoryGesture(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { 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) { PreferredType = AssetType.Unknown; Version = 1; DescendentCount = 0; } /// /// /// /// public override string ToString() { return Name; } /// /// Get Serilization data for this InventoryFolder object /// override public void GetObjectData(SerializationInfo info, StreamingContext ctxt) { base.GetObjectData(info, ctxt); info.AddValue("PreferredType", PreferredType, typeof(AssetType)); info.AddValue("Version", Version); info.AddValue("DescendentCount", DescendentCount); } /// /// Construct an InventoryFolder object from a serialization stream /// public InventoryFolder(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt) { PreferredType = (AssetType)info.GetValue("PreferredType", typeof(AssetType)); Version = (int)info.GetValue("Version", typeof(int)); DescendentCount = (int)info.GetValue("DescendentCount", typeof(int)); } /// /// /// /// 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 { /// Used for converting shadow_id to asset_id public static readonly UUID MAGIC_ID = new UUID("3c115e51-04f4-523c-9fa6-98aff1034730"); 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); /// The event subscribers, null of no subscribers private EventHandler m_ItemReceived; ///Raises the ItemReceived Event /// A ItemReceivedEventArgs object containing /// the data sent from the simulator protected virtual void OnItemReceived(ItemReceivedEventArgs e) { EventHandler handler = m_ItemReceived; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ItemReceivedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler ItemReceived { add { lock (m_ItemReceivedLock) { m_ItemReceived += value; } } remove { lock (m_ItemReceivedLock) { m_ItemReceived -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_FolderUpdated; ///Raises the FolderUpdated Event /// A FolderUpdatedEventArgs object containing /// the data sent from the simulator protected virtual void OnFolderUpdated(FolderUpdatedEventArgs e) { EventHandler handler = m_FolderUpdated; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_FolderUpdatedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler FolderUpdated { add { lock (m_FolderUpdatedLock) { m_FolderUpdated += value; } } remove { lock (m_FolderUpdatedLock) { m_FolderUpdated -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_InventoryObjectOffered; ///Raises the InventoryObjectOffered Event /// A InventoryObjectOfferedEventArgs object containing /// the data sent from the simulator protected virtual void OnInventoryObjectOffered(InventoryObjectOfferedEventArgs e) { EventHandler handler = m_InventoryObjectOffered; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_InventoryObjectOfferedLock = new object(); /// Raised when the simulator sends us data containing /// an inventory object sent by another avatar or primitive public event EventHandler InventoryObjectOffered { add { lock (m_InventoryObjectOfferedLock) { m_InventoryObjectOffered += value; } } remove { lock (m_InventoryObjectOfferedLock) { m_InventoryObjectOffered -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_TaskItemReceived; ///Raises the TaskItemReceived Event /// A TaskItemReceivedEventArgs object containing /// the data sent from the simulator protected virtual void OnTaskItemReceived(TaskItemReceivedEventArgs e) { EventHandler handler = m_TaskItemReceived; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_TaskItemReceivedLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler TaskItemReceived { add { lock (m_TaskItemReceivedLock) { m_TaskItemReceived += value; } } remove { lock (m_TaskItemReceivedLock) { m_TaskItemReceived -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_FindObjectByPathReply; ///Raises the FindObjectByPath Event /// A FindObjectByPathEventArgs object containing /// the data sent from the simulator protected virtual void OnFindObjectByPathReply(FindObjectByPathReplyEventArgs e) { EventHandler handler = m_FindObjectByPathReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_FindObjectByPathReplyLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler FindObjectByPathReply { add { lock (m_FindObjectByPathReplyLock) { m_FindObjectByPathReply += value; } } remove { lock (m_FindObjectByPathReplyLock) { m_FindObjectByPathReply -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_TaskInventoryReply; ///Raises the TaskInventoryReply Event /// A TaskInventoryReplyEventArgs object containing /// the data sent from the simulator protected virtual void OnTaskInventoryReply(TaskInventoryReplyEventArgs e) { EventHandler handler = m_TaskInventoryReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_TaskInventoryReplyLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler TaskInventoryReply { add { lock (m_TaskInventoryReplyLock) { m_TaskInventoryReply += value; } } remove { lock (m_TaskInventoryReplyLock) { m_TaskInventoryReply -= value; } } } /// /// Reply received when uploading an inventory asset /// /// Has upload been successful /// Error message if upload failed /// Inventory asset UUID /// New asset UUID public delegate void InventoryUploadedAssetCallback(bool success, string status, UUID itemID, UUID assetID); /// The event subscribers, null of no subscribers private EventHandler m_SaveAssetToInventory; ///Raises the SaveAssetToInventory Event /// A SaveAssetToInventoryEventArgs object containing /// the data sent from the simulator protected virtual void OnSaveAssetToInventory(SaveAssetToInventoryEventArgs e) { EventHandler handler = m_SaveAssetToInventory; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SaveAssetToInventoryLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler SaveAssetToInventory { add { lock (m_SaveAssetToInventoryLock) { m_SaveAssetToInventory += value; } } remove { lock (m_SaveAssetToInventoryLock) { m_SaveAssetToInventory -= value; } } } /// /// /// /// /// /// /// public delegate void ScriptUpdatedCallback(bool success, string status, UUID itemID, UUID assetID); /// The event subscribers, null of no subscribers private EventHandler m_ScriptRunningReply; ///Raises the ScriptRunningReply Event /// A ScriptRunningReplyEventArgs object containing /// the data sent from the simulator protected virtual void OnScriptRunningReply(ScriptRunningReplyEventArgs e) { EventHandler handler = m_ScriptRunningReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ScriptRunningReplyLock = new object(); /// Raised when the simulator sends us data containing /// ... public event EventHandler ScriptRunningReply { add { lock (m_ScriptRunningReplyLock) { m_ScriptRunningReply += value; } } remove { lock (m_ScriptRunningReplyLock) { m_ScriptRunningReply -= value; } } } #endregion Delegates #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" }; #endregion String Arrays 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 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, UpdateCreateInventoryItemHandler); Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, SaveAssetIntoInventoryHandler); Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, BulkUpdateInventoryHandler); Client.Network.RegisterCallback(PacketType.MoveInventoryItem, MoveInventoryItemHandler); Client.Network.RegisterCallback(PacketType.InventoryDescendents, InventoryDescendentsHandler); Client.Network.RegisterCallback(PacketType.FetchInventoryReply, FetchInventoryReplyHandler); Client.Network.RegisterCallback(PacketType.ReplyTaskInventory, ReplyTaskInventoryHandler); Client.Network.RegisterEventCallback("ScriptRunningReply", new Caps.EventQueueCallback(ScriptRunningReplyMessageHandler)); // Watch for inventory given to us through instant message Client.Self.IM += Self_IM; // 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; EventHandler callback = delegate(object sender, ItemReceivedEventArgs e) { if (e.Item.UUID == itemID) { fetchedItem = e.Item; fetchEvent.Set(); } }; ItemReceived += callback; RequestFetchInventory(itemID, ownerID); fetchEvent.WaitOne(timeoutMS, false); ItemReceived -= 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); EventHandler callback = delegate(object sender, FolderUpdatedEventArgs e) { if (e.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(); } }; FolderUpdated += callback; RequestFolderContents(folder, owner, folders, items, order); if (fetchEvent.WaitOne(timeoutMS, false)) objects = _Store.GetContents(folder); FolderUpdated -= 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; EventHandler callback = delegate(object sender, FindObjectByPathReplyEventArgs e) { if (e.Path == path) { foundItem = e.InventoryObjectID; findEvent.Set(); } }; FindObjectByPathReply += callback; RequestFindObjectByPath(baseFolder, inventoryOwner, path); findEvent.WaitOne(timeoutMS, false); FindObjectByPathReply -= 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; inv.ParentUUID = newparentID; _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.Name = newName; 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 = Utils.EmptyBytes; 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; } /// /// Create an inventory item and upload asset data /// /// Asset data /// Inventory item name /// Inventory item description /// Asset type /// Inventory type /// Put newly created inventory in this folder /// Delegate that will receive feedback on success or failure public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, InventoryType invType, UUID folderID, ItemCreatedFromAssetCallback callback) { Permissions permissions = new Permissions(); permissions.EveryoneMask = PermissionMask.None; permissions.GroupMask = PermissionMask.None; permissions.NextOwnerMask = PermissionMask.All; RequestCreateItemFromAsset(data, name, description, assetType, invType, folderID, permissions, callback); } /// /// Create an inventory item and upload asset data /// /// Asset data /// Inventory item name /// Inventory item description /// Asset type /// Inventory type /// Put newly created inventory in this folder /// Permission of the newly created item /// (EveryoneMask, GroupMask, and NextOwnerMask of Permissions struct are supported) /// Delegate that will receive feedback on success or failure public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, InventoryType invType, UUID folderID, Permissions permissions, 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) { OSDMap query = new OSDMap(); query.Add("folder_id", OSD.FromUUID(folderID)); query.Add("asset_type", OSD.FromString(Utils.AssetTypeToString(assetType))); query.Add("inventory_type", OSD.FromString(Utils.InventoryTypeToString(invType))); query.Add("name", OSD.FromString(name)); query.Add("description", OSD.FromString(description)); query.Add("everyone_mask", OSD.FromInteger((int)permissions.EveryoneMask)); query.Add("group_mask", OSD.FromInteger((int)permissions.GroupMask)); query.Add("next_owner_mask", OSD.FromInteger((int)permissions.NextOwnerMask)); query.Add("expected_upload_cost", OSD.FromInteger(Client.Settings.UPLOAD_COST)); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += CreateItemFromAssetResponse; request.UserData = new object[] { callback, data, Client.Settings.CAPS_TIMEOUT, query }; request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } 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 = Utils.EmptyBytes; } Client.Network.SendPacket(copy); } /// /// Request a copy of an asset embedded within a notecard /// /// Usually UUID.Zero for copying an asset from a notecard /// UUID of the notecard to request an asset from /// Target folder for asset to go to in your inventory /// UUID of the embedded asset /// callback to run when item is copied to inventory public void RequestCopyItemFromNotecard(UUID objectID, UUID notecardID, UUID folderID, UUID itemID, ItemCopiedCallback callback) { _ItemCopiedCallbacks[0] = callback; //Notecards always use callback ID 0 Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("CopyInventoryFromNotecard"); if (url != null) { CopyInventoryFromNotecardMessage message = new CopyInventoryFromNotecardMessage(); message.CallbackID = 0; message.FolderID = folderID; message.ItemID = itemID; message.NotecardID = notecardID; message.ObjectID = objectID; CapsClient request = new CapsClient(url); request.BeginGetResponse(message.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { 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 = item.TransactionID; block.Type = (sbyte)item.AssetType; update.InventoryData[i] = block; } Client.Network.SendPacket(update); } /// /// /// /// /// /// public void RequestUploadNotecardAsset(byte[] data, UUID notecardID, InventoryUploadedAssetCallback 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) { OSDMap query = new OSDMap(); query.Add("item_id", OSD.FromUUID(notecardID)); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += UploadInventoryAssetResponse; request.UserData = new object[] { new KeyValuePair(callback, data), notecardID }; request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); } } /// /// Upload new gesture asset for an inventory gesture item /// /// Encoded gesture asset /// Gesture inventory UUID /// Callback whick will be called when upload is complete public void RequestUploadGestureAsset(byte[] data, UUID gestureID, InventoryUploadedAssetCallback callback) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) throw new Exception("UpdateGestureAgentInventory capability is not currently available"); Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("UpdateGestureAgentInventory"); if (url != null) { OSDMap query = new OSDMap(); query.Add("item_id", OSD.FromUUID(gestureID)); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += UploadInventoryAssetResponse; request.UserData = new object[] { new KeyValuePair(callback, data), gestureID }; request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { throw new Exception("UpdateGestureAgentInventory capability is not currently available"); } } /// /// Update an existing script in an agents Inventory /// /// A byte[] array containing the encoded scripts contents /// the itemID of the script /// if true, sets the script content to run on the mono interpreter /// public void RequestUpdateScriptAgentInventory(byte[] data, UUID itemID, bool mono, ScriptUpdatedCallback callback) { Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("UpdateScriptAgent"); if (url != null) { UpdateScriptAgentRequestMessage msg = new UpdateScriptAgentRequestMessage(); msg.ItemID = itemID; msg.Target = mono ? "mono" : "lsl2"; CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(UpdateScriptAgentInventoryResponse); request.UserData = new object[2] { new KeyValuePair(callback, data), itemID }; request.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { throw new Exception("UpdateScriptAgent 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 /// InventoryItem object containing item details public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryItem item) { return RequestRezFromInventory(simulator, rotation, position, item, Client.Self.ActiveGroup, UUID.Random(), true); } /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// Vector of where to place object /// InventoryItem object containing item details /// UUID of group to own the object public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryItem item, UUID groupOwner) { return RequestRezFromInventory(simulator, rotation, position, item, groupOwner, UUID.Random(), true); } /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// Vector of where to place object /// InventoryItem object containing item details /// UUID of group to own the object /// User defined queryID to correlate replies /// If set to true, the CreateSelected flag /// will be set on the rezzed object public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryItem item, UUID groupOwner, UUID queryID, bool rezSelected) { 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 = rezSelected; 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 /// If objectLocalID is a child primitive in a linkset, the entire linkset will be derezzed public void RequestDeRezToInventory(uint objectLocalID) { RequestDeRezToInventory(objectLocalID, DeRezDestination.AgentInventoryTake, 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 /// If objectLocalID is a child primitive in a linkset, the entire linkset will be derezzed 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); } /// /// Rez an item from inventory to its previous simulator location /// /// /// /// /// public UUID RequestRestoreRezFromInventory(Simulator simulator, InventoryItem item, UUID queryID) { RezRestoreToWorldPacket add = new RezRestoreToWorldPacket(); add.AgentData.AgentID = Client.Self.AgentID; add.AgentData.SessionID = Client.Self.SessionID; 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; } /// /// 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 /// /// Copy or move an from agent inventory to a task (primitive) inventory /// /// The target object /// The item to copy or move from inventory /// /// For items with copy permissions a copy of the item is placed in the tasks inventory, /// for no-copy items the object is moved to the tasks inventory // DocTODO: what does the return UUID correlate to if anything? 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; } /// /// Retrieve a listing of the items contained in 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 or null /// if a timeout occurs /// This request blocks until the response from the simulator arrives /// or timeoutMS is exceeded public List GetTaskInventory(UUID objectID, UInt32 objectLocalID, Int32 timeoutMS) { String filename = null; AutoResetEvent taskReplyEvent = new AutoResetEvent(false); EventHandler callback = delegate(object sender, TaskInventoryReplyEventArgs e) { if (e.ItemID == objectID) { filename = e.AssetFilename; taskReplyEvent.Set(); } }; TaskInventoryReply += callback; RequestTaskInventory(objectLocalID); if (taskReplyEvent.WaitOne(timeoutMS, false)) { TaskInventoryReply -= 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, true); 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 new List(0); } } else { Logger.Log("Timed out waiting for task inventory reply for " + objectLocalID, Helpers.LogLevel.Warning, Client); TaskInventoryReply -= callback; return null; } } /// /// Request the contents of a tasks (primitives) inventory from the /// current simulator /// /// The LocalID of the object /// 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); } /// /// Move an item from a tasks (Primitive) inventory to the specified folder in the avatars inventory /// /// LocalID of the object in the simulator /// UUID of the task item to move /// The ID of the destination folder in this agents inventory /// Simulator Object /// Raises the event 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 /// You can confirm the removal by comparing the tasks inventory serial before and after the /// request with the request combined with /// the event 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); } /// /// Copy an InventoryScript item from the Agents Inventory into a primitives task inventory /// /// An unsigned integer representing a primitive being simulated /// An which represents a script object from the agents inventory /// true to set the scripts running state to enabled /// A Unique Transaction ID /// /// The following example shows the basic steps necessary to copy a script from the agents inventory into a tasks inventory /// and assumes the script exists in the agents inventory. /// /// uint primID = 95899503; // Fake prim ID /// UUID scriptID = UUID.Parse("92a7fe8a-e949-dd39-a8d8-1681d8673232"); // Fake Script UUID in Inventory /// /// Client.Inventory.FolderContents(Client.Inventory.FindFolderForType(AssetType.LSLText), Client.Self.AgentID, /// false, true, InventorySortOrder.ByName, 10000); /// /// Client.Inventory.RezScript(primID, (InventoryItem)Client.Inventory.Store[scriptID]); /// /// // DocTODO: what does the return UUID correlate to if anything? public UUID CopyScriptToTask(uint objectLocalID, InventoryItem item, bool enableScript) { UUID transactionID = UUID.Random(); RezScriptPacket ScriptPacket = new RezScriptPacket(); ScriptPacket.AgentData.AgentID = Client.Self.AgentID; ScriptPacket.AgentData.SessionID = Client.Self.SessionID; ScriptPacket.UpdateBlock.ObjectLocalID = objectLocalID; ScriptPacket.UpdateBlock.Enabled = enableScript; ScriptPacket.InventoryBlock.ItemID = item.UUID; ScriptPacket.InventoryBlock.FolderID = item.ParentUUID; ScriptPacket.InventoryBlock.CreatorID = item.CreatorID; ScriptPacket.InventoryBlock.OwnerID = item.OwnerID; ScriptPacket.InventoryBlock.GroupID = item.GroupID; ScriptPacket.InventoryBlock.BaseMask = (uint)item.Permissions.BaseMask; ScriptPacket.InventoryBlock.OwnerMask = (uint)item.Permissions.OwnerMask; ScriptPacket.InventoryBlock.GroupMask = (uint)item.Permissions.GroupMask; ScriptPacket.InventoryBlock.EveryoneMask = (uint)item.Permissions.EveryoneMask; ScriptPacket.InventoryBlock.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; ScriptPacket.InventoryBlock.GroupOwned = item.GroupOwned; ScriptPacket.InventoryBlock.TransactionID = transactionID; ScriptPacket.InventoryBlock.Type = (sbyte)item.AssetType; ScriptPacket.InventoryBlock.InvType = (sbyte)item.InventoryType; ScriptPacket.InventoryBlock.Flags = (uint)item.Flags; ScriptPacket.InventoryBlock.SaleType = (byte)item.SaleType; ScriptPacket.InventoryBlock.SalePrice = item.SalePrice; ScriptPacket.InventoryBlock.Name = Utils.StringToBytes(item.Name); ScriptPacket.InventoryBlock.Description = Utils.StringToBytes(item.Description); ScriptPacket.InventoryBlock.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); ScriptPacket.InventoryBlock.CRC = ItemCRC(item); Client.Network.SendPacket(ScriptPacket); return transactionID; } /// /// Request the running status of a script contained in a task (primitive) inventory /// /// The ID of the primitive containing the script /// The ID of the script /// The event can be used to obtain the results of the /// request /// public void RequestGetScriptRunning(UUID objectID, UUID scriptID) { GetScriptRunningPacket request = new GetScriptRunningPacket(); request.Script.ObjectID = objectID; request.Script.ItemID = scriptID; Client.Network.SendPacket(request); } /// /// Send a request to set the running state of a script contained in a task (primitive) inventory /// /// The ID of the primitive containing the script /// The ID of the script /// true to set the script running, false to stop a running script /// To verify the change you can use the method combined /// with the event public void RequestSetScriptRunning(UUID objectID, UUID scriptID, bool running) { SetScriptRunningPacket request = new SetScriptRunningPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Script.Running = running; request.Script.ItemID = scriptID; request.Script.ObjectID = objectID; Client.Network.SendPacket(request); } #endregion Task #region Helper Functions 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; } /// /// Reverses a cheesy XORing with a fixed UUID to convert a shadow_id to an asset_id /// /// Obfuscated shadow_id value /// Deobfuscated asset_id value public static UUID DecryptShadowID(UUID shadowID) { return shadowID ^ MAGIC_ID; } /// /// Does a cheesy XORing with a fixed UUID to convert an asset_id to a shadow_id /// /// asset_id value to obfuscate /// Obfuscated shadow_id value public static UUID EncryptAssetID(UUID assetID) { return assetID ^ MAGIC_ID; } /// /// 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) { // 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 = Utils.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 = Utils.StringToSaleType(value); } else if (key == "sale_price") { Int32.TryParse(value, out salePrice); } } } #endregion sale_info } else if (key == "shadow_id") { UUID shadowID; if (UUID.TryParse(value, out shadowID)) assetID = DecryptShadowID(shadowID); } else if (key == "asset_id") { UUID.TryParse(value, out assetID); } else if (key == "type") { assetType = Utils.StringToAssetType(value); } else if (key == "inv_type") { inventoryType = Utils.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.LastOwnerID = lastOwnerID; 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: " + Environment.NewLine + taskData, Helpers.LogLevel.Error); } } } return items; } #endregion Helper Functions #region Internal Callbacks void Self_IM(object sender, InstantMessageEventArgs e) { // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we // handle it here? if (m_InventoryObjectOffered != null && (e.IM.Dialog == InstantMessageDialog.InventoryOffered || e.IM.Dialog == InstantMessageDialog.TaskInventoryOffered)) { AssetType type = AssetType.Unknown; UUID objectID = UUID.Zero; bool fromTask = false; if (e.IM.Dialog == InstantMessageDialog.InventoryOffered) { if (e.IM.BinaryBucket.Length == 17) { type = (AssetType)e.IM.BinaryBucket[0]; objectID = new UUID(e.IM.BinaryBucket, 1); fromTask = false; } else { Logger.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning, Client); return; } } else if (e.IM.Dialog == InstantMessageDialog.TaskInventoryOffered) { if (e.IM.BinaryBucket.Length == 1) { type = (AssetType)e.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 = e.IM.FromAgentID; imp.MessageBlock.Offline = 0; imp.MessageBlock.ID = e.IM.IMSessionID; imp.MessageBlock.Timestamp = 0; imp.MessageBlock.FromAgentName = Utils.StringToBytes(Client.Self.Name); imp.MessageBlock.Message = Utils.EmptyBytes; imp.MessageBlock.ParentEstateID = 0; imp.MessageBlock.RegionID = UUID.Zero; imp.MessageBlock.Position = Client.Self.SimPosition; InventoryObjectOfferedEventArgs args = new InventoryObjectOfferedEventArgs(e.IM, type, objectID, fromTask); OnInventoryObjectOffered(args); if (args.Accept) { // Accept the inventory offer switch (e.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; } if (args.FolderID != UUID.Zero) { imp.MessageBlock.BinaryBucket = args.FolderID.GetBytes(); } else { imp.MessageBlock.BinaryBucket = destinationFolderID.GetBytes(); } } else { // Decline the inventory offer switch (e.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 = Utils.EmptyBytes; } Client.Network.SendPacket(imp, e.Simulator); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } } } private void CreateItemFromAssetResponse(CapsClient client, OSD result, Exception error) { object[] args = (object[])client.UserData; ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[0]; byte[] itemData = (byte[])args[1]; int millisecondsTimeout = (int)args[2]; OSDMap request = (OSDMap)args[3]; 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; } OSDMap contents = (OSDMap)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.OnComplete += CreateItemFromAssetResponse; upload.UserData = new object[] { callback, itemData, millisecondsTimeout, request }; upload.BeginGetResponse(itemData, "application/octet-stream", millisecondsTimeout); } else if (status == "complete") { Logger.DebugLog("CreateItemFromAsset: completed"); if (contents.ContainsKey("new_inventory_item") && contents.ContainsKey("new_asset")) { // Request full update on the item in order to update the local store RequestFetchInventory(contents["new_inventory_item"].AsUUID(), Client.Self.AgentID); 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 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 UploadInventoryAssetResponse(CapsClient client, OSD result, Exception error) { OSDMap contents = result as OSDMap; KeyValuePair kvp = (KeyValuePair)(((object[])client.UserData)[0]); InventoryUploadedAssetCallback callback = kvp.Key; byte[] itemData = (byte[])kvp.Value; if (error == null && contents != null) { string status = contents["state"].AsString(); if (status == "upload") { Uri uploadURL = contents["uploader"].AsUri(); if (uploadURL != null) { // 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(uploadURL); upload.OnComplete += UploadInventoryAssetResponse; upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; upload.BeginGetResponse(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); } else { try { callback(false, "Missing uploader URL", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (status == "complete") { if (contents.ContainsKey("new_asset")) { // Request full item update so we keep store in sync RequestFetchInventory((UUID)(((object[])client.UserData)[1]), contents["new_asset"].AsUUID()); 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 { try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else { string message = "Unrecognized or empty response"; if (error != null) { if (error is WebException) message = ((HttpWebResponse)((WebException)error).Response).StatusDescription; if (message == null || message == "None") message = error.Message; } try { callback(false, message, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } private void UpdateScriptAgentInventoryResponse(CapsClient client, OSD result, Exception error) { KeyValuePair kvp = (KeyValuePair)(((object[])client.UserData)[0]); ScriptUpdatedCallback callback = kvp.Key; byte[] itemData = (byte[])kvp.Value; 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; } OSDMap contents = (OSDMap)result; string status = contents["state"].AsString(); if (status == "upload") { string uploadURL = contents["uploader"].AsString(); CapsClient upload = new CapsClient(new Uri(uploadURL)); upload.OnComplete += new CapsClient.CompleteCallback(UpdateScriptAgentInventoryResponse); upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; upload.BeginGetResponse(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); } else if (status == "complete" && callback != null) { if (contents.ContainsKey("new_asset")) { // Request full item update so we keep store in sync RequestFetchInventory((UUID)(((object[])client.UserData)[1]), contents["new_asset"].AsUUID()); try { callback(true, status, (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 UUID", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (callback != null) { try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } #endregion Internal Handlers #region Packet Handlers /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void SaveAssetIntoInventoryHandler(object sender, PacketReceivedEventArgs e) { if (m_SaveAssetToInventory != null) { Packet packet = e.Packet; SaveAssetIntoInventoryPacket save = (SaveAssetIntoInventoryPacket)packet; OnSaveAssetToInventory(new SaveAssetToInventoryEventArgs(save.InventoryData.ItemID, save.InventoryData.NewAssetID)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void InventoryDescendentsHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; 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++) { // If folder already exists then ignore, we assume the version cache // logic is working and if the folder is stale then it should not be present. if (!_Store.Contains(reply.FolderData[i].FolderID)) { 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; _Store.GetNodeFor(reply.AgentData.FolderID).NeedsUpdate = false; #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 (m_FindObjectByPathReply != null) { OnFindObjectByPathReply(new FindObjectByPathReplyEventArgs(String.Join("/", search.Path), folderContents[j].UUID)); } // 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 (m_FolderUpdated != null) { OnFolderUpdated(new FolderUpdatedEventArgs(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 /// /// The sender /// The EventArgs object containing the packet data protected void UpdateCreateInventoryItemHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; 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; /* * When attaching new objects, an UpdateCreateInventoryItem packet will be * returned by the server that has a FolderID/ParentUUID of zero. It is up * to the client to make sure that the item gets a good folder, otherwise * it will end up inaccesible in inventory. */ if (item.ParentUUID == UUID.Zero) { // assign default folder for type item.ParentUUID = FindFolderForType(item.AssetType); Logger.Log("Received an item through UpdateCreateInventoryItem with no parent folder, assigning to folder " + item.ParentUUID, Helpers.LogLevel.Info); // send update to the sim RequestUpdateItem(item); } // 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 ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } } // 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 ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } } //This is triggered when an item is received from a task if (m_TaskItemReceived != null) { OnTaskItemReceived(new TaskItemReceivedEventArgs(item.UUID, dataBlock.FolderID, item.CreatorID, item.AssetUUID, item.InventoryType)); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void MoveInventoryItemHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; 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); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void BulkUpdateInventoryHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; 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 ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } } // Look for an "item copied" callback ItemCopiedCallback copyCallback; if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) { _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); try { copyCallback(item); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } } } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void FetchInventoryReplyHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; 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.InventoryType = (InventoryType)dataBlock.InvType; 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; item.UUID = dataBlock.ItemID; _Store[item.UUID] = item; // Fire the callback for an item being fetched if (m_ItemReceived != null) { OnItemReceived(new ItemReceivedEventArgs(item)); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ReplyTaskInventoryHandler(object sender, PacketReceivedEventArgs e) { if (m_TaskInventoryReply != null) { Packet packet = e.Packet; ReplyTaskInventoryPacket reply = (ReplyTaskInventoryPacket)packet; OnTaskInventoryReply(new TaskInventoryReplyEventArgs(reply.InventoryData.TaskID, reply.InventoryData.Serial, Utils.BytesToString(reply.InventoryData.Filename))); } } protected void ScriptRunningReplyMessageHandler(string capsKey, Interfaces.IMessage message, Simulator simulator) { if (m_ScriptRunningReply != null) { ScriptRunningReplyMessage msg = (ScriptRunningReplyMessage)message; OnScriptRunningReply(new ScriptRunningReplyEventArgs(msg.ObjectID, msg.ItemID, msg.Mono, msg.Running)); } } #endregion Packet Handlers } #region EventArgs public class InventoryObjectOfferedEventArgs : EventArgs { private readonly InstantMessage m_Offer; private readonly AssetType m_AssetType; private readonly UUID m_ObjectID; private readonly bool m_FromTask; /// Set to true to accept offer, false to decline it public bool Accept { get; set; } /// The folder to accept the inventory into, if null default folder for will be used public UUID FolderID { get; set; } public InstantMessage Offer { get { return m_Offer; } } public AssetType AssetType { get { return m_AssetType; } } public UUID ObjectID { get { return m_ObjectID; } } public bool FromTask { get { return m_FromTask; } } public InventoryObjectOfferedEventArgs(InstantMessage offerDetails, AssetType type, UUID objectID, bool fromTask) { this.Accept = false; this.m_Offer = offerDetails; this.m_AssetType = type; this.m_ObjectID = objectID; this.m_FromTask = fromTask; } } public class FolderUpdatedEventArgs : EventArgs { private readonly UUID m_FolderID; public UUID FolderID { get { return m_FolderID; } } public FolderUpdatedEventArgs(UUID folderID) { this.m_FolderID = folderID; } } public class ItemReceivedEventArgs : EventArgs { private readonly InventoryItem m_Item; public InventoryItem Item { get { return m_Item; } } public ItemReceivedEventArgs(InventoryItem item) { this.m_Item = item; } } public class FindObjectByPathReplyEventArgs : EventArgs { private readonly String m_Path; private readonly UUID m_InventoryObjectID; public String Path { get { return m_Path; } } public UUID InventoryObjectID { get { return m_InventoryObjectID; } } public FindObjectByPathReplyEventArgs(string path, UUID inventoryObjectID) { this.m_Path = path; this.m_InventoryObjectID = inventoryObjectID; } } /// /// 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 class TaskItemReceivedEventArgs : EventArgs { private readonly UUID m_ItemID; private readonly UUID m_FolderID; private readonly UUID m_CreatorID; private readonly UUID m_AssetID; private readonly InventoryType m_Type; public UUID ItemID { get { return m_ItemID; } } public UUID FolderID { get { return m_FolderID; } } public UUID CreatorID { get { return m_CreatorID; } } public UUID AssetID { get { return m_AssetID; } } public InventoryType Type { get { return m_Type; } } public TaskItemReceivedEventArgs(UUID itemID, UUID folderID, UUID creatorID, UUID assetID, InventoryType type) { this.m_ItemID = itemID; this.m_FolderID = folderID; this.m_CreatorID = creatorID; this.m_AssetID = assetID; this.m_Type = type; } } public class TaskInventoryReplyEventArgs : EventArgs { private readonly UUID m_ItemID; private readonly Int16 m_Serial; private readonly String m_AssetFilename; public UUID ItemID { get { return m_ItemID; } } public Int16 Serial { get { return m_Serial; } } public String AssetFilename { get { return m_AssetFilename; } } public TaskInventoryReplyEventArgs(UUID itemID, short serial, string assetFilename) { this.m_ItemID = itemID; this.m_Serial = serial; this.m_AssetFilename = assetFilename; } } public class SaveAssetToInventoryEventArgs : EventArgs { private readonly UUID m_ItemID; private readonly UUID m_NewAssetID; public UUID ItemID { get { return m_ItemID; } } public UUID NewAssetID { get { return m_NewAssetID; } } public SaveAssetToInventoryEventArgs(UUID itemID, UUID newAssetID) { this.m_ItemID = itemID; this.m_NewAssetID = newAssetID; } } public class ScriptRunningReplyEventArgs : EventArgs { private readonly UUID m_ObjectID; private readonly UUID m_ScriptID; private readonly bool m_IsMono; private readonly bool m_IsRunning; public UUID ObjectID { get { return m_ObjectID; } } public UUID ScriptID { get { return m_ScriptID; } } public bool IsMono { get { return m_IsMono; } } public bool IsRunning { get { return m_IsRunning; } } public ScriptRunningReplyEventArgs(UUID objectID, UUID sctriptID, bool isMono, bool isRunning) { this.m_ObjectID = objectID; this.m_ScriptID = sctriptID; this.m_IsMono = isMono; this.m_IsRunning = isRunning; } } #endregion }