/* * Copyright (c) 2006-2014, 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.Threading; using System.Collections.Generic; using OpenMetaverse.Packets; using OpenMetaverse.Interfaces; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { /// /// Access to the data server which allows searching for land, events, people, etc /// public class DirectoryManager { #region Enums /// Classified Ad categories public enum ClassifiedCategories { /// Classified is listed in the Any category Any = 0, /// Classified is shopping related Shopping, /// Classified is LandRental, /// PropertyRental, /// SpecialAttraction, /// NewProducts, /// Employment, /// Wanted, /// Service, /// Personal } /// Event Categories public enum EventCategories { /// All = 0, /// Discussion = 18, /// Sports = 19, /// LiveMusic = 20, /// Commercial = 22, /// Nightlife = 23, /// Games = 24, /// Pageants = 25, /// Education = 26, /// Arts = 27, /// Charity = 28, /// Miscellaneous = 29 } /// /// Query Flags used in many of the DirectoryManager methods to specify which query to execute and how to return the results. /// /// Flags can be combined using the | (pipe) character, not all flags are available in all queries /// [Flags] public enum DirFindFlags { /// Query the People database People = 1 << 0, /// Online = 1 << 1, // //[Obsolete] //Places = 1 << 2, /// Events = 1 << 3, /// Query the Groups database Groups = 1 << 4, /// Query the Events database DateEvents = 1 << 5, /// Query the land holdings database for land owned by the currently connected agent AgentOwned = 1 << 6, /// ForSale = 1 << 7, /// Query the land holdings database for land which is owned by a Group GroupOwned = 1 << 8, // //[Obsolete] //Auction = 1 << 9, /// Specifies the query should pre sort the results based upon traffic /// when searching the Places database DwellSort = 1 << 10, /// PgSimsOnly = 1 << 11, /// PicturesOnly = 1 << 12, /// PgEventsOnly = 1 << 13, /// MatureSimsOnly = 1 << 14, /// Specifies the query should pre sort the results in an ascending order when searching the land sales database. /// This flag is only used when searching the land sales database SortAsc = 1 << 15, /// Specifies the query should pre sort the results using the SalePrice field when searching the land sales database. /// This flag is only used when searching the land sales database PricesSort = 1 << 16, /// Specifies the query should pre sort the results by calculating the average price/sq.m (SalePrice / Area) when searching the land sales database. /// This flag is only used when searching the land sales database PerMeterSort = 1 << 17, /// Specifies the query should pre sort the results using the ParcelSize field when searching the land sales database. /// This flag is only used when searching the land sales database AreaSort = 1 << 18, /// Specifies the query should pre sort the results using the Name field when searching the land sales database. /// This flag is only used when searching the land sales database NameSort = 1 << 19, /// When set, only parcels less than the specified Price will be included when searching the land sales database. /// This flag is only used when searching the land sales database LimitByPrice = 1 << 20, /// When set, only parcels greater than the specified Size will be included when searching the land sales database. /// This flag is only used when searching the land sales database LimitByArea = 1 << 21, /// FilterMature = 1 << 22, /// PGOnly = 1 << 23, /// Include PG land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludePG = 1 << 24, /// Include Mature land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludeMature = 1 << 25, /// Include Adult land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludeAdult = 1 << 26, /// AdultOnly = 1 << 27 } /// /// Land types to search dataserver for /// [Flags] public enum SearchTypeFlags { /// Search Auction, Mainland and Estate Any = -1, /// Land which is currently up for auction Auction = 1 << 1, // Land available to new landowners (formerly the FirstLand program) //[Obsolete] //Newbie = 1 << 2, /// Parcels which are on the mainland (Linden owned) continents Mainland = 1 << 3, /// Parcels which are on privately owned simulators Estate = 1 << 4 } /// /// The content rating of the event /// public enum EventFlags { /// Event is PG PG = 0, /// Event is Mature Mature = 1, /// Event is Adult Adult = 2 } /// /// Classified Ad Options /// /// There appear to be two formats the flags are packed in. /// This set of flags is for the newer style [Flags] public enum ClassifiedFlags : byte { /// None = 1 << 0, /// Mature = 1 << 1, /// Enabled = 1 << 2, // HasPrice = 1 << 3, // Deprecated /// UpdateTime = 1 << 4, /// AutoRenew = 1 << 5 } /// /// Classified ad query options /// [Flags] public enum ClassifiedQueryFlags { /// Include all ads in results All = PG | Mature | Adult, /// Include PG ads in results PG = 1 << 2, /// Include Mature ads in results Mature = 1 << 3, /// Include Adult ads in results Adult = 1 << 6, } /// /// The For Sale flag in PlacesReplyData /// public enum PlacesFlags : byte { /// Parcel is not listed for sale NotForSale = 0, /// Parcel is For Sale ForSale = 128 } #endregion #region Structs /// /// A classified ad on the grid /// public struct Classified { /// UUID for this ad, useful for looking up detailed /// information about it public UUID ID; /// The title of this classified ad public string Name; /// Flags that show certain options applied to the classified public ClassifiedFlags 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; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// A parcel retrieved from the dataserver such as results from the /// "For-Sale" listings or "Places" Search /// public struct DirectoryParcel { /// The unique dataserver parcel ID /// This id is used to obtain additional information from the entry /// by using the method public UUID ID; /// A string containing the name of the parcel public string Name; /// The size of the parcel /// This field is not returned for Places searches public int ActualArea; /// The price of the parcel /// This field is not returned for Places searches public int SalePrice; /// If True, this parcel is flagged to be auctioned public bool Auction; /// If true, this parcel is currently set for sale public bool ForSale; /// Parcel traffic public float Dwell; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// An Avatar returned from the dataserver /// public struct AgentSearchData { /// Online status of agent /// This field appears to be obsolete and always returns false public bool Online; /// The agents first name public string FirstName; /// The agents last name public string LastName; /// The agents public UUID AgentID; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// Response to a "Groups" Search /// public struct GroupSearchData { /// The Group ID public UUID GroupID; /// The name of the group public string GroupName; /// The current number of members public int Members; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// Parcel information returned from a request /// /// Represents one of the following: /// A parcel of land on the grid that has its Show In Search flag set /// A parcel of land owned by the agent making the request /// A parcel of land owned by a group the agent making the request is a member of /// /// /// In a request for Group Land, the First record will contain an empty record /// /// Note: This is not the same as searching the land for sale data source /// public struct PlacesSearchData { /// The ID of the Agent of Group that owns the parcel public UUID OwnerID; /// The name public string Name; /// The description public string Desc; /// The Size of the parcel public int ActualArea; /// The billable Size of the parcel, for mainland /// parcels this will match the ActualArea field. For Group owned land this will be 10 percent smaller /// than the ActualArea. For Estate land this will always be 0 public int BillableArea; /// Indicates the ForSale status of the parcel public PlacesFlags Flags; /// The Gridwide X position public float GlobalX; /// The Gridwide Y position public float GlobalY; /// The Z position of the parcel, or 0 if no landing point set public float GlobalZ; /// The name of the Region the parcel is located in public string SimName; /// The Asset ID of the parcels Snapshot texture public UUID SnapshotID; /// The calculated visitor traffic public float Dwell; /// The billing product SKU /// Known values are: /// /// 023Mainland / Full Region /// 024Estate / Full Region /// 027Estate / Openspace /// 029Estate / Homestead /// 129Mainland / Homestead (Linden Owned) /// /// public string SKU; /// No longer used, will always be 0 public int Price; /// Get a SL URL for the parcel /// A string, containing a standard SLURL public string ToSLurl() { float x, y; Helpers.GlobalPosToRegionHandle(this.GlobalX, this.GlobalY, out x, out y); return "secondlife://" + this.SimName + "/" + x + "/" + y + "/" + this.GlobalZ; } /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// An "Event" Listing summary /// public struct EventsSearchData { /// The ID of the event creator public UUID Owner; /// The name of the event public string Name; /// The events ID public uint ID; /// A string containing the short date/time the event will begin public string Date; /// The event start time in Unixtime (seconds since epoch) public uint Time; /// The events maturity rating public EventFlags Flags; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// The details of an "Event" /// public struct EventInfo { /// The events ID public uint ID; /// The ID of the event creator public UUID Creator; /// The name of the event public string Name; /// The category public EventCategories Category; /// The events description public string Desc; /// The short date/time the event will begin public string Date; /// The event start time in Unixtime (seconds since epoch) UTC adjusted public uint DateUTC; /// The length of the event in minutes public uint Duration; /// 0 if no cover charge applies public uint Cover; /// The cover charge amount in L$ if applicable public uint Amount; /// The name of the region where the event is being held public string SimName; /// The gridwide location of the event public Vector3d GlobalPos; /// The maturity rating public EventFlags Flags; /// Get a SL URL for the parcel where the event is hosted /// A string, containing a standard SLURL public string ToSLurl() { float x, y; Helpers.GlobalPosToRegionHandle((float)this.GlobalPos.X, (float)this.GlobalPos.Y, out x, out y); return "secondlife://" + this.SimName + "/" + x + "/" + y + "/" + this.GlobalPos.Z; } /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } #endregion Structs #region Event delegates, Raise Events /// The event subscribers. null if no subcribers private EventHandler m_EventInfoReply; /// Raises the EventInfoReply event /// An EventInfoReplyEventArgs object containing the /// data returned from the data server protected virtual void OnEventInfo(EventInfoReplyEventArgs e) { EventHandler handler = m_EventInfoReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_EventDetailLock = new object(); /// Raised when the data server responds to a request. public event EventHandler EventInfoReply { add { lock (m_EventDetailLock) { m_EventInfoReply += value; } } remove { lock (m_EventDetailLock) { m_EventInfoReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirEvents; /// Raises the DirEventsReply event /// An DirEventsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirEvents(DirEventsReplyEventArgs e) { EventHandler handler = m_DirEvents; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirEventsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirEventsReply { add { lock (m_DirEventsLock) { m_DirEvents += value; } } remove { lock (m_DirEventsLock) { m_DirEvents -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_Places; /// Raises the PlacesReply event /// A PlacesReplyEventArgs object containing the /// data returned from the data server protected virtual void OnPlaces(PlacesReplyEventArgs e) { EventHandler handler = m_Places; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_PlacesLock = new object(); /// Raised when the data server responds to a request. public event EventHandler PlacesReply { add { lock (m_PlacesLock) { m_Places += value; } } remove { lock (m_PlacesLock) { m_Places -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirPlaces; /// Raises the DirPlacesReply event /// A DirPlacesReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirPlaces(DirPlacesReplyEventArgs e) { EventHandler handler = m_DirPlaces; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirPlacesLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirPlacesReply { add { lock (m_DirPlacesLock) { m_DirPlaces += value; } } remove { lock (m_DirPlacesLock) { m_DirPlaces -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirClassifieds; /// Raises the DirClassifiedsReply event /// A DirClassifiedsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirClassifieds(DirClassifiedsReplyEventArgs e) { EventHandler handler = m_DirClassifieds; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirClassifiedsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirClassifiedsReply { add { lock (m_DirClassifiedsLock) { m_DirClassifieds += value; } } remove { lock (m_DirClassifiedsLock) { m_DirClassifieds -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirGroups; /// Raises the DirGroupsReply event /// A DirGroupsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirGroups(DirGroupsReplyEventArgs e) { EventHandler handler = m_DirGroups; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirGroupsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirGroupsReply { add { lock (m_DirGroupsLock) { m_DirGroups += value; } } remove { lock (m_DirGroupsLock) { m_DirGroups -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirPeople; /// Raises the DirPeopleReply event /// A DirPeopleReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirPeople(DirPeopleReplyEventArgs e) { EventHandler handler = m_DirPeople; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirPeopleLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirPeopleReply { add { lock (m_DirPeopleLock) { m_DirPeople += value; } } remove { lock (m_DirPeopleLock) { m_DirPeople -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirLandReply; /// Raises the DirLandReply event /// A DirLandReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirLand(DirLandReplyEventArgs e) { EventHandler handler = m_DirLandReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DirLandLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirLandReply { add { lock (m_DirLandLock) { m_DirLandReply += value; } } remove { lock (m_DirLandLock) { m_DirLandReply -= value; } } } #endregion #region Private Members private GridClient Client; #endregion #region Constructors /// /// Constructs a new instance of the DirectoryManager class /// /// An instance of GridClient public DirectoryManager(GridClient client) { Client = client; Client.Network.RegisterCallback(PacketType.DirClassifiedReply, DirClassifiedReplyHandler); // Deprecated, replies come in over capabilities Client.Network.RegisterCallback(PacketType.DirLandReply, DirLandReplyHandler); Client.Network.RegisterEventCallback("DirLandReply", DirLandReplyEventHandler); Client.Network.RegisterCallback(PacketType.DirPeopleReply, DirPeopleReplyHandler); Client.Network.RegisterCallback(PacketType.DirGroupsReply, DirGroupsReplyHandler); // Deprecated as of viewer 1.2.3 Client.Network.RegisterCallback(PacketType.PlacesReply, PlacesReplyHandler); Client.Network.RegisterEventCallback("PlacesReply", PlacesReplyEventHandler); Client.Network.RegisterCallback(PacketType.DirEventsReply, EventsReplyHandler); Client.Network.RegisterCallback(PacketType.EventInfoReply, EventInfoReplyHandler); Client.Network.RegisterCallback(PacketType.DirPlacesReply, DirPlacesReplyHandler); } #endregion #region Public Methods // Obsoleted due to new Adult search option [Obsolete("Use Overload with ClassifiedQueryFlags option instead")] public UUID StartClassifiedSearch(string searchText, ClassifiedCategories category, bool mature) { return UUID.Zero; } /// /// Query the data server for a list of classified ads containing the specified string. /// Defaults to searching for classified placed in any category, and includes PG, Adult and Mature /// results. /// /// Responses are sent 16 per response packet, there is no way to know how many results a query reply will contain however assuming /// the reply packets arrived ordered, a response with less than 16 entries would indicate all results have been received /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for /// A UUID to correlate the results when the event is raised public UUID StartClassifiedSearch(string searchText) { return StartClassifiedSearch(searchText, ClassifiedCategories.Any, ClassifiedQueryFlags.All); } /// /// Query the data server for a list of classified ads which contain specified keywords (Overload) /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for /// The category to search /// A set of flags which can be ORed to modify query options /// such as classified maturity rating. /// A UUID to correlate the results when the event is raised /// /// Search classified ads containing the key words "foo" and "bar" in the "Any" category that are either PG or Mature /// /// UUID searchID = StartClassifiedSearch("foo bar", ClassifiedCategories.Any, ClassifiedQueryFlags.PG | ClassifiedQueryFlags.Mature); /// /// /// /// Responses are sent 16 at a time, there is no way to know how many results a query reply will contain however assuming /// the reply packets arrived ordered, a response with less than 16 entries would indicate all results have been received /// public UUID StartClassifiedSearch(string searchText, ClassifiedCategories category, ClassifiedQueryFlags queryFlags) { DirClassifiedQueryPacket query = new DirClassifiedQueryPacket(); UUID queryID = UUID.Random(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Category = (uint)category; query.QueryData.QueryFlags = (uint)queryFlags; query.QueryData.QueryID = queryID; query.QueryData.QueryText = Utils.StringToBytes(searchText); Client.Network.SendPacket(query); return queryID; } /// /// Starts search for places (Overloaded) /// /// The event is raised when a response is received from the simulator /// /// Search text /// Each request is limited to 100 places /// being returned. To get the first 100 result entries of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// A UUID to correlate the results when the event is raised public UUID StartDirPlacesSearch(string searchText, int queryStart) { return StartDirPlacesSearch(searchText, DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, ParcelCategory.Any, queryStart); } /// /// Queries the dataserver for parcels of land which are flagged to be shown in search /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for separated by a space character /// A set of flags which can be ORed to modify query options /// such as classified maturity rating. /// The category to search /// Each request is limited to 100 places /// being returned. To get the first 100 result entries of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// A UUID to correlate the results when the event is raised /// /// Search places containing the key words "foo" and "bar" in the "Any" category that are either PG or Adult /// /// UUID searchID = StartDirPlacesSearch("foo bar", DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeAdult, ParcelCategory.Any, 0); /// /// /// /// Additional information on the results can be obtained by using the ParcelManager.InfoRequest method /// public UUID StartDirPlacesSearch(string searchText, DirFindFlags queryFlags, ParcelCategory category, int queryStart) { DirPlacesQueryPacket query = new DirPlacesQueryPacket(); UUID queryID = UUID.Random(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Category = (sbyte)category; query.QueryData.QueryFlags = (uint)queryFlags; query.QueryData.QueryID = queryID; query.QueryData.QueryText = Utils.StringToBytes(searchText); query.QueryData.QueryStart = queryStart; query.QueryData.SimName = Utils.StringToBytes(string.Empty); Client.Network.SendPacket(query); return queryID; } /// /// Starts a search for land sales using the directory /// /// The event is raised when a response is received from the simulator /// /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// 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 void StartLandSearch(SearchTypeFlags typeFlags) { StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0); } /// /// Starts a search for land sales using the directory /// /// The event is raised when a response is received from the simulator /// /// 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. /// 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 void StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice | DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart); } /// /// Send a request to the data server for land sales listings /// /// /// Flags sent to specify query options /// /// Available flags: /// Specify the parcel rating with one or more of the following: /// IncludePG IncludeMature IncludeAdult /// /// Specify the field to pre sort the results with ONLY ONE of the following: /// PerMeterSort NameSort AreaSort PricesSort /// /// Specify the order the results are returned in, if not specified the results are pre sorted in a Descending Order /// SortAsc /// /// Specify additional filters to limit the results with one or both of the following: /// LimitByPrice LimitByArea /// /// Flags can be combined by separating them with the | (pipe) character /// /// Additional details can be found in /// /// What type of land to search for. Auction, /// Estate or Mainland /// Maximum price to search for when the /// DirFindFlags.LimitByPrice flag is specified in findFlags /// Maximum area to search for when the /// DirFindFlags.LimitByArea flag is specified in findFlags /// 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. /// The event will be raised with the response from the simulator /// /// 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 reply. /// /// Any land set for sale to either anybody or specific to the connected agent will be included in the /// results if the land is included in the query /// /// /// // request all mainland, any maturity rating that is larger than 512 sq.m /// StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByArea | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, SearchTypeFlags.Mainland, 0, 512, 0); /// public void StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { 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 = UUID.Random(); Client.Network.SendPacket(query); } /// /// Search for Groups /// /// The name or portion of the name of the group you wish to search for /// Start from the match number /// public UUID StartGroupSearch(string searchText, int queryStart) { return StartGroupSearch(searchText, queryStart, DirFindFlags.Groups | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult); } /// /// Search for Groups /// /// The name or portion of the name of the group you wish to search for /// Start from the match number /// Search flags /// public UUID StartGroupSearch(string searchText, int queryStart, DirFindFlags flags) { DirFindQueryPacket find = new DirFindQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.QueryData.QueryFlags = (uint)flags; find.QueryData.QueryText = Utils.StringToBytes(searchText); find.QueryData.QueryID = UUID.Random(); find.QueryData.QueryStart = queryStart; Client.Network.SendPacket(find); return find.QueryData.QueryID; } /// /// Search the People directory for other avatars /// /// The name or portion of the name of the avatar you wish to search for /// /// public UUID StartPeopleSearch(string searchText, int queryStart) { DirFindQueryPacket find = new DirFindQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.QueryData.QueryFlags = (uint)DirFindFlags.People; find.QueryData.QueryText = Utils.StringToBytes(searchText); find.QueryData.QueryID = UUID.Random(); find.QueryData.QueryStart = queryStart; Client.Network.SendPacket(find); return find.QueryData.QueryID; } /// /// Search Places for parcels of land you personally own /// public UUID StartPlacesSearch() { return StartPlacesSearch(DirFindFlags.AgentOwned, ParcelCategory.Any, String.Empty, String.Empty, UUID.Zero, UUID.Random()); } /// /// Searches Places for land owned by the specified group /// /// ID of the group you want to recieve land list for (You must be a member of the group) /// Transaction (Query) ID which can be associated with results from your request. public UUID StartPlacesSearch(UUID groupID) { return StartPlacesSearch(DirFindFlags.GroupOwned, ParcelCategory.Any, String.Empty, String.Empty, groupID, UUID.Random()); } /// /// Search the Places directory for parcels that are listed in search and contain the specified keywords /// /// A string containing the keywords to search for /// Transaction (Query) ID which can be associated with results from your request. public UUID StartPlacesSearch(string searchText) { return StartPlacesSearch(DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, ParcelCategory.Any, searchText, String.Empty, UUID.Zero, UUID.Random()); } /// /// 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 /// A string containing a list of keywords to search for separated by a space character /// 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 UUID StartPlacesSearch(DirFindFlags findFlags, ParcelCategory searchCategory, string searchText, string simulatorName, UUID groupID, UUID 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 = Utils.StringToBytes(searchText); find.QueryData.QueryFlags = (uint)findFlags; find.QueryData.Category = (sbyte)searchCategory; find.QueryData.SimName = Utils.StringToBytes(simulatorName); Client.Network.SendPacket(find); return transactionID; } /// /// Search All Events with specifid searchText in all categories, includes PG, Mature and Adult /// /// A string containing a list of keywords to search for separated by a space character /// Each request is limited to 100 entries /// being returned. To get the first group of entries of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// UUID of query to correlate results in callback. public UUID StartEventsSearch(string searchText, uint queryStart) { return StartEventsSearch(searchText, DirFindFlags.DateEvents | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, "u", queryStart, EventCategories.All); } /// /// Search Events /// /// A string containing a list of keywords to search for separated by a space character /// One or more of the following flags: DateEvents, IncludePG, IncludeMature, IncludeAdult /// from the Enum /// /// Multiple flags can be combined by separating the flags with the | (pipe) character /// "u" for in-progress and upcoming events, -or- number of days since/until event is scheduled /// For example "0" = Today, "1" = tomorrow, "2" = following day, "-1" = yesterday, etc. /// Each request is limited to 100 entries /// being returned. To get the first group of entries of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// EventCategory event is listed under. /// UUID of query to correlate results in callback. public UUID StartEventsSearch(string searchText, DirFindFlags queryFlags, string eventDay, uint queryStart, EventCategories category) { DirFindQueryPacket find = new DirFindQueryPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; UUID queryID = UUID.Random(); find.QueryData.QueryID = queryID; find.QueryData.QueryText = Utils.StringToBytes(eventDay + "|" + (int)category + "|" + searchText); find.QueryData.QueryFlags = (uint)queryFlags; find.QueryData.QueryStart = (int)queryStart; Client.Network.SendPacket(find); return queryID; } /// Requests Event Details /// ID of Event returned from the method public void EventInfoRequest(uint eventID) { EventInfoRequestPacket find = new EventInfoRequestPacket(); find.AgentData.AgentID = Client.Self.AgentID; find.AgentData.SessionID = Client.Self.SessionID; find.EventData.EventID = eventID; Client.Network.SendPacket(find); } #endregion #region Blocking Functions [Obsolete("Use the async StartPeoplSearch method instead")] public bool PeopleSearch(DirFindFlags findFlags, string searchText, int queryStart, int timeoutMS, out List results) { AutoResetEvent searchEvent = new AutoResetEvent(false); UUID id = UUID.Zero; List people = null; EventHandler callback = delegate(object sender, DirPeopleReplyEventArgs e) { if (id == e.QueryID) { people = e.MatchedPeople; searchEvent.Set(); } }; DirPeopleReply += callback; id = StartPeopleSearch(searchText, queryStart); searchEvent.WaitOne(timeoutMS, false); DirPeopleReply -= callback; results = people; return (results != null); } #endregion Blocking Functions #region Packet Handlers /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirClassifiedReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirClassifieds != null) { DirClassifiedReplyPacket reply = (DirClassifiedReplyPacket)e.Packet; List classifieds = new List(); foreach (DirClassifiedReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { Classified classified = new Classified(); classified.CreationDate = Utils.UnixTimeToDateTime(block.CreationDate); classified.ExpirationDate = Utils.UnixTimeToDateTime(block.ExpirationDate); classified.Flags = (ClassifiedFlags)block.ClassifiedFlags; classified.ID = block.ClassifiedID; classified.Name = Utils.BytesToString(block.Name); classified.Price = block.PriceForListing; classifieds.Add(classified); } OnDirClassifieds(new DirClassifiedsReplyEventArgs(classifieds)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirLandReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirLandReply != null) { List parcelsForSale = new List(); DirLandReplyPacket reply = (DirLandReplyPacket)e.Packet; foreach (DirLandReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { DirectoryParcel dirParcel = new DirectoryParcel(); dirParcel.ActualArea = block.ActualArea; dirParcel.ID = block.ParcelID; dirParcel.Name = Utils.BytesToString(block.Name); dirParcel.SalePrice = block.SalePrice; dirParcel.Auction = block.Auction; dirParcel.ForSale = block.ForSale; parcelsForSale.Add(dirParcel); } OnDirLand(new DirLandReplyEventArgs(parcelsForSale)); } } /// Process an incoming event message /// The Unique Capabilities Key /// The event message containing the data /// The simulator the message originated from protected void DirLandReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_DirLandReply != null) { List parcelsForSale = new List(); DirLandReplyMessage reply = (DirLandReplyMessage)message; foreach (DirLandReplyMessage.QueryReply block in reply.QueryReplies) { DirectoryParcel dirParcel = new DirectoryParcel(); dirParcel.ActualArea = block.ActualArea; dirParcel.ID = block.ParcelID; dirParcel.Name = block.Name; dirParcel.SalePrice = block.SalePrice; dirParcel.Auction = block.Auction; dirParcel.ForSale = block.ForSale; parcelsForSale.Add(dirParcel); } OnDirLand(new DirLandReplyEventArgs(parcelsForSale)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirPeopleReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirPeople != null) { DirPeopleReplyPacket peopleReply = e.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 = Utils.BytesToString(reply.FirstName); searchData.LastName = Utils.BytesToString(reply.LastName); searchData.AgentID = reply.AgentID; matches.Add(searchData); } OnDirPeople(new DirPeopleReplyEventArgs(peopleReply.QueryData.QueryID, matches)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirGroupsReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirGroups != null) { Packet packet = e.Packet; DirGroupsReplyPacket groupsReply = (DirGroupsReplyPacket)packet; List matches = new List(groupsReply.QueryReplies.Length); foreach (DirGroupsReplyPacket.QueryRepliesBlock reply in groupsReply.QueryReplies) { GroupSearchData groupsData = new GroupSearchData(); groupsData.GroupID = reply.GroupID; groupsData.GroupName = Utils.BytesToString(reply.GroupName); groupsData.Members = reply.Members; matches.Add(groupsData); } OnDirGroups(new DirGroupsReplyEventArgs(groupsReply.QueryData.QueryID, matches)); } } /// Process an incoming event message /// The Unique Capabilities Key /// The event message containing the data /// The simulator the message originated from protected void PlacesReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_Places != null) { PlacesReplyMessage replyMessage = (PlacesReplyMessage)message; List places = new List(); for (int i = 0; i < replyMessage.QueryDataBlocks.Length; i++) { PlacesSearchData place = new PlacesSearchData(); place.ActualArea = replyMessage.QueryDataBlocks[i].ActualArea; place.BillableArea = replyMessage.QueryDataBlocks[i].BillableArea; place.Desc = replyMessage.QueryDataBlocks[i].Description; place.Dwell = replyMessage.QueryDataBlocks[i].Dwell; place.Flags = (DirectoryManager.PlacesFlags)(byte)replyMessage.QueryDataBlocks[i].Flags; place.GlobalX = replyMessage.QueryDataBlocks[i].GlobalX; place.GlobalY = replyMessage.QueryDataBlocks[i].GlobalY; place.GlobalZ = replyMessage.QueryDataBlocks[i].GlobalZ; place.Name = replyMessage.QueryDataBlocks[i].Name; place.OwnerID = replyMessage.QueryDataBlocks[i].OwnerID; place.Price = replyMessage.QueryDataBlocks[i].Price; place.SimName = replyMessage.QueryDataBlocks[i].SimName; place.SnapshotID = replyMessage.QueryDataBlocks[i].SnapShotID; place.SKU = replyMessage.QueryDataBlocks[i].ProductSku; places.Add(place); } OnPlaces(new PlacesReplyEventArgs(replyMessage.QueryID, places)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void PlacesReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_Places != null) { Packet packet = e.Packet; 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 = Utils.BytesToString(block.Name); place.Desc = Utils.BytesToString(block.Desc); place.ActualArea = block.ActualArea; place.BillableArea = block.BillableArea; place.Flags = (PlacesFlags)block.Flags; place.GlobalX = block.GlobalX; place.GlobalY = block.GlobalY; place.GlobalZ = block.GlobalZ; place.SimName = Utils.BytesToString(block.SimName); place.SnapshotID = block.SnapshotID; place.Dwell = block.Dwell; place.Price = block.Price; places.Add(place); } OnPlaces(new PlacesReplyEventArgs(placesReply.TransactionData.TransactionID, places)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void EventsReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirEvents != null) { Packet packet = e.Packet; DirEventsReplyPacket eventsReply = (DirEventsReplyPacket)packet; List matches = new List(eventsReply.QueryReplies.Length); foreach (DirEventsReplyPacket.QueryRepliesBlock reply in eventsReply.QueryReplies) { EventsSearchData eventsData = new EventsSearchData(); eventsData.Owner = reply.OwnerID; eventsData.Name = Utils.BytesToString(reply.Name); eventsData.ID = reply.EventID; eventsData.Date = Utils.BytesToString(reply.Date); eventsData.Time = reply.UnixTime; eventsData.Flags = (EventFlags)reply.EventFlags; matches.Add(eventsData); } OnDirEvents(new DirEventsReplyEventArgs(eventsReply.QueryData.QueryID, matches)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void EventInfoReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_EventInfoReply != null) { Packet packet = e.Packet; EventInfoReplyPacket eventReply = (EventInfoReplyPacket)packet; EventInfo evinfo = new EventInfo(); evinfo.ID = eventReply.EventData.EventID; evinfo.Name = Utils.BytesToString(eventReply.EventData.Name); evinfo.Desc = Utils.BytesToString(eventReply.EventData.Desc); evinfo.Amount = eventReply.EventData.Amount; evinfo.Category = (EventCategories)Utils.BytesToUInt(eventReply.EventData.Category); evinfo.Cover = eventReply.EventData.Cover; evinfo.Creator = (UUID)Utils.BytesToString(eventReply.EventData.Creator); evinfo.Date = Utils.BytesToString(eventReply.EventData.Date); evinfo.DateUTC = eventReply.EventData.DateUTC; evinfo.Duration = eventReply.EventData.Duration; evinfo.Flags = (EventFlags)eventReply.EventData.EventFlags; evinfo.SimName = Utils.BytesToString(eventReply.EventData.SimName); evinfo.GlobalPos = eventReply.EventData.GlobalPos; OnEventInfo(new EventInfoReplyEventArgs(evinfo)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirPlacesReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirPlaces != null) { Packet packet = e.Packet; DirPlacesReplyPacket reply = (DirPlacesReplyPacket)packet; List result = new List(); for (int i = 0; i < reply.QueryReplies.Length; i++) { DirectoryParcel p = new DirectoryParcel(); p.ID = reply.QueryReplies[i].ParcelID; p.Name = Utils.BytesToString(reply.QueryReplies[i].Name); p.Dwell = reply.QueryReplies[i].Dwell; p.Auction = reply.QueryReplies[i].Auction; p.ForSale = reply.QueryReplies[i].ForSale; result.Add(p); } OnDirPlaces(new DirPlacesReplyEventArgs(reply.QueryData[0].QueryID, result)); } } #endregion Packet Handlers } #region DirectoryManager EventArgs Classes /// Contains the Event data returned from the data server from an EventInfoRequest public class EventInfoReplyEventArgs : EventArgs { private readonly DirectoryManager.EventInfo m_MatchedEvent; /// /// A single EventInfo object containing the details of an event /// public DirectoryManager.EventInfo MatchedEvent { get { return m_MatchedEvent; } } /// Construct a new instance of the EventInfoReplyEventArgs class /// A single EventInfo object containing the details of an event public EventInfoReplyEventArgs(DirectoryManager.EventInfo matchedEvent) { this.m_MatchedEvent = matchedEvent; } } /// Contains the "Event" detail data returned from the data server public class DirEventsReplyEventArgs : EventArgs { private readonly UUID m_QueryID; /// The ID returned by public UUID QueryID { get { return m_QueryID; } } private readonly List m_matchedEvents; /// A list of "Events" returned by the data server public List MatchedEvents { get { return m_matchedEvents; } } /// Construct a new instance of the DirEventsReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing the "Events" returned by the search query public DirEventsReplyEventArgs(UUID queryID, List matchedEvents) { this.m_QueryID = queryID; this.m_matchedEvents = matchedEvents; } } /// Contains the "Event" list data returned from the data server public class PlacesReplyEventArgs : EventArgs { private readonly UUID m_QueryID; /// The ID returned by public UUID QueryID { get { return m_QueryID; } } private readonly List m_MatchedPlaces; /// A list of "Places" returned by the data server public List MatchedPlaces { get { return m_MatchedPlaces; } } /// Construct a new instance of PlacesReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing the "Places" returned by the data server query public PlacesReplyEventArgs(UUID queryID, List matchedPlaces) { this.m_QueryID = queryID; this.m_MatchedPlaces = matchedPlaces; } } /// Contains the places data returned from the data server public class DirPlacesReplyEventArgs : EventArgs { private readonly UUID m_QueryID; /// The ID returned by public UUID QueryID { get { return m_QueryID; } } private readonly List m_MatchedParcels; /// A list containing Places data returned by the data server public List MatchedParcels { get { return m_MatchedParcels; } } /// Construct a new instance of the DirPlacesReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing land data returned by the data server public DirPlacesReplyEventArgs(UUID queryID, List matchedParcels) { this.m_QueryID = queryID; this.m_MatchedParcels = matchedParcels; } } /// Contains the classified data returned from the data server public class DirClassifiedsReplyEventArgs : EventArgs { private readonly List m_Classifieds; /// A list containing Classified Ads returned by the data server public List Classifieds { get { return m_Classifieds; } } /// Construct a new instance of the DirClassifiedsReplyEventArgs class /// A list of classified ad data returned from the data server public DirClassifiedsReplyEventArgs(List classifieds) { this.m_Classifieds = classifieds; } } /// Contains the group data returned from the data server public class DirGroupsReplyEventArgs : EventArgs { private readonly UUID m_QueryID; /// The ID returned by public UUID QueryID { get { return m_QueryID; } } private readonly List m_matchedGroups; /// A list containing Groups data returned by the data server public List MatchedGroups { get { return m_matchedGroups; } } /// Construct a new instance of the DirGroupsReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list of groups data returned by the data server public DirGroupsReplyEventArgs(UUID queryID, List matchedGroups) { this.m_QueryID = queryID; this.m_matchedGroups = matchedGroups; } } /// Contains the people data returned from the data server public class DirPeopleReplyEventArgs : EventArgs { private readonly UUID m_QueryID; /// The ID returned by public UUID QueryID { get { return m_QueryID; } } private readonly List m_MatchedPeople; /// A list containing People data returned by the data server public List MatchedPeople { get { return m_MatchedPeople; } } /// Construct a new instance of the DirPeopleReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list of people data returned by the data server public DirPeopleReplyEventArgs(UUID queryID, List matchedPeople) { this.m_QueryID = queryID; this.m_MatchedPeople = matchedPeople; } } /// Contains the land sales data returned from the data server public class DirLandReplyEventArgs : EventArgs { private readonly List m_DirParcels; /// A list containing land forsale data returned by the data server public List DirParcels { get { return m_DirParcels; } } /// Construct a new instance of the DirLandReplyEventArgs class /// A list of parcels for sale returned by the data server public DirLandReplyEventArgs(List dirParcels) { this.m_DirParcels = dirParcels; } } #endregion }