Files
libremetaverse/OpenMetaverse/DirectoryManager.cs
Jim Radford 371fde3dbb LIBOMV-671 Adds adult flag to login options to enable age verified accounts to obtain adult specific content and results from searches
* Adds additional inline documentation for DirFindFlags, flags in this enum without inline comments do not appear to be in use any longer
* Additional DirectoryManager documentation added
* Adult specific result/query implemented for all DirectoryManager public Methods
* Additional Decoders added to PacketDecoder for EventFlags
* New TestClient command for searching Places 

git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@3133 52acb1d6-8a22-11de-b505-999d5b087335
2009-10-08 02:56:37 +00:00

1238 lines
57 KiB
C#

/*
* 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.Threading;
using OpenMetaverse.Packets;
using OpenMetaverse.Interfaces;
using OpenMetaverse.Messages.Linden;
namespace OpenMetaverse
{
/// <summary>
/// Access to the dataserver which allows searching for land, events, people, etc
/// </summary>
public class DirectoryManager
{
/// <summary>
/// The different categories a classified ad can be placed in
/// </summary>
public enum ClassifiedCategories
{
/// <summary>Classified is listed in the Any category</summary>
Any = 0,
/// <summary>Classified is shopping related</summary>
Shopping,
/// <summary>Classified is </summary>
LandRental,
/// <summary></summary>
PropertyRental,
/// <summary></summary>
SpecialAttraction,
/// <summary></summary>
NewProducts,
/// <summary></summary>
Employment,
/// <summary></summary>
Wanted,
/// <summary></summary>
Service,
/// <summary></summary>
Personal
}
/// <summary></summary>
public enum EventCategories
{
/// <summary></summary>
All = 0,
/// <summary></summary>
Discussion = 18,
/// <summary></summary>
Sports = 19,
/// <summary></summary>
LiveMusic = 20,
/// <summary></summary>
Commercial = 22,
/// <summary></summary>
Nightlife = 23,
/// <summary></summary>
Games = 24,
/// <summary></summary>
Pageants = 25,
/// <summary></summary>
Education = 26,
/// <summary></summary>
Arts = 27,
/// <summary></summary>
Charity = 28,
/// <summary></summary>
Miscellaneous = 29
}
// I can be deleted as soon as the flags for DirLandQuery can be verified to not use
// the below listed combinations. e.g: PgSimsOnly *should not* be necessary any longer as it looks redundant
// -- Jim
//
// <summary>
// Modifier flags sent to DirectoryManager to change the behavior of the
// query
// </summary>
// Land Search Flags required in addition to specify land maturity rating:
// <example>
// <code>PgSimsOnly | SortAsc | PerMeterSort | IncludePG | IncludeAdult</code>
// would sort the results ascending, sort by sale price/m2, Include only Adult and PG Land
// in the search results.
// </example>
// <list type="table">
// <listheader><term>Desired Query</term><description>Required Flags</description></listheader>
// <item><term>PG</term><description>PgSimsOnly | IncludePG</description></item>
// <item><term>PG+Mature</term><description>IncludePG | IncludeMature</description></item>
// <item><term>PG+Mature+Adult</term><description>IncludePG | IncludeMature | IncludeAdult</description></item>
// <item><term>Mature+Adult</term><description>MatureSimsOnly | IncludeMature | IncludeAdult</description></item>
// <item><term>Adult</term><description>IncludeAdult</description></item>
// <item><term>Mature</term><description>MatureSimsOnly | IncludeMature</description></item>
// <item><term>PG+Adult</term><description>PgSimsOnly | IncludePG | IncludeAdult</description></item>
// </list>
/// <summary>
/// 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
/// </summary>
[Flags]
public enum DirFindFlags
{
/// <summary>Query the People database</summary>
People = 1 << 0,
/// <summary></summary>
Online = 1 << 1,
// <summary></summary>
//[Obsolete]
//Places = 1 << 2,
/// <summary></summary>
Events = 1 << 3,
/// <summary></summary>
Groups = 1 << 4,
/// <summary>Query the Events database</summary>
DateEvents = 1 << 5,
/// <summary>Query the land holdings database for land owned by the currently connected agent</summary>
AgentOwned = 1 << 6,
/// <summary></summary>
ForSale = 1 << 7,
/// <summary>Query the land holdings database for land which is owned by a Group</summary>
GroupOwned = 1 << 8,
// <summary></summary>
//[Obsolete]
//Auction = 1 << 9,
/// <summary>Specifies the query should pre sort the results based upon traffic
/// when searching the Places database</summary>
DwellSort = 1 << 10,
/// <summary></summary>
PgSimsOnly = 1 << 11,
/// <summary></summary>
PicturesOnly = 1 << 12,
/// <summary></summary>
PgEventsOnly = 1 << 13,
/// <summary></summary>
MatureSimsOnly = 1 << 14,
/// <summary>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</summary>
SortAsc = 1 << 15,
/// <summary>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</summary>
PricesSort = 1 << 16,
/// <summary>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</summary>
PerMeterSort = 1 << 17,
/// <summary>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</summary>
AreaSort = 1 << 18,
/// <summary>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</summary>
NameSort = 1 << 19,
/// <summary>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</summary>
LimitByPrice = 1 << 20,
/// <summary>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</summary>
LimitByArea = 1 << 21,
/// <summary></summary>
FilterMature = 1 << 22,
/// <summary></summary>
PGOnly = 1 << 23,
/// <summary>Include PG land in results. This flag is used when searching both the Events and Land sales databases</summary>
IncludePG = 1 << 24,
/// <summary>Include Mature land in results. This flag is used when searching both the Events and Land sales databases</summary>
IncludeMature = 1 << 25,
/// <summary>Include Adult land in results. This flag is used when searching both the Events and Land sales databases</summary>
IncludeAdult = 1 << 26,
/// <summary></summary>
AdultOnly = 1 << 27
}
/// <summary>
/// Land types to search dataserver for
/// </summary>
[Flags]
public enum SearchTypeFlags
{
/// <summary>Search Auction, Mainland and Estate</summary>
Any = -1,
/// <summary>Do not search</summary>
None = 0,
/// <summary>Land which is currently up for auction</summary>
Auction = 1 << 1,
// <summary>Land available to new landowners (formerly the FirstLand program)</summary>
//[Obsolete]
//Newbie = 1 << 2,
/// <summary>Parcels which are on the mainland (Linden owned) continents</summary>
Mainland = 1 << 3,
/// <summary>Parcels which are on privately owned simulators</summary>
Estate = 1 << 4
}
/// <summary>
/// The content rating of the event
/// </summary>
public enum EventFlags
{
/// <summary>Event is PG</summary>
PG = 0,
/// <summary>Event is Mature</summary>
Mature = 1,
/// <summary>Event is Adult</summary>
Adult = 2
}
/// <summary>
/// A classified ad on the grid
/// </summary>
public struct Classified
{
/// <summary>UUID for this ad, useful for looking up detailed
/// information about it</summary>
public UUID ID;
/// <summary>The title of this classified ad</summary>
public string Name;
/// <summary>Flags that show certain options applied to the classified</summary>
public ClassifiedFlags Flags;
/// <summary>Creation date of the ad</summary>
public DateTime CreationDate;
/// <summary>Expiration date of the ad</summary>
public DateTime ExpirationDate;
/// <summary>Price that was paid for this ad</summary>
public int Price;
/// <summary>
/// Display a classified ad in a one line human readable format
/// </summary>
/// <returns>A string representing a single classified ad</returns>
public override string ToString()
{
return String.Format("Classified Ad: ID: {0}, Name: {1}, Flags: [{2}] {3}, Created: {4}, Expires: {5}, Paid: {6}",
this.ID, this.Name, this.Flags, this.Flags.ToString(), this.CreationDate, this.ExpirationDate, this.Price);
}
}
/// <summary>
/// Classified Ad Options
/// </summary>
/// <remarks>There appear to be two formats the flags are packed in.
/// This set of flags is for the newer style</remarks>
[Flags]
public enum ClassifiedFlags : byte
{
None = 1 << 0,
Mature = 1 << 1,
Enabled = 1 << 2,
// HasPrice = 1 << 3, // Deprecated
UpdateTime = 1 << 4,
AutoRenew = 1 << 5
}
/// <summary>
/// Classified ad query options
/// </summary>
[Flags]
public enum ClassifiedQueryFlags
{
/// <summary>Include all ads in results</summary>
All = PG | Mature | Adult,
/// <summary>Include PG ads in results</summary>
PG = 1 << 2,
/// <summary>Include Mature ads in results</summary>
Mature = 1 << 3,
/// <summary>Include Adult ads in results</summary>
Adult = 1 << 6,
}
/// <summary>
/// A parcel retrieved from the dataserver such as results from the
/// "For-Sale" listings or "Places" Search
/// </summary>
public struct DirectoryParcel
{
/// <summary>The unique dataserver parcel ID</summary>
/// <remarks>This id is used to obtain additional information from the entry
/// by using the <see cref="ParcelManager.InfoRequest"/> method</remarks>
public UUID ID;
/// <summary>A string containing the name of the parcel</summary>
public string Name;
/// <summary>The size of the parcel</summary>
/// <remarks>This field is not returned for Places searches</remarks>
public int ActualArea;
/// <summary>The price of the parcel</summary>
/// <remarks>This field is not returned for Places searches</remarks>
public int SalePrice;
/// <summary>If True, this parcel is flagged to be auctioned</summary>
public bool Auction;
/// <summary>If true, this parcel is currently set for sale</summary>
public bool ForSale;
/// <summary>Parcel traffic</summary>
public float Dwell;
/// <summary>
/// Display an entry in a one line human readable format
/// </summary>
/// <returns>A string representing a single parcel entry</returns>
public override string ToString()
{
return String.Format("Parcel ID: {0}, Name: {1}, Area: {2}, Price: {3}, IsAuction: {4}, IsForSale: {5}, Dwell: {6}" + System.Environment.NewLine,
this.ID, this.Name, this.ActualArea, this.SalePrice, this.Auction, this.ForSale, this.Dwell);
}
}
/// <summary>
/// An Avatar returned from the dataserver
/// </summary>
public struct AgentSearchData
{
/// <summary>Online status of agent</summary>
public bool Online;
/// <summary>Agents first name</summary>
public string FirstName;
/// <summary>Agents last name</summary>
public string LastName;
/// <summary>Agents <see cref="UUID"/></summary>
public UUID AgentID;
}
/// <summary>
/// Response to a "Groups" Search
/// </summary>
public struct GroupSearchData
{
public UUID GroupID;
public string GroupName;
public int Members;
}
/// <summary>
/// Response to a "Places" Search, e.g. My Land, Group Land, etc.
/// Note: This is not DirPlacesReply
/// </summary>
public struct PlacesSearchData
{
public UUID 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 UUID SnapshotID;
public float Dwell;
public int Price;
}
/// <summary>
/// Response to "Events" search
/// </summary>
public struct EventsSearchData
{
public UUID Owner;
public string Name;
public uint ID;
public string Date;
public uint Time;
public EventFlags Flags;
}
/// <summary>
/// an Event returned from the dataserver
/// </summary>
public struct EventInfo
{
public uint ID;
public UUID Creator;
public string Name;
public EventCategories Category;
public string Desc;
public string Date;
public UInt32 DateUTC;
public UInt32 Duration;
public UInt32 Cover;
public UInt32 Amount;
public string SimName;
public Vector3d GlobalPos;
public EventFlags Flags;
}
/// <summary>
///
/// </summary>
/// <param name="classifieds"></param>
public delegate void ClassifiedReplyCallback(List<Classified> classifieds);
/// <summary>
///
/// </summary>
/// <param name="dirParcels"></param>
public delegate void DirLandReplyCallback(List<DirectoryParcel> dirParcels);
/// <summary>
///
/// </summary>
/// <param name="queryID"></param>
/// <param name="matchedPeople"></param>
public delegate void DirPeopleReplyCallback(UUID queryID, List<AgentSearchData> matchedPeople);
/// <summary>
///
/// </summary>
/// <param name="queryID"></param>
/// <param name="matchedGroups"></param>
public delegate void DirGroupsReplyCallback(UUID queryID, List<GroupSearchData> matchedGroups);
/// <summary>
/// Passes results of search -> places
/// </summary>
/// <param name="queryID">UUID of this request</param>
/// <param name="matchedParcels">List of parcels found in search</param>
public delegate void DirPlacesReplyCallback(UUID queryID, List<DirectoryParcel> matchedParcels);
/// <summary>
///
/// </summary>
/// <param name="queryID"></param>
/// <param name="matchedPlaces"></param>
public delegate void PlacesReplyCallback(UUID queryID, List<PlacesSearchData> matchedPlaces);
/// <summary>
///
/// </summary>
/// <param name="queryID"></param>
/// <param name="matchedEvents"></param>
public delegate void EventReplyCallback(UUID queryID, List<EventsSearchData> matchedEvents);
/// <summary>
///
/// </summary>
/// <param name="matchedEvent"></param>
public delegate void EventInfoCallback(EventInfo matchedEvent);
/// <summary>
///
/// </summary>
public event ClassifiedReplyCallback OnClassifiedReply;
/// <summary>
///
/// </summary>
public event DirLandReplyCallback OnDirLandReply;
public event DirPeopleReplyCallback OnDirPeopleReply;
public event DirGroupsReplyCallback OnDirGroupsReply;
/// <summary>
/// When we have result of search->places
/// </summary>
public event DirPlacesReplyCallback OnDirPlacesReply;
public event PlacesReplyCallback OnPlacesReply;
// List of Events
public event EventReplyCallback OnEventsReply;
// Event Details
public event EventInfoCallback OnEventInfo;
private GridClient Client;
/// <summary>
/// Constructs a new instance of the DirectoryManager class
/// </summary>
/// <param name="client">An instance of GridClient</param>
public DirectoryManager(GridClient client)
{
Client = client;
Client.Network.RegisterCallback(PacketType.DirClassifiedReply, new NetworkManager.PacketCallback(DirClassifiedReplyHandler));
// Deprecated, replies come in over capabilities
Client.Network.RegisterCallback(PacketType.DirLandReply, new NetworkManager.PacketCallback(DirLandReplyHandler));
Client.Network.RegisterEventCallback("DirLandReply", DirLandReplyEventHandler);
Client.Network.RegisterCallback(PacketType.DirPeopleReply, new NetworkManager.PacketCallback(DirPeopleReplyHandler));
Client.Network.RegisterCallback(PacketType.DirGroupsReply, new NetworkManager.PacketCallback(DirGroupsReplyHandler));
// Deprecated as of viewer 1.2.3
Client.Network.RegisterCallback(PacketType.PlacesReply, new NetworkManager.PacketCallback(PlacesReplyHandler));
Client.Network.RegisterEventCallback("PlacesReply", PlacesReplyEventHandler);
Client.Network.RegisterCallback(PacketType.DirEventsReply, new NetworkManager.PacketCallback(EventsReplyHandler));
Client.Network.RegisterCallback(PacketType.EventInfoReply, new NetworkManager.PacketCallback(EventInfoReplyHandler));
Client.Network.RegisterCallback(PacketType.DirPlacesReply, new NetworkManager.PacketCallback(DirPlacesReplyHandler));
}
// 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;
}
/// <summary>
/// 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 <see cref="OnClassifiedReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="searchText">A string containing a list of keywords to search for</param>
/// <returns>A UUID to correlate the results when the <see cref="OnClassifiedReply"/> event is raised</returns>
public UUID StartClassifiedSearch(string searchText)
{
return StartClassifiedSearch(searchText, ClassifiedCategories.Any, ClassifiedQueryFlags.All);
}
/// <summary>
/// Query the data server for a list of classified ads which contain specified keywords (Overload)
///
/// The <see cref="OnClassifiedReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="searchText">A string containing a list of keywords to search for</param>
/// <param name="category">The category to search</param>
/// <param name="queryFlags">A set of flags which can be ORed to modify query options
/// such as classified maturity rating.</param>
/// <returns>A UUID to correlate the results when the <see cref="OnClassifiedReply"/> event is raised</returns>
/// <example>
/// Search classified ads containing the key words "foo" and "bar" in the "Any" category that are either PG or Mature
/// <code>
/// UUID searchID = StartClassifiedSearch("foo bar", ClassifiedCategories.Any, ClassifiedQueryFlags.PG | ClassifiedQueryFlags.Mature);
/// </code>
/// </example>
/// <remarks>
/// 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
/// </remarks>
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;
}
/// <summary>
/// Starts search for places (Overloaded)
///
/// The <see cref="OnDirPlacesReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="searchText">Search text</param>
/// <param name="startAtResult">Start result (we get 100 results at a time, so we start with 0, then 100, etc).</param>
/// <returns>A UUID to correlate the results when the <see cref="OnDirPlacesReply"/> event is raised</returns>
public UUID StartDirPlacesSearch(string searchText, int queryStart)
{
return StartDirPlacesSearch(searchText, DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeMature
| DirFindFlags.IncludeAdult, ParcelCategory.Any, queryStart);
}
/// <summary>
/// Queries the dataserver for parcels of land which are flagged to be shown in search
///
/// The <see cref="OnDirPlacesReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryFlags">A set of flags which can be ORed to modify query options
/// such as classified maturity rating.</param>
/// <param name="category">The category to search</param>
/// <param name="queryStart">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.</param>
/// <returns>A UUID to correlate the results when the <see cref="OnDirPlacesReply"/> event is raised</returns>
/// <example>
/// Search places containing the key words "foo" and "bar" in the "Any" category that are either PG or Adult
/// <code>
/// UUID searchID = StartDirPlacesSearch("foo bar", DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeAdult, ParcelCategory.Any, 0);
/// </code>
/// </example>
/// <remarks>
/// Additional information on the results can be obtained by using the ParcelManager.InfoRequest method
/// </remarks>
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;
}
/// <summary>
/// Starts a search for land sales using the directory
///
/// The <see cref="OnDirLandReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="typeFlags">What type of land to search for. Auction,
/// estate, mainland, "first land", etc</param>
/// <returns>A unique identifier that can identify packets associated
/// with this query from other queries</returns>
/// <remarks>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.</remarks>
public UUID StartLandSearch(SearchTypeFlags typeFlags)
{
return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0);
}
/// <summary>
/// Starts a search for land sales using the directory
///
/// The <seealso cref="OnDirLandReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="typeFlags">What type of land to search for. Auction,
/// estate, mainland, "first land", etc</param>
/// <param name="priceLimit">Maximum price to search for</param>
/// <param name="areaLimit">Maximum area to search for</param>
/// <param name="queryStart">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.</param>
/// <returns>A unique identifier that can identify packets associated
/// with this query from other queries</returns>
/// <remarks>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.</remarks>
public UUID StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart)
{
return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice |
DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart);
}
/// <summary>
/// Request land listed for public sale
/// </summary>
///
/// <param name="findFlags">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 <see cref="DirFindFlags"/>
/// </param>
/// <param name="typeFlags">What type of land to search for. Auction,
/// Estate or Mainland</param>
/// <param name="priceLimit">Maximum price to search for when the
/// DirFindFlags.LimitByPrice flag is specified in findFlags</param>
/// <param name="areaLimit">Maximum area to search for when the
/// DirFindFlags.LimitByArea flag is specified in findFlags</param>
/// <param name="queryStart">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.</param>
/// <returns>A unique identifier that can identify packets associated
/// with this query from other queries</returns>
/// <remarks><para>The <seealso cref="OnDirLandReply"/> 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.</para>
///
/// <para>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</para></remarks>
/// <example>
/// <code>
/// // 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);
/// </code></example>
public UUID StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit,
int areaLimit, int queryStart)
{
UUID queryID = UUID.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;
}
/// <summary>
/// Starts a search for a Group in the directory manager
/// </summary>
/// <param name="findFlags"></param>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryStart">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.</param>
/// <returns>A unique identifier that can identify packets associated
/// with this query from other queries</returns>
/// <remarks>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.</remarks>
public UUID StartGroupSearch(DirFindFlags findFlags, string searchText, int queryStart)
{
return StartGroupSearch(findFlags, searchText, queryStart, UUID.Random());
}
/// <summary>
/// Search for groups
/// </summary>
/// <param name="findFlags"></param>
/// <param name="searchText"></param>
/// <param name="queryStart"></param>
/// <param name="queryID"></param>
/// <returns></returns>
public UUID StartGroupSearch(DirFindFlags findFlags, string searchText, int queryStart, UUID 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 = Utils.StringToBytes(searchText);
find.QueryData.QueryID = queryID;
find.QueryData.QueryStart = queryStart;
Client.Network.SendPacket(find);
return queryID;
}
/// <summary>
/// Search the people directory for other avatars
///
/// The <see cref="OnDirPeopleReply"/> event is raised when a response is received from the simulator
/// </summary>
/// <param name="findFlags"></param>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryStart"></param>
/// <returns></returns>
public UUID StartPeopleSearch(DirFindFlags findFlags, string searchText, int queryStart)
{
return StartPeopleSearch(findFlags, searchText, queryStart, UUID.Random());
}
/// <summary>
///
/// </summary>
/// <param name="findFlags"></param>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryStart"></param>
/// <param name="queryID"></param>
/// <returns></returns>
public UUID StartPeopleSearch(DirFindFlags findFlags, string searchText, int queryStart, UUID 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 = Utils.StringToBytes(searchText);
find.QueryData.QueryID = queryID;
find.QueryData.QueryStart = queryStart;
Client.Network.SendPacket(find);
return queryID;
}
/// <summary>
/// Search "places" for parcels of land you personally own
/// </summary>
public UUID StartPlacesSearch()
{
return StartPlacesSearch(DirFindFlags.AgentOwned, ParcelCategory.Any, String.Empty, String.Empty,
UUID.Zero, UUID.Zero);
}
/// <summary>
/// Searches Places for Land owned by a specific user or group
/// </summary>
/// <param name="findFlags">One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc.</param>
/// <param name="groupID">LLUID of group you want to recieve land list for (You must be in group), or
/// LLUID.Zero for Your own land</param>
/// <returns>Transaction (Query) ID which can be associated with results from your request.</returns>
public UUID StartPlacesSearch(DirFindFlags findFlags, UUID groupID)
{
return StartPlacesSearch(findFlags, ParcelCategory.Any, String.Empty, String.Empty, groupID,
UUID.Random());
}
/// <summary>
/// Search Places
/// </summary>
/// <param name="findFlags">One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc.</param>
/// <param name="searchCategory">One of the values from the SearchCategory Struct, ie: Any, Linden, Newcomer</param>
/// <param name="groupID">UUID of group you want to recieve results for</param>
/// <param name="transactionID">Transaction (Query) ID which can be associated with results from your request.</param>
/// <returns>Transaction (Query) ID which can be associated with results from your request.</returns>
public UUID StartPlacesSearch(DirFindFlags findFlags, ParcelCategory searchCategory, UUID groupID, UUID transactionID)
{
return StartPlacesSearch(findFlags, searchCategory, String.Empty, String.Empty, groupID, transactionID);
}
/// <summary>
/// Search Places - All Options
/// </summary>
/// <param name="findFlags">One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc.</param>
/// <param name="searchCategory">One of the values from the SearchCategory Struct, ie: Any, Linden, Newcomer</param>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="simulatorName">String Simulator Name to search in</param>
/// <param name="groupID">LLUID of group you want to recieve results for</param>
/// <param name="transactionID">Transaction (Query) ID which can be associated with results from your request.</param>
/// <returns>Transaction (Query) ID which can be associated with results from your request.</returns>
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;
}
/// <summary>
/// Search All Events with specifid searchText in all categories, includes PG, Mature and Adult
/// </summary>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryStart">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.</param>
/// <returns>UUID of query to correlate results in callback.</returns>
public UUID StartEventsSearch(string searchText, uint queryStart)
{
return StartEventsSearch(searchText, DirFindFlags.DateEvents | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, "u", queryStart, EventCategories.All);
}
/// <summary>
/// Search Events
/// </summary>
/// <param name="searchText">A string containing a list of keywords to search for separated by a space character</param>
/// <param name="queryFlags">One or more of the following flags: DateEvents, IncludePG, IncludeMature, IncludeAdult
/// from the <see cref="DirFindFlags"/> Enum
///
/// Multiple flags can be combined by separating the flags with the | (pipe) character</param>
/// <param name="eventDay">"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.</param>
/// <param name="queryStart">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.</param>
/// <param name="category">EventCategory event is listed under.</param>
/// <param name="queryID">a UUID that can be used to track queries with results.</param>
/// <returns>UUID of query to correlate results in callback.</returns>
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;
}
/// <summary>Requests Event Details</summary>
/// <param name="eventID">ID of Event returned from Places Search</param>
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);
}
#region Blocking Functions
[Obsolete("Use the async StartPeoplSearch method instead")]
public bool PeopleSearch(DirFindFlags findFlags, string searchText, int queryStart,
int timeoutMS, out List<AgentSearchData> results)
{
AutoResetEvent searchEvent = new AutoResetEvent(false);
UUID id = UUID.Random();
List<AgentSearchData> people = null;
DirPeopleReplyCallback callback =
delegate(UUID queryid, List<AgentSearchData> 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<Classified> classifieds = new List<Classified>();
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);
}
try { OnClassifiedReply(classifieds); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void DirLandReplyHandler(Packet packet, Simulator simulator)
{
if (OnDirLandReply != null)
{
List<DirectoryParcel> parcelsForSale = new List<DirectoryParcel>();
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 = Utils.BytesToString(block.Name);
dirParcel.SalePrice = block.SalePrice;
dirParcel.Auction = block.Auction;
dirParcel.ForSale = block.ForSale;
parcelsForSale.Add(dirParcel);
}
try { OnDirLandReply(parcelsForSale); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void DirLandReplyEventHandler(string capsKey, IMessage message, Simulator simulator)
{
if (OnDirLandReply != null)
{
List<DirectoryParcel> parcelsForSale = new List<DirectoryParcel>();
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);
}
try { OnDirLandReply(parcelsForSale); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
protected void DirPeopleReplyHandler(Packet packet, Simulator simulator)
{
if (OnDirPeopleReply != null)
{
DirPeopleReplyPacket peopleReply = packet as DirPeopleReplyPacket;
List<AgentSearchData> matches = new List<AgentSearchData>(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);
}
try { OnDirPeopleReply(peopleReply.QueryData.QueryID, matches); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
protected void DirGroupsReplyHandler(Packet packet, Simulator simulator)
{
if (OnDirGroupsReply != null)
{
DirGroupsReplyPacket groupsReply = packet as DirGroupsReplyPacket;
List<GroupSearchData> matches = new List<GroupSearchData>(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);
}
try { OnDirGroupsReply(groupsReply.QueryData.QueryID, matches); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void PlacesReplyEventHandler(string capsKey, IMessage message, Simulator simulator)
{
if (OnPlacesReply != null)
{
PlacesReplyMessage replyMessage = (PlacesReplyMessage)message;
List<PlacesSearchData> places = new List<PlacesSearchData>();
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 = (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;
places.Add(place);
}
try { OnPlacesReply(replyMessage.TransactionID, places); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void PlacesReplyHandler(Packet packet, Simulator simulator)
{
if (OnPlacesReply != null)
{
PlacesReplyPacket placesReply = packet as PlacesReplyPacket;
List<PlacesSearchData> places = new List<PlacesSearchData>();
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 = 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);
}
try { OnPlacesReply(placesReply.TransactionData.TransactionID, places); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void EventsReplyHandler(Packet packet, Simulator simulator)
{
if (OnEventsReply != null)
{
DirEventsReplyPacket eventsReply = packet as DirEventsReplyPacket;
List<EventsSearchData> matches = new List<EventsSearchData>(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);
}
try { OnEventsReply(eventsReply.QueryData.QueryID, matches); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void EventInfoReplyHandler(Packet packet, Simulator simulator)
{
if (OnEventInfo != null)
{
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;
try { OnEventInfo(evinfo); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void DirPlacesReplyHandler(Packet packet, Simulator simulator)
{
if (OnDirPlacesReply != null)
{
DirPlacesReplyPacket reply = (DirPlacesReplyPacket)packet;
List<DirectoryParcel> result = new List<DirectoryParcel>();
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);
}
try { OnDirPlacesReply(reply.QueryData[0].QueryID, result); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
#endregion Packet Handlers
}
}