/* * Copyright (c) 2006-2008, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.org nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Text; using System.Runtime.Serialization; using OpenMetaverse.Http; using OpenMetaverse.StructuredData; using OpenMetaverse.Packets; namespace OpenMetaverse { #region Enums /// /// Inventory Item Types, eg Script, Notecard, Folder, etc /// public enum InventoryType : sbyte { /// Unknown Unknown = -1, /// Texture Texture = 0, /// Sound Sound = 1, /// Calling Card CallingCard = 2, /// Landmark Landmark = 3, /* /// Script //[Obsolete("See LSL")] Script = 4, /// Clothing //[Obsolete("See Wearable")] Clothing = 5, /// Object, both single and coalesced */ Object = 6, /// Notecard Notecard = 7, /// Category = 8, /// Folder Folder = 8, /// RootCategory = 9, /// an LSL Script LSL = 10, /* /// //[Obsolete("See LSL")] LSLBytecode = 11, /// //[Obsolete("See Texture")] TextureTGA = 12, /// //[Obsolete] Bodypart = 13, /// //[Obsolete] Trash = 14, */ /// Snapshot = 15, /* /// //[Obsolete] LostAndFound = 16, */ /// Attachment = 17, /// Wearable = 18, /// Animation = 19, /// Gesture = 20 } /// /// Item Sale Status /// public enum SaleType : byte { /// Not for sale Not = 0, /// The original is for sale Original = 1, /// Copies are for sale Copy = 2, /// The contents of the object are for sale Contents = 3 } [Flags] public enum InventorySortOrder : int { /// Sort by name ByName = 0, /// Sort by date ByDate = 1, /// Sort folders by name, regardless of whether items are /// sorted by name or date FoldersByName = 2, /// Place system folders at the top SystemFoldersToTop = 4 } /// /// Possible destinations for DeRezObject request /// public enum DeRezDestination : byte { /// 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 readonly UUID UUID; /// of parent folder public UUID ParentUUID; /// Name of item/folder public string Name; /// Item/Folder Owners public UUID OwnerID; /// /// Constructor, takes an itemID as a parameter /// /// The of the item public InventoryBase(UUID itemID) { if (itemID == UUID.Zero) Logger.Log("Initializing an InventoryBase with UUID.Zero", Helpers.LogLevel.Warning); UUID = itemID; } /// /// /// /// public 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; /// /// 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; } /// /// /// /// new 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); } /// /// /// /// 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)); } /// /// Generates a number corresponding to the value of the object to support the use of a hash table. /// Suitable for use in hashing algorithms and data structures such as a hash table /// /// A Hashcode of all the combined InventoryItem fields public override int GetHashCode() { return AssetUUID.GetHashCode() ^ Permissions.GetHashCode() ^ AssetType.GetHashCode() ^ InventoryType.GetHashCode() ^ Description.GetHashCode() ^ GroupID.GetHashCode() ^ GroupOwned.GetHashCode() ^ SalePrice.GetHashCode() ^ SaleType.GetHashCode() ^ Flags.GetHashCode() ^ CreationDate.GetHashCode(); } /// /// Compares an object /// /// The object to compare /// true if comparison object matches public override bool Equals(object o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same public override bool Equals(InventoryBase o) { InventoryItem item = o as InventoryItem; return item != null && Equals(item); } /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same public bool Equals(InventoryItem o) { return base.Equals(o as InventoryBase) && o.AssetType == AssetType && o.AssetUUID == AssetUUID && o.CreationDate == CreationDate && o.Description == Description && o.Flags == Flags && o.GroupID == GroupID && o.GroupOwned == GroupOwned && o.InventoryType == InventoryType && o.Permissions.Equals(Permissions) && o.SalePrice == SalePrice && o.SaleType == SaleType; } } /// /// InventoryTexture Class representing a graphical image /// /// public class InventoryTexture : InventoryItem { /// /// Construct an InventoryTexture object /// /// A which becomes the /// objects AssetUUID public InventoryTexture(UUID itemID) : base(itemID) { InventoryType = InventoryType.Texture; } /// /// 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); } } } /// /// 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) { } /// /// /// /// public override string ToString() { return Name; } /// /// Get Serilization data for this InventoryFolder object /// new 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 { protected struct InventorySearch { public UUID Folder; public UUID Owner; public string[] Path; public int Level; } #region Delegates /// /// Callback for inventory item creation finishing /// /// Whether the request to create an inventory /// item succeeded or not /// Inventory item being created. If success is /// false this will be null public delegate void ItemCreatedCallback(bool success, InventoryItem item); /// /// Callback for an inventory item being create from an uploaded asset /// /// true if inventory item creation was successful /// /// /// public delegate void ItemCreatedFromAssetCallback(bool success, string status, UUID itemID, UUID assetID); /// /// /// /// public delegate void ItemCopiedCallback(InventoryBase item); /// /// /// /// public delegate void ItemReceivedCallback(InventoryItem item); /// /// Callback for an inventory folder updating /// /// UUID of the folder that was updated public delegate void FolderUpdatedCallback(UUID folderID); /// /// Callback for when an inventory item is offered to us by another avatar or an object /// /// A object containing specific /// details on the item being offered, eg who its from /// The AssetType being offered /// Will be null if item is offered from an object /// will be true of item is offered from an object /// Return true to accept the offer, or false to decline it public delegate bool ObjectOfferedCallback(InstantMessage offerDetails, AssetType type, UUID objectID, bool fromTask); /// /// Callback when an inventory object is accepted and received from a /// task inventory. This is the callback in which you actually get /// the ItemID, as in ObjectOfferedCallback it is null when received /// from a task. /// /// /// /// /// /// public delegate void TaskItemReceivedCallback(UUID itemID, UUID folderID, UUID creatorID, UUID assetID, InventoryType type); /// /// /// /// /// public delegate void FindObjectByPathCallback(string path, UUID inventoryObjectID); /// /// Reply received after calling RequestTaskInventory, /// contains a filename that can be used in an asset download request /// /// UUID of the inventory item /// Version number of the task inventory asset /// Filename of the task inventory asset public delegate void TaskInventoryReplyCallback(UUID itemID, short serial, string assetFilename); /// /// /// /// /// /// /// public delegate void NotecardUploadedAssetCallback(bool success, string status, UUID itemID, UUID assetID); #endregion Delegates #region Events /// /// Fired when a reply to a RequestFetchInventory() is received /// /// public event ItemReceivedCallback OnItemReceived; /// /// Fired when a response to a RequestFolderContents() is received /// /// public event FolderUpdatedCallback OnFolderUpdated; /// /// Fired when an object or another avatar offers us an inventory item /// public event ObjectOfferedCallback OnObjectOffered; /// /// Fired when a response to FindObjectByPath() is received /// /// public event FindObjectByPathCallback OnFindObjectByPath; /// /// Fired when a task inventory item is received /// /// This may occur when an object that's rezzed in world is /// taken into inventory, when an item is created using the CreateInventoryItem /// packet, or when an object is purchased /// public event TaskItemReceivedCallback OnTaskItemReceived; /// /// Fired in response to a request for a tasks (primitive) inventory /// /// /// public event TaskInventoryReplyCallback OnTaskInventoryReply; #endregion Events private GridClient _Client; private Inventory _Store; //private Random _RandNumbers = new Random(); private object _CallbacksLock = new object(); private uint _CallbackPos; private Dictionary _ItemCreatedCallbacks = new Dictionary(); private Dictionary _ItemCopiedCallbacks = new Dictionary(); private List _Searches = new List(); #region String Arrays /// Partial mapping of AssetTypes to folder names private static readonly string[] _NewFolderNames = new string[] { "Textures", "Sounds", "Calling Cards", "Landmarks", "Scripts", "Clothing", "Objects", "Notecards", "New Folder", "Inventory", "Scripts", "Scripts", "Uncompressed Images", "Body Parts", "Trash", "Photo Album", "Lost And Found", "Uncompressed Sounds", "Uncompressed Images", "Uncompressed Images", "Animations", "Gestures" }; private static readonly string[] _AssetTypeNames = new string[] { "texture", "sound", "callcard", "landmark", "script", "clothing", "object", "notecard", "category", "root", "lsltext", "lslbyte", "txtr_tga", "bodypart", "trash", "snapshot", "lstndfnd", "snd_wav", "img_tga", "jpeg", "animatn", "gesture", "simstate" }; private static readonly string[] _InventoryTypeNames = new string[] { "texture", "sound", "callcard", "landmark", String.Empty, String.Empty, "object", "notecard", "category", "root", "script", String.Empty, String.Empty, String.Empty, String.Empty, "snapshot", String.Empty, "attach", "wearable", "animation", "gesture", }; private static readonly string[] _SaleTypeNames = new string[] { "not", "orig", "copy", "cntn" }; #endregion String Arrays #region Properties /// /// Get this agents Inventory data /// public Inventory Store { get { return _Store; } } #endregion Properties /// /// Default constructor /// /// Reference to the GridClient object public InventoryManager(GridClient client) { _Client = client; _Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler)); _Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler)); _Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler)); _Client.Network.RegisterCallback(PacketType.MoveInventoryItem, new NetworkManager.PacketCallback(MoveInventoryItemHandler)); _Client.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler)); _Client.Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler)); _Client.Network.RegisterCallback(PacketType.ReplyTaskInventory, new NetworkManager.PacketCallback(ReplyTaskInventoryHandler)); // Watch for inventory given to us through instant message _Client.Self.OnInstantMessage += new AgentManager.InstantMessageCallback(Self_OnInstantMessage); // Register extra parameters with login and parse the inventory data that comes back _Client.Network.RegisterLoginResponseCallback( new NetworkManager.LoginResponseCallback(Network_OnLoginResponse), new string[] { "inventory-root", "inventory-skeleton", "inventory-lib-root", "inventory-lib-owner", "inventory-skel-lib"}); } #region Fetch /// /// Fetch an inventory item from the dataserver /// /// The items /// The item Owners /// a integer representing the number of milliseconds to wait for results /// An object on success, or null if no item was found /// Items will also be sent to the event public InventoryItem FetchItem(UUID itemID, UUID ownerID, int timeoutMS) { AutoResetEvent fetchEvent = new AutoResetEvent(false); InventoryItem fetchedItem = null; ItemReceivedCallback callback = delegate(InventoryItem item) { if (item.UUID == itemID) { fetchedItem = item; fetchEvent.Set(); } }; OnItemReceived += callback; RequestFetchInventory(itemID, ownerID); fetchEvent.WaitOne(timeoutMS, false); OnItemReceived -= callback; return fetchedItem; } /// /// Request A single inventory item /// /// The items /// The item Owners /// public void RequestFetchInventory(UUID itemID, UUID ownerID) { FetchInventoryPacket fetch = new FetchInventoryPacket(); fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[1]; fetch.InventoryData[0] = new FetchInventoryPacket.InventoryDataBlock(); fetch.InventoryData[0].ItemID = itemID; fetch.InventoryData[0].OwnerID = ownerID; _Client.Network.SendPacket(fetch); } /// /// Request inventory items /// /// Inventory items to request /// Owners of the inventory items /// public void RequestFetchInventory(List itemIDs, List ownerIDs) { if (itemIDs.Count != ownerIDs.Count) throw new ArgumentException("itemIDs and ownerIDs must contain the same number of entries"); FetchInventoryPacket fetch = new FetchInventoryPacket(); fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count]; for (int i = 0; i < itemIDs.Count; i++) { fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock(); fetch.InventoryData[i].ItemID = itemIDs[i]; fetch.InventoryData[i].OwnerID = ownerIDs[i]; } _Client.Network.SendPacket(fetch); } /// /// Get contents of a folder /// /// The of the folder to search /// The of the folders owner /// true to retrieve folders /// true to retrieve items /// sort order to return results in /// a integer representing the number of milliseconds to wait for results /// A list of inventory items matching search criteria within folder /// /// InventoryFolder.DescendentCount will only be accurate if both folders and items are /// requested public List FolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, int timeoutMS) { List objects = null; AutoResetEvent fetchEvent = new AutoResetEvent(false); FolderUpdatedCallback callback = delegate(UUID folderID) { if (folderID == folder && _Store[folder] is InventoryFolder) { // InventoryDescendentsHandler only stores DescendendCount if both folders and items are fetched. if (_Store.GetContents(folder).Count >= ((InventoryFolder)_Store[folder]).DescendentCount) { fetchEvent.Set(); } } else { fetchEvent.Set(); } }; OnFolderUpdated += callback; RequestFolderContents(folder, owner, folders, items, order); if (fetchEvent.WaitOne(timeoutMS, false)) objects = _Store.GetContents(folder); OnFolderUpdated -= callback; return objects; } /// /// Request the contents of an inventory folder /// /// The folder to search /// The folder owners /// true to return s contained in folder /// true to return s containd in folder /// the sort order to return items in /// public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order) { FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket(); fetch.AgentData.AgentID = _Client.Self.AgentID; fetch.AgentData.SessionID = _Client.Self.SessionID; fetch.InventoryData.FetchFolders = folders; fetch.InventoryData.FetchItems = items; fetch.InventoryData.FolderID = folder; fetch.InventoryData.OwnerID = owner; fetch.InventoryData.SortOrder = (int)order; _Client.Network.SendPacket(fetch); } #endregion Fetch #region Find /// /// Returns the UUID of the folder (category) that defaults to /// containing 'type'. The folder is not necessarily only for that /// type /// /// This will return the root folder if one does not exist /// /// The UUID of the desired folder if found, the UUID of the RootFolder /// if not found, or UUID.Zero on failure public UUID FindFolderForType(AssetType type) { if (_Store == null) { Logger.Log("Inventory is null, FindFolderForType() lookup cannot continue", Helpers.LogLevel.Error, _Client); return UUID.Zero; } // Folders go in the root if (type == AssetType.Folder) return _Store.RootFolder.UUID; // Loop through each top-level directory and check if PreferredType // matches the requested type List contents = _Store.GetContents(_Store.RootFolder.UUID); foreach (InventoryBase inv in contents) { if (inv is InventoryFolder) { InventoryFolder folder = inv as InventoryFolder; if (folder.PreferredType == type) return folder.UUID; } } // No match found, return Root Folder ID return _Store.RootFolder.UUID; } /// /// Find an object in inventory using a specific path to search /// /// The folder to begin the search in /// The object owners /// A string path to search /// milliseconds to wait for a reply /// Found items or if /// timeout occurs or item is not found public UUID FindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path, int timeoutMS) { AutoResetEvent findEvent = new AutoResetEvent(false); UUID foundItem = UUID.Zero; FindObjectByPathCallback callback = delegate(string thisPath, UUID inventoryObjectID) { if (thisPath == path) { foundItem = inventoryObjectID; findEvent.Set(); } }; OnFindObjectByPath += callback; RequestFindObjectByPath(baseFolder, inventoryOwner, path); findEvent.WaitOne(timeoutMS, false); OnFindObjectByPath -= callback; return foundItem; } /// /// Find inventory items by path /// /// The folder to begin the search in /// The object owners /// A string path to search, folders/objects separated by a '/' /// Results are sent to the event public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path) { if (path == null || path.Length == 0) throw new ArgumentException("Empty path is not supported"); // Store this search InventorySearch search; search.Folder = baseFolder; search.Owner = inventoryOwner; search.Path = path.Split('/'); search.Level = 0; lock (_Searches) _Searches.Add(search); // Start the search RequestFolderContents(baseFolder, inventoryOwner, true, true, InventorySortOrder.ByName); } /// /// Search inventory Store object for an item or folder /// /// The folder to begin the search in /// An array which creates a path to search /// Number of levels below baseFolder to conduct searches /// if True, will stop searching after first match is found /// A list of inventory items found public List LocalFind(UUID baseFolder, string[] path, int level, bool firstOnly) { List objects = new List(); //List folders = new List(); List contents = _Store.GetContents(baseFolder); foreach (InventoryBase inv in contents) { if (inv.Name.CompareTo(path[level]) == 0) { if (level == path.Length - 1) { objects.Add(inv); if (firstOnly) return objects; } else if (inv is InventoryFolder) objects.AddRange(LocalFind(inv.UUID, path, level + 1, firstOnly)); } } return objects; } #endregion Find #region Move/Rename /// /// Move an inventory item or folder to a new location /// /// The item or folder to move /// The to move item or folder to public void Move(InventoryBase item, InventoryFolder newParent) { if (item is InventoryFolder) MoveFolder(item.UUID, newParent.UUID); else MoveItem(item.UUID, newParent.UUID); } /// /// Move an inventory item or folder to a new location and change its name /// /// The item or folder to move /// The to move item or folder to /// The name to change the item or folder to public void Move(InventoryBase item, InventoryFolder newParent, string newName) { if (item is InventoryFolder) MoveFolder(item.UUID, newParent.UUID, newName); else MoveItem(item.UUID, newParent.UUID, newName); } /// /// Move and rename a folder /// /// The source folders /// The destination folders /// The name to change the folder to public void MoveFolder(UUID folderID, UUID newparentID, string newName) { lock (Store) { if (_Store.Contains(folderID)) { InventoryBase inv = Store[folderID]; inv.Name = newName; _Store.UpdateNodeFor(inv); } } UpdateInventoryFolderPacket move = new UpdateInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.FolderData = new UpdateInventoryFolderPacket.FolderDataBlock[1]; move.FolderData[0] = new UpdateInventoryFolderPacket.FolderDataBlock(); move.FolderData[0].FolderID = folderID; move.FolderData[0].ParentID = newparentID; move.FolderData[0].Name = Utils.StringToBytes(newName); move.FolderData[0].Type = -1; _Client.Network.SendPacket(move); } /// /// Move a folder /// /// The source folders /// The destination folders public void MoveFolder(UUID folderID, UUID newParentID) { lock (Store) { if (_Store.Contains(folderID)) { InventoryBase inv = Store[folderID]; inv.ParentUUID = newParentID; _Store.UpdateNodeFor(inv); } } MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[1]; move.InventoryData[0] = new MoveInventoryFolderPacket.InventoryDataBlock(); move.InventoryData[0].FolderID = folderID; move.InventoryData[0].ParentID = newParentID; _Client.Network.SendPacket(move); } /// /// Move multiple folders, the keys in the Dictionary parameter, /// to a new parents, the value of that folder's key. /// /// A Dictionary containing the /// of the source as the key, and the /// of the destination as the value public void MoveFolders(Dictionary foldersNewParents) { // FIXME: Use two List to stay consistent lock (Store) { foreach (KeyValuePair entry in foldersNewParents) { if (_Store.Contains(entry.Key)) { InventoryBase inv = _Store[entry.Key]; inv.ParentUUID = entry.Value; _Store.UpdateNodeFor(inv); } } } //TODO: Test if this truly supports multiple-folder move MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[foldersNewParents.Count]; int index = 0; foreach (KeyValuePair folder in foldersNewParents) { MoveInventoryFolderPacket.InventoryDataBlock block = new MoveInventoryFolderPacket.InventoryDataBlock(); block.FolderID = folder.Key; block.ParentID = folder.Value; move.InventoryData[index++] = block; } _Client.Network.SendPacket(move); } /// /// Move an inventory item to a new folder /// /// The of the source item to move /// The of the destination folder public void MoveItem(UUID itemID, UUID folderID) { MoveItem(itemID, folderID, String.Empty); } /// /// Move and rename an inventory item /// /// The of the source item to move /// The of the destination folder /// The name to change the folder to public void MoveItem(UUID itemID, UUID folderID, string newName) { lock (_Store) { if (_Store.Contains(itemID)) { InventoryBase inv = _Store[itemID]; inv.ParentUUID = folderID; _Store.UpdateNodeFor(inv); } } MoveInventoryItemPacket move = new MoveInventoryItemPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[1]; move.InventoryData[0] = new MoveInventoryItemPacket.InventoryDataBlock(); move.InventoryData[0].ItemID = itemID; move.InventoryData[0].FolderID = folderID; move.InventoryData[0].NewName = Utils.StringToBytes(newName); _Client.Network.SendPacket(move); } /// /// Move multiple inventory items to new locations /// /// A Dictionary containing the /// of the source item as the key, and the /// of the destination folder as the value public void MoveItems(Dictionary itemsNewParents) { lock (_Store) { foreach (KeyValuePair entry in itemsNewParents) { if (_Store.Contains(entry.Key)) { InventoryBase inv = _Store[entry.Key]; inv.ParentUUID = entry.Value; _Store.UpdateNodeFor(inv); } } } MoveInventoryItemPacket move = new MoveInventoryItemPacket(); move.AgentData.AgentID = _Client.Self.AgentID; move.AgentData.SessionID = _Client.Self.SessionID; move.AgentData.Stamp = false; //FIXME: ?? move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[itemsNewParents.Count]; int index = 0; foreach (KeyValuePair entry in itemsNewParents) { MoveInventoryItemPacket.InventoryDataBlock block = new MoveInventoryItemPacket.InventoryDataBlock(); block.ItemID = entry.Key; block.FolderID = entry.Value; block.NewName = 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; } public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, InventoryType invType, UUID folderID, CapsClient.ProgressCallback progCallback, ItemCreatedFromAssetCallback callback) { if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("NewFileAgentInventory capability is not currently available"); Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); if (url != null) { OSDMap query = new OSDMap(); query.Add("folder_id", OSD.FromUUID(folderID)); query.Add("asset_type", OSD.FromString(AssetTypeToString(assetType))); query.Add("inventory_type", OSD.FromString(InventoryTypeToString(invType))); query.Add("name", OSD.FromString(name)); query.Add("description", OSD.FromString(description)); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); request.UserData = new object[] { progCallback, callback, data }; request.StartRequest(query); } else { throw new Exception("NewFileAgentInventory capability is not currently available"); } } #endregion Create #region Copy /// /// /// /// /// /// /// public void RequestCopyItem(UUID item, UUID newParent, string newName, ItemCopiedCallback callback) { RequestCopyItem(item, newParent, newName, _Client.Self.AgentID, callback); } /// /// /// /// /// /// /// /// public void RequestCopyItem(UUID item, UUID newParent, string newName, UUID oldOwnerID, ItemCopiedCallback callback) { List items = new List(1); items.Add(item); List folders = new List(1); folders.Add(newParent); List names = new List(1); names.Add(newName); RequestCopyItems(items, folders, names, oldOwnerID, callback); } /// /// /// /// /// /// /// /// public void RequestCopyItems(List items, List targetFolders, List newNames, UUID oldOwnerID, ItemCopiedCallback callback) { if (items.Count != targetFolders.Count || (newNames != null && items.Count != newNames.Count)) throw new ArgumentException("All list arguments must have an equal number of entries"); uint callbackID = RegisterItemsCopiedCallback(callback); CopyInventoryItemPacket copy = new CopyInventoryItemPacket(); copy.AgentData.AgentID = _Client.Self.AgentID; copy.AgentData.SessionID = _Client.Self.SessionID; copy.InventoryData = new CopyInventoryItemPacket.InventoryDataBlock[items.Count]; for (int i = 0; i < items.Count; ++i) { copy.InventoryData[i] = new CopyInventoryItemPacket.InventoryDataBlock(); copy.InventoryData[i].CallbackID = callbackID; copy.InventoryData[i].NewFolderID = targetFolders[i]; copy.InventoryData[i].OldAgentID = oldOwnerID; copy.InventoryData[i].OldItemID = items[i]; if (newNames != null && !String.IsNullOrEmpty(newNames[i])) copy.InventoryData[i].NewName = Utils.StringToBytes(newNames[i]); else copy.InventoryData[i].NewName = 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) { 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; _ItemCopiedCallbacks[0] = callback; //Notecards always use callback ID 0 _Client.Network.SendPacket(copy); } #endregion Copy #region Update /// /// /// /// public void RequestUpdateItem(InventoryItem item) { List items = new List(1); items.Add(item); RequestUpdateItems(items, UUID.Random()); } /// /// /// /// public void RequestUpdateItems(List items) { RequestUpdateItems(items, UUID.Random()); } /// /// /// /// /// public void RequestUpdateItems(List items, UUID transactionID) { UpdateInventoryItemPacket update = new UpdateInventoryItemPacket(); update.AgentData.AgentID = _Client.Self.AgentID; update.AgentData.SessionID = _Client.Self.SessionID; update.AgentData.TransactionID = transactionID; update.InventoryData = new UpdateInventoryItemPacket.InventoryDataBlock[items.Count]; for (int i = 0; i < items.Count; i++) { InventoryItem item = items[i]; UpdateInventoryItemPacket.InventoryDataBlock block = new UpdateInventoryItemPacket.InventoryDataBlock(); block.BaseMask = (uint)item.Permissions.BaseMask; block.CRC = ItemCRC(item); block.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); block.CreatorID = item.CreatorID; block.Description = Utils.StringToBytes(item.Description); block.EveryoneMask = (uint)item.Permissions.EveryoneMask; block.Flags = (uint)item.Flags; block.FolderID = item.ParentUUID; block.GroupID = item.GroupID; block.GroupMask = (uint)item.Permissions.GroupMask; block.GroupOwned = item.GroupOwned; block.InvType = (sbyte)item.InventoryType; block.ItemID = item.UUID; block.Name = Utils.StringToBytes(item.Name); block.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; block.OwnerID = item.OwnerID; block.OwnerMask = (uint)item.Permissions.OwnerMask; block.SalePrice = item.SalePrice; block.SaleType = (byte)item.SaleType; block.TransactionID = UUID.Zero; block.Type = (sbyte)item.AssetType; update.InventoryData[i] = block; } _Client.Network.SendPacket(update); } /// /// /// /// /// /// public void RequestUploadNotecardAsset(byte[] data, UUID notecardID, NotecardUploadedAssetCallback callback) { if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("UpdateNotecardAgentInventory"); if (url != null) { OSDMap query = new OSDMap(); query.Add("item_id", OSD.FromUUID(notecardID)); byte[] postData = StructuredData.OSDParser.SerializeLLSDXmlBytes(query); // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); request.UserData = new object[2] { new KeyValuePair(callback, data), notecardID }; request.StartRequest(postData); } else { throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); } } #endregion Update #region Rez/Give /// /// Rez an object from inventory /// /// Simulator to place object in /// Rotation of the object when rezzed /// Vector of where to place object /// 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(), false); } /// /// 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(), false); } /// /// 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 simulator /// will automatically send object detail packet(s) back to the client public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, InventoryItem item, UUID groupOwner, UUID queryID, bool requestObjectDetails) { RezObjectPacket add = new RezObjectPacket(); add.AgentData.AgentID = _Client.Self.AgentID; add.AgentData.SessionID = _Client.Self.SessionID; add.AgentData.GroupID = groupOwner; add.RezData.FromTaskID = UUID.Zero; add.RezData.BypassRaycast = 1; add.RezData.RayStart = position; add.RezData.RayEnd = position; add.RezData.RayTargetID = UUID.Zero; add.RezData.RayEndIsIntersection = false; add.RezData.RezSelected = requestObjectDetails; add.RezData.RemoveItem = false; add.RezData.ItemFlags = (uint)item.Flags; add.RezData.GroupMask = (uint)item.Permissions.GroupMask; add.RezData.EveryoneMask = (uint)item.Permissions.EveryoneMask; add.RezData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; add.InventoryData.ItemID = item.UUID; add.InventoryData.FolderID = item.ParentUUID; add.InventoryData.CreatorID = item.CreatorID; add.InventoryData.OwnerID = item.OwnerID; add.InventoryData.GroupID = item.GroupID; add.InventoryData.BaseMask = (uint)item.Permissions.BaseMask; add.InventoryData.OwnerMask = (uint)item.Permissions.OwnerMask; add.InventoryData.GroupMask = (uint)item.Permissions.GroupMask; add.InventoryData.EveryoneMask = (uint)item.Permissions.EveryoneMask; add.InventoryData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; add.InventoryData.GroupOwned = item.GroupOwned; add.InventoryData.TransactionID = queryID; add.InventoryData.Type = (sbyte)item.InventoryType; add.InventoryData.InvType = (sbyte)item.InventoryType; add.InventoryData.Flags = (uint)item.Flags; add.InventoryData.SaleType = (byte)item.SaleType; add.InventoryData.SalePrice = item.SalePrice; add.InventoryData.Name = Utils.StringToBytes(item.Name); add.InventoryData.Description = Utils.StringToBytes(item.Description); add.InventoryData.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); _Client.Network.SendPacket(add, simulator); return queryID; } /// /// DeRez an object from the simulator to the agents Objects folder in the agents Inventory /// /// The simulator Local ID of the object public void RequestDeRezToInventory(uint objectLocalID) { RequestDeRezToInventory(objectLocalID, DeRezDestination.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 public void RequestDeRezToInventory(uint objectLocalID, DeRezDestination destType, UUID destFolder, UUID transactionID) { DeRezObjectPacket take = new DeRezObjectPacket(); take.AgentData.AgentID = _Client.Self.AgentID; take.AgentData.SessionID = _Client.Self.SessionID; take.AgentBlock = new DeRezObjectPacket.AgentBlockBlock(); take.AgentBlock.GroupID = UUID.Zero; take.AgentBlock.Destination = (byte)destType; take.AgentBlock.DestinationID = destFolder; take.AgentBlock.PacketCount = 1; take.AgentBlock.PacketNumber = 1; take.AgentBlock.TransactionID = transactionID; take.ObjectData = new DeRezObjectPacket.ObjectDataBlock[1]; take.ObjectData[0] = new DeRezObjectPacket.ObjectDataBlock(); take.ObjectData[0].ObjectLocalID = objectLocalID; _Client.Network.SendPacket(take); } /// /// Give an inventory item to another avatar /// /// The of the item to give /// The name of the item /// The type of the item from the enum /// The of the recipient /// true to generate a beameffect during transfer public void GiveItem(UUID itemID, string itemName, AssetType assetType, UUID recipient, bool doEffect) { byte[] bucket; bucket = new byte[17]; bucket[0] = (byte)assetType; Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16); _Client.Self.InstantMessage( _Client.Self.Name, recipient, itemName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, _Client.Self.SimPosition, _Client.Network.CurrentSim.ID, bucket); if (doEffect) { _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } /// /// Give an inventory Folder with contents to another avatar /// /// The of the Folder to give /// The name of the folder /// The type of the item from the enum /// The of the recipient /// true to generate a beameffect during transfer public void GiveFolder(UUID folderID, string folderName, AssetType assetType, UUID recipient, bool doEffect) { byte[] bucket; List folderContents = new List(); _Client.Inventory.FolderContents(folderID, _Client.Self.AgentID, false, true, InventorySortOrder.ByDate, 1000 * 15).ForEach( delegate(InventoryBase ib) { folderContents.Add(_Client.Inventory.FetchItem(ib.UUID, _Client.Self.AgentID, 1000 * 10)); }); bucket = new byte[17 * (folderContents.Count + 1)]; //Add parent folder (first item in bucket) bucket[0] = (byte)assetType; Buffer.BlockCopy(folderID.GetBytes(), 0, bucket, 1, 16); //Add contents to bucket after folder for (int i = 1; i <= folderContents.Count; ++i) { bucket[i * 17] = (byte)folderContents[i - 1].AssetType; Buffer.BlockCopy(folderContents[i - 1].UUID.GetBytes(), 0, bucket, i * 17 + 1, 16); } _Client.Self.InstantMessage( _Client.Self.Name, recipient, folderName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, _Client.Self.SimPosition, _Client.Network.CurrentSim.ID, bucket); if (doEffect) { _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } #endregion Rez/Give #region Task /// /// /// /// /// /// public UUID UpdateTaskInventory(uint objectLocalID, InventoryItem item) { UUID transactionID = UUID.Random(); UpdateTaskInventoryPacket update = new UpdateTaskInventoryPacket(); update.AgentData.AgentID = _Client.Self.AgentID; update.AgentData.SessionID = _Client.Self.SessionID; update.UpdateData.Key = 0; update.UpdateData.LocalID = objectLocalID; update.InventoryData.ItemID = item.UUID; update.InventoryData.FolderID = item.ParentUUID; update.InventoryData.CreatorID = item.CreatorID; update.InventoryData.OwnerID = item.OwnerID; update.InventoryData.GroupID = item.GroupID; update.InventoryData.BaseMask = (uint)item.Permissions.BaseMask; update.InventoryData.OwnerMask = (uint)item.Permissions.OwnerMask; update.InventoryData.GroupMask = (uint)item.Permissions.GroupMask; update.InventoryData.EveryoneMask = (uint)item.Permissions.EveryoneMask; update.InventoryData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask; update.InventoryData.GroupOwned = item.GroupOwned; update.InventoryData.TransactionID = transactionID; update.InventoryData.Type = (sbyte)item.AssetType; update.InventoryData.InvType = (sbyte)item.InventoryType; update.InventoryData.Flags = (uint)item.Flags; update.InventoryData.SaleType = (byte)item.SaleType; update.InventoryData.SalePrice = item.SalePrice; update.InventoryData.Name = Utils.StringToBytes(item.Name); update.InventoryData.Description = Utils.StringToBytes(item.Description); update.InventoryData.CreationDate = (int)Utils.DateTimeToUnixTime(item.CreationDate); update.InventoryData.CRC = ItemCRC(item); _Client.Network.SendPacket(update); return transactionID; } /// /// Get the inventory of a Task (Primitive) /// /// The tasks /// The tasks simulator local ID /// milliseconds to wait for reply from simulator /// A List containing the inventory items inside the task public List GetTaskInventory(UUID objectID, uint objectLocalID, int timeoutMS) { string filename = null; AutoResetEvent taskReplyEvent = new AutoResetEvent(false); TaskInventoryReplyCallback callback = delegate(UUID itemID, short serial, string assetFilename) { if (itemID == objectID) { filename = assetFilename; taskReplyEvent.Set(); } }; OnTaskInventoryReply += callback; RequestTaskInventory(objectLocalID); if (taskReplyEvent.WaitOne(timeoutMS, false)) { OnTaskInventoryReply -= callback; if (!String.IsNullOrEmpty(filename)) { byte[] assetData = null; ulong xferID = 0; AutoResetEvent taskDownloadEvent = new AutoResetEvent(false); AssetManager.XferReceivedCallback xferCallback = delegate(XferDownload xfer) { if (xfer.XferID == xferID) { assetData = xfer.AssetData; taskDownloadEvent.Set(); } }; _Client.Assets.OnXferReceived += xferCallback; // Start the actual asset xfer xferID = _Client.Assets.RequestAssetXfer(filename, true, false, UUID.Zero, AssetType.Unknown, 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 null; } } else { Logger.Log("Timed out waiting for task inventory reply for " + objectLocalID, Helpers.LogLevel.Warning, _Client); OnTaskInventoryReply -= callback; return null; } } /// /// /// /// public void RequestTaskInventory(uint objectLocalID) { RequestTaskInventory(objectLocalID, _Client.Network.CurrentSim); } /// /// Request the contents of a tasks (primitives) inventory /// /// The simulator Local ID of the object /// A reference to the simulator object that contains the object public void RequestTaskInventory(uint objectLocalID, Simulator simulator) { RequestTaskInventoryPacket request = new RequestTaskInventoryPacket(); request.AgentData.AgentID = _Client.Self.AgentID; request.AgentData.SessionID = _Client.Self.SessionID; request.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(request, simulator); } /// /// Moves an Item from an objects (Prim) Inventory to the specified folder in the avatars inventory /// /// LocalID of the object in the simulator /// UUID of the task item to move /// UUID of the folder to move the item to /// Simulator Object public void MoveTaskInventory(uint objectLocalID, UUID taskItemID, UUID inventoryFolderID, Simulator simulator) { MoveTaskInventoryPacket request = new MoveTaskInventoryPacket(); request.AgentData.AgentID = _Client.Self.AgentID; request.AgentData.SessionID = _Client.Self.SessionID; request.AgentData.FolderID = inventoryFolderID; request.InventoryData.ItemID = taskItemID; request.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(request, simulator); } /// /// Remove an item from an objects (Prim) Inventory /// /// LocalID of the object in the simulator /// UUID of the task item to remove /// Simulator Object public void RemoveTaskInventory(uint objectLocalID, UUID taskItemID, Simulator simulator) { RemoveTaskInventoryPacket remove = new RemoveTaskInventoryPacket(); remove.AgentData.AgentID = _Client.Self.AgentID; remove.AgentData.SessionID = _Client.Self.SessionID; remove.InventoryData.ItemID = taskItemID; remove.InventoryData.LocalID = objectLocalID; _Client.Network.SendPacket(remove, simulator); } #endregion Task #region Helper Functions /// /// Takes an AssetType and returns the string representation /// /// The source /// The string version of the AssetType public static string AssetTypeToString(AssetType type) { return _AssetTypeNames[(int)type]; } /// /// Translate a string name of an AssetType into the proper Type /// /// A string containing the AssetType name /// The AssetType which matches the string name, or AssetType.Unknown if no match was found public static AssetType StringToAssetType(string type) { for (int i = 0; i < _AssetTypeNames.Length; i++) { if (_AssetTypeNames[i] == type) return (AssetType)i; } return AssetType.Unknown; } /// /// Convert an InventoryType to a string /// /// The to convert /// A string representation of the source public static string InventoryTypeToString(InventoryType type) { return _InventoryTypeNames[(int)type]; } /// /// Convert a string into a valid InventoryType /// /// A string representation of the InventoryType to convert /// A InventoryType object which matched the type public static InventoryType StringToInventoryType(string type) { for (int i = 0; i < _InventoryTypeNames.Length; i++) { if (_InventoryTypeNames[i] == type) return (InventoryType)i; } return InventoryType.Unknown; } public static string SaleTypeToString(SaleType type) { return _SaleTypeNames[(int)type]; } public static SaleType StringToSaleType(string value) { for (int i = 0; i < _SaleTypeNames.Length; i++) { if (value == _SaleTypeNames[i]) return (SaleType)i; } return SaleType.Not; } private uint RegisterItemCreatedCallback(ItemCreatedCallback callback) { lock (_CallbacksLock) { if (_CallbackPos == UInt32.MaxValue) _CallbackPos = 0; _CallbackPos++; if (_ItemCreatedCallbacks.ContainsKey(_CallbackPos)) Logger.Log("Overwriting an existing ItemCreatedCallback", Helpers.LogLevel.Warning, _Client); _ItemCreatedCallbacks[_CallbackPos] = callback; return _CallbackPos; } } private uint RegisterItemsCopiedCallback(ItemCopiedCallback callback) { lock (_CallbacksLock) { if (_CallbackPos == UInt32.MaxValue) _CallbackPos = 0; _CallbackPos++; if (_ItemCopiedCallbacks.ContainsKey(_CallbackPos)) Logger.Log("Overwriting an existing ItemsCopiedCallback", Helpers.LogLevel.Warning, _Client); _ItemCopiedCallbacks[_CallbackPos] = callback; return _CallbackPos; } } /// /// Create a CRC from an InventoryItem /// /// The source InventoryItem /// A uint representing the source InventoryItem as a CRC public static uint ItemCRC(InventoryItem iitem) { uint CRC = 0; // IDs CRC += iitem.AssetUUID.CRC(); // AssetID CRC += iitem.ParentUUID.CRC(); // FolderID CRC += iitem.UUID.CRC(); // ItemID // Permission stuff CRC += iitem.CreatorID.CRC(); // CreatorID CRC += iitem.OwnerID.CRC(); // OwnerID CRC += iitem.GroupID.CRC(); // GroupID // CRC += another 4 words which always seem to be zero -- unclear if this is a UUID or what CRC += (uint)iitem.Permissions.OwnerMask; //owner_mask; // Either owner_mask or next_owner_mask may need to be CRC += (uint)iitem.Permissions.NextOwnerMask; //next_owner_mask; // switched with base_mask -- 2 values go here and in my CRC += (uint)iitem.Permissions.EveryoneMask; //everyone_mask; // study item, the three were identical. CRC += (uint)iitem.Permissions.GroupMask; //group_mask; // The rest of the CRC fields CRC += (uint)iitem.Flags; // Flags CRC += (uint)iitem.InventoryType; // InvType CRC += (uint)iitem.AssetType; // Type CRC += (uint)Utils.DateTimeToUnixTime(iitem.CreationDate); // CreationDate CRC += (uint)iitem.SalePrice; // SalePrice CRC += (uint)((uint)iitem.SaleType * 0x07073096); // SaleType return CRC; } /// /// Wrapper for creating a new object /// /// The type of item from the enum /// The of the newly created object /// An object with the type and id passed public static InventoryItem CreateInventoryItem(InventoryType type, UUID id) { switch (type) { case InventoryType.Texture: return new InventoryTexture(id); case InventoryType.Sound: return new InventorySound(id); case InventoryType.CallingCard: return new InventoryCallingCard(id); case InventoryType.Landmark: return new InventoryLandmark(id); case InventoryType.Object: return new InventoryObject(id); case InventoryType.Notecard: return new InventoryNotecard(id); case InventoryType.Category: return new InventoryCategory(id); case InventoryType.LSL: return new InventoryLSL(id); case InventoryType.Snapshot: return new InventorySnapshot(id); case InventoryType.Attachment: return new InventoryAttachment(id); case InventoryType.Wearable: return new InventoryWearable(id); case InventoryType.Animation: return new InventoryAnimation(id); case InventoryType.Gesture: return new InventoryGesture(id); default: return new InventoryItem(type, id); } } private InventoryItem SafeCreateInventoryItem(InventoryType InvType, UUID ItemID) { InventoryItem ret = null; if (_Store.Contains(ItemID)) ret = _Store[ItemID] as InventoryItem; if (ret == null) ret = CreateInventoryItem(InvType, ItemID); return ret; } private static bool ParseLine(string line, out string key, out string value) { // Clean up and convert tabs to spaces line = line.Trim(); line = line.Replace('\t', ' '); // Shrink all whitespace down to single spaces while (line.IndexOf(" ") > 0) line = line.Replace(" ", " "); if (line.Length > 2) { int sep = line.IndexOf(' '); if (sep > 0) { key = line.Substring(0, sep); value = line.Substring(sep + 1); return true; } } else if (line.Length == 1) { key = line; value = String.Empty; return true; } key = null; value = null; return false; } /// /// Parse the results of a RequestTaskInventory() response /// /// A string which contains the data from the task reply /// A List containing the items contained within the tasks inventory public static List ParseTaskInventory(string taskData) { List items = new List(); int lineNum = 0; string[] lines = taskData.Replace("\r\n", "\n").Split('\n'); while (lineNum < lines.Length) { string key, value; if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "inv_object") { #region inv_object // In practice this appears to only be used for folders UUID itemID = UUID.Zero; UUID parentID = UUID.Zero; string name = String.Empty; AssetType assetType = AssetType.Unknown; while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "obj_id") { UUID.TryParse(value, out itemID); } else if (key == "parent_id") { UUID.TryParse(value, out parentID); } else if (key == "type") { assetType = StringToAssetType(value); } else if (key == "name") { name = value.Substring(0, value.IndexOf('|')); } } } if (assetType == AssetType.Folder) { InventoryFolder folder = new InventoryFolder(itemID); folder.Name = name; folder.ParentUUID = parentID; items.Add(folder); } else { InventoryItem item = new InventoryItem(itemID); item.Name = name; item.ParentUUID = parentID; item.AssetType = assetType; items.Add(item); } #endregion inv_object } else if (key == "inv_item") { #region inv_item // Any inventory item that links to an assetID, has permissions, etc UUID itemID = UUID.Zero; UUID assetID = UUID.Zero; UUID parentID = UUID.Zero; UUID creatorID = UUID.Zero; UUID ownerID = UUID.Zero; UUID lastOwnerID = UUID.Zero; UUID groupID = UUID.Zero; bool groupOwned = false; string name = String.Empty; string desc = String.Empty; AssetType assetType = AssetType.Unknown; InventoryType inventoryType = InventoryType.Unknown; DateTime creationDate = Utils.Epoch; uint flags = 0; Permissions perms = Permissions.NoPermissions; SaleType saleType = SaleType.Not; int salePrice = 0; while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "item_id") { UUID.TryParse(value, out itemID); } else if (key == "parent_id") { UUID.TryParse(value, out parentID); } else if (key == "permissions") { #region permissions while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "creator_mask") { // Deprecated uint val; if (Utils.TryParseHex(value, out val)) perms.BaseMask = (PermissionMask)val; } else if (key == "base_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.BaseMask = (PermissionMask)val; } else if (key == "owner_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.OwnerMask = (PermissionMask)val; } else if (key == "group_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.GroupMask = (PermissionMask)val; } else if (key == "everyone_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.EveryoneMask = (PermissionMask)val; } else if (key == "next_owner_mask") { uint val; if (Utils.TryParseHex(value, out val)) perms.NextOwnerMask = (PermissionMask)val; } else if (key == "creator_id") { UUID.TryParse(value, out creatorID); } else if (key == "owner_id") { UUID.TryParse(value, out ownerID); } else if (key == "last_owner_id") { UUID.TryParse(value, out lastOwnerID); } else if (key == "group_id") { UUID.TryParse(value, out groupID); } else if (key == "group_owned") { uint val; if (UInt32.TryParse(value, out val)) groupOwned = (val != 0); } } } #endregion permissions } else if (key == "sale_info") { #region sale_info while (lineNum < lines.Length) { if (ParseLine(lines[lineNum++], out key, out value)) { if (key == "{") { continue; } else if (key == "}") { break; } else if (key == "sale_type") { saleType = StringToSaleType(value); } else if (key == "sale_price") { Int32.TryParse(value, out salePrice); } } } #endregion sale_info } else if (key == "shadow_id") { //FIXME: } else if (key == "asset_id") { UUID.TryParse(value, out assetID); } else if (key == "type") { assetType = StringToAssetType(value); } else if (key == "inv_type") { inventoryType = StringToInventoryType(value); } else if (key == "flags") { UInt32.TryParse(value, out flags); } else if (key == "name") { name = value.Substring(0, value.IndexOf('|')); } else if (key == "desc") { desc = value.Substring(0, value.IndexOf('|')); } else if (key == "creation_date") { uint timestamp; if (UInt32.TryParse(value, out timestamp)) creationDate = Utils.UnixTimeToDateTime(timestamp); else Logger.Log("Failed to parse creation_date " + value, Helpers.LogLevel.Warning); } } } InventoryItem item = CreateInventoryItem(inventoryType, itemID); item.AssetUUID = assetID; item.AssetType = assetType; item.CreationDate = creationDate; item.CreatorID = creatorID; item.Description = desc; item.Flags = flags; item.GroupID = groupID; item.GroupOwned = groupOwned; item.Name = name; item.OwnerID = ownerID; item.ParentUUID = parentID; item.Permissions = perms; item.SalePrice = salePrice; item.SaleType = saleType; items.Add(item); #endregion inv_item } else { Logger.Log("Unrecognized token " + key + " in: " + Environment.NewLine + taskData, Helpers.LogLevel.Error); } } } return items; } #endregion Helper Functions #region Callbacks private void CreateItemFromAssetResponse(CapsClient client, OSD result, Exception error) { object[] args = (object[])client.UserData; CapsClient.ProgressCallback progCallback = (CapsClient.ProgressCallback)args[0]; ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[1]; byte[] itemData = (byte[])args[2]; if (result == null) { try { callback(false, error.Message, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } return; } 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.OnProgress += progCallback; upload.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); upload.UserData = new object[] { null, callback, itemData }; upload.StartRequest(itemData, "application/octet-stream"); } else if (status == "complete") { Logger.DebugLog("CreateItemFromAsset: completed"); if (contents.ContainsKey("new_inventory_item") && contents.ContainsKey("new_asset")) { try { callback(true, String.Empty, contents["new_inventory_item"].AsUUID(), contents["new_asset"].AsUUID()); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } else { try { callback(false, "Failed to parse asset and item UUIDs", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } else { // Failure try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } private void SaveAssetIntoInventoryHandler(Packet packet, Simulator simulator) { //SaveAssetIntoInventoryPacket save = (SaveAssetIntoInventoryPacket)packet; // FIXME: Find this item in the inventory structure and mark the parent as needing an update //save.InventoryData.ItemID; Logger.Log("SaveAssetIntoInventory packet received, someone write this function!", Helpers.LogLevel.Error, _Client); } private void InventoryDescendentsHandler(Packet packet, Simulator simulator) { InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet; if (reply.AgentData.Descendents > 0) { // InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders if (reply.FolderData[0].FolderID != UUID.Zero) { // Iterate folders in this packet for (int i = 0; i < reply.FolderData.Length; i++) { InventoryFolder folder = new InventoryFolder(reply.FolderData[i].FolderID); folder.ParentUUID = reply.FolderData[i].ParentID; folder.Name = Utils.BytesToString(reply.FolderData[i].Name); folder.PreferredType = (AssetType)reply.FolderData[i].Type; folder.OwnerID = reply.AgentData.OwnerID; _Store[folder.UUID] = folder; } } // InventoryDescendantsReply sends a null item if the parent doesnt contain any items. if (reply.ItemData[0].ItemID != UUID.Zero) { // Iterate items in this packet for (int i = 0; i < reply.ItemData.Length; i++) { if (reply.ItemData[i].ItemID != UUID.Zero) { InventoryItem item; /* * Objects that have been attached in-world prior to being stored on the * asset server are stored with the InventoryType of 0 (Texture) * instead of 17 (Attachment) * * This corrects that behavior by forcing Object Asset types that have an * invalid InventoryType with the proper InventoryType of Attachment. */ if ((AssetType)reply.ItemData[i].Type == AssetType.Object && (InventoryType)reply.ItemData[i].InvType == InventoryType.Texture) { item = CreateInventoryItem(InventoryType.Attachment, reply.ItemData[i].ItemID); item.InventoryType = InventoryType.Attachment; } else { item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID); item.InventoryType = (InventoryType)reply.ItemData[i].InvType; } item.ParentUUID = reply.ItemData[i].FolderID; item.CreatorID = reply.ItemData[i].CreatorID; item.AssetType = (AssetType)reply.ItemData[i].Type; item.AssetUUID = reply.ItemData[i].AssetID; item.CreationDate = Utils.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate); item.Description = Utils.BytesToString(reply.ItemData[i].Description); item.Flags = reply.ItemData[i].Flags; item.Name = Utils.BytesToString(reply.ItemData[i].Name); item.GroupID = reply.ItemData[i].GroupID; item.GroupOwned = reply.ItemData[i].GroupOwned; item.Permissions = new Permissions( reply.ItemData[i].BaseMask, reply.ItemData[i].EveryoneMask, reply.ItemData[i].GroupMask, reply.ItemData[i].NextOwnerMask, reply.ItemData[i].OwnerMask); item.SalePrice = reply.ItemData[i].SalePrice; item.SaleType = (SaleType)reply.ItemData[i].SaleType; item.OwnerID = reply.AgentData.OwnerID; _Store[item.UUID] = item; } } } } InventoryFolder parentFolder = null; if (_Store.Contains(reply.AgentData.FolderID) && _Store[reply.AgentData.FolderID] is InventoryFolder) { parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder; } else { Logger.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToString() + " or it is not a folder", Helpers.LogLevel.Error, _Client); return; } if (reply.AgentData.Version < parentFolder.Version) { Logger.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name + ", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version, Helpers.LogLevel.Warning, _Client); return; } parentFolder.Version = reply.AgentData.Version; // FIXME: reply.AgentData.Descendants is not parentFolder.DescendentCount if we didn't // request items and folders parentFolder.DescendentCount = reply.AgentData.Descendents; #region FindObjectsByPath Handling if (_Searches.Count > 0) { lock (_Searches) { StartSearch: // Iterate over all of the outstanding searches for (int i = 0; i < _Searches.Count; i++) { InventorySearch search = _Searches[i]; List folderContents = _Store.GetContents(search.Folder); // Iterate over all of the inventory objects in the base search folder for (int j = 0; j < folderContents.Count; j++) { // Check if this inventory object matches the current path node if (folderContents[j].Name == search.Path[search.Level]) { if (search.Level == search.Path.Length - 1) { Logger.DebugLog("Finished path search of " + String.Join("/", search.Path), _Client); // This is the last node in the path, fire the callback and clean up if (OnFindObjectByPath != null) { try { OnFindObjectByPath(String.Join("/", search.Path), folderContents[j].UUID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // Remove this entry and restart the loop since we are changing the collection size _Searches.RemoveAt(i); goto StartSearch; } else { // We found a match but it is not the end of the path, request the next level Logger.DebugLog(String.Format("Matched level {0}/{1} in a path search of {2}", search.Level, search.Path.Length - 1, String.Join("/", search.Path)), _Client); search.Folder = folderContents[j].UUID; search.Level++; _Searches[i] = search; RequestFolderContents(search.Folder, search.Owner, true, true, InventorySortOrder.ByName); } } } } } } #endregion FindObjectsByPath Handling // Callback for inventory folder contents being updated if (OnFolderUpdated != null) { try { OnFolderUpdated(parentFolder.UUID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } /// /// UpdateCreateInventoryItem packets are received when a new inventory item /// is created. This may occur when an object that's rezzed in world is /// taken into inventory, when an item is created using the CreateInventoryItem /// packet, or when an object is purchased /// private void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator) { UpdateCreateInventoryItemPacket reply = packet as UpdateCreateInventoryItemPacket; foreach (UpdateCreateInventoryItemPacket.InventoryDataBlock dataBlock in reply.InventoryData) { if (dataBlock.InvType == (sbyte)InventoryType.Folder) { Logger.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet, this should not happen!", Helpers.LogLevel.Error, _Client); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; /* * 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); // 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 e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // TODO: Is this callback even triggered when items are copied? // Look for an "item copied" callback ItemCopiedCallback copyCallback; if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) { _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); try { copyCallback(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } //This is triggered when an item is received from a task if (OnTaskItemReceived != null) { try { OnTaskItemReceived(item.UUID, dataBlock.FolderID, item.CreatorID, item.AssetUUID, item.InventoryType); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } private void MoveInventoryItemHandler(Packet packet, Simulator simulator) { MoveInventoryItemPacket move = (MoveInventoryItemPacket)packet; for (int i = 0; i < move.InventoryData.Length; i++) { // FIXME: Do something here string newName = Utils.BytesToString(move.InventoryData[i].NewName); Logger.Log(String.Format( "MoveInventoryItemHandler: Item {0} is moving to Folder {1} with new name \"{2}\". Someone write this function!", move.InventoryData[i].ItemID.ToString(), move.InventoryData[i].FolderID.ToString(), newName), Helpers.LogLevel.Warning, _Client); } } private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator) { BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket; if (update.FolderData.Length > 0 && update.FolderData[0].FolderID != UUID.Zero) { foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData) { if (!_Store.Contains(dataBlock.FolderID)) Logger.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning, _Client); InventoryFolder folder = new InventoryFolder(dataBlock.FolderID); folder.Name = Utils.BytesToString(dataBlock.Name); folder.OwnerID = update.AgentData.AgentID; folder.ParentUUID = dataBlock.ParentID; _Store[folder.UUID] = folder; } } if (update.ItemData.Length > 0 && update.ItemData[0].ItemID != UUID.Zero) { for (int i = 0; i < update.ItemData.Length; i++) { BulkUpdateInventoryPacket.ItemDataBlock dataBlock = update.ItemData[i]; // If we are given a folder of items, the item information might arrive before the folder // (parent) is in the store if (!_Store.Contains(dataBlock.ItemID)) Logger.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning, _Client); InventoryItem item = SafeCreateInventoryItem((InventoryType)dataBlock.InvType, dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; if (dataBlock.AssetID != UUID.Zero) item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; _Store[item.UUID] = item; // Look for an "item created" callback ItemCreatedCallback callback; if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback)) { _ItemCreatedCallbacks.Remove(dataBlock.CallbackID); try { callback(true, item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } // Look for an "item copied" callback ItemCopiedCallback copyCallback; if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) { _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); try { copyCallback(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } } private void FetchInventoryReplyHandler(Packet packet, Simulator simulator) { FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket; foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData) { if (dataBlock.InvType == (sbyte)InventoryType.Folder) { Logger.Log("Received FetchInventoryReply for an inventory folder, this should not happen!", Helpers.LogLevel.Error, _Client); continue; } InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = Utils.UnixTimeToDateTime(dataBlock.CreationDate); item.CreatorID = dataBlock.CreatorID; item.Description = Utils.BytesToString(dataBlock.Description); item.Flags = dataBlock.Flags; item.GroupID = dataBlock.GroupID; item.GroupOwned = dataBlock.GroupOwned; item.Name = Utils.BytesToString(dataBlock.Name); item.OwnerID = dataBlock.OwnerID; item.ParentUUID = dataBlock.FolderID; item.Permissions = new Permissions( dataBlock.BaseMask, dataBlock.EveryoneMask, dataBlock.GroupMask, dataBlock.NextOwnerMask, dataBlock.OwnerMask); item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; _Store[item.UUID] = item; // Fire the callback for an item being fetched if (OnItemReceived != null) { try { OnItemReceived(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } private void ReplyTaskInventoryHandler(Packet packet, Simulator simulator) { if (OnTaskInventoryReply != null) { ReplyTaskInventoryPacket reply = (ReplyTaskInventoryPacket)packet; try { OnTaskInventoryReply(reply.InventoryData.TaskID, reply.InventoryData.Serial, Utils.BytesToString(reply.InventoryData.Filename)); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } private void Self_OnInstantMessage(InstantMessage im, Simulator simulator) { // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we // handle it here? if (OnObjectOffered != null && (im.Dialog == InstantMessageDialog.InventoryOffered || im.Dialog == InstantMessageDialog.TaskInventoryOffered)) { AssetType type = AssetType.Unknown; UUID objectID = UUID.Zero; bool fromTask = false; if (im.Dialog == InstantMessageDialog.InventoryOffered) { if (im.BinaryBucket.Length == 17) { type = (AssetType)im.BinaryBucket[0]; objectID = new UUID(im.BinaryBucket, 1); fromTask = false; } else { Logger.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning, _Client); return; } } else if (im.Dialog == InstantMessageDialog.TaskInventoryOffered) { if (im.BinaryBucket.Length == 1) { type = (AssetType)im.BinaryBucket[0]; fromTask = true; } else { Logger.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning, _Client); return; } } // Find the folder where this is going to go UUID destinationFolderID = FindFolderForType(type); // Fire the callback try { ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket(); imp.AgentData.AgentID = _Client.Self.AgentID; imp.AgentData.SessionID = _Client.Self.SessionID; imp.MessageBlock.FromGroup = false; imp.MessageBlock.ToAgentID = im.FromAgentID; imp.MessageBlock.Offline = 0; imp.MessageBlock.ID = im.IMSessionID; imp.MessageBlock.Timestamp = 0; imp.MessageBlock.FromAgentName = Utils.StringToBytes(_Client.Self.Name); imp.MessageBlock.Message = Utils.EmptyBytes; imp.MessageBlock.ParentEstateID = 0; imp.MessageBlock.RegionID = UUID.Zero; imp.MessageBlock.Position = _Client.Self.SimPosition; if (OnObjectOffered(im, type, objectID, fromTask)) { // Accept the inventory offer switch (im.Dialog) { case InstantMessageDialog.InventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryAccepted; break; case InstantMessageDialog.TaskInventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryAccepted; break; case InstantMessageDialog.GroupNotice: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryAccepted; break; } imp.MessageBlock.BinaryBucket = destinationFolderID.GetBytes(); } else { // Decline the inventory offer switch (im.Dialog) { case InstantMessageDialog.InventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryDeclined; break; case InstantMessageDialog.TaskInventoryOffered: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryDeclined; break; case InstantMessageDialog.GroupNotice: imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryDeclined; break; } imp.MessageBlock.BinaryBucket = Utils.EmptyBytes; } _Client.Network.SendPacket(imp, simulator); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData replyData) { if (loginSuccess) { // Initialize the store here so we know who owns it: _Store = new Inventory(_Client, this, _Client.Self.AgentID); Logger.DebugLog("Setting InventoryRoot to " + replyData.InventoryRoot.ToString(), _Client); InventoryFolder rootFolder = new InventoryFolder(replyData.InventoryRoot); rootFolder.Name = String.Empty; rootFolder.ParentUUID = UUID.Zero; _Store.RootFolder = rootFolder; for (int i = 0; i < replyData.InventorySkeleton.Length; i++) _Store.UpdateNodeFor(replyData.InventorySkeleton[i]); InventoryFolder libraryRootFolder = new InventoryFolder(replyData.LibraryRoot); libraryRootFolder.Name = String.Empty; libraryRootFolder.ParentUUID = UUID.Zero; _Store.LibraryFolder = libraryRootFolder; for(int i = 0; i < replyData.LibrarySkeleton.Length; i++) _Store.UpdateNodeFor(replyData.LibrarySkeleton[i]); } } private void UploadNotecardAssetResponse(CapsClient client, OSD result, Exception error) { OSDMap contents = (OSDMap)result; KeyValuePair kvp = (KeyValuePair)(((object[])client.UserData)[0]); NotecardUploadedAssetCallback callback = kvp.Key; byte[] itemData = (byte[])kvp.Value; string status = contents["state"].AsString(); if (status == "upload") { string uploadURL = contents["uploader"].AsString(); // This makes the assumption that all uploads go to CurrentSim, to avoid // the problem of HttpRequestState not knowing anything about simulators CapsClient upload = new CapsClient(new Uri(uploadURL)); upload.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; upload.StartRequest(itemData, "application/octet-stream"); } else if (status == "complete") { if (contents.ContainsKey("new_asset")) { try { callback(true, String.Empty, (UUID)(((object[])client.UserData)[1]), contents["new_asset"].AsUUID()); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } else { try { callback(false, "Failed to parse asset and item UUIDs", UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } else { // Failure try { callback(false, status, UUID.Zero, UUID.Zero); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } #endregion Callbacks } }