/* * Copyright (c) 2006-2007, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Threading; using libsecondlife.Packets; namespace libsecondlife { /// /// /// public class DirectoryManager { /// /// The different categories a classified ad can be placed in /// public enum ClassifiedCategories { /// Any = 0, /// Shopping, /// LandRental, /// PropertyRental, /// SpecialAttraction, /// NewProducts, /// Employment, /// Wanted, /// Service, /// Personal } /// /// /// [Flags] public enum DirFindFlags { /// People = 1 << 0, /// Online = 1 << 1, /// [Obsolete] Places = 1 << 2, /// Events = 1 << 3, /// Groups = 1 << 4, /// DateEvents = 1 << 5, /// AgentOwned = 1 << 6, /// ForSale = 1 << 7, /// GroupOwned = 1 << 8, /// [Obsolete] Auction = 1 << 9, /// DwellSort = 1 << 10, /// PgSimsOnly = 1 << 11, /// PicturesOnly = 1 << 12, /// PgEventsOnly = 1 << 13, /// MatureSimsOnly = 1 << 14, /// SortAsc = 1 << 15, /// PricesSort = 1 << 16, /// PerMeterSort = 1 << 17, /// AreaSort = 1 << 18, /// NameSort = 1 << 19, /// LimitByPrice = 1 << 20, /// LimitByArea = 1 << 21 } /// /// /// [Flags] public enum SearchTypeFlags { /// None = 0, /// Auction = 1 << 1, /// Newbie = 1 << 2, /// Mainland = 1 << 3, /// Estate = 1 << 4 } /// /// A classified ad in Second Life /// public struct Classified { /// UUID for this ad, useful for looking up detailed /// information about it public LLUUID ID; /// The title of this classified ad public string Name; /// Unknown public byte Flags; /// Creation date of the ad public DateTime CreationDate; /// Expiration date of the ad public DateTime ExpirationDate; /// Price that was paid for this ad public int Price; } /// /// A parcel retrieved from the dataserver such as results from the /// "For-Sale" listings /// public struct DirectoryParcel { /// public LLUUID ID; /// public string Name; /// public int ActualArea; /// public int SalePrice; /// public bool Auction; /// public bool ForSale; } public struct AgentSearchData { public bool Online; public string FirstName; public string LastName; public LLUUID AgentID; } /// /// Response to a "Groups" Search /// public struct GroupSearchData { public LLUUID GroupID; public string GroupName; public int Members; } /// /// Response to a "Places" Search /// Note: This is not DirPlacesReply /// public struct PlacesSearchData { public LLUUID OwnerID; public string Name; public string Desc; public int ActualArea; public int BillableArea; public byte Flags; public float GlobalX; public float GlobalY; public float GlobalZ; public string SimName; public LLUUID SnapshotID; public float Dwell; public int Price; } /*/// public LLUUID OwnerID; /// public LLUUID SnapshotID; /// public ulong RegionHandle; /// public string SimName; /// public string Desc; /// public LLVector3 GlobalPosition; /// public LLVector3 SimPosition; /// public float Dwell;*/ /// /// /// /// public delegate void ClassifiedReplyCallback(List classifieds); /// /// /// /// public delegate void DirLandReplyCallback(List dirParcels); /// /// /// /// public delegate void DirPeopleReplyCallback(LLUUID queryID, List matchedPeople); /// /// /// /// /// public delegate void DirGroupsReplyCallback(LLUUID queryID, List matchedGroups); /// /// /// /// /// public delegate void PlacesReplyCallback(LLUUID queryID, List matchedPlaces); /// /// /// public event ClassifiedReplyCallback OnClassifiedReply; /// /// /// public event DirLandReplyCallback OnDirLandReply; public event DirPeopleReplyCallback OnDirPeopleReply; public event DirGroupsReplyCallback OnDirGroupsReply; public event PlacesReplyCallback OnPlacesReply; private SecondLife Client; public DirectoryManager(SecondLife client) { Client = client; Client.Network.RegisterCallback(PacketType.DirClassifiedReply, new NetworkManager.PacketCallback(DirClassifiedReplyHandler)); Client.Network.RegisterCallback(PacketType.DirLandReply, new NetworkManager.PacketCallback(DirLandReplyHandler)); Client.Network.RegisterCallback(PacketType.DirPeopleReply, new NetworkManager.PacketCallback(DirPeopleReplyHandler)); Client.Network.RegisterCallback(PacketType.DirGroupsReply, new NetworkManager.PacketCallback(DirGroupsReplyHandler)); Client.Network.RegisterCallback(PacketType.PlacesReply, new NetworkManager.PacketCallback(PlacesReplyHandler)); } public LLUUID StartClassifiedSearch(string searchText, ClassifiedCategories categories, bool mature) { DirClassifiedQueryPacket query = new DirClassifiedQueryPacket(); LLUUID queryID = LLUUID.Random(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Category = (uint)categories; query.QueryData.QueryFlags = (uint)(mature ? 0 : 2); query.QueryData.QueryID = queryID; query.QueryData.QueryText = Helpers.StringToField(searchText); Client.Network.SendPacket(query); return queryID; } /// /// Starts a search for land sales using the directory /// /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// A unique identifier that can identify packets associated /// with this query from other queries /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public LLUUID StartLandSearch(SearchTypeFlags typeFlags) { return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0); } /// /// Starts a search for land sales using the directory /// /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// Maximum price to search for /// Maximum area to search for /// Each request is limited to 100 parcels /// being returned. To get the first 100 parcels of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// A unique identifier that can identify packets associated /// with this query from other queries /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public LLUUID StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice | DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart); } /// /// Starts a search for land sales using the directory /// /// A flags parameter that can modify the way /// search results are returned, for example changing the ordering of /// results or limiting based on price or area /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// Maximum price to search for, the /// DirFindFlags.LimitByPrice flag must be set /// Maximum area to search for, the /// DirFindFlags.LimitByArea flag must be set /// Each request is limited to 100 parcels /// being returned. To get the first 100 parcels of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// A unique identifier that can identify packets associated /// with this query from other queries /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public LLUUID StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { LLUUID queryID = LLUUID.Random(); DirLandQueryPacket query = new DirLandQueryPacket(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Area = areaLimit; query.QueryData.Price = priceLimit; query.QueryData.QueryStart = queryStart; query.QueryData.SearchType = (uint)typeFlags; query.QueryData.QueryFlags = (uint)findFlags; query.QueryData.QueryID = queryID; Client.Network.SendPacket(query); return queryID; } /// /// Starts a search for a Group in the directory manager /// /// /// The text to search for /// Each request is limited to 100 parcels /// being returned. To get the first 100 parcels of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// A unique identifier that can identify packets associated /// with this query from other queries /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public LLUUID StartGroupSearch(DirFindFlags findFlags, string searchText, int queryStart) { return StartGroupSearch(findFlags, searchText, queryStart, LLUUID.Random()); } public LLUUID StartGroupSearch(DirFindFlags findFlags, string searchText, int queryStart, LLUUID queryID) { DirFindQueryPacket find = new DirFindQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.QueryData.QueryFlags = (uint)findFlags; find.QueryData.QueryText = Helpers.StringToField(searchText); find.QueryData.QueryID = queryID; find.QueryData.QueryStart = queryStart; Client.Network.SendPacket(find); return queryID; } public LLUUID StartPeopleSearch(DirFindFlags findFlags, string searchText, int queryStart) { return StartPeopleSearch(findFlags, searchText, queryStart, LLUUID.Random()); } public LLUUID StartPeopleSearch(DirFindFlags findFlags, string searchText, int queryStart, LLUUID queryID) { DirFindQueryPacket find = new DirFindQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.QueryData.QueryFlags = (uint)findFlags; find.QueryData.QueryText = Helpers.StringToField(searchText); find.QueryData.QueryID = queryID; find.QueryData.QueryStart = queryStart; Client.Network.SendPacket(find); return queryID; } /// /// Search "places" for Land you personally own /// public LLUUID StartPlacesSearch() { return StartPlacesSearch(DirFindFlags.AgentOwned, Parcel.ParcelCategory.Any, String.Empty, String.Empty, LLUUID.Zero, LLUUID.Zero); } /// /// Searches Places for Land owned by a specific user or group /// /// One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc. /// LLUID of group you want to recieve land list for (You must be in group), or /// LLUID.Zero for Your own land /// Transaction (Query) ID which can be associated with results from your request. public LLUUID StartPlacesSearch(DirFindFlags findFlags, LLUUID groupID) { return StartPlacesSearch(findFlags, Parcel.ParcelCategory.Any, String.Empty, String.Empty, groupID, LLUUID.Random()); } /// /// Search Places /// /// One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc. /// One of the values from the SearchCategory Struct, ie: Any, Linden, Newcomer /// LLUID of group you want to recieve results for /// Transaction (Query) ID which can be associated with results from your request. /// Transaction (Query) ID which can be associated with results from your request. public LLUUID StartPlacesSearch(DirFindFlags findFlags, Parcel.ParcelCategory searchCategory, LLUUID groupID, LLUUID transactionID) { return StartPlacesSearch(findFlags, searchCategory, String.Empty, String.Empty, groupID, transactionID); } /// /// Search Places - All Options /// /// One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc. /// One of the values from the SearchCategory Struct, ie: Any, Linden, Newcomer /// String Text to search for /// String Simulator Name to search in /// LLUID of group you want to recieve results for /// Transaction (Query) ID which can be associated with results from your request. /// Transaction (Query) ID which can be associated with results from your request. public LLUUID StartPlacesSearch(DirFindFlags findFlags, Parcel.ParcelCategory searchCategory, string searchText, string simulatorName, LLUUID groupID, LLUUID transactionID) { PlacesQueryPacket find = new PlacesQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.AgentData.QueryID = groupID; find.TransactionData.TransactionID = transactionID; find.QueryData.QueryText = Helpers.StringToField(searchText); find.QueryData.QueryFlags = (uint)findFlags; find.QueryData.Category = (sbyte)searchCategory; find.QueryData.SimName = Helpers.StringToField(simulatorName); Client.Network.SendPacket(find); return transactionID; } #region Blocking Functions public bool PeopleSearch(DirFindFlags findFlags, string searchText, int queryStart, int timeoutMS, out List results) { AutoResetEvent searchEvent = new AutoResetEvent(false); LLUUID id = LLUUID.Random(); List people = null; DirPeopleReplyCallback callback = delegate(LLUUID queryid, List matches) { if (id == queryid) { people = matches; searchEvent.Set(); } }; OnDirPeopleReply += callback; StartPeopleSearch(findFlags, searchText, queryStart, id); searchEvent.WaitOne(timeoutMS, false); OnDirPeopleReply -= callback; results = people; return (results != null); } #endregion Blocking Functions #region Packet Handlers private void DirClassifiedReplyHandler(Packet packet, Simulator simulator) { if (OnClassifiedReply != null) { DirClassifiedReplyPacket reply = (DirClassifiedReplyPacket)packet; List classifieds = new List(); foreach (DirClassifiedReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { Classified classified = new Classified(); classified.CreationDate = Helpers.UnixTimeToDateTime(block.CreationDate); classified.ExpirationDate = Helpers.UnixTimeToDateTime(block.ExpirationDate); classified.Flags = block.ClassifiedFlags; classified.ID = block.ClassifiedID; classified.Name = Helpers.FieldToUTF8String(block.Name); classified.Price = block.PriceForListing; classifieds.Add(classified); } try { OnClassifiedReply(classifieds); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void DirLandReplyHandler(Packet packet, Simulator simulator) { if (OnDirLandReply != null) { List parcelsForSale = new List(); DirLandReplyPacket reply = (DirLandReplyPacket)packet; foreach (DirLandReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { DirectoryParcel dirParcel = new DirectoryParcel(); dirParcel.ActualArea = block.ActualArea; dirParcel.ID = block.ParcelID; dirParcel.Name = Helpers.FieldToUTF8String(block.Name); dirParcel.SalePrice = block.SalePrice; dirParcel.Auction = block.Auction; dirParcel.ForSale = block.ForSale; parcelsForSale.Add(dirParcel); } try { OnDirLandReply(parcelsForSale); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void DirPeopleReplyHandler(Packet packet, Simulator simulator) { if (OnDirPeopleReply != null) { DirPeopleReplyPacket peopleReply = packet as DirPeopleReplyPacket; List matches = new List(peopleReply.QueryReplies.Length); foreach (DirPeopleReplyPacket.QueryRepliesBlock reply in peopleReply.QueryReplies) { AgentSearchData searchData = new AgentSearchData(); searchData.Online = reply.Online; searchData.FirstName = Helpers.FieldToUTF8String(reply.FirstName); searchData.LastName = Helpers.FieldToUTF8String(reply.LastName); searchData.AgentID = reply.AgentID; matches.Add(searchData); } try { OnDirPeopleReply(peopleReply.QueryData.QueryID, matches); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void DirGroupsReplyHandler(Packet packet, Simulator simulator) { if (OnDirGroupsReply != null) { DirGroupsReplyPacket groupsReply = packet as DirGroupsReplyPacket; List matches = new List(groupsReply.QueryReplies.Length); foreach (DirGroupsReplyPacket.QueryRepliesBlock reply in groupsReply.QueryReplies) { GroupSearchData groupsData = new GroupSearchData(); groupsData.GroupID = reply.GroupID; groupsData.GroupName = Helpers.FieldToUTF8String(reply.GroupName); groupsData.Members = reply.Members; matches.Add(groupsData); } try { OnDirGroupsReply(groupsReply.QueryData.QueryID, matches); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void PlacesReplyHandler(Packet packet, Simulator simulator) { if (OnPlacesReply != null) { PlacesReplyPacket placesReply = packet as PlacesReplyPacket; List places = new List(); foreach (PlacesReplyPacket.QueryDataBlock block in placesReply.QueryData) { PlacesSearchData place = new PlacesSearchData(); place.OwnerID = block.OwnerID; place.Name = Helpers.FieldToUTF8String(block.Name); place.Desc = Helpers.FieldToUTF8String(block.Desc); place.ActualArea = block.ActualArea; place.BillableArea = block.BillableArea; place.Flags = block.Flags; place.GlobalX = block.GlobalX; place.GlobalY = block.GlobalY; place.GlobalZ = block.GlobalZ; place.SimName = Helpers.FieldToUTF8String(block.SimName); place.SnapshotID = block.SnapshotID; place.Dwell = block.Dwell; place.Price = block.Price; places.Add(place); } try { OnPlacesReply(placesReply.TransactionData.TransactionID, places); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } #endregion Packet Handlers } }