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 m_Scenes = new List(); + 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(); + 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(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(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 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 classifieds = new Dictionary(); + + 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 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 picks = new Dictionary(); + + 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 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 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 userInfo; + + if (!foreign) + { + account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, avatarID); + } + else + { + userInfo = new Dictionary(); + } + + 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 userInfo) + { + IUserManagement uManage = UserManagementModule; + Dictionary info = new Dictionary(); + + + 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 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 @@ + + + + + ../../../bin/ + + + + + ../../../bin/ + + + + ../../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + 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 m_Scenes = new List(); + 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(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(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 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 mapitems = new List(); + 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 mapitems = new List(); + 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 @@ + + + + + ../../../bin/ + + + + + ../../../bin/ + + + + ../../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.1