From 958c10d0f13489c2ab3a26b802911df8144528ef Mon Sep 17 00:00:00 2001
From: David Walter Seikel
Date: Sat, 23 Feb 2013 19:18:44 +1000
Subject: Add profile and search modules.

---
 .../OpenSimProfile/GridCommon.ini.example          |   5 +
 .../Modules/ProfileModule/OpenProfile.cs           | 982 +++++++++++++++++++++
 addon-modules/OpenSimProfile/Robust.HG.ini.example |   3 +
 addon-modules/OpenSimProfile/prebuild.xml          |  40 +
 .../Modules/SearchModule/OpenSearch.cs             | 753 ++++++++++++++++
 addon-modules/OpenSimSearch/README                 | 294 ++++++
 addon-modules/OpenSimSearch/prebuild.xml           |  39 +
 7 files changed, 2116 insertions(+)
 create mode 100644 addon-modules/OpenSimProfile/GridCommon.ini.example
 create mode 100644 addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs
 create mode 100644 addon-modules/OpenSimProfile/Robust.HG.ini.example
 create mode 100644 addon-modules/OpenSimProfile/prebuild.xml
 create mode 100644 addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs
 create mode 100644 addon-modules/OpenSimSearch/README
 create mode 100644 addon-modules/OpenSimSearch/prebuild.xml

(limited to 'addon-modules')

diff --git a/addon-modules/OpenSimProfile/GridCommon.ini.example b/addon-modules/OpenSimProfile/GridCommon.ini.example
new file mode 100644
index 0000000..8afe23d
--- /dev/null
+++ b/addon-modules/OpenSimProfile/GridCommon.ini.example
@@ -0,0 +1,5 @@
+Add the following to your own GridCommon.ini file to make the UserProfile service working
+
+[Profile]
+; Change it to your own HTTP server to have the Profile server work
+; ProfileURL = http://192.168.0.1/profile.php
diff --git a/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs b/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs
new file mode 100644
index 0000000..775e8b1
--- /dev/null
+++ b/addon-modules/OpenSimProfile/Modules/ProfileModule/OpenProfile.cs
@@ -0,0 +1,982 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Xml;
+using OpenMetaverse;
+using log4net;
+using Nini.Config;
+using Nwc.XmlRpc;
+using OpenSim.Framework;
+using OpenSim.Region.Framework.Interfaces;
+using OpenSim.Region.Framework.Scenes;
+using OpenSim.Services.Interfaces;
+using Mono.Addins;
+using OpenSim.Services.Connectors.Hypergrid;
+
+[assembly: Addin("OpenProfileModule", "0.1")]
+[assembly: AddinDependency("OpenSim", "0.5")]
+
+namespace OpenSimProfile.Modules.OpenProfile
+{
+    [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
+    public class OpenProfileModule : IProfileModule, ISharedRegionModule
+    {
+        //
+        // Log module
+        //
+        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+        //
+        // Module vars
+        //
+        private IConfigSource m_Config;
+        private List<Scene> m_Scenes = new List<Scene>();
+        private string m_ProfileServer = "";
+        private bool m_Enabled = true;
+
+        IUserManagement m_uMan;
+        IUserManagement UserManagementModule
+        {
+            get
+            {
+                if (m_uMan == null)
+                    m_uMan = m_Scenes[0].RequestModuleInterface<IUserManagement>();
+                return m_uMan;
+            }
+        }
+
+        #region IRegionModuleBase implementation
+        public void Initialise(IConfigSource source)
+        {
+            m_Config = source;
+
+            IConfig profileConfig = m_Config.Configs["Profile"];
+
+            if (profileConfig == null)
+            {
+                m_Enabled = false;
+                return;
+            }
+            m_ProfileServer = profileConfig.GetString("ProfileURL", "");
+            if (m_ProfileServer == "")
+            {
+                m_Enabled = false;
+                return;
+            }
+            else
+            {
+                m_log.Info("[PROFILE] OpenProfile module is activated");
+                m_Enabled = true;
+            }
+        }
+
+        public void AddRegion(Scene scene)
+        {
+            if (!m_Enabled)
+                return;
+
+            // Hook up events
+            scene.EventManager.OnNewClient += OnNewClient;
+
+            // Take ownership of the IProfileModule service
+            scene.RegisterModuleInterface<IProfileModule>(this);
+
+            // Add our scene to our list...
+            lock(m_Scenes)
+            {
+                m_Scenes.Add(scene);
+            }
+        }
+
+        public void RemoveRegion(Scene scene)
+        {
+            if (!m_Enabled)
+                return;
+
+            scene.UnregisterModuleInterface<IProfileModule>(this);
+            m_Scenes.Remove(scene);
+        }
+
+        public void RegionLoaded(Scene scene)
+        {
+        }
+
+        public Type ReplaceableInterface
+        {
+            get { return null; }
+        }
+
+        public void PostInitialise()
+        {
+        }
+
+        public void Close()
+        {
+        }
+
+        public string Name
+        {
+            get { return "ProfileModule"; }
+        }
+        #endregion
+
+        private ScenePresence FindPresence(UUID clientID)
+        {
+            ScenePresence p;
+
+            foreach (Scene s in m_Scenes)
+            {
+                p = s.GetScenePresence(clientID);
+                if (p != null && !p.IsChildAgent)
+                    return p;
+            }
+            return null;
+        }
+
+        /// New Client Event Handler
+        private void OnNewClient(IClientAPI client)
+        {
+            // Subscribe to messages
+
+            // Classifieds
+            client.AddGenericPacketHandler("avatarclassifiedsrequest", HandleAvatarClassifiedsRequest);
+            client.OnClassifiedInfoUpdate += ClassifiedInfoUpdate;
+            client.OnClassifiedDelete += ClassifiedDelete;
+
+            // Picks
+            client.AddGenericPacketHandler("avatarpicksrequest", HandleAvatarPicksRequest);
+            client.AddGenericPacketHandler("pickinforequest", HandlePickInfoRequest);
+            client.OnPickInfoUpdate += PickInfoUpdate;
+            client.OnPickDelete += PickDelete;
+
+            // Notes
+            client.AddGenericPacketHandler("avatarnotesrequest", HandleAvatarNotesRequest);
+            client.OnAvatarNotesUpdate += AvatarNotesUpdate;
+
+            //Profile
+            client.OnRequestAvatarProperties += RequestAvatarProperties;
+            client.OnUpdateAvatarProperties += UpdateAvatarProperties;
+            client.OnAvatarInterestUpdate += AvatarInterestsUpdate;
+            client.OnUserInfoRequest += UserPreferencesRequest;
+            client.OnUpdateUserInfo += UpdateUserPreferences;
+        }
+
+        //
+        // Make external XMLRPC request
+        //
+        private Hashtable GenericXMLRPCRequest(Hashtable ReqParams, string method, string server)
+        {
+            ArrayList SendParams = new ArrayList();
+            SendParams.Add(ReqParams);
+
+            // Send Request
+            XmlRpcResponse Resp;
+            try
+            {
+                XmlRpcRequest Req = new XmlRpcRequest(method, SendParams);
+                Resp = Req.Send(server, 30000);
+            }
+            catch (WebException ex)
+            {
+                m_log.ErrorFormat("[PROFILE]: Unable to connect to Profile " +
+                        "Server {0}.  Exception {1}", m_ProfileServer, ex);
+
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            catch (SocketException ex)
+            {
+                m_log.ErrorFormat(
+                        "[PROFILE]: Unable to connect to Profile Server {0}. Method {1}, params {2}. " +
+                        "Exception {3}", m_ProfileServer, method, ReqParams, ex);
+
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            catch (XmlException ex)
+            {
+                m_log.ErrorFormat(
+                        "[PROFILE]: Unable to connect to Profile Server {0}. Method {1}, params {2}. " +
+                        "Exception {3}", m_ProfileServer, method, ReqParams.ToString(), ex);
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            if (Resp.IsFault)
+            {
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to fetch profile data at this time. ";
+                ErrorHash["errorURI"] = "";
+                return ErrorHash;
+            }
+            Hashtable RespData = (Hashtable)Resp.Value;
+
+            return RespData;
+        }
+
+        // Classifieds Handler
+        public void HandleAvatarClassifiedsRequest(Object sender, string method, List<String> args)
+        {
+            if (!(sender is IClientAPI))
+                return;
+
+            IClientAPI remoteClient = (IClientAPI)sender;
+
+            UUID targetID;
+            UUID.TryParse(args[0], out targetID);
+
+            // Can't handle NPC yet...
+            ScenePresence p = FindPresence(targetID);
+
+            if (null != p)
+            {
+                if (p.PresenceType == PresenceType.Npc)
+                    return;
+            }
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(targetID, out serverURI);
+
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["uuid"] = args[0];
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    method, serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            Dictionary<UUID, string> classifieds = new Dictionary<UUID, string>();
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                classifieds[new UUID(d["classifiedid"].ToString())] = d["name"].ToString();
+            }
+
+            remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds);
+        }
+
+        // Classifieds Update
+        public void ClassifiedInfoUpdate(UUID queryclassifiedID, uint queryCategory, string queryName, string queryDescription, UUID queryParcelID,
+                                         uint queryParentEstate, UUID querySnapshotID, Vector3 queryGlobalPos, byte queryclassifiedFlags,
+                                         int queryclassifiedPrice, IClientAPI remoteClient)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            Scene s = (Scene) remoteClient.Scene;
+            Vector3 pos = remoteClient.SceneAgent.AbsolutePosition;
+            ILandObject land = s.LandChannel.GetLandObject(pos.X, pos.Y);
+
+            if (land == null)
+                ReqHash["parcelname"] = String.Empty;
+            else
+                ReqHash["parcelname"] = land.LandData.Name;
+
+            ReqHash["creatorUUID"] = remoteClient.AgentId.ToString();
+            ReqHash["classifiedUUID"] = queryclassifiedID.ToString();
+            ReqHash["category"] = queryCategory.ToString();
+            ReqHash["name"] = queryName;
+            ReqHash["description"] = queryDescription;
+            ReqHash["parentestate"] = queryParentEstate.ToString();
+            ReqHash["snapshotUUID"] = querySnapshotID.ToString();
+            ReqHash["sim_name"] = remoteClient.Scene.RegionInfo.RegionName;
+            ReqHash["globalpos"] = queryGlobalPos.ToString();
+            ReqHash["classifiedFlags"] = queryclassifiedFlags.ToString();
+            ReqHash["classifiedPrice"] = queryclassifiedPrice.ToString();
+
+            ScenePresence p = FindPresence(remoteClient.AgentId);
+
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Vector3 avaPos = p.AbsolutePosition;
+
+            // Getting the parceluuid for this parcel
+            ReqHash["parcelUUID"] = p.currentParcelUUID.ToString();
+
+            // Getting the global position for the Avatar
+            Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.RegionLocX * Constants.RegionSize + avaPos.X,
+                                            remoteClient.Scene.RegionInfo.RegionLocY * Constants.RegionSize + avaPos.Y,
+                                            avaPos.Z);
+
+            ReqHash["pos_global"] = posGlobal.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "classified_update", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Classifieds Delete
+        public void ClassifiedDelete(UUID queryClassifiedID, IClientAPI remoteClient)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            ReqHash["classifiedID"] = queryClassifiedID.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "classified_delete", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Picks Handler
+        public void HandleAvatarPicksRequest(Object sender, string method, List<String> args)
+        {
+            if (!(sender is IClientAPI))
+                return;
+
+            IClientAPI remoteClient = (IClientAPI)sender;
+
+            UUID targetID;
+            UUID.TryParse(args[0], out targetID);
+
+            // Can't handle NPC yet...
+            ScenePresence p = FindPresence(targetID);
+
+            if (null != p)
+            {
+                if (p.PresenceType == PresenceType.Npc)
+                    return;
+            }
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(targetID, out serverURI);
+
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["uuid"] = args[0];
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    method, serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            Dictionary<UUID, string> picks = new Dictionary<UUID, string>();
+
+            if (dataArray != null)
+            {
+                foreach (Object o in dataArray)
+                {
+                    Hashtable d = (Hashtable)o;
+
+                    picks[new UUID(d["pickid"].ToString())] = d["name"].ToString();
+                }
+            }
+
+            remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks);
+        }
+
+        // Picks Request
+        public void HandlePickInfoRequest(Object sender, string method, List<String> args)
+        {
+            if (!(sender is IClientAPI))
+                return;
+
+            IClientAPI remoteClient = (IClientAPI)sender;
+
+            Hashtable ReqHash = new Hashtable();
+
+            UUID targetID;
+            UUID.TryParse(args[0], out targetID);
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(targetID, out serverURI);
+
+            ReqHash["avatar_id"] = args[0];
+            ReqHash["pick_id"] = args[1];
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    method, serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            Hashtable d = (Hashtable)dataArray[0];
+
+            Vector3 globalPos = new Vector3();
+            Vector3.TryParse(d["posglobal"].ToString(), out globalPos);
+
+            if (d["description"] == null)
+                d["description"] = String.Empty;
+
+            remoteClient.SendPickInfoReply(
+                    new UUID(d["pickuuid"].ToString()),
+                    new UUID(d["creatoruuid"].ToString()),
+                    Convert.ToBoolean(d["toppick"]),
+                    new UUID(d["parceluuid"].ToString()),
+                    d["name"].ToString(),
+                    d["description"].ToString(),
+                    new UUID(d["snapshotuuid"].ToString()),
+                    d["user"].ToString(),
+                    d["originalname"].ToString(),
+                    d["simname"].ToString(),
+                    globalPos,
+                    Convert.ToInt32(d["sortorder"]),
+                    Convert.ToBoolean(d["enabled"]));
+        }
+
+        // Picks Update
+        public void PickInfoUpdate(IClientAPI remoteClient, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["agent_id"] = remoteClient.AgentId.ToString();
+            ReqHash["pick_id"] = pickID.ToString();
+            ReqHash["creator_id"] = creatorID.ToString();
+            ReqHash["top_pick"] = topPick.ToString();
+            ReqHash["name"] = name;
+            ReqHash["desc"] = desc;
+            ReqHash["snapshot_id"] = snapshotID.ToString();
+            ReqHash["sort_order"] = sortOrder.ToString();
+            ReqHash["enabled"] = enabled.ToString();
+            ReqHash["sim_name"] = remoteClient.Scene.RegionInfo.RegionName;
+
+            ScenePresence p = FindPresence(remoteClient.AgentId);
+
+            Vector3 avaPos = p.AbsolutePosition;
+
+            // Getting the parceluuid for this parcel
+            ReqHash["parcel_uuid"] = p.currentParcelUUID.ToString();
+
+            // Getting the global position for the Avatar
+            Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.RegionLocX*Constants.RegionSize + avaPos.X,
+                                            remoteClient.Scene.RegionInfo.RegionLocY*Constants.RegionSize + avaPos.Y,
+                                            avaPos.Z);
+
+            ReqHash["pos_global"] = posGlobal.ToString();
+
+            // Getting the owner of the parcel
+            ReqHash["user"] = "";   //FIXME: Get avatar/group who owns parcel
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            // Do the request
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "picks_update", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Picks Delete
+        public void PickDelete(IClientAPI remoteClient, UUID queryPickID)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["pick_id"] = queryPickID.ToString();
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "picks_delete", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Notes Handler
+        public void HandleAvatarNotesRequest(Object sender, string method, List<String> args)
+        {
+            string targetid;
+            string notes = "";
+
+            if (!(sender is IClientAPI))
+                return;
+
+            IClientAPI remoteClient = (IClientAPI)sender;
+
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+            ReqHash["uuid"] = args[0];
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    method, serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            if (dataArray != null && dataArray[0] != null)
+            {
+                Hashtable d = (Hashtable)dataArray[0];
+
+                targetid = d["targetid"].ToString();
+                if (d["notes"] != null)
+                    notes = d["notes"].ToString();
+
+                remoteClient.SendAvatarNotesReply(new UUID(targetid), notes);
+            }
+        }
+
+        // Notes Update
+        public void AvatarNotesUpdate(IClientAPI remoteClient, UUID queryTargetID, string queryNotes)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+            ReqHash["target_id"] = queryTargetID.ToString();
+            ReqHash["notes"] = queryNotes;
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "avatar_notes_update", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Standard Profile bits
+        public void AvatarInterestsUpdate(IClientAPI remoteClient, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+            ReqHash["wantmask"] = wantmask.ToString();
+            ReqHash["wanttext"] = wanttext;
+            ReqHash["skillsmask"] = skillsmask.ToString();
+            ReqHash["skillstext"] = skillstext;
+            ReqHash["languages"] = languages;
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "avatar_interests_update", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        public void UserPreferencesRequest(IClientAPI remoteClient)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "user_preferences_request", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            if (dataArray != null && dataArray[0] != null)
+            {
+                Hashtable d = (Hashtable)dataArray[0];
+                string mail = "";
+
+                if (d["email"] != null)
+                    mail = d["email"].ToString();
+
+                remoteClient.SendUserInfoReply(
+                        Convert.ToBoolean(d["imviaemail"]),
+                        Convert.ToBoolean(d["visible"]),
+                        mail);
+            }
+        }
+
+        public void UpdateUserPreferences(bool imViaEmail, bool visible, IClientAPI remoteClient)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+            ReqHash["imViaEmail"] = imViaEmail.ToString();
+            ReqHash["visible"] = visible.ToString();
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "user_preferences_update", serverURI);
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+            }
+        }
+
+        // Profile data like the WebURL
+        private Hashtable GetProfileData(UUID userID)
+        {
+            Hashtable ReqHash = new Hashtable();
+
+            // Can't handle NPC yet...
+            ScenePresence p = FindPresence(userID);
+
+            if (null != p)
+            {
+                if (p.PresenceType == PresenceType.Npc)
+                {
+                    Hashtable npc =new Hashtable();
+                    npc["success"] = "false";
+                    npc["errorMessage"] = "Presence is NPC. ";
+                    return npc;
+                }
+            }
+
+            ReqHash["avatar_id"] = userID.ToString();
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(userID, out serverURI);
+
+            // This is checking a friend on the home grid
+            // Not HG friend
+            if ( String.IsNullOrEmpty(serverURI))
+            {
+                Hashtable nop =new Hashtable();
+                nop["success"] = "false";
+                nop["errorMessage"] = "No Presence - foreign friend";
+                return nop;
+
+            }
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "avatar_properties_request", serverURI);
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            if (dataArray != null && dataArray[0] != null)
+            {
+                Hashtable d = (Hashtable)dataArray[0];
+                return d;
+            }
+            return result;
+        }
+
+        public void RequestAvatarProperties(IClientAPI remoteClient, UUID avatarID)
+        {
+            if ( String.IsNullOrEmpty(avatarID.ToString()) || String.IsNullOrEmpty(remoteClient.AgentId.ToString()))
+            {
+                // Looking for a reason that some viewers are sending null Id's
+                m_log.InfoFormat("[PROFILE]: This should not happen remoteClient.AgentId {0} - avatarID {1}", remoteClient.AgentId, avatarID);
+                return;
+            }
+
+            // Can't handle NPC yet...
+            ScenePresence p = FindPresence(avatarID);
+
+            if (null != p)
+            {
+                if (p.PresenceType == PresenceType.Npc)
+                    return;
+            }
+
+            IScene s = remoteClient.Scene;
+            if (!(s is Scene))
+                return;
+
+            Scene scene = (Scene)s;
+
+            string serverURI = string.Empty;
+            bool foreign = GetUserProfileServerURI(avatarID, out serverURI);
+
+            UserAccount account = null;
+            Dictionary<string,object> userInfo;
+
+            if (!foreign)
+            {
+                account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, avatarID);
+            }
+            else
+            {
+                userInfo = new Dictionary<string, object>();
+            }
+
+            Byte[] charterMember = new Byte[1];
+            string born = String.Empty;
+            uint flags = 0x00;
+
+            if (null != account)
+            {
+                if (account.UserTitle == "")
+                {
+                    charterMember[0] = (Byte)((account.UserFlags & 0xf00) >> 8);
+                }
+                else
+                {
+                    charterMember = Utils.StringToBytes(account.UserTitle);
+                }
+
+                born = Util.ToDateTime(account.Created).ToString(
+                                  "M/d/yyyy", CultureInfo.InvariantCulture);
+                flags = (uint)(account.UserFlags & 0xff);
+            }
+            else
+            {
+                if (GetUserProfileData(avatarID, out userInfo) == true)
+                {
+                    if ((string)userInfo["user_title"] == "")
+                    {
+                        charterMember[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8);
+                    }
+                    else
+                    {
+                        charterMember = Utils.StringToBytes((string)userInfo["user_title"]);
+                    }
+
+                    int val_born = (int)userInfo["user_created"];
+                    born = Util.ToDateTime(val_born).ToString(
+                                  "M/d/yyyy", CultureInfo.InvariantCulture);
+
+                    // picky, picky
+                    int val_flags = (int)userInfo["user_flags"];
+                    flags = (uint)(val_flags & 0xff);
+                }
+            }
+
+	    Hashtable profileData = GetProfileData(avatarID);
+	    string profileUrl = string.Empty;
+	    string aboutText = String.Empty;
+	    string firstLifeAboutText = String.Empty;
+	    UUID image = UUID.Zero;
+	    UUID firstLifeImage = UUID.Zero;
+	    UUID partner = UUID.Zero;
+	    uint   wantMask = 0;
+	    string wantText = String.Empty;
+	    uint   skillsMask = 0;
+	    string skillsText = String.Empty;
+	    string languages = String.Empty;
+
+	    if (profileData["ProfileUrl"] != null)
+		profileUrl = profileData["ProfileUrl"].ToString();
+	    if (profileData["AboutText"] != null)
+		aboutText = profileData["AboutText"].ToString();
+	    if (profileData["FirstLifeAboutText"] != null)
+		firstLifeAboutText = profileData["FirstLifeAboutText"].ToString();
+	    if (profileData["Image"] != null)
+		image = new UUID(profileData["Image"].ToString());
+	    if (profileData["FirstLifeImage"] != null)
+		firstLifeImage = new UUID(profileData["FirstLifeImage"].ToString());
+	    if (profileData["Partner"] != null)
+		partner = new UUID(profileData["Partner"].ToString());
+
+	    // The PROFILE information is no longer stored in the user
+	    // account. It now needs to be taken from the XMLRPC
+	    //
+	    remoteClient.SendAvatarProperties(avatarID, aboutText,born,
+		      charterMember, firstLifeAboutText,
+		  flags,
+		      firstLifeImage, image, profileUrl, partner);
+
+	    //Viewer expects interest data when it asks for properties.
+	    if (profileData["wantmask"] != null)
+		wantMask = Convert.ToUInt32(profileData["wantmask"].ToString());
+	    if (profileData["wanttext"] != null)
+		wantText = profileData["wanttext"].ToString();
+
+	    if (profileData["skillsmask"] != null)
+		skillsMask = Convert.ToUInt32(profileData["skillsmask"].ToString());
+	    if (profileData["skillstext"] != null)
+		skillsText = profileData["skillstext"].ToString();
+
+	    if (profileData["languages"] != null)
+		languages = profileData["languages"].ToString();
+
+	    remoteClient.SendAvatarInterestsReply(avatarID, wantMask, wantText,
+						  skillsMask, skillsText, languages);
+        }
+
+        public void UpdateAvatarProperties(IClientAPI remoteClient, UserProfileData newProfile)
+        {
+            // if it's the profile of the user requesting the update, then we change only a few things.
+            if (remoteClient.AgentId == newProfile.ID)
+            {
+                Hashtable ReqHash = new Hashtable();
+
+                ReqHash["avatar_id"] = remoteClient.AgentId.ToString();
+                ReqHash["ProfileUrl"] = newProfile.ProfileUrl;
+                ReqHash["Image"] = newProfile.Image.ToString();
+                ReqHash["AboutText"] = newProfile.AboutText;
+                ReqHash["FirstLifeImage"] = newProfile.FirstLifeImage.ToString();
+                ReqHash["FirstLifeAboutText"] = newProfile.FirstLifeAboutText;
+
+                string serverURI = string.Empty;
+                bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI);
+
+                Hashtable result = GenericXMLRPCRequest(ReqHash,
+                        "avatar_properties_update", serverURI);
+
+                if (!Convert.ToBoolean(result["success"]))
+                {
+                    remoteClient.SendAgentAlertMessage(
+                            result["errorMessage"].ToString(), false);
+                }
+
+                RequestAvatarProperties(remoteClient, newProfile.ID);
+            }
+        }
+
+        private bool GetUserProfileServerURI(UUID userID, out string serverURI)
+        {
+            IUserManagement uManage = UserManagementModule;
+
+            if (!uManage.IsLocalGridUser(userID))
+            {
+                serverURI = uManage.GetUserServerURL(userID, "ProfileServerURI");
+                // Is Foreign
+                return true;
+            }
+            else
+            {
+                serverURI = m_ProfileServer;
+                // Is local
+                return false;
+            }
+        }
+
+        //
+        // Get the UserAccountBits
+        //
+        private bool GetUserProfileData(UUID userID, out Dictionary<string, object> userInfo)
+        {
+            IUserManagement uManage = UserManagementModule;
+            Dictionary<string,object> info = new Dictionary<string, object>();
+
+
+            if (!uManage.IsLocalGridUser(userID))
+            {
+                // Is Foreign
+                string home_url = uManage.GetUserServerURL(userID, "HomeURI");
+
+                if (String.IsNullOrEmpty(home_url))
+                {
+                    info["user_flags"] = 0;
+                    info["user_created"] = 0;
+                    info["user_title"] = "Unavailable";
+
+                    userInfo = info;
+                    return true;
+                }
+
+                UserAgentServiceConnector uConn = new UserAgentServiceConnector(home_url);
+
+                Dictionary<string, object> account = uConn.GetUserInfo(userID);
+
+                if (account.Count > 0)
+                {
+                    if (account.ContainsKey("user_flags"))
+                        info["user_flags"] = account["user_flags"];
+                    else
+                        info["user_flags"] = "";
+
+                    if (account.ContainsKey("user_created"))
+                        info["user_created"] = account["user_created"];
+                    else
+                        info["user_created"] = "";
+
+                    info["user_title"] = "HG Visitor";
+                }
+                else
+                {
+                   info["user_flags"] = 0;
+                   info["user_created"] = 0;
+                   info["user_title"] = "HG Visitor";
+                }
+                userInfo = info;
+                return true;
+            }
+            else
+            {
+                // Is local
+                Scene scene = m_Scenes[0];
+                IUserAccountService uas = scene.UserAccountService;
+                UserAccount account = uas.GetUserAccount(scene.RegionInfo.ScopeID, userID);
+
+                info["user_flags"] = account.UserFlags;
+                info["user_created"] = account.Created;
+
+                if (!String.IsNullOrEmpty(account.UserTitle))
+                    info["user_title"] = account.UserTitle;
+                else
+                    info["user_title"] = "";
+
+                userInfo = info;
+
+                return false;
+            }
+        }
+    }
+}
diff --git a/addon-modules/OpenSimProfile/Robust.HG.ini.example b/addon-modules/OpenSimProfile/Robust.HG.ini.example
new file mode 100644
index 0000000..d9407cb
--- /dev/null
+++ b/addon-modules/OpenSimProfile/Robust.HG.ini.example
@@ -0,0 +1,3 @@
+;; Update [LogonService] section to point to your profile server's url
+;; 
+SRV_ProfileServerURI = "http://127.0.0.1/profile.php"
diff --git a/addon-modules/OpenSimProfile/prebuild.xml b/addon-modules/OpenSimProfile/prebuild.xml
new file mode 100644
index 0000000..e653dee
--- /dev/null
+++ b/addon-modules/OpenSimProfile/prebuild.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" ?>
+<Project frameworkVersion="v3_5" name="OpenSimProfile.Modules" path="addon-modules/OpenSimProfile/Modules" type="Library">
+     <Configuration name="Debug">
+       <Options>
+         <OutputPath>../../../bin/</OutputPath>
+       </Options>
+     </Configuration>
+     <Configuration name="Release">
+       <Options>
+         <OutputPath>../../../bin/</OutputPath>
+       </Options>
+     </Configuration>
+
+     <ReferencePath>../../../bin/</ReferencePath>
+     <Reference localCopy="false" name="System"/>
+     <Reference name="System.Xml"/>
+     <Reference name="System.Drawing"/>
+     <Reference name="System.Runtime.Remoting"/>
+     <Reference name="OpenMetaverseTypes" path="../../../bin/"/>
+     <Reference name="OpenMetaverse" path="../../../bin/"/>
+     <Reference name="Axiom.MathLib" path="../../../bin/"/>
+     <Reference name="OpenSim.Framework"/>
+     <Reference name="OpenSim.Data"/>
+     <Reference name="OpenSim.Region.Framework"/>
+     <Reference name="OpenSim.Framework.Console"/>
+     <Reference name="OpenSim.Framework.Servers"/>
+     <Reference name="OpenSim.Framework.Statistics"/>
+     <Reference name="OpenSim.Framework.Communications"/>
+     <Reference name="OpenSim.Region.Physics.Manager"/>
+     <Reference name="OpenSim.Services.Interfaces"/>
+     <Reference name="OpenSim.Services.Connectors"/>
+     <Reference name="Nini" path="../../../bin/"/>
+     <Reference name="log4net" path="../../../bin/"/>
+     <Reference name="XMLRPC" path="../../../bin/"/>
+     <Reference name="Mono.Addins" path="../../../bin/"/>
+
+     <Files>
+       <Match pattern="*.cs" recurse="true"/>
+     </Files>
+</Project>
diff --git a/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs b/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs
new file mode 100644
index 0000000..2544614
--- /dev/null
+++ b/addon-modules/OpenSimSearch/Modules/SearchModule/OpenSearch.cs
@@ -0,0 +1,753 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Xml;
+using OpenMetaverse;
+using log4net;
+using Nini.Config;
+using Nwc.XmlRpc;
+using OpenSim.Framework;
+using OpenSim.Region.Framework.Interfaces;
+using OpenSim.Region.Framework.Scenes;
+using OpenSim.Services.Interfaces;
+using Mono.Addins;
+
+[assembly: Addin("OpenSearchModule", "0.1")]
+[assembly: AddinDependency("OpenSim", "0.5")]
+
+namespace OpenSimSearch.Modules.OpenSearch
+{
+    [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
+    public class OpenSearchModule : ISearchModule, ISharedRegionModule
+    {
+        //
+        // Log module
+        //
+        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+        //
+        // Module vars
+        //
+        private List<Scene> m_Scenes = new List<Scene>();
+        private string m_SearchServer = "";
+        private bool m_Enabled = true;
+
+        public void Initialise(IConfigSource config)
+        {
+            IConfig searchConfig = config.Configs["Search"];
+
+            if (searchConfig == null)
+            {
+                m_Enabled = false;
+                return;
+            }
+            if (searchConfig.GetString("Module", "OpenSimSearch") != "OpenSimSearch")
+            {
+                m_Enabled = false;
+                return;
+            }
+
+            m_SearchServer = searchConfig.GetString("SearchURL", "");
+            if (m_SearchServer == "")
+            {
+                m_log.Error("[SEARCH] No search server, disabling search");
+                m_Enabled = false;
+                return;
+            }
+            else
+            {
+                m_log.Info("[SEARCH] Search module is activated");
+                m_Enabled = true;
+            }
+        }
+
+        public void AddRegion(Scene scene)
+        {
+            if (!m_Enabled)
+                return;
+
+            // Hook up events
+            scene.EventManager.OnNewClient += OnNewClient;
+
+            // Take ownership of the ISearchModule service
+            scene.RegisterModuleInterface<ISearchModule>(this);
+
+            // Add our scene to our list...
+            lock(m_Scenes)
+            {
+                m_Scenes.Add(scene);
+            }
+
+        }
+
+        public void RemoveRegion(Scene scene)
+        {
+            if (!m_Enabled)
+                return;
+
+            scene.UnregisterModuleInterface<ISearchModule>(this);
+            m_Scenes.Remove(scene);
+        }
+
+        public void RegionLoaded(Scene scene)
+        {
+        }
+
+        public Type ReplaceableInterface
+        {
+            get { return null; }
+        }
+
+        public void PostInitialise()
+        {
+        }
+
+        public void Close()
+        {
+        }
+
+        public string Name
+        {
+            get { return "SearchModule"; }
+        }
+
+        public bool IsSharedModule
+        {
+            get { return true; }
+        }
+
+        /// New Client Event Handler
+        private void OnNewClient(IClientAPI client)
+        {
+            // Subscribe to messages
+            client.OnDirPlacesQuery += DirPlacesQuery;
+            client.OnDirFindQuery += DirFindQuery;
+            client.OnDirPopularQuery += DirPopularQuery;
+            client.OnDirLandQuery += DirLandQuery;
+            client.OnDirClassifiedQuery += DirClassifiedQuery;
+            // Response after Directory Queries
+            client.OnEventInfoRequest += EventInfoRequest;
+            client.OnClassifiedInfoRequest += ClassifiedInfoRequest;
+            client.OnMapItemRequest += HandleMapItemRequest;
+        }
+
+        //
+        // Make external XMLRPC request
+        //
+        private Hashtable GenericXMLRPCRequest(Hashtable ReqParams, string method)
+        {
+            ArrayList SendParams = new ArrayList();
+            SendParams.Add(ReqParams);
+
+            // Send Request
+            XmlRpcResponse Resp;
+            try
+            {
+                XmlRpcRequest Req = new XmlRpcRequest(method, SendParams);
+                Resp = Req.Send(m_SearchServer, 30000);
+            }
+            catch (WebException ex)
+            {
+                m_log.ErrorFormat("[SEARCH]: Unable to connect to Search " +
+                        "Server {0}.  Exception {1}", m_SearchServer, ex);
+
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to search at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            catch (SocketException ex)
+            {
+                m_log.ErrorFormat(
+                        "[SEARCH]: Unable to connect to Search Server {0}. " +
+                        "Exception {1}", m_SearchServer, ex);
+
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to search at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            catch (XmlException ex)
+            {
+                m_log.ErrorFormat(
+                        "[SEARCH]: Unable to connect to Search Server {0}. " +
+                        "Exception {1}", m_SearchServer, ex);
+
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to search at this time. ";
+                ErrorHash["errorURI"] = "";
+
+                return ErrorHash;
+            }
+            if (Resp.IsFault)
+            {
+                Hashtable ErrorHash = new Hashtable();
+                ErrorHash["success"] = false;
+                ErrorHash["errorMessage"] = "Unable to search at this time. ";
+                ErrorHash["errorURI"] = "";
+                return ErrorHash;
+            }
+            Hashtable RespData = (Hashtable)Resp.Value;
+
+            return RespData;
+        }
+
+        protected void DirPlacesQuery(IClientAPI remoteClient, UUID queryID,
+                string queryText, int queryFlags, int category, string simName,
+                int queryStart)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["text"] = queryText;
+            ReqHash["flags"] = queryFlags.ToString();
+            ReqHash["category"] = category.ToString();
+            ReqHash["sim_name"] = simName;
+            ReqHash["query_start"] = queryStart.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "dir_places_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            int count = dataArray.Count;
+            if (count > 100)
+                count = 101;
+
+            DirPlacesReplyData[] data = new DirPlacesReplyData[count];
+
+            int i = 0;
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                data[i] = new DirPlacesReplyData();
+                data[i].parcelID = new UUID(d["parcel_id"].ToString());
+                data[i].name = d["name"].ToString();
+                data[i].forSale = Convert.ToBoolean(d["for_sale"]);
+                data[i].auction = Convert.ToBoolean(d["auction"]);
+                data[i].dwell = Convert.ToSingle(d["dwell"]);
+
+                if (++i >= count)
+                    break;
+            }
+
+            remoteClient.SendDirPlacesReply(queryID, data);
+        }
+
+        public void DirPopularQuery(IClientAPI remoteClient, UUID queryID, uint queryFlags)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["flags"] = queryFlags.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "dir_popular_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            int count = dataArray.Count;
+            if (count > 100)
+                count = 101;
+
+            DirPopularReplyData[] data = new DirPopularReplyData[count];
+
+            int i = 0;
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                data[i] = new DirPopularReplyData();
+                data[i].parcelID = new UUID(d["parcel_id"].ToString());
+                data[i].name = d["name"].ToString();
+                data[i].dwell = Convert.ToSingle(d["dwell"]);
+
+                if (++i >= count)
+                    break;
+            }
+
+            remoteClient.SendDirPopularReply(queryID, data);
+        }
+
+        public void DirLandQuery(IClientAPI remoteClient, UUID queryID,
+                uint queryFlags, uint searchType, int price, int area,
+                int queryStart)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["flags"] = queryFlags.ToString();
+            ReqHash["type"] = searchType.ToString();
+            ReqHash["price"] = price.ToString();
+            ReqHash["area"] = area.ToString();
+            ReqHash["query_start"] = queryStart.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "dir_land_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+            int count = 0;
+
+            /* Count entries in dataArray with valid region name to */
+            /* prevent allocating data array with too many entries. */
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                if (d["name"] != null)
+                    ++count;
+            }
+
+            if (count > 100)
+                count = 101;
+
+            DirLandReplyData[] data = new DirLandReplyData[count];
+
+            int i = 0;
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                if (d["name"] == null)
+                    continue;
+
+                data[i] = new DirLandReplyData();
+                data[i].parcelID = new UUID(d["parcel_id"].ToString());
+                data[i].name = d["name"].ToString();
+                data[i].auction = Convert.ToBoolean(d["auction"]);
+                data[i].forSale = Convert.ToBoolean(d["for_sale"]);
+                data[i].salePrice = Convert.ToInt32(d["sale_price"]);
+                data[i].actualArea = Convert.ToInt32(d["area"]);
+
+                if (++i >= count)
+                    break;
+            }
+
+            remoteClient.SendDirLandReply(queryID, data);
+        }
+
+        public void DirFindQuery(IClientAPI remoteClient, UUID queryID,
+                string queryText, uint queryFlags, int queryStart)
+        {
+            if ((queryFlags & 1) != 0)      //People (1 << 0)
+            {
+                DirPeopleQuery(remoteClient, queryID, queryText, queryFlags,
+                        queryStart);
+                return;
+            }
+            else if ((queryFlags & 32) != 0)    //DateEvents (1 << 5)
+            {
+                DirEventsQuery(remoteClient, queryID, queryText, queryFlags,
+                        queryStart);
+                return;
+            }
+        }
+
+        public void DirPeopleQuery(IClientAPI remoteClient, UUID queryID,
+                string queryText, uint queryFlags, int queryStart)
+        {
+            List<UserAccount> accounts = m_Scenes[0].UserAccountService.GetUserAccounts(m_Scenes[0].RegionInfo.ScopeID, queryText);
+
+            DirPeopleReplyData[] data =
+                    new DirPeopleReplyData[accounts.Count];
+
+            int i = 0;
+
+            foreach (UserAccount item in accounts)
+            {
+                data[i] = new DirPeopleReplyData();
+
+                data[i].agentID = item.PrincipalID;
+                data[i].firstName = item.FirstName;
+                data[i].lastName = item.LastName;
+                data[i].group = "";
+                data[i].online = false;
+                data[i].reputation = 0;
+                i++;
+            }
+
+            remoteClient.SendDirPeopleReply(queryID, data);
+        }
+
+        public void DirEventsQuery(IClientAPI remoteClient, UUID queryID,
+                string queryText, uint queryFlags, int queryStart)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["text"] = queryText;
+            ReqHash["flags"] = queryFlags.ToString();
+            ReqHash["query_start"] = queryStart.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "dir_events_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            int count = dataArray.Count;
+            if (count > 100)
+                count = 101;
+
+            DirEventsReplyData[] data = new DirEventsReplyData[count];
+
+            int i = 0;
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                data[i] = new DirEventsReplyData();
+                data[i].ownerID = new UUID(d["owner_id"].ToString());
+                data[i].name = d["name"].ToString();
+                data[i].eventID = Convert.ToUInt32(d["event_id"]);
+                data[i].date = d["date"].ToString();
+                data[i].unixTime = Convert.ToUInt32(d["unix_time"]);
+                data[i].eventFlags = Convert.ToUInt32(d["event_flags"]);
+
+                if (++i >= count)
+                    break;
+            }
+
+            remoteClient.SendDirEventsReply(queryID, data);
+        }
+
+        public void DirClassifiedQuery(IClientAPI remoteClient, UUID queryID,
+                string queryText, uint queryFlags, uint category,
+                int queryStart)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["text"] = queryText;
+            ReqHash["flags"] = queryFlags.ToString();
+            ReqHash["category"] = category.ToString();
+            ReqHash["query_start"] = queryStart.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "dir_classified_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+
+            int count = dataArray.Count;
+            if (count > 100)
+                count = 101;
+
+            DirClassifiedReplyData[] data = new DirClassifiedReplyData[count];
+
+            int i = 0;
+
+            foreach (Object o in dataArray)
+            {
+                Hashtable d = (Hashtable)o;
+
+                data[i] = new DirClassifiedReplyData();
+                data[i].classifiedID = new UUID(d["classifiedid"].ToString());
+                data[i].name = d["name"].ToString();
+                data[i].classifiedFlags = Convert.ToByte(d["classifiedflags"]);
+                data[i].creationDate = Convert.ToUInt32(d["creation_date"]);
+                data[i].expirationDate = Convert.ToUInt32(d["expiration_date"]);
+                data[i].price = Convert.ToInt32(d["priceforlisting"]);
+
+                if (++i >= count)
+                    break;
+            }
+
+            remoteClient.SendDirClassifiedReply(queryID, data);
+        }
+
+        public void EventInfoRequest(IClientAPI remoteClient, uint queryEventID)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["eventID"] = queryEventID.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "event_info_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            ArrayList dataArray = (ArrayList)result["data"];
+            if (dataArray.Count == 0)
+            {
+                // something bad happened here, if we could return an
+                // event after the search,
+                // we should be able to find it here
+                // TODO do some (more) sensible error-handling here
+                remoteClient.SendAgentAlertMessage("Couldn't find this event.",
+                        false);
+                return;
+            }
+
+            Hashtable d = (Hashtable)dataArray[0];
+            EventData data = new EventData();
+            data.eventID = Convert.ToUInt32(d["event_id"]);
+            data.creator = d["creator"].ToString();
+            data.name = d["name"].ToString();
+            data.category = d["category"].ToString();
+            data.description = d["description"].ToString();
+            data.date = d["date"].ToString();
+            data.dateUTC = Convert.ToUInt32(d["dateUTC"]);
+            data.duration = Convert.ToUInt32(d["duration"]);
+            data.cover = Convert.ToUInt32(d["covercharge"]);
+            data.amount = Convert.ToUInt32(d["coveramount"]);
+            data.simName = d["simname"].ToString();
+            Vector3.TryParse(d["globalposition"].ToString(), out data.globalPos);
+            data.eventFlags = Convert.ToUInt32(d["eventflags"]);
+
+            remoteClient.SendEventInfoReply(data);
+        }
+
+        public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient)
+        {
+            Hashtable ReqHash = new Hashtable();
+            ReqHash["classifiedID"] = queryClassifiedID.ToString();
+
+            Hashtable result = GenericXMLRPCRequest(ReqHash,
+                    "classifieds_info_query");
+
+            if (!Convert.ToBoolean(result["success"]))
+            {
+                remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                return;
+            }
+
+            //The viewer seems to issue an info request even when it is
+            //creating a new classified which means the data hasn't been
+            //saved to the database yet so there is no info to find.
+            ArrayList dataArray = (ArrayList)result["data"];
+            if (dataArray.Count == 0)
+            {
+                // Something bad happened here if we could not return an
+                // event after the search. We should be able to find it here.
+                // TODO do some (more) sensible error-handling here
+//                remoteClient.SendAgentAlertMessage("Couldn't find data for classified ad.",
+//                        false);
+                return;
+            }
+
+            Hashtable d = (Hashtable)dataArray[0];
+
+            Vector3 globalPos = new Vector3();
+            Vector3.TryParse(d["posglobal"].ToString(), out globalPos);
+
+            remoteClient.SendClassifiedInfoReply(
+                    new UUID(d["classifieduuid"].ToString()),
+                    new UUID(d["creatoruuid"].ToString()),
+                    Convert.ToUInt32(d["creationdate"]),
+                    Convert.ToUInt32(d["expirationdate"]),
+                    Convert.ToUInt32(d["category"]),
+                    d["name"].ToString(),
+                    d["description"].ToString(),
+                    new UUID(d["parceluuid"].ToString()),
+                    Convert.ToUInt32(d["parentestate"]),
+                    new UUID(d["snapshotuuid"].ToString()),
+                    d["simname"].ToString(),
+                    globalPos,
+                    d["parcelname"].ToString(),
+                    Convert.ToByte(d["classifiedflags"]),
+                    Convert.ToInt32(d["priceforlisting"]));
+        }
+
+        public void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
+                                         uint EstateID, bool godlike,
+                                         uint itemtype, ulong regionhandle)
+        {
+            //The following constant appears to be from GridLayerType enum
+            //defined in OpenMetaverse/GridManager.cs of libopenmetaverse.
+            if (itemtype == (uint)OpenMetaverse.GridItemType.LandForSale)
+            {
+                Hashtable ReqHash = new Hashtable();
+
+                //The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17)
+                ReqHash["flags"] = "163840";
+                ReqHash["type"] = "4294967295"; //This is -1 in 32 bits
+                ReqHash["price"] = "0";
+                ReqHash["area"] = "0";
+                ReqHash["query_start"] = "0";
+
+                Hashtable result = GenericXMLRPCRequest(ReqHash,
+                                                        "dir_land_query");
+
+                if (!Convert.ToBoolean(result["success"]))
+                {
+                    remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                    return;
+                }
+
+                ArrayList dataArray = (ArrayList)result["data"];
+
+                int count = dataArray.Count;
+                if (count > 100)
+                    count = 101;
+
+                List<mapItemReply> mapitems = new List<mapItemReply>();
+                string ParcelRegionUUID;
+                string[] landingpoint;
+
+                foreach (Object o in dataArray)
+                {
+                    Hashtable d = (Hashtable)o;
+
+                    if (d["name"] == null)
+                        continue;
+
+                    mapItemReply mapitem = new mapItemReply();
+
+                    ParcelRegionUUID = d["region_UUID"].ToString();
+
+                    foreach (Scene scene in m_Scenes)
+                    {
+                        if (scene.RegionInfo.RegionID.ToString() == ParcelRegionUUID)
+                        {
+                            landingpoint = d["landing_point"].ToString().Split('/');
+
+                            mapitem.x = (uint)((scene.RegionInfo.RegionLocX * 256) +
+                                                Convert.ToDecimal(landingpoint[0]));
+                            mapitem.y = (uint)((scene.RegionInfo.RegionLocY * 256) +
+                                                Convert.ToDecimal(landingpoint[1]));
+                            break;
+                        }
+                    }
+
+                    mapitem.id = new UUID(d["parcel_id"].ToString());
+                    mapitem.Extra = Convert.ToInt32(d["area"]);
+                    mapitem.Extra2 = Convert.ToInt32(d["sale_price"]);
+                    mapitem.name = d["name"].ToString();
+
+                    mapitems.Add(mapitem);
+                }
+
+                remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
+                mapitems.Clear();
+            }
+
+            if (itemtype == (uint)OpenMetaverse.GridItemType.PgEvent ||
+                itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent ||
+                itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent)
+            {
+                Hashtable ReqHash = new Hashtable();
+
+                //Find the maturity level
+                int maturity = (1 << 24);
+
+                //Find the maturity level
+                if (itemtype == (uint)OpenMetaverse.GridItemType.MatureEvent)
+                    maturity = (1 << 25);
+                else
+                {
+                    if (itemtype == (uint)OpenMetaverse.GridItemType.AdultEvent)
+                        maturity = (1 << 26);
+                }
+
+                //The flags are: SortAsc (1 << 15), PerMeterSort (1 << 17)
+                maturity |= 163840;
+
+                //Character before | is number of days before/after current date
+                //Characters after | is the number for a category
+                ReqHash["text"] = "0|0";
+                ReqHash["flags"] = maturity.ToString();
+                ReqHash["query_start"] = "0";
+
+                Hashtable result = GenericXMLRPCRequest(ReqHash,
+                                                        "dir_events_query");
+
+                if (!Convert.ToBoolean(result["success"]))
+                {
+                    remoteClient.SendAgentAlertMessage(
+                        result["errorMessage"].ToString(), false);
+                    return;
+                }
+
+                ArrayList dataArray = (ArrayList)result["data"];
+
+                List<mapItemReply> mapitems = new List<mapItemReply>();
+                string ParcelRegionUUID;
+                string[] landingpoint;
+
+                foreach (Object o in dataArray)
+                {
+                    Hashtable d = (Hashtable)o;
+
+                    if (d["name"] == null)
+                        continue;
+
+                    mapItemReply mapitem = new mapItemReply();
+
+                    ParcelRegionUUID = d["region_UUID"].ToString();
+
+                    foreach (Scene scene in m_Scenes)
+                    {
+                        if (scene.RegionInfo.RegionID.ToString() == ParcelRegionUUID)
+                        {
+                            landingpoint = d["landing_point"].ToString().Split('/');
+
+                            mapitem.x = (uint)((scene.RegionInfo.RegionLocX * 256) +
+                                                Convert.ToDecimal(landingpoint[0]));
+                            mapitem.y = (uint)((scene.RegionInfo.RegionLocY * 256) +
+                                                Convert.ToDecimal(landingpoint[1]));
+                            break;
+                        }
+                    }
+
+                    mapitem.id = UUID.Random();
+                    mapitem.Extra = (int)Convert.ToInt32(d["unix_time"]);
+                    mapitem.Extra2 = (int)Convert.ToInt32(d["event_id"]);
+                    mapitem.name = d["name"].ToString();
+
+                    mapitems.Add(mapitem);
+                }
+
+                remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
+                mapitems.Clear();
+            }
+        }
+
+        public void Refresh()
+        {
+        }
+    }
+}
diff --git a/addon-modules/OpenSimSearch/README b/addon-modules/OpenSimSearch/README
new file mode 100644
index 0000000..ce826e3
--- /dev/null
+++ b/addon-modules/OpenSimSearch/README
@@ -0,0 +1,294 @@
+OpenSimSearch add-on module for Open Simulator
+
+Requirements
+
+The webserver needs PHP support with support for CURL and XMLRPC.
+NOTE: Not all webservers may have CURL and XMLRPC support for PHP.
+
+
+About the files
+
+README - The file you are currently reading
+
+bin/OpenSimSearch.Modules.dll - A pre-compiled module you can use
+
+OpenSimSearch/ - Source code for OpenSim search module
+
+webroot/*.php - Files to install on webserver
+
+webroot/sql/ossearch.sql - This will create the needed database tables
+webroot/sql/update-*.sql - Files used to update an older installation
+
+webroot/wiredux/ - Files to add to an installation of wiredux
+
+
+How it works
+
+If you are looking for a detailed explanation of how the add-on search system
+works you should study the contents of the files which accompany this README
+file. What follows is a general overview of the search package to give you an
+idea of how the pieces fit together in case you have any problems.
+
+There are three main components to OpenSimSearch which are a database, a DLL
+file, and several PHP files.
+
+Most of the tables in the search database contain details on items which can
+appear in the results of a search. The exception is the hostsregister table. 
+The hostsregister table contains the list of OpenSim instances which will be
+checked periodically for information about searchable items.
+
+When an OpenSim instance starts it accesses the register.php script (using the
+URL of the data_services entry in OpenSim.ini) to record the host address and
+port of the instance in the hostsregister table of the database. The host and
+port entries are used by parser.php to retrieve the data to be indexed from
+the running instances.
+
+Take note of the last part of the previous paragraph where it stated that
+parser.php is used to index the data from an OpenSim instance and not from a
+region. The parser retrieves data from an OpenSim instance. If the instance
+is running a single region the data for a single region will be updated. If
+the instance is running multiple regions the data for all regions hosted by
+that instance will be indexed. 
+
+OpenSim instances use the data snapshot module to create an XML based record
+of all searchable items in the regions they host. The XML record is retrieved
+by parser.php using a URL created from the host, port and the query string
+"?method=collector" (eg. http://127.0.0.1:9001/?method=collector). The
+parser.php file will get one host/port pair from the hostregister table each
+time it is called. It will parse the XML data from the OpenSim instance and
+save the data to the various tables of the search database.
+
+The query.php file is the heart of the search process. It receives an XML
+message from the DLL based on the search request originating in a viewer.
+The XML message is parsed to determine the type of search being performed,
+SQL queries are built and executed to retrieve the data from the database
+tables, and finally, the results are sent to the DLL file as an XML message
+for any final processing before being passed along to the viewer for display.
+
+
+Before you begin
+
+In the information that follows there are references to settings to be made
+in the OpenSim.ini files. As of the release of the 0.7 version of OpenSim
+some of the settings may no longer be in the OpenSim.ini file but may be
+found in the OpenSimDefault.ini file. If a section of the OpenSim.ini file
+mentioned in the information which follows doesn't exist in your copy of
+the file, you will either need to add the section and settings to the main
+OpenSim.ini file or make the changes to the OpenSimDefaults.ini file.
+
+
+Compiling the module
+
+Adding the OpenSimSearch C# source file to the source tree of OpenSim so it
+will be compiled at the same time as OpenSim is compiled is the easiest way
+to create to the DLL file needed by OpenSim.
+
+Copy the OpenSimSearch directory in to the addon-modules directory of your
+OpenSim source tree. After you have done that, compile OpenSim in the usual
+way (runprebuild and nant) to create the DLL file.  When nant finishes, you
+will have an OpenSimSearch.Modules.dll file in the main bin directory of
+your OpenSim source tree along with a matching .pdb (or .mdb) file. These
+two files will be copied to the bin directory of your OpenSim instances
+during the installation or update steps.
+
+
+First time installation
+
+The first installation step is the creation of a database that will hold
+the data to be searched. If you have already installed the add-on module
+osprofile you should use the same database as osprofile as it shares some
+tables in common with ossearch. If you don't have osprofile installed you
+will need to create a new database that will hold the search tables. Having
+decided on the database to be used, use ossearch.sql (located in the
+webroot/sql directory) to create the required tables in the database. The
+name of this database will be needed when configuring one of the PHP files
+in the next steps.
+
+Copy the PHP files (located in the webroot directory) to your webserver.
+Remember to set the permissions on the file so the webserver may access the
+files. Use a text editor to open databaseinfo.php and enter the name or IP
+address of the database server, the name of the database, and the user name
+and password required to access the database.
+
+The next part of the installation process is to set up and configure your
+OpenSim instances.
+
+Copy the two OpenSimSearch.Modules files created during the compile steps
+(above) in to the bin directory of each of your OpenSim instances. The next
+part of the installation process requires some changes to the OpenSim.ini in
+all of your OpenSim instances.
+
+Add the following lines to all OpenSim.ini files:
+
+  [Search]
+  ;///////////////////////////////////////////////////////////////////////////
+  ;// The SearchURL is important. It must be correct or search won't work.  //
+  ;//                                                                       //
+  ;// Change the URL to point to the query.php file on your own HTTP server //
+  ;SearchURL = http://192.168.0.1/query.php
+  ;//                                                                       //
+  ;///////////////////////////////////////////////////////////////////////////
+
+NOTE: You do not need to indent the above lines when adding them to your own
+OpenSim.ini files. The lines were indented to offset them from the rest of the
+text in this file.
+
+Uncomment and edit the SearchURL line so it contains the correct address for
+the webserver and needed path to where the query.php file was installed on
+your webserver.
+
+To allow the Teleport and Show on Map buttons to work properly (for search
+results containing locations) the following two lines must be added to the
+[Modules] section of the OpenSim.ini file:
+
+    LandServices = "RemoteLandServicesConnector"
+    LandServiceInConnector = "true"
+
+The last changes to be made to the OpenSim.ini file are in the [DataSnapshot]
+section. Change index_sims to true. You can have data_exposure set to all but
+it is better to leave it on minimum so users can control what items can appear
+in search results by using the "Show In Search" checkboxes. You can leave
+default_snapshot_period commented out or you can uncomment it and set it to
+whatever value you wish. The section on optimizing the configuration will
+help you to decide an appropriate value for this setting.
+
+The last change for OpenSim.ini is the setting for data_services. This line
+must be uncommented and contain a valid URL for your webserver and the path
+to the register.php file which you installed earlier. If you do not enter a
+valid URL the OS instance will not get listed in the hostsregister table and
+search data for the regions hosted by the OS instance will not be recorded or
+updated. After entering the URL, exit and save the updated OpenSim.ini file.
+
+
+Updating an existing installation
+
+Updating an existing installation of ossearch is just a matter of copying a
+few files to the same places where you had previously installed the files.
+
+Copy all of the PHP files (located in the webroot directory) EXCEPT for
+databaseinfo.php to the directory on your webserver where you place the
+previous copies. If you also copy databaseinfo.php when copying the other
+PHP files you will have to edit databaseinfo.php and reset the information
+used to connect to the database.
+
+Copy the two OpenSimSearch.Modules files created during compilation to the
+bin directory of each of your OpenSim instances.
+
+Finally, execute all of the SQL files located in the webroot/sql directory
+where the filenames start with "update-". This step is very important to
+make certain your database tables are up-to-date.
+
+
+Configuration
+
+With everything in place, the next step is to set up a task on your webserver
+(or some other machine) which will invoke the URL to the parser.php file on
+your webserver on a regular basis to ensure the contents of the database are
+kept up-to-date. For a machine running Linux you can set up a cron job. For
+a machine running Windows you can use Windows Scheduler.
+
+See the section on optimizing the configuration to help you decide how often
+the parser.php should be run.
+
+
+Optimizing the configuration
+
+When you change what items are to be found in search by clicking the checkbox
+"Show In Search" or by removing an item that was set to show in search results
+there is a delay before the change is reflected in the database tables. There
+are four main factors that affect the length of this delay. They are: the
+number of OpenSim instances, the value of default_snapshot_period used by the
+DataSnapshot module, the time between calls to parser.php, and the number of
+OpenSim instances processed each time parser.php is run. You can't easily
+control the number of instances but you can control the other factors.
+
+To explain how the factors affect the delay take a grid with 100 regions. If
+you have one region per instance you have 100 instances. Using the default
+settings and calling parser once an hour it would take 100 hours, or more than
+four days, for changes to appear in search results. A more realistic setup is
+one where you have an average of 4 regions run by each OpenSim instance. This
+reduces the delay to 25 hours. This is a rather long delay for a relatively
+small number of regions and instances. The delay can be reduced substantially.
+
+A simple way to reduce the delay is to run parser.php more frequently. If
+parser.php is run once every 15 minutes instead of once an hour (60 minutes)
+the delay is reduced by a factor of 4 from 25 hours to 6.25 hours. Much better
+but still a long delay. The delay can be reduced even further but to do so
+requires a change to the parser.php file.
+
+Near the end of the parser.php file is a SQL query that ends with "LIMIT 0,1".
+It is the value "1" which is important. That value limits the number of OS
+instances that will be processed each time parser.php is run. If the value is
+changed from 1 to 3 then three OS instances will be processed on each run of
+the parser.php file. This reduces the delay by a factor of 3 to just over
+2 hours. This is a much better delay than the original value of 25 hours.
+
+For those of you who like math, the amount of delay can be expressed using
+the following simple formula:
+  delay = # instances * time between runs of parser / limit value in parser
+
+Three factors affecting the delay have been discussed but earlier it was
+stated that there are four factors. The fourth factor is the value of
+default_snapshot_period value located in the [DataSnapshot] section of the
+OpenSim.ini file. This setting is specified in seconds and controls how often
+the data used by parser.php will be updated. This setting doesn't have any
+direct impact on the delay between updates of the database but if it is set
+incorrecty it can efffectively increase the delay between database updates.
+
+The example grid setup discussed earlier was adjusted to reduce the update
+delay to just over 2 hours (2 hours and 5 minutes to be more precise). If the
+value of default_snapshot_period is set to a value slightly greater than the
+calculated delay it would be possible for parser.php to be called twice before
+the data it retrieves would have been updated. This would turn a delay of two
+hours into a delay of four hours. The proper setting is one that is *less*
+than the delay calculated from the other three factors discussed earlier.
+
+Given a delay of just over 2 hours, a good value for default_snapshot_period
+would be 7200 (2 hours expressed in seconds). By keeping the value of this
+setting close to, but less than, the delay between when parser.php is used
+to get the data for an instance will minimize any overhead imposed on the
+OpenSim instance when it creates the snapshot of searchable items while, at
+the same time, ensures the data will have been updated by the next time the
+parser.php is run to update the database.
+
+A final comment about the setting for the time between runs of parser.php and
+the value in the limit statement in that file. Avoid running parser.php too
+frequently or setting the value in the LIMIT statement too high. Doing either
+can cause unnecessary overhead or high loads on the webserver used to run
+parser.php, or on the database server while it updates all the tables with
+the latest information from the OpenSim instances.
+
+
+Additional Information
+
+A few words about event listings and the events database table.
+Support is included for events but the event listings need to be created
+using an external webpage. 
+ 
+The category for an event is stored as a number. The numbers for the
+categories are as follows:
+0 - Any  (NOTE: Event information will show "*Unspecified*")
+18- Discussion
+19- Sports
+20- Live Music
+22- Commercial
+23- Nightlife/Entertainment
+24- Games/Contests
+25- Pageants
+26- Education
+27- Arts and Culture
+28- Charity/Support Groups
+29- Miscellaneous
+
+The dateUTC field is a timestamp for the event in UTC time.
+
+The covercharge field is a boolean. Set it to 0 if there is no cover charge
+for the event. When covercharge is not 0, the amount is in the coveramount
+field. (It seems silly to require the boolean but this has been left in to
+avoid any compatability issues.)
+
+The globalPos field is the location of the event as a global grid coordinate.
+The format is "x/y/z". where x and y are the grid X and Y positions (times
+256) plus the x and y offset within the region named by the simname field.
+
+The eventflags field is 0 for a PG event, 1 for Mature, and 2 for Adult.
diff --git a/addon-modules/OpenSimSearch/prebuild.xml b/addon-modules/OpenSimSearch/prebuild.xml
new file mode 100644
index 0000000..961ebf9
--- /dev/null
+++ b/addon-modules/OpenSimSearch/prebuild.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" ?>
+<Project frameworkVersion="v3_5" name="OpenSimSearch.Modules" path="addon-modules/OpenSimSearch/Modules" type="Library">
+   <Configuration name="Debug">
+     <Options>
+       <OutputPath>../../../bin/</OutputPath>
+     </Options>
+   </Configuration>
+   <Configuration name="Release">
+     <Options>
+       <OutputPath>../../../bin/</OutputPath>
+     </Options>
+   </Configuration>
+
+   <ReferencePath>../../../bin/</ReferencePath>
+   <Reference localCopy="false" name="System"/>
+   <Reference name="System.Xml"/>
+   <Reference name="System.Drawing"/>
+   <Reference name="System.Runtime.Remoting"/>
+   <Reference name="OpenMetaverseTypes" path="../../../bin/"/>
+   <Reference name="OpenMetaverse" path="../../../bin/"/>
+   <Reference name="Axiom.MathLib" path="../../../bin/"/>
+   <Reference name="OpenSim.Framework"/>
+   <Reference name="OpenSim.Data"/>
+   <Reference name="OpenSim.Region.Framework"/>
+   <Reference name="OpenSim.Framework.Console"/>
+   <Reference name="OpenSim.Framework.Servers"/>
+   <Reference name="OpenSim.Framework.Statistics"/>
+   <Reference name="OpenSim.Framework.Communications"/>
+   <Reference name="OpenSim.Region.Physics.Manager"/>
+   <Reference name="OpenSim.Services.Interfaces"/>
+   <Reference name="Nini" path="../../../bin/"/>
+   <Reference name="log4net" path="../../../bin/"/>
+   <Reference name="XMLRPC" path="../../../bin/"/>
+   <Reference name="Mono.Addins" path="../../../bin/"/>
+
+   <Files>
+     <Match pattern="*.cs" recurse="true"/>
+   </Files>
+</Project>
-- 
cgit v1.1