using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Globalization; using OpenMetaverse; namespace Simian { [Flags] public enum InventorySortOrder : uint { // ItemsByName = 0, /// ByDate = 1, /// FoldersByName = 2, /// SystemFoldersToTop = 4 } #region Inventory Item Containers /// /// Base class that inventory items and folders inherit from /// public abstract class InventoryObject { /// of the inventory item public UUID ID; /// of the parent folder public UUID ParentID; /// Item name public string Name = String.Empty; /// Item owner public UUID OwnerID; /// Parent folder public InventoryObject Parent; public override int GetHashCode() { return ID.GetHashCode(); } } /// /// Inventory item /// public class InventoryItem : InventoryObject { /// of the asset this item points to public UUID AssetID; /// 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; /// The s this item is set to or owned by public UUID GroupID; /// A Description of this item public string Description = String.Empty; /// If true, item is owned by a group public bool GroupOwned; /// The combined of this item public Permissions Permissions; /// 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; public override bool Equals(object obj) { if (!(obj is ItemData)) return false; InventoryItem o = (InventoryItem)obj; return o.ID == ID && o.ParentID == ParentID && o.Name == Name && o.OwnerID == OwnerID && o.AssetType == AssetType && o.AssetID == AssetID && o.CreationDate == CreationDate && o.Description == Description && o.Flags == Flags && o.GroupID == GroupID && o.GroupOwned == GroupOwned && o.InventoryType == InventoryType && o.Permissions.Equals(Permissions) && o.SalePrice == SalePrice && o.SaleType == SaleType; } public static bool operator ==(InventoryItem lhs, InventoryItem rhs) { return lhs.Equals(rhs); } public static bool operator !=(InventoryItem lhs, InventoryItem rhs) { return !(lhs == rhs); } /// /// Returns the ItemData in the hierarchical bracket format /// used in the Second Life client's notecards and inventory cache. /// /// a string representation of this ItemData public override string ToString() { StringWriter writer = new StringWriter(); ToString(writer); return writer.ToString(); } /// /// Writes the inventory item to the TextWriter in the hierarchical bracket format /// used in the Second Life client's notecards and inventory cache. /// /// Writer to write to. public void ToString(TextWriter writer) { writer.WriteLine("inv_item\t0"); writer.WriteLine('{'); writer.WriteLine("\titem_id\t{0}", ID.ToString()); writer.WriteLine("\tparent_id\t{0}", ParentID.ToString()); // Permissions: writer.WriteLine("permissions\t0"); writer.WriteLine('{'); writer.WriteLine("\tbase_mask\t{0}", String.Format("{0:x}", (uint)Permissions.BaseMask).PadLeft(8, '0')); writer.WriteLine("\towner_mask\t{0}", String.Format("{0:x}", (uint)Permissions.OwnerMask).PadLeft(8, '0')); writer.WriteLine("\tgroup_mask\t{0}", String.Format("{0:x}", (uint)Permissions.GroupMask).PadLeft(8, '0')); writer.WriteLine("\teveryone_mask\t{0}", String.Format("{0:x}", (uint)Permissions.EveryoneMask).PadLeft(8, '0')); writer.WriteLine("\tnext_owner_mask\t{0}", String.Format("{0:x}", (uint)Permissions.NextOwnerMask).PadLeft(8, '0')); writer.WriteLine("\tcreator_id\t{0}", CreatorID.ToString()); writer.WriteLine("\towner_id\t{0}", OwnerID.ToString()); writer.WriteLine("\tlast_owner_id\t{0}", UUID.Zero); // FIXME? writer.WriteLine("\tgroup_id\t{0}", GroupID.ToString()); writer.WriteLine('}'); writer.WriteLine("\tasset_id\t{0}", AssetID.ToString()); writer.WriteLine("\ttype\t{0}", AssetTypeParser.StringValueOf(AssetType)); writer.WriteLine("\tinv_type\t{0}", InventoryTypeParser.StringValueOf(InventoryType)); writer.WriteLine("\tflags\t{0}", string.Format("{0:x}", Flags).PadLeft(8, '0')); // Sale info: writer.WriteLine("sale_info\t0"); writer.WriteLine('{'); writer.WriteLine("\tsale_type\t{0}", SaleTypeParser.StringValueOf(SaleType)); writer.WriteLine("\tsale_price\t{0}", SalePrice); writer.WriteLine('}'); writer.WriteLine("\tname\t{0}|", Name); writer.WriteLine("\tdesc\t{0}|", Description); writer.WriteLine("\tcreation_date\t{0}", Utils.DateTimeToUnixTime(CreationDate)); writer.WriteLine('}'); } /// /// Reads the ItemData from a string source. The string is wrapped /// in a and passed to the /// other method. /// /// String to parse ItemData from. /// Parsed ItemData public static ItemData Parse(string src) { return Parse(new StringReader(src)); } /// /// Reads an ItemData from a TextReader source. The format of the text /// should be the same as the one used by Second Life Notecards. The TextReader should /// be placed ideally on the line containing "inv_item" but parsing will succeed as long /// as it is before the opening bracket immediately following the inv_item line. /// The TextReader will be placed on the line following the inv_item's closing bracket. /// /// text source /// Parsed item. public static ItemData Parse(TextReader reader) { ItemData item = new ItemData(); #region Parsing TextData invItem = TextHierarchyParser.Parse(reader); Console.WriteLine(invItem); //if (invItem.Name == "inv_item") // YAY item.UUID = new UUID(invItem.Nested["item_id"].Value); item.ParentUUID = new UUID(invItem.Nested["parent_id"].Value); item.AssetUUID = new UUID(invItem.Nested["asset_id"].Value); item.AssetType = AssetTypeParser.Parse(invItem.Nested["type"].Value); item.InventoryType = InventoryTypeParser.Parse(invItem.Nested["inv_type"].Value); Utils.TryParseHex(invItem.Nested["flags"].Value, out item.Flags); string rawName = invItem.Nested["name"].Value; item.Name = rawName.Substring(0, rawName.LastIndexOf('|')); string rawDesc = invItem.Nested["desc"].Value; item.Description = rawDesc.Substring(0, rawDesc.LastIndexOf('|')); item.CreationDate = Utils.UnixTimeToDateTime(uint.Parse(invItem.Nested["creation_date"].Value)); // Sale info: TextData saleInfo = invItem.Nested["sale_info"]; item.SalePrice = int.Parse(saleInfo.Nested["sale_price"].Value); item.SaleType = SaleTypeParser.Parse(saleInfo.Nested["sale_type"].Value); TextData permissions = invItem.Nested["permissions"]; item.Permissions = new Permissions(); item.Permissions.BaseMask = (PermissionMask)uint.Parse(permissions.Nested["base_mask"].Value, NumberStyles.HexNumber); item.Permissions.EveryoneMask = (PermissionMask)uint.Parse(permissions.Nested["everyone_mask"].Value, NumberStyles.HexNumber); item.Permissions.GroupMask = (PermissionMask)uint.Parse(permissions.Nested["group_mask"].Value, NumberStyles.HexNumber); item.Permissions.OwnerMask = (PermissionMask)uint.Parse(permissions.Nested["owner_mask"].Value, NumberStyles.HexNumber); item.Permissions.NextOwnerMask = (PermissionMask)uint.Parse(permissions.Nested["next_owner_mask"].Value, NumberStyles.HexNumber); item.CreatorID = new UUID(permissions.Nested["creator_id"].Value); item.OwnerID = new UUID(permissions.Nested["owner_id"].Value); item.GroupID = new UUID(permissions.Nested["group_id"].Value); // permissions.Nested["last_owner_id"] // FIXME? #endregion return item; } /// /// /// /// String to parse from. /// Parsed ItemData. /// true if successful, false otherwise. public static bool TryParse(string str, out ItemData item) { return TryParse(new StringReader(str), out item); } /// /// /// /// Text source. /// Parsed ItemData. /// true if successful false otherwise. public static bool TryParse(TextReader reader, out ItemData item) { try { item = Parse(reader); } catch (Exception e) { item = new ItemData(); Logger.Log(e.Message, Helpers.LogLevel.Error, e); return false; } return true; } } /// /// Inventory folder /// public class InventoryFolder : InventoryObject { /// The Preferred for a folder. public AssetType PreferredType; /// The Version of this folder public int Version; /// Number of child items this folder contains public InternalDictionary Children = new InternalDictionary(); public override bool Equals(object obj) { if (!(obj is FolderData)) return false; InventoryFolder o = (InventoryFolder)obj; return o.ID == ID; } public static bool operator ==(InventoryFolder lhs, InventoryFolder rhs) { return lhs.Equals(rhs); } public static bool operator !=(InventoryFolder lhs, InventoryFolder rhs) { return !(lhs == rhs); } /// /// Returns the FolderData in the hierarchical bracket format /// used in the Second Life client's notecards and inventory cache. /// /// a string representation of this FolderData public override string ToString() { StringWriter writer = new StringWriter(); ToString(writer); return writer.ToString(); } /// /// Writes the FolderData to the TextWriter in the hierarchical bracket format /// used in the Second Life client's notecards and inventory cache. /// /// Writer to write to. public void ToString(TextWriter writer) { writer.WriteLine("inv_category\t0"); writer.WriteLine('{'); writer.WriteLine("\tcat_id\t{0}", ID.ToString()); writer.WriteLine("\tparent_id\t{0}", ParentID.ToString()); writer.WriteLine("\ttype\tcategory"); // TODO: Some folders have "-1" as their perf_type, investigate this. writer.WriteLine("\tpref_type\t{0}", AssetTypeParser.StringValueOf(PreferredType)); writer.WriteLine("\tname\t{0}|", Name); writer.WriteLine("\towner_id\t{0}", OwnerID.ToString()); writer.WriteLine("\tversion\t{0}", Version); writer.WriteLine('}'); } /// /// Reads the FolderData from a string source. The string is wrapped /// in a and passed to the /// other method. /// /// String to parse FolderData from. /// Parsed FolderData public static FolderData Parse(string src) { return Parse(new StringReader(src)); } /// /// Reads an InventoryItem from a TextReader source. The format of the text /// should be the same as the one used by Second Life Notecards. The TextReader should /// be placed ideally on the line containing "inv_category" but parsing will succeed as long /// as it is before the opening bracket immediately following the inv_category line. /// The TextReader will be placed on the line following the inv_category's closing bracket. /// /// text source /// Parsed item. public static FolderData Parse(TextReader reader) { FolderData folder = new FolderData(); #region Parsing TextData invCategory = TextHierarchyParser.Parse(reader); //if (invCategory.Name == "inv_category") // YAY folder.UUID = new UUID(invCategory.Nested["cat_id"].Value); string rawName = invCategory.Nested["name"].Value; folder.Name = rawName.Substring(0, rawName.LastIndexOf('|')); folder.OwnerID = new UUID(invCategory.Nested["owner_id"].Value); folder.ParentUUID = new UUID(invCategory.Nested["parent_id"].Value); folder.PreferredType = AssetTypeParser.Parse(invCategory.Nested["pref_type"].Value); folder.Version = int.Parse(invCategory.Nested["version"].Value); // TODO: Investigate invCategory.Nested["type"] #endregion return folder; } public static bool TryParse(string str, out FolderData folder) { return TryParse(new StringReader(str), out folder); } public static bool TryParse(TextReader reader, out FolderData folder) { try { folder = Parse(reader); } catch (Exception e) { folder = new FolderData(); Logger.Log(e.Message, Helpers.LogLevel.Error, e); return false; } return true; } } #endregion Inventory Item Containers }