From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- .../Server/IRCClientView.cs | 37 +- .../InternetRelayClientView/Server/IRCServer.cs | 2 +- .../Agent/UDP/Linden/LindenUDPInfoModule.cs | 219 ++-- .../Avatar/Appearance/AppearanceInfoModule.cs | 196 +++- .../Avatar/Attachments/AttachmentsCommandModule.cs | 17 +- .../Avatar/Attachments/TempAttachmentsModule.cs | 5 +- .../OptionalModules/Avatar/Chat/ChannelState.cs | 160 +-- .../OptionalModules/Avatar/Chat/IRCBridgeModule.cs | 41 +- .../OptionalModules/Avatar/Chat/IRCConnector.cs | 303 ++--- .../OptionalModules/Avatar/Chat/RegionState.cs | 98 +- .../Avatar/Concierge/ConciergeModule.cs | 6 +- .../Avatar/SitStand/SitStandCommandsModule.cs | 220 ++++ .../Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs | 41 +- .../Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs | 75 +- .../Avatar/XmlRpcGroups/GroupsMessagingModule.cs | 289 +++-- .../Avatar/XmlRpcGroups/GroupsModule.cs | 272 +++-- .../SimianGroupsServicesConnectorModule.cs | 6 +- .../Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs | 214 +++- .../XmlRpcGroupsServicesConnectorModule.cs | 71 +- .../DataSnapshot/DataRequestHandler.cs | 99 ++ .../DataSnapshot/DataSnapshotManager.cs | 487 ++++++++ .../OptionalModules/DataSnapshot/EstateSnapshot.cs | 149 +++ .../DataSnapshot/Interfaces/IDataSnapshot.cs | 36 + .../Interfaces/IDataSnapshotProvider.cs | 46 + .../OptionalModules/DataSnapshot/LLSDDiscovery.cs | 44 + .../OptionalModules/DataSnapshot/LandSnapshot.cs | 433 +++++++ .../OptionalModules/DataSnapshot/ObjectSnapshot.cs | 264 +++++ .../OptionalModules/DataSnapshot/SnapshotStore.cs | 337 ++++++ .../BareBonesNonShared/BareBonesNonSharedModule.cs | 6 + .../BareBonesShared/BareBonesSharedModule.cs | 6 + .../WebSocketEchoTest/WebSocketEchoModule.cs | 175 +++ .../OptionalModules/Materials/MaterialsModule.cs | 608 ++++++++++ .../PhysicsParameters/PhysicsParameters.cs | 21 +- .../PrimLimitsModule/PrimLimitsModule.cs | 99 +- .../OptionalModules/Properties/AssemblyInfo.cs | 8 +- .../RegionCombinerClientEventForwarder.cs | 94 ++ .../RegionCombinerIndividualEventForwarder.cs | 139 +++ .../RegionCombinerLargeLandChannel.cs | 201 ++++ .../RegionCombinerModule/RegionCombinerModule.cs | 880 ++++++++++++++ .../RegionCombinerPermissionModule.cs | 270 +++++ .../RegionCombinerModule/RegionConnections.cs | 94 ++ .../RegionCombinerModule/RegionCourseLocation.cs | 43 + .../RegionCombinerModule/RegionData.cs | 40 + .../Scripting/JsonStore/JsonStore.cs | 391 ++++++- .../Scripting/JsonStore/JsonStoreCommands.cs | 195 ++++ .../Scripting/JsonStore/JsonStoreModule.cs | 200 +++- .../Scripting/JsonStore/JsonStoreScriptModule.cs | 482 ++++++-- .../JsonStore/Tests/JsonStoreScriptModuleTests.cs | 900 +++++++++++++++ .../Scripting/Minimodule/MRMModule.cs | 2 + .../Scripting/Minimodule/SOPObject.cs | 2 +- .../RegionReadyModule/RegionReadyModule.cs | 19 +- .../XmlRpcRouterModule/XmlRpcGridRouterModule.cs | 1 - .../UserStatistics/ActiveConnectionsAJAX.cs | 308 +++++ .../UserStatistics/Clients_report.cs | 329 ++++++ .../UserStatistics/Default_Report.cs | 277 +++++ .../OptionalModules/UserStatistics/HTMLUtil.cs | 263 +++++ .../OptionalModules/UserStatistics/IStatsReport.cs | 39 + .../OptionalModules/UserStatistics/LogLinesAJAX.cs | 159 +++ .../UserStatistics/Prototype_distributor.cs | 80 ++ .../UserStatistics/Sessions_Report.cs | 288 +++++ .../OptionalModules/UserStatistics/SimStatsAJAX.cs | 276 +++++ .../UserStatistics/Updater_distributor.cs | 70 ++ .../UserStatistics/WebStatsModule.cs | 1203 ++++++++++++++++++++ .../ViewerSupport/CameraOnlyModeModule.cs | 176 +++ .../ViewerSupport/DynamicFloaterModule.cs | 238 ++++ .../ViewerSupport/DynamicMenuModule.cs | 304 +++++ .../ViewerSupport/GodNamesModule.cs | 144 +++ .../ViewerSupport/SimulatorFeaturesHelper.cs | 171 +++ .../ViewerSupport/SpecialUIModule.cs | 165 +++ .../World/AutoBackup/AutoBackupModule.cs | 123 +- .../World/AutoBackup/AutoBackupModuleState.cs | 14 + .../World/MoneyModule/SampleMoneyModule.cs | 34 +- .../Region/OptionalModules/World/NPC/NPCAvatar.cs | 69 +- .../Region/OptionalModules/World/NPC/NPCModule.cs | 98 +- .../World/NPC/Tests/NPCModuleTests.cs | 154 ++- .../World/SceneCommands/SceneCommandsModule.cs | 142 ++- .../World/TreePopulator/TreePopulatorModule.cs | 4 +- .../World/WorldView/WorldViewRequestHandler.cs | 2 +- 78 files changed, 13158 insertions(+), 1015 deletions(-) create mode 100644 OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/DataRequestHandler.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/DataSnapshotManager.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/EstateSnapshot.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshot.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshotProvider.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/LLSDDiscovery.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/LandSnapshot.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/ObjectSnapshot.cs create mode 100644 OpenSim/Region/OptionalModules/DataSnapshot/SnapshotStore.cs create mode 100644 OpenSim/Region/OptionalModules/Example/WebSocketEchoTest/WebSocketEchoModule.cs create mode 100644 OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerClientEventForwarder.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerIndividualEventForwarder.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerLargeLandChannel.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerModule.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerPermissionModule.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionConnections.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCourseLocation.cs create mode 100644 OpenSim/Region/OptionalModules/RegionCombinerModule/RegionData.cs create mode 100644 OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs create mode 100644 OpenSim/Region/OptionalModules/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/ActiveConnectionsAJAX.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/Clients_report.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/Default_Report.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/HTMLUtil.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/IStatsReport.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/LogLinesAJAX.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/Prototype_distributor.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/Sessions_Report.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/SimStatsAJAX.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/Updater_distributor.cs create mode 100644 OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/CameraOnlyModeModule.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/DynamicFloaterModule.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/DynamicMenuModule.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/SimulatorFeaturesHelper.cs create mode 100644 OpenSim/Region/OptionalModules/ViewerSupport/SpecialUIModule.cs (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs index 781539a..6fe86b2 100644 --- a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs +++ b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs @@ -71,7 +71,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server m_client = client; m_scene = scene; - Watchdog.StartThread(InternalLoop, "IRCClientView", ThreadPriority.Normal, false, true); + WorkManager.StartThread(InternalLoop, "IRCClientView", ThreadPriority.Normal, false, true); } private void SendServerCommand(string command) @@ -516,7 +516,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server public Vector3 StartPos { - get { return new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 50); } + get { return new Vector3(m_scene.RegionInfo.RegionSizeX * 0.5f, m_scene.RegionInfo.RegionSizeY * 0.5f, 50f); } set { } } @@ -660,6 +660,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server public event BakeTerrain OnBakeTerrain; public event EstateChangeInfo OnEstateChangeInfo; public event EstateManageTelehub OnEstateManageTelehub; + public event CachedTextureRequest OnCachedTextureRequest; public event SetAppearance OnSetAppearance; public event AvatarNowWearing OnAvatarNowWearing; public event RezSingleAttachmentFromInv OnRezSingleAttachmentFromInv; @@ -686,6 +687,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server public event Action OnCompleteMovementToRegion; public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; + public event UpdateAgent OnAgentCameraUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; public event AvatarPickerRequest OnAvatarPickerRequest; @@ -901,12 +903,12 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server public void Start() { - m_scene.AddNewClient(this, PresenceType.User); + m_scene.AddNewAgent(this, PresenceType.User); // Mimicking LLClientView which gets always set appearance from client. AvatarAppearance appearance; m_scene.GetAvatarAppearance(this, out appearance); - OnSetAppearance(this, appearance.Texture, (byte[])appearance.VisualParams.Clone()); + OnSetAppearance(this, appearance.Texture, (byte[])appearance.VisualParams.Clone(),appearance.AvatarSize, new WearableCacheItem[0]); } public void SendRegionHandshake(RegionInfo regionInfo, RegionHandshakeArgs args) @@ -938,13 +940,18 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server { } + + public void SendCachedTextureResponse(ISceneEntity avatar, int serial, List cachedTextures) + { + } + public void SendStartPingCheck(byte seq) { } - public void SendKillObject(ulong regionHandle, List localID) + public void SendKillObject(List localID) { } @@ -971,12 +978,12 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server // TODO } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { } @@ -1050,7 +1057,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server { } - public void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance) + public void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance, int transactionType, UUID sourceID, bool sourceIsGroup, UUID destID, bool destIsGroup, int amount, string item) { } @@ -1190,11 +1197,6 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server } - public bool AddMoney(int debit) - { - return true; - } - public void SendSunPos(Vector3 sunPos, Vector3 sunVel, ulong CurrentTime, uint SecondsPerSunCycle, uint SecondsPerYear, float OrbitalPosition) { @@ -1424,9 +1426,11 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server return new byte[0]; } +#pragma warning disable 0067 public event ViewerEffectEventHandler OnViewerEffect; public event Action OnLogout; public event Action OnConnectionClosed; +#pragma warning restore 0067 public void SendBlueBoxMessage(UUID FromAvatarID, string FromAvatarName, string Message) { @@ -1671,12 +1675,17 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server { } - public void StopFlying(ISceneEntity presence) + public void SendAgentTerseUpdate(ISceneEntity presence) { } public void SendPlacesReply(UUID queryID, UUID transactionID, PlacesReplyData[] data) { } + + public void SendPartPhysicsProprieties(ISceneEntity entity) + { + } + } } diff --git a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCServer.cs b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCServer.cs index 9d27386..a1682d2 100644 --- a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCServer.cs +++ b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCServer.cs @@ -58,7 +58,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server m_listener.Start(50); - Watchdog.StartThread(ListenLoop, "IRCServer", ThreadPriority.Normal, false, true); + WorkManager.StartThread(ListenLoop, "IRCServer", ThreadPriority.Normal, false, true); m_baseScene = baseScene; } diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 992f38e..08d0fbf 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text; using log4net; @@ -51,7 +52,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "LindenUDPInfoModule")] public class LindenUDPInfoModule : ISharedRegionModule { -// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected Dictionary m_scenes = new Dictionary(); @@ -130,6 +131,15 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden "Go on/off emergency monitoring mode", "Go on/off emergency monitoring mode", HandleEmergencyMonitoring); + + scene.AddCommand( + "Comms", this, "show client stats", + "show client stats [first_name last_name]", + "Show client request stats", + "Without the 'first_name last_name' option, all clients are shown." + + " With the 'first_name last_name' option only a specific client is shown.", + (mod, cmd) => MainConsole.Instance.Output(HandleClientStatsReport(cmd))); + } public void RemoveRegion(Scene scene) @@ -294,7 +304,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden private string GetImageQueuesReport(string[] showParams) { if (showParams.Length < 5 || showParams.Length > 6) - return "Usage: image queues show [full]"; + return "Usage: show image queues [full]"; string firstName = showParams[3]; string lastName = showParams[4]; @@ -385,7 +395,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.Append(GetColumnEntry("Type", maxTypeLength, columnPadding)); report.AppendFormat( - "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7} {12,7}\n", + "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7}\n", "Since", "Pkts", "Pkts", @@ -397,12 +407,11 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden "Q Pkts", "Q Pkts", "Q Pkts", - "Q Pkts", "Q Pkts"); report.AppendFormat("{0,-" + totalInfoFieldsLength + "}", ""); report.AppendFormat( - "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7} {12,7}\n", + "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7}\n", "Last In", "In", "Out", @@ -414,8 +423,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden "Cloud", "Task", "Texture", - "Asset", - "State"); + "Asset"); lock (m_scenes) { @@ -424,24 +432,24 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden scene.ForEachClient( delegate(IClientAPI client) { - bool isChild = client.SceneAgent.IsChildAgent; - if (isChild && !showChildren) - return; - - string name = client.Name; - if (pname != "" && name != pname) - return; - - string regionName = scene.RegionInfo.RegionName; - - report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); - report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding)); - report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); - if (client is IStatsCollector) { - IStatsCollector stats = (IStatsCollector)client; + + bool isChild = client.SceneAgent.IsChildAgent; + if (isChild && !showChildren) + return; + string name = client.Name; + if (pname != "" && name != pname) + return; + + string regionName = scene.RegionInfo.RegionName; + + report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); + report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding)); + report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); + + IStatsCollector stats = (IStatsCollector)client; report.AppendLine(stats.Report()); } }); @@ -479,8 +487,10 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.Append(GetColumnEntry("Type", maxTypeLength, columnPadding)); report.AppendFormat( - "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}\n", - "Total", + "{0,8} {1,8} {2,7} {3,8} {4,7} {5,7} {6,7} {7,7} {8,9} {9,7}\n", + "Max", + "Target", + "Actual", "Resend", "Land", "Wind", @@ -491,7 +501,9 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.AppendFormat("{0,-" + totalInfoFieldsLength + "}", ""); report.AppendFormat( - "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}", + "{0,8} {1,8} {2,7} {3,8} {4,7} {5,7} {6,7} {7,7} {8,9} {9,7}\n", + "kb/s", + "kb/s", "kb/s", "kb/s", "kb/s", @@ -503,8 +515,6 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.AppendLine(); - bool firstClient = true; - lock (m_scenes) { foreach (Scene scene in m_scenes.Values) @@ -516,12 +526,6 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden { LLClientView llClient = client as LLClientView; - if (firstClient) - { - report.AppendLine(GetServerThrottlesReport(llClient.UDPServer)); - firstClient = false; - } - bool isChild = client.SceneAgent.IsChildAgent; if (isChild && !showChildren) return; @@ -540,7 +544,11 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); report.AppendFormat( - "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}", + "{0,8} {1,8} {2,7} {3,8} {4,7} {5,7} {6,7} {7,7} {8,9} {9,7}\n", + ci.maxThrottle > 0 ? ((ci.maxThrottle * 8) / 1000).ToString() : "-", + llUdpClient.FlowThrottle.AdaptiveEnabled + ? ((ci.targetThrottle * 8) / 1000).ToString() + : (llUdpClient.FlowThrottle.TotalDripRequest * 8 / 1000).ToString(), (ci.totalThrottle * 8) / 1000, (ci.resendThrottle * 8) / 1000, (ci.landThrottle * 8) / 1000, @@ -548,9 +556,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden (ci.cloudThrottle * 8) / 1000, (ci.taskThrottle * 8) / 1000, (ci.textureThrottle * 8) / 1000, - (ci.assetThrottle * 8) / 1000); - - report.AppendLine(); + (ci.assetThrottle * 8) / 1000); } }); } @@ -558,35 +564,116 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden return report.ToString(); } - - protected string GetServerThrottlesReport(LLUDPServer udpServer) + + /// + /// Show client stats data + /// + /// + /// + protected string HandleClientStatsReport(string[] showParams) { - StringBuilder report = new StringBuilder(); - - int columnPadding = 2; - int maxNameLength = 18; - int maxRegionNameLength = 14; - int maxTypeLength = 4; - - string name = "SERVER AGENT RATES"; - - report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); - report.Append(GetColumnEntry("-", maxRegionNameLength, columnPadding)); - report.Append(GetColumnEntry("-", maxTypeLength, columnPadding)); - - ThrottleRates throttleRates = udpServer.ThrottleRates; - report.AppendFormat( - "{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}", - (throttleRates.Total * 8) / 1000, - (throttleRates.Resend * 8) / 1000, - (throttleRates.Land * 8) / 1000, - (throttleRates.Wind * 8) / 1000, - (throttleRates.Cloud * 8) / 1000, - (throttleRates.Task * 8) / 1000, - (throttleRates.Texture * 8) / 1000, - (throttleRates.Asset * 8) / 1000); + // NOTE: This writes to m_log on purpose. We want to store this information + // in case we need to analyze it later. + // + if (showParams.Length <= 4) + { + m_log.InfoFormat("[INFO]: {0,-12} {1,-20} {2,-6} {3,-11} {4,-11} {5,-16}", "Region", "Name", "Root", "Time", "Reqs/min", "AgentUpdates"); + foreach (Scene scene in m_scenes.Values) + { + scene.ForEachClient( + delegate(IClientAPI client) + { + if (client is LLClientView) + { + LLClientView llClient = client as LLClientView; + ClientInfo cinfo = llClient.UDPClient.GetClientInfo(); + int avg_reqs = cinfo.AsyncRequests.Values.Sum() + cinfo.GenericRequests.Values.Sum() + cinfo.SyncRequests.Values.Sum(); + avg_reqs = avg_reqs / ((DateTime.Now - cinfo.StartedTime).Minutes + 1); + + string childAgentStatus; + + if (llClient.SceneAgent != null) + childAgentStatus = llClient.SceneAgent.IsChildAgent ? "N" : "Y"; + else + childAgentStatus = "Off!"; + + m_log.InfoFormat("[INFO]: {0,-12} {1,-20} {2,-6} {3,-11} {4,-11} {5,-16}", + scene.RegionInfo.RegionName, llClient.Name, + childAgentStatus, + (DateTime.Now - cinfo.StartedTime).Minutes, + avg_reqs, + string.Format( + "{0} ({1:0.00}%)", + llClient.TotalAgentUpdates, + cinfo.SyncRequests.ContainsKey("AgentUpdate") + ? (float)cinfo.SyncRequests["AgentUpdate"] / llClient.TotalAgentUpdates * 100 + : 0)); + } + }); + } + return string.Empty; + } - return report.ToString(); - } + string fname = "", lname = ""; + + if (showParams.Length > 3) + fname = showParams[3]; + if (showParams.Length > 4) + lname = showParams[4]; + + foreach (Scene scene in m_scenes.Values) + { + scene.ForEachClient( + delegate(IClientAPI client) + { + if (client is LLClientView) + { + LLClientView llClient = client as LLClientView; + + if (llClient.Name == fname + " " + lname) + { + + ClientInfo cinfo = llClient.GetClientInfo(); + AgentCircuitData aCircuit = scene.AuthenticateHandler.GetAgentCircuitData(llClient.CircuitCode); + if (aCircuit == null) // create a dummy one + aCircuit = new AgentCircuitData(); + + if (!llClient.SceneAgent.IsChildAgent) + m_log.InfoFormat("[INFO]: {0} # {1} # {2}", llClient.Name, Util.GetViewerName(aCircuit), aCircuit.Id0); + + int avg_reqs = cinfo.AsyncRequests.Values.Sum() + cinfo.GenericRequests.Values.Sum() + cinfo.SyncRequests.Values.Sum(); + avg_reqs = avg_reqs / ((DateTime.Now - cinfo.StartedTime).Minutes + 1); + + m_log.InfoFormat("[INFO]:"); + m_log.InfoFormat("[INFO]: {0} # {1} # Time: {2}min # Avg Reqs/min: {3}", scene.RegionInfo.RegionName, + (llClient.SceneAgent.IsChildAgent ? "Child" : "Root"), (DateTime.Now - cinfo.StartedTime).Minutes, avg_reqs); + + Dictionary sortedDict = (from entry in cinfo.AsyncRequests orderby entry.Value descending select entry) + .ToDictionary(pair => pair.Key, pair => pair.Value); + PrintRequests("TOP ASYNC", sortedDict, cinfo.AsyncRequests.Values.Sum()); + + sortedDict = (from entry in cinfo.SyncRequests orderby entry.Value descending select entry) + .ToDictionary(pair => pair.Key, pair => pair.Value); + PrintRequests("TOP SYNC", sortedDict, cinfo.SyncRequests.Values.Sum()); + + sortedDict = (from entry in cinfo.GenericRequests orderby entry.Value descending select entry) + .ToDictionary(pair => pair.Key, pair => pair.Value); + PrintRequests("TOP GENERIC", sortedDict, cinfo.GenericRequests.Values.Sum()); + } + } + }); + } + return string.Empty; + } + + private void PrintRequests(string type, Dictionary sortedDict, int sum) + { + m_log.InfoFormat("[INFO]:"); + m_log.InfoFormat("[INFO]: {0,25}", type); + foreach (KeyValuePair kvp in sortedDict.Take(12)) + m_log.InfoFormat("[INFO]: {0,25} {1,-6}", kvp.Key, kvp.Value); + m_log.InfoFormat("[INFO]: {0,25}", "..."); + m_log.InfoFormat("[INFO]: {0,25} {1,-6}", "Total", sum); + } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs index d718a2f..2f9bb1e 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs @@ -51,7 +51,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance { // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private Dictionary m_scenes = new Dictionary(); + private List m_scenes = new List(); + // private IAvatarFactoryModule m_avatarFactory; public string Name { get { return "Appearance Information Module"; } } @@ -83,7 +84,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance // m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName); lock (m_scenes) - m_scenes.Remove(scene.RegionInfo.RegionID); + m_scenes.Remove(scene); } public void RegionLoaded(Scene scene) @@ -91,7 +92,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance // m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); lock (m_scenes) - m_scenes[scene.RegionInfo.RegionID] = scene; + m_scenes.Add(scene); scene.AddCommand( "Users", this, "show appearance", @@ -102,7 +103,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance scene.AddCommand( "Users", this, "appearance show", "appearance show [ ]", - "Show appearance information for each avatar in the simulator.", + "Show appearance information for avatars.", "This command checks whether the simulator has all the baked textures required to display an avatar to other viewers. " + "\nIf not, then appearance is 'corrupt' and other avatars will continue to see it as a cloud." + "\nOptionally, you can view just a particular avatar's appearance information." @@ -132,6 +133,21 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance "Find out which avatar uses the given asset as a baked texture, if any.", "You can specify just the beginning of the uuid, e.g. 2008a8d. A longer UUID must be in dashed format.", HandleFindAppearanceCommand); + + scene.AddCommand( + "Users", this, "wearables show", + "wearables show [ ]", + "Show information about wearables for avatars.", + "If no avatar name is given then a general summary for all avatars in the scene is shown.\n" + + "If an avatar name is given then specific information about current wearables is shown.", + HandleShowWearablesCommand); + + scene.AddCommand( + "Users", this, "wearables check", + "wearables check ", + "Check that the wearables of a given avatar in the scene are valid.", + "This currently checks that the wearable assets themselves and any assets referenced by them exist.", + HandleCheckWearablesCommand); } private void HandleSendAppearanceCommand(string module, string[] cmd) @@ -155,7 +171,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance lock (m_scenes) { - foreach (Scene scene in m_scenes.Values) + foreach (Scene scene in m_scenes) { if (targetNameSupplied) { @@ -186,7 +202,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance } } - protected void HandleShowAppearanceCommand(string module, string[] cmd) + private void HandleShowAppearanceCommand(string module, string[] cmd) { if (cmd.Length != 2 && cmd.Length < 4) { @@ -207,7 +223,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance lock (m_scenes) { - foreach (Scene scene in m_scenes.Values) + foreach (Scene scene in m_scenes) { if (targetNameSupplied) { @@ -222,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance { bool bakedTextureValid = scene.AvatarFactory.ValidateBakedTextureCache(sp); MainConsole.Instance.OutputFormat( - "{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "corrupt"); + "{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "incomplete"); } ); } @@ -243,7 +259,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance lock (m_scenes) { - foreach (Scene scene in m_scenes.Values) + foreach (Scene scene in m_scenes) { ScenePresence sp = scene.GetScenePresence(firstname, lastname); if (sp != null && !sp.IsChildAgent) @@ -263,7 +279,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance } } - protected void HandleFindAppearanceCommand(string module, string[] cmd) + private void HandleFindAppearanceCommand(string module, string[] cmd) { if (cmd.Length != 3) { @@ -277,7 +293,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance lock (m_scenes) { - foreach (Scene scene in m_scenes.Values) + foreach (Scene scene in m_scenes) { scene.ForEachRootScenePresence( sp => @@ -304,5 +320,163 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance string.Join(", ", matchedAvatars.ToList().ConvertAll(sp => sp.Name).ToArray())); } } + + protected void HandleShowWearablesCommand(string module, string[] cmd) + { + if (cmd.Length != 2 && cmd.Length < 4) + { + MainConsole.Instance.OutputFormat("Usage: wearables show [ ]"); + return; + } + + bool targetNameSupplied = false; + string optionalTargetFirstName = null; + string optionalTargetLastName = null; + + if (cmd.Length >= 4) + { + targetNameSupplied = true; + optionalTargetFirstName = cmd[2]; + optionalTargetLastName = cmd[3]; + } + + StringBuilder sb = new StringBuilder(); + + if (targetNameSupplied) + { + lock (m_scenes) + { + foreach (Scene scene in m_scenes) + { + ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName); + if (sp != null && !sp.IsChildAgent) + AppendWearablesDetailReport(sp, sb); + } + } + } + else + { + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Name", ConsoleDisplayUtil.UserNameSize); + cdt.AddColumn("Wearables", 2); + + lock (m_scenes) + { + foreach (Scene scene in m_scenes) + { + scene.ForEachRootScenePresence( + sp => + { + int count = 0; + + for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++) + count += sp.Appearance.Wearables[i].Count; + + cdt.AddRow(sp.Name, count); + } + ); + } + } + + sb.Append(cdt.ToString()); + } + + MainConsole.Instance.Output(sb.ToString()); + } + + private void HandleCheckWearablesCommand(string module, string[] cmd) + { + if (cmd.Length != 4) + { + MainConsole.Instance.OutputFormat("Usage: wearables check "); + return; + } + + string firstname = cmd[2]; + string lastname = cmd[3]; + + StringBuilder sb = new StringBuilder(); + UuidGatherer uuidGatherer = new UuidGatherer(m_scenes[0].AssetService); + + lock (m_scenes) + { + foreach (Scene scene in m_scenes) + { + ScenePresence sp = scene.GetScenePresence(firstname, lastname); + if (sp != null && !sp.IsChildAgent) + { + sb.AppendFormat("Wearables checks for {0}\n\n", sp.Name); + + for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++) + { + AvatarWearable aw = sp.Appearance.Wearables[i]; + + if (aw.Count > 0) + { + sb.Append(Enum.GetName(typeof(WearableType), i)); + sb.Append("\n"); + + for (int j = 0; j < aw.Count; j++) + { + WearableItem wi = aw[j]; + + ConsoleDisplayList cdl = new ConsoleDisplayList(); + cdl.Indent = 2; + cdl.AddRow("Item UUID", wi.ItemID); + cdl.AddRow("Assets", ""); + sb.Append(cdl.ToString()); + + uuidGatherer.AddForInspection(wi.AssetID); + uuidGatherer.GatherAll(); + string[] assetStrings + = Array.ConvertAll(uuidGatherer.GatheredUuids.Keys.ToArray(), u => u.ToString()); + + bool[] existChecks = scene.AssetService.AssetsExist(assetStrings); + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.Indent = 4; + cdt.AddColumn("Type", 10); + cdt.AddColumn("UUID", ConsoleDisplayUtil.UuidSize); + cdt.AddColumn("Found", 5); + + for (int k = 0; k < existChecks.Length; k++) + cdt.AddRow( + (AssetType)uuidGatherer.GatheredUuids[new UUID(assetStrings[k])], + assetStrings[k], existChecks[k] ? "yes" : "no"); + + sb.Append(cdt.ToString()); + sb.Append("\n"); + } + } + } + } + } + } + + MainConsole.Instance.Output(sb.ToString()); + } + + private void AppendWearablesDetailReport(ScenePresence sp, StringBuilder sb) + { + sb.AppendFormat("\nWearables for {0}\n", sp.Name); + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Type", 10); + cdt.AddColumn("Item UUID", ConsoleDisplayUtil.UuidSize); + cdt.AddColumn("Asset UUID", ConsoleDisplayUtil.UuidSize); + + for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++) + { + AvatarWearable aw = sp.Appearance.Wearables[i]; + + for (int j = 0; j < aw.Count; j++) + { + WearableItem wi = aw[j]; + cdt.AddRow(Enum.GetName(typeof(WearableType), i), wi.ItemID, wi.AssetID); + } + } + + sb.Append(cdt.ToString()); + } } } \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs index d97e3b3..0333747 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs @@ -176,16 +176,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments // " {0,-36} {1,-10} {2,-36} {3,-14} {4,-15}\n", // attachmentObject.Name, attachmentObject.LocalId, attachmentObject.FromItemID, // (AttachmentPoint)attachmentObject.AttachmentPoint, attachmentObject.RootPart.AttachedPos); - ct.Rows.Add( - new ConsoleDisplayTableRow( - new List() - { - attachmentObject.Name, - attachmentObject.LocalId.ToString(), - attachmentObject.FromItemID.ToString(), - ((AttachmentPoint)attachmentObject.AttachmentPoint).ToString(), - attachmentObject.RootPart.AttachedPos.ToString() - })); + + ct.AddRow( + attachmentObject.Name, + attachmentObject.LocalId, + attachmentObject.FromItemID, + ((AttachmentPoint)attachmentObject.AttachmentPoint), + attachmentObject.RootPart.AttachedPos); // } } diff --git a/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs b/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs index d7fb272..535bf67 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs @@ -40,6 +40,7 @@ using OpenSim.Framework.Monitoring; using OpenSim.Region.ClientStack.LindenUDP; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.OptionalModules.Avatar.Attachments { @@ -76,7 +77,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments if (m_console != null) { - m_console.AddCommand("TempATtachModule", false, "set auto_grant_attach_perms", "set auto_grant_attach_perms true|false", "Allow objects owned by the region owner os estate managers to obtain attach permissions without asking the user", SetAutoGrantAttachPerms); + m_console.AddCommand("TempAttachModule", false, "set auto_grant_attach_perms", "set auto_grant_attach_perms true|false", "Allow objects owned by the region owner or estate managers to obtain attach permissions without asking the user", SetAutoGrantAttachPerms); } } else @@ -183,7 +184,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments hostPart.ParentGroup.RootPart.ScheduleFullUpdate(); } - return attachmentsModule.AttachObject(target, hostPart.ParentGroup, (uint)attachmentPoint, false, true) ? 1 : 0; + return attachmentsModule.AttachObject(target, hostPart.ParentGroup, (uint)attachmentPoint, false, false, true) ? 1 : 0; } } } diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs index 66265d8..b5d9fda 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs @@ -55,42 +55,42 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // These are the IRC Connector configurable parameters with hard-wired // default values (retained for compatability). - internal string Server = null; - internal string Password = null; - internal string IrcChannel = null; - internal string BaseNickname = "OSimBot"; - internal uint Port = 6667; - internal string User = null; - - internal bool ClientReporting = true; - internal bool RelayChat = true; - internal bool RelayPrivateChannels = false; - internal int RelayChannel = 1; + internal string Server = null; + internal string Password = null; + internal string IrcChannel = null; + internal string BaseNickname = "OSimBot"; + internal uint Port = 6667; + internal string User = null; + + internal bool ClientReporting = true; + internal bool RelayChat = true; + internal bool RelayPrivateChannels = false; + internal int RelayChannel = 1; internal List ValidInWorldChannels = new List(); // Connector agnostic parameters. These values are NOT shared with the // connector and do not differentiate at an IRC level internal string PrivateMessageFormat = "PRIVMSG {0} :<{2}> {1} {3}"; - internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}"; - internal int RelayChannelOut = -1; - internal bool RandomizeNickname = true; - internal bool CommandsEnabled = false; - internal int CommandChannel = -1; - internal int ConnectDelay = 10; - internal int PingDelay = 15; - internal string DefaultZone = "Sim"; - - internal string _accessPassword = String.Empty; - internal Regex AccessPasswordRegex = null; - internal List ExcludeList = new List(); + internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}"; + internal int RelayChannelOut = -1; + internal bool RandomizeNickname = true; + internal bool CommandsEnabled = false; + internal int CommandChannel = -1; + internal int ConnectDelay = 10; + internal int PingDelay = 15; + internal string DefaultZone = "Sim"; + + internal string _accessPassword = String.Empty; + internal Regex AccessPasswordRegex = null; + internal List ExcludeList = new List(); internal string AccessPassword { get { return _accessPassword; } - set + set { _accessPassword = value; - AccessPasswordRegex = new Regex(String.Format(@"^{0},\s*(?[^,]+),\s*(?.+)$", _accessPassword), + AccessPasswordRegex = new Regex(String.Format(@"^{0},\s*(?[^,]+),\s*(?.+)$", _accessPassword), RegexOptions.Compiled); } } @@ -99,9 +99,9 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // IRC connector reference - internal IRCConnector irc = null; + internal IRCConnector irc = null; - internal int idn = _idk_++; + internal int idn = _idk_++; // List of regions dependent upon this connection @@ -119,29 +119,29 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat internal ChannelState(ChannelState model) { - Server = model.Server; - Password = model.Password; - IrcChannel = model.IrcChannel; - Port = model.Port; - BaseNickname = model.BaseNickname; - RandomizeNickname = model.RandomizeNickname; - User = model.User; - CommandsEnabled = model.CommandsEnabled; - CommandChannel = model.CommandChannel; - RelayChat = model.RelayChat; + Server = model.Server; + Password = model.Password; + IrcChannel = model.IrcChannel; + Port = model.Port; + BaseNickname = model.BaseNickname; + RandomizeNickname = model.RandomizeNickname; + User = model.User; + CommandsEnabled = model.CommandsEnabled; + CommandChannel = model.CommandChannel; + RelayChat = model.RelayChat; RelayPrivateChannels = model.RelayPrivateChannels; - RelayChannelOut = model.RelayChannelOut; - RelayChannel = model.RelayChannel; + RelayChannelOut = model.RelayChannelOut; + RelayChannel = model.RelayChannel; ValidInWorldChannels = model.ValidInWorldChannels; PrivateMessageFormat = model.PrivateMessageFormat; - NoticeMessageFormat = model.NoticeMessageFormat; - ClientReporting = model.ClientReporting; - AccessPassword = model.AccessPassword; - DefaultZone = model.DefaultZone; - ConnectDelay = model.ConnectDelay; - PingDelay = model.PingDelay; + NoticeMessageFormat = model.NoticeMessageFormat; + ClientReporting = model.ClientReporting; + AccessPassword = model.AccessPassword; + DefaultZone = model.DefaultZone; + ConnectDelay = model.ConnectDelay; + PingDelay = model.PingDelay; } - + // Read the configuration file, performing variable substitution and any // necessary aliasing. See accompanying documentation for how this works. // If you don't need variables, then this works exactly as before. @@ -160,54 +160,54 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_log.DebugFormat("[IRC-Channel-{0}] Initial request by Region {1} to connect to IRC", cs.idn, rs.Region); - cs.Server = Substitute(rs, config.GetString("server", null)); + cs.Server = Substitute(rs, config.GetString("server", null)); m_log.DebugFormat("[IRC-Channel-{0}] Server : <{1}>", cs.idn, cs.Server); - cs.Password = Substitute(rs, config.GetString("password", null)); + cs.Password = Substitute(rs, config.GetString("password", null)); // probably not a good idea to put a password in the log file - cs.User = Substitute(rs, config.GetString("user", null)); - cs.IrcChannel = Substitute(rs, config.GetString("channel", null)); + cs.User = Substitute(rs, config.GetString("user", null)); + cs.IrcChannel = Substitute(rs, config.GetString("channel", null)); m_log.DebugFormat("[IRC-Channel-{0}] IrcChannel : <{1}>", cs.idn, cs.IrcChannel); - cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port)))); + cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port)))); m_log.DebugFormat("[IRC-Channel-{0}] Port : <{1}>", cs.idn, cs.Port); - cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname)); + cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname)); m_log.DebugFormat("[IRC-Channel-{0}] BaseNickname : <{1}>", cs.idn, cs.BaseNickname); - cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname)))); + cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname)))); m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname); - cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname)))); + cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname)))); m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname); - cs.User = Substitute(rs, config.GetString("username", cs.User)); + cs.User = Substitute(rs, config.GetString("username", cs.User)); m_log.DebugFormat("[IRC-Channel-{0}] User : <{1}>", cs.idn, cs.User); - cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled)))); + cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled)))); m_log.DebugFormat("[IRC-Channel-{0}] CommandsEnabled : <{1}>", cs.idn, cs.CommandsEnabled); - cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel)))); + cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel)))); m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel); - cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel)))); + cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel)))); m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel); - cs.RelayChat = Convert.ToBoolean(Substitute(rs, config.GetString("relay_chat", Convert.ToString(cs.RelayChat)))); + cs.RelayChat = Convert.ToBoolean(Substitute(rs, config.GetString("relay_chat", Convert.ToString(cs.RelayChat)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayChat : <{1}>", cs.idn, cs.RelayChat); cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("relay_private_channels", Convert.ToString(cs.RelayPrivateChannels)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels); cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("useworldcomm", Convert.ToString(cs.RelayPrivateChannels)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels); - cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut)))); + cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayChannelOut : <{1}>", cs.idn, cs.RelayChannelOut); - cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel)))); + cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel); - cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel)))); + cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel)))); m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel); cs.PrivateMessageFormat = Substitute(rs, config.GetString("msgformat", cs.PrivateMessageFormat)); m_log.DebugFormat("[IRC-Channel-{0}] PrivateMessageFormat : <{1}>", cs.idn, cs.PrivateMessageFormat); cs.NoticeMessageFormat = Substitute(rs, config.GetString("noticeformat", cs.NoticeMessageFormat)); m_log.DebugFormat("[IRC-Channel-{0}] NoticeMessageFormat : <{1}>", cs.idn, cs.NoticeMessageFormat); - cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting?"1":"0"))) > 0; + cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting ? "1" : "0"))) > 0; m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting); - cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting)))); + cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting)))); m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting); - cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone)); + cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone)); m_log.DebugFormat("[IRC-Channel-{0}] DefaultZone : <{1}>", cs.idn, cs.DefaultZone); - cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay)))); + cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay)))); m_log.DebugFormat("[IRC-Channel-{0}] ConnectDelay : <{1}>", cs.idn, cs.ConnectDelay); - cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay)))); + cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay)))); m_log.DebugFormat("[IRC-Channel-{0}] PingDelay : <{1}>", cs.idn, cs.PingDelay); cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword)); m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword); @@ -217,7 +217,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { cs.ExcludeList.Add(name.Trim().ToLower()); } - + // Fail if fundamental information is still missing if (cs.Server == null) @@ -306,8 +306,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat IRCBridgeModule.m_channels.Add(cs); - m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}", - cs.idn, rs.Region, cs.DefaultZone, + m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}", + cs.idn, rs.Region, cs.DefaultZone, cs.CommandsEnabled ? "enabled" : "not enabled", cs.RelayPrivateChannels ? "relayed" : "not relayed"); } @@ -417,7 +417,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat private bool IsAConnectionMatchFor(ChannelState cs) { return ( - Server == cs.Server && + Server == cs.Server && IrcChannel == cs.IrcChannel && Port == cs.Port && BaseNickname == cs.BaseNickname && @@ -461,7 +461,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat string result = instr; - if (result == null || result.Length == 0) + if (string.IsNullOrEmpty(result)) return result; // Repeatedly scan the string until all possible @@ -473,27 +473,27 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { string vvar = arg.Match(result).ToString(); - string var = vvar.Substring(1,vvar.Length-2).Trim(); + string var = vvar.Substring(1, vvar.Length - 2).Trim(); switch (var.ToLower()) { - case "%region" : + case "%region": result = result.Replace(vvar, rs.Region); break; - case "%host" : + case "%host": result = result.Replace(vvar, rs.Host); break; - case "%locx" : + case "%locx": result = result.Replace(vvar, rs.LocX); break; - case "%locy" : + case "%locy": result = result.Replace(vvar, rs.LocY); break; - case "%k" : + case "%k": result = result.Replace(vvar, rs.IDK); break; - default : - result = result.Replace(vvar, rs.config.GetString(var,var)); + default: + result = result.Replace(vvar, rs.config.GetString(var, var)); break; } // m_log.DebugFormat("[IRC-Channel] Parse[2]: {0}", result); diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs index 2e1d03d..351dbfe 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs @@ -46,18 +46,18 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - internal static bool m_pluginEnabled = false; + internal static bool Enabled = false; internal static IConfig m_config = null; internal static List m_channels = new List(); - internal static List m_regions = new List(); + internal static List m_regions = new List(); internal static string m_password = String.Empty; internal RegionState m_region = null; #region INonSharedRegionModule Members - public Type ReplaceableInterface + public Type ReplaceableInterface { get { return null; } } @@ -72,13 +72,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_config = config.Configs["IRC"]; if (m_config == null) { -// m_log.InfoFormat("[IRC-Bridge] module not configured"); + // m_log.InfoFormat("[IRC-Bridge] module not configured"); return; } if (!m_config.GetBoolean("enabled", false)) { -// m_log.InfoFormat("[IRC-Bridge] module disabled in configuration"); + // m_log.InfoFormat("[IRC-Bridge] module disabled in configuration"); return; } @@ -87,19 +87,22 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_password = config.Configs["RemoteAdmin"].GetString("access_password", m_password); } - m_pluginEnabled = true; - m_log.InfoFormat("[IRC-Bridge]: Module enabled"); + Enabled = true; + + m_log.InfoFormat("[IRC-Bridge]: Module is enabled"); } public void AddRegion(Scene scene) { - if (m_pluginEnabled) + if (Enabled) { try { m_log.InfoFormat("[IRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName); + if (!String.IsNullOrEmpty(m_password)) MainServer.Instance.AddXmlRPCHandler("irc_admin", XmlRpcAdminMethod, false); + m_region = new RegionState(scene, m_config); lock (m_regions) m_regions.Add(m_region); m_region.Open(); @@ -123,7 +126,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void RemoveRegion(Scene scene) { - if (!m_pluginEnabled) + if (!Enabled) return; if (m_region == null) @@ -150,12 +153,12 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_log.Debug("[IRC-Bridge]: XML RPC Admin Entry"); XmlRpcResponse response = new XmlRpcResponse(); - Hashtable responseData = new Hashtable(); + Hashtable responseData = new Hashtable(); try { Hashtable requestData = (Hashtable)request.Params[0]; - bool found = false; + bool found = false; string region = String.Empty; if (m_password != String.Empty) @@ -169,18 +172,18 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat if (!requestData.ContainsKey("region")) throw new Exception("No region name specified"); region = (string)requestData["region"]; - + foreach (RegionState rs in m_regions) { if (rs.Region == region) { - responseData["server"] = rs.cs.Server; - responseData["port"] = (int)rs.cs.Port; - responseData["user"] = rs.cs.User; - responseData["channel"] = rs.cs.IrcChannel; - responseData["enabled"] = rs.cs.irc.Enabled; + responseData["server"] = rs.cs.Server; + responseData["port"] = (int)rs.cs.Port; + responseData["user"] = rs.cs.User; + responseData["channel"] = rs.cs.IrcChannel; + responseData["enabled"] = rs.cs.irc.Enabled; responseData["connected"] = rs.cs.irc.Connected; - responseData["nickname"] = rs.cs.irc.Nick; + responseData["nickname"] = rs.cs.irc.Nick; found = true; break; } @@ -195,7 +198,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_log.ErrorFormat("[IRC-Bridge] XML RPC Admin request failed : {0}", e.Message); responseData["success"] = "false"; - responseData["error"] = e.Message; + responseData["error"] = e.Message; } finally { diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs index a014798..6985371 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs @@ -52,17 +52,19 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // Local constants + // This computation is not the real region center if the region is larger than 256. + // This computation isn't fixed because there is not a handle back to the region. private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20); - private static readonly char[] CS_SPACE = { ' ' }; + private static readonly char[] CS_SPACE = { ' ' }; - private const int WD_INTERVAL = 1000; // base watchdog interval - private static int PING_PERIOD = 15; // WD intervals per PING - private static int ICCD_PERIOD = 10; // WD intervals between Connects - private static int L_TIMEOUT = 25; // Login time out interval + private const int WD_INTERVAL = 1000; // base watchdog interval + private static int PING_PERIOD = 15; // WD intervals per PING + private static int ICCD_PERIOD = 10; // WD intervals between Connects + private static int L_TIMEOUT = 25; // Login time out interval - private static int _idk_ = 0; // core connector identifier - private static int _pdk_ = 0; // ping interval counter - private static int _icc_ = ICCD_PERIOD; // IRC connect counter + private static int _idk_ = 0; // core connector identifier + private static int _pdk_ = 0; // ping interval counter + private static int _icc_ = ICCD_PERIOD; // IRC connect counter // List of configured connectors @@ -107,13 +109,9 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat internal int m_resetk = 0; - // Working threads - - private Thread m_listener = null; - private Object msyncConnect = new Object(); - internal bool m_randomizeNick = true; // add random suffix + internal bool m_randomizeNick = true; // add random suffix internal string m_baseNick = null; // base name for randomizing internal string m_nick = null; // effective nickname @@ -122,7 +120,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat get { return m_nick; } set { m_nick = value; } } - + private bool m_enabled = false; // connector enablement public bool Enabled { @@ -130,8 +128,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat } private bool m_connected = false; // connection status - private bool m_pending = false; // login disposition - private int m_timeout = L_TIMEOUT; // login timeout counter + private bool m_pending = false; // login disposition + private int m_timeout = L_TIMEOUT; // login timeout counter public bool Connected { get { return m_connected; } @@ -143,9 +141,9 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat get { return m_ircChannel; } set { m_ircChannel = value; } } - + private uint m_port = 6667; // session port - public uint Port + public uint Port { get { return m_port; } set { m_port = value; } @@ -172,10 +170,10 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // Network interface - private TcpClient m_tcp; + private TcpClient m_tcp; private NetworkStream m_stream = null; - private StreamReader m_reader; - private StreamWriter m_writer; + private StreamReader m_reader; + private StreamWriter m_writer; // Channel characteristic info (if available) @@ -193,26 +191,26 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // Prepare network interface - m_tcp = null; + m_tcp = null; m_writer = null; m_reader = null; // Setup IRC session parameters - m_server = cs.Server; - m_password = cs.Password; - m_baseNick = cs.BaseNickname; + m_server = cs.Server; + m_password = cs.Password; + m_baseNick = cs.BaseNickname; m_randomizeNick = cs.RandomizeNickname; - m_ircChannel = cs.IrcChannel; - m_port = cs.Port; - m_user = cs.User; + m_ircChannel = cs.IrcChannel; + m_port = cs.Port; + m_user = cs.User; if (m_watchdog == null) { // Non-differentiating - ICCD_PERIOD = cs.ConnectDelay; - PING_PERIOD = cs.PingDelay; + ICCD_PERIOD = cs.ConnectDelay; + PING_PERIOD = cs.PingDelay; // Smaller values are not reasonable @@ -235,7 +233,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat if (m_randomizeNick) m_nick = m_baseNick + Util.RandomClass.Next(1, 99); - else + else m_nick = m_baseNick; m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn); @@ -295,18 +293,22 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_nick, m_ircChannel, m_server)); m_writer.Flush(); } - catch (Exception) {} - + catch (Exception) { } + m_connected = false; - try { m_writer.Close(); } catch (Exception) {} - try { m_reader.Close(); } catch (Exception) {} - try { m_stream.Close(); } catch (Exception) {} - try { m_tcp.Close(); } catch (Exception) {} + try { m_writer.Close(); } + catch (Exception) { } + try { m_reader.Close(); } + catch (Exception) { } + try { m_stream.Close(); } + catch (Exception) { } + try { m_tcp.Close(); } + catch (Exception) { } } - + lock (m_connectors) m_connectors.Remove(this); @@ -347,20 +349,17 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat if (m_connected) return; m_connected = true; - m_pending = true; - m_timeout = L_TIMEOUT; + m_pending = true; + m_timeout = L_TIMEOUT; - m_tcp = new TcpClient(m_server, (int)m_port); + m_tcp = new TcpClient(m_server, (int)m_port); m_stream = m_tcp.GetStream(); m_reader = new StreamReader(m_stream); m_writer = new StreamWriter(m_stream); - m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port); + m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port); - m_listener = new Thread(new ThreadStart(ListenerRun)); - m_listener.Name = "IRCConnectorListenerThread"; - m_listener.IsBackground = true; - m_listener.Start(); + WorkManager.StartThread(ListenerRun, "IRCConnectionListenerThread", ThreadPriority.Normal, true, false); // This is the message order recommended by RFC 2812 if (m_password != null) @@ -418,12 +417,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // the socket and it will disappear of its own accord, once this // processing is completed. - try { m_writer.Close(); } catch (Exception) {} - try { m_reader.Close(); } catch (Exception) {} - try { m_tcp.Close(); } catch (Exception) {} + try { m_writer.Close(); } + catch (Exception) { } + try { m_reader.Close(); } + catch (Exception) { } + try { m_tcp.Close(); } + catch (Exception) { } m_connected = false; - m_pending = false; + m_pending = false; m_resetk++; } @@ -495,27 +497,26 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { string inputLine; - int resetk = m_resetk; + int resetk = m_resetk; try { while (m_enabled && m_connected) { - if ((inputLine = m_reader.ReadLine()) == null) throw new Exception("Listener input socket closed"); + Watchdog.UpdateThread(); + // m_log.Info("[IRCConnector]: " + inputLine); if (inputLine.Contains("PRIVMSG")) { - Dictionary data = ExtractMsg(inputLine); // Any chat ??? if (data != null) { - OSChatMessage c = new OSChatMessage(); c.Message = data["msg"]; c.Type = ChatTypeEnum.Region; @@ -531,9 +532,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9)); ChannelState.OSChat(this, c, false); - } - } else { @@ -553,9 +552,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat if (m_enabled && (m_resetk == resetk)) Reconnect(); + + Watchdog.RemoveThread(); } - private Regex RE = new Regex(@":(?[\w-]*)!(?\S*) PRIVMSG (?\S+) :(?.*)", + private Regex RE = new Regex(@":(?[\w-]*)!(?\S*) PRIVMSG (?\S+) :(?.*)", RegexOptions.Multiline); private Dictionary ExtractMsg(string input) @@ -617,8 +618,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat string[] commArgs; string c_server = m_server; - string pfx = String.Empty; - string cmd = String.Empty; + string pfx = String.Empty; + string cmd = String.Empty; string parms = String.Empty; // ":" indicates that a prefix is present @@ -627,15 +628,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // ":" indicates that the remainder of the // line is a single parameter value. - commArgs = command.Split(CS_SPACE,2); + commArgs = command.Split(CS_SPACE, 2); if (commArgs[0].StartsWith(":")) { pfx = commArgs[0].Substring(1); - commArgs = commArgs[1].Split(CS_SPACE,2); + commArgs = commArgs[1].Split(CS_SPACE, 2); } - cmd = commArgs[0]; + cmd = commArgs[0]; parms = commArgs[1]; // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd); @@ -646,44 +647,44 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // Messages 001-004 are always sent // following signon. - case "001" : // Welcome ... - case "002" : // Server information - case "003" : // Welcome ... + case "001": // Welcome ... + case "002": // Server information + case "003": // Welcome ... break; - case "004" : // Server information + case "004": // Server information m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms); commArgs = parms.Split(CS_SPACE); c_server = commArgs[1]; m_server = c_server; - version = commArgs[2]; - usermod = commArgs[3]; - chanmod = commArgs[4]; + version = commArgs[2]; + usermod = commArgs[3]; + chanmod = commArgs[4]; break; - case "005" : // Server information + case "005": // Server information break; - case "042" : - case "250" : - case "251" : - case "252" : - case "254" : - case "255" : - case "265" : - case "266" : - case "332" : // Subject - case "333" : // Subject owner (?) - case "353" : // Name list - case "366" : // End-of-Name list marker - case "372" : // MOTD body - case "375" : // MOTD start + case "042": + case "250": + case "251": + case "252": + case "254": + case "255": + case "265": + case "266": + case "332": // Subject + case "333": // Subject owner (?) + case "353": // Name list + case "366": // End-of-Name list marker + case "372": // MOTD body + case "375": // MOTD start // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]); break; - case "376" : // MOTD end + case "376": // MOTD end // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]); motd = true; break; - case "451" : // Not registered + case "451": // Not registered break; - case "433" : // Nickname in use + case "433": // Nickname in use // Gen a new name m_nick = m_baseNick + Util.RandomClass.Next(1, 99); m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick); @@ -695,29 +696,29 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel)); m_writer.Flush(); break; - case "479" : // Bad channel name, etc. This will never work, so disable the connection - m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]); + case "479": // Bad channel name, etc. This will never work, so disable the connection + m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]); m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd); - m_enabled = false; + m_enabled = false; m_connected = false; - m_pending = false; + m_pending = false; break; - case "NOTICE" : + case "NOTICE": // m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]); break; - case "ERROR" : - m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]); + case "ERROR": + m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]); if (parms.Contains("reconnect too fast")) ICCD_PERIOD++; - m_pending = false; + m_pending = false; Reconnect(); break; - case "PING" : + case "PING": m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms); m_writer.WriteLine(String.Format("PONG {0}", parms)); m_writer.Flush(); break; - case "PONG" : + case "PONG": break; case "JOIN": if (m_pending) @@ -748,19 +749,19 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms); eventIrcQuit(pfx, cmd, parms); break; - default : + default: m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms); break; } // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd); - + } public void eventIrcJoin(string prefix, string command, string parms) { - string[] args = parms.Split(CS_SPACE,2); - string IrcUser = prefix.Split('!')[0]; + string[] args = parms.Split(CS_SPACE, 2); + string IrcUser = prefix.Split('!')[0]; string IrcChannel = args[0]; if (IrcChannel.StartsWith(":")) @@ -772,8 +773,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void eventIrcPart(string prefix, string command, string parms) { - string[] args = parms.Split(CS_SPACE,2); - string IrcUser = prefix.Split('!')[0]; + string[] args = parms.Split(CS_SPACE, 2); + string IrcUser = prefix.Split('!')[0]; string IrcChannel = args[0]; m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel); @@ -782,7 +783,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void eventIrcMode(string prefix, string command, string parms) { - string[] args = parms.Split(CS_SPACE,2); + string[] args = parms.Split(CS_SPACE, 2); string UserMode = args[1]; m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel); @@ -794,7 +795,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void eventIrcNickChange(string prefix, string command, string parms) { - string[] args = parms.Split(CS_SPACE,2); + string[] args = parms.Split(CS_SPACE, 2); string UserOldNick = prefix.Split('!')[0]; string UserNewNick = args[0].Remove(0, 1); @@ -804,11 +805,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void eventIrcKick(string prefix, string command, string parms) { - string[] args = parms.Split(CS_SPACE,3); - string UserKicker = prefix.Split('!')[0]; - string IrcChannel = args[0]; - string UserKicked = args[1]; - string KickMessage = args[2]; + string[] args = parms.Split(CS_SPACE, 3); + string UserKicker = prefix.Split('!')[0]; + string IrcChannel = args[0]; + string UserKicked = args[1]; + string KickMessage = args[2]; m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel); BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage); @@ -822,7 +823,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat public void eventIrcQuit(string prefix, string command, string parms) { - string IrcUser = prefix.Split('!')[0]; + string IrcUser = prefix.Split('!')[0]; string QuitMessage = parms; m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel); @@ -842,65 +843,65 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_); - _pdk_ = (_pdk_+1)%PING_PERIOD; // cycle the ping trigger + _pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger _icc_++; // increment the inter-consecutive-connect-delay counter lock (m_connectors) - foreach (IRCConnector connector in m_connectors) - { + foreach (IRCConnector connector in m_connectors) + { - // m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector); + // m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector); - if (connector.Enabled) - { - if (!connector.Connected) + if (connector.Enabled) { - try + if (!connector.Connected) { - // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel); - connector.Connect(); + try + { + // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel); + connector.Connect(); + } + catch (Exception e) + { + m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message); + } } - catch (Exception e) + else { - m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message); - } - } - else - { - if (connector.m_pending) - { - if (connector.m_timeout == 0) + if (connector.m_pending) { - m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn); - connector.Reconnect(); + if (connector.m_timeout == 0) + { + m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn); + connector.Reconnect(); + } + else + connector.m_timeout--; } - else - connector.m_timeout--; - } - // Being marked connected is not enough to ping. Socket establishment can sometimes take a long - // time, in which case the watch dog might try to ping the server before the socket has been - // set up, with nasty side-effects. + // Being marked connected is not enough to ping. Socket establishment can sometimes take a long + // time, in which case the watch dog might try to ping the server before the socket has been + // set up, with nasty side-effects. - else if (_pdk_ == 0) - { - try - { - connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server)); - connector.m_writer.Flush(); - } - catch (Exception e) + else if (_pdk_ == 0) { - m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message); - m_log.Debug(e); - connector.Reconnect(); + try + { + connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server)); + connector.m_writer.Flush(); + } + catch (Exception e) + { + m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message); + m_log.Debug(e); + connector.Reconnect(); + } } - } + } } } - } // m_log.InfoFormat("[IRC-Watchdog] Status scan completed"); diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs index 53b103e..5505001 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs @@ -41,49 +41,73 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat internal class RegionState { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + // This computation is not the real region center if the region is larger than 256. + // This computation isn't fixed because there is not a handle back to the region. private static readonly OpenMetaverse.Vector3 CenterOfRegion = new OpenMetaverse.Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20); - private const int DEBUG_CHANNEL = 2147483647; + private const int DEBUG_CHANNEL = 2147483647; - private static int _idk_ = 0; + private static int _idk_ = 0; // Runtime variables; these values are assigned when the // IrcState is created and remain constant thereafter. - internal string Region = String.Empty; - internal string Host = String.Empty; - internal string LocX = String.Empty; - internal string LocY = String.Empty; - internal string IDK = String.Empty; + internal string Region = String.Empty; + internal string Host = String.Empty; + internal string LocX = String.Empty; + internal string LocY = String.Empty; + internal string IDK = String.Empty; // System values - used only be the IRC classes themselves - internal ChannelState cs = null; // associated IRC configuration - internal Scene scene = null; // associated scene - internal IConfig config = null; // configuration file reference - internal bool enabled = true; - + internal ChannelState cs = null; // associated IRC configuration + internal Scene scene = null; // associated scene + internal IConfig config = null; // configuration file reference + internal bool enabled = true; + + //AgentAlert + internal bool showAlert = false; + internal string alertMessage = String.Empty; + internal IDialogModule dialogModule = null; + // This list is used to keep track of who is here, and by // implication, who is not. - internal List clients = new List(); + internal List clients = new List(); // Setup runtime variable values public RegionState(Scene p_scene, IConfig p_config) { - - scene = p_scene; + scene = p_scene; config = p_config; Region = scene.RegionInfo.RegionName; - Host = scene.RegionInfo.ExternalHostName; - LocX = Convert.ToString(scene.RegionInfo.RegionLocX); - LocY = Convert.ToString(scene.RegionInfo.RegionLocY); - IDK = Convert.ToString(_idk_++); + Host = scene.RegionInfo.ExternalHostName; + LocX = Convert.ToString(scene.RegionInfo.RegionLocX); + LocY = Convert.ToString(scene.RegionInfo.RegionLocY); + IDK = Convert.ToString(_idk_++); + + showAlert = config.GetBoolean("alert_show", false); + string alertServerInfo = String.Empty; + + if (showAlert) + { + bool showAlertServerInfo = config.GetBoolean("alert_show_serverinfo", true); + + if (showAlertServerInfo) + alertServerInfo = String.Format("\nServer: {0}\nPort: {1}\nChannel: {2}\n\n", + config.GetString("server", ""), config.GetString("port", ""), config.GetString("channel", "")); + + string alertPreMessage = config.GetString("alert_msg_pre", "This region is linked to Irc."); + string alertPostMessage = config.GetString("alert_msg_post", "Everything you say in public chat can be listened."); + + alertMessage = String.Format("{0}\n{1}{2}", alertPreMessage, alertServerInfo, alertPostMessage); + + dialogModule = scene.RequestModuleInterface(); + } // OpenChannel conditionally establishes a connection to the // IRC server. The request will either succeed, or it will @@ -93,9 +117,9 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // Connect channel to world events - scene.EventManager.OnChatFromWorld += OnSimChat; + scene.EventManager.OnChatFromWorld += OnSimChat; scene.EventManager.OnChatFromClient += OnSimChat; - scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; + scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; scene.EventManager.OnMakeChildAgent += OnMakeChildAgent; m_log.InfoFormat("[IRC-Region {0}] Initialization complete", Region); @@ -106,8 +130,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat ~RegionState() { - if (cs != null) - cs.RemoveRegion(this); + if (cs != null) + cs.RemoveRegion(this); } // Called by PostInitialize after all regions have been created @@ -138,7 +162,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { if (clients.Contains(client)) { - if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting)) + if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting)) { m_log.InfoFormat("[IRC-Region {0}]: {1} has left", Region, client.Name); //Check if this person is excluded from IRC @@ -147,7 +171,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", client.Name)); } } - client.OnLogout -= OnClientLoggedOut; + client.OnLogout -= OnClientLoggedOut; client.OnConnectionClosed -= OnClientLoggedOut; clients.Remove(client); } @@ -171,13 +195,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat { if (clients.Contains(client)) { - if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting)) + if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting)) { string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname); m_log.DebugFormat("[IRC-Region {0}] {1} has left", Region, clientName); cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", clientName)); } - client.OnLogout -= OnClientLoggedOut; + client.OnLogout -= OnClientLoggedOut; client.OnConnectionClosed -= OnClientLoggedOut; clients.Remove(client); } @@ -195,14 +219,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat private void OnMakeRootAgent(ScenePresence presence) { - IClientAPI client = presence.ControllingClient; try { if (!clients.Contains(client)) { - client.OnLogout += OnClientLoggedOut; + client.OnLogout += OnClientLoggedOut; client.OnConnectionClosed += OnClientLoggedOut; clients.Add(client); if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting)) @@ -216,17 +239,18 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat } } } + + if (dialogModule != null && showAlert) + dialogModule.SendAlertToUser(client, alertMessage, true); } catch (Exception ex) { m_log.ErrorFormat("[IRC-Region {0}]: MakeRootAgent exception: {1}", Region, ex.Message); m_log.Debug(ex); } - } // This handler detects chat events int he virtual world. - public void OnSimChat(Object sender, OSChatMessage msg) { @@ -317,14 +341,14 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat // that evident. default: - m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}", + m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}", Region, msg.Message); cs.irc.Send(msg.Message); break; } } catch (Exception ex) - { + { m_log.WarnFormat("[IRC-Region {0}] error processing in-world command channel input: {1}", Region, ex.Message); m_log.Debug(ex); @@ -366,7 +390,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat m_log.DebugFormat("[IRC-Region {0}] heard on channel {1} : {2}", Region, msg.Channel, msg.Message); - if (null != avatar && cs.RelayChat && (msg.Channel == 0 || msg.Channel == DEBUG_CHANNEL)) + if (null != avatar && cs.RelayChat && (msg.Channel == 0 || msg.Channel == DEBUG_CHANNEL)) { string txt = msg.Message; if (txt.StartsWith("/me ")) @@ -376,13 +400,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat return; } - if (null == avatar && cs.RelayPrivateChannels && null != cs.AccessPassword && + if (null == avatar && cs.RelayPrivateChannels && null != cs.AccessPassword && msg.Channel == cs.RelayChannelOut) { Match m = cs.AccessPasswordRegex.Match(msg.Message); if (null != m) { - m_log.DebugFormat("[IRC] relaying message from {0}: {1}", m.Groups["avatar"].ToString(), + m_log.DebugFormat("[IRC] relaying message from {0}: {1}", m.Groups["avatar"].ToString(), m.Groups["message"].ToString()); cs.irc.PrivMsg(cs.PrivateMessageFormat, m.Groups["avatar"].ToString(), scene.RegionInfo.RegionName, m.Groups["message"].ToString()); diff --git a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs index 018357a..c48e585 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs @@ -375,11 +375,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Concierge scene.GetRootAgentCount(), scene.RegionInfo.RegionName, scene.RegionInfo.RegionID, DateTime.UtcNow.ToString("s"))); + scene.ForEachRootScenePresence(delegate(ScenePresence sp) { - list.Append(String.Format(" \n", sp.Name, sp.UUID)); - list.Append(""); + list.Append(String.Format(" \n", sp.Name, sp.UUID)); }); + + list.Append(""); string payload = list.ToString(); // post via REST to broker diff --git a/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs b/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs new file mode 100644 index 0000000..5a6b284 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs @@ -0,0 +1,220 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using Mono.Addins; +using NDesk.Options; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.Avatar.SitStand +{ + /// + /// A module that just holds commands for changing avatar sitting and standing states. + /// + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AnimationsCommandModule")] + public class SitStandCommandModule : INonSharedRegionModule + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + + public string Name { get { return "SitStand Command Module"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) + { +// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: INITIALIZED MODULE"); + } + + public void PostInitialise() + { +// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: POST INITIALIZED MODULE"); + } + + public void Close() + { +// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: CLOSED MODULE"); + } + + public void AddRegion(Scene scene) + { +// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName); + } + + public void RemoveRegion(Scene scene) + { +// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + } + + public void RegionLoaded(Scene scene) + { +// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); + + m_scene = scene; + + scene.AddCommand( + "Users", this, "sit user name", + "sit user name [--regex] ", + "Sit the named user on an unoccupied object with a sit target.", + "If there are no such objects then nothing happens.\n" + + "If --regex is specified then the names are treated as regular expressions.", + HandleSitUserNameCommand); + + scene.AddCommand( + "Users", this, "stand user name", + "stand user name [--regex] ", + "Stand the named user.", + "If --regex is specified then the names are treated as regular expressions.", + HandleStandUserNameCommand); + } + + private void HandleSitUserNameCommand(string module, string[] cmd) + { + if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null) + return; + + if (cmd.Length < 5) + { + MainConsole.Instance.Output("Usage: sit user name [--regex] "); + return; + } + + List scenePresences = GetScenePresences(cmd); + + foreach (ScenePresence sp in scenePresences) + { + if (sp.SitGround || sp.IsSatOnObject) + continue; + + SceneObjectPart sitPart = null; + List sceneObjects = m_scene.GetSceneObjectGroups(); + + foreach (SceneObjectGroup sceneObject in sceneObjects) + { + if (sceneObject.IsAttachment) + continue; + + foreach (SceneObjectPart part in sceneObject.Parts) + { + if (part.IsSitTargetSet && part.SitTargetAvatar == UUID.Zero) + { + sitPart = part; + break; + } + } + } + + if (sitPart != null) + { + MainConsole.Instance.OutputFormat( + "Sitting {0} on {1} {2} in {3}", + sp.Name, sitPart.ParentGroup.Name, sitPart.ParentGroup.UUID, m_scene.Name); + + sp.HandleAgentRequestSit(sp.ControllingClient, sp.UUID, sitPart.UUID, Vector3.Zero); + sp.HandleAgentSit(sp.ControllingClient, sp.UUID); + } + else + { + MainConsole.Instance.OutputFormat( + "Could not find any unoccupied set seat on which to sit {0} in {1}. Aborting", + sp.Name, m_scene.Name); + + break; + } + } + } + + private void HandleStandUserNameCommand(string module, string[] cmd) + { + if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null) + return; + + if (cmd.Length < 5) + { + MainConsole.Instance.Output("Usage: stand user name [--regex] "); + return; + } + + List scenePresences = GetScenePresences(cmd); + + foreach (ScenePresence sp in scenePresences) + { + if (sp.SitGround || sp.IsSatOnObject) + { + MainConsole.Instance.OutputFormat("Standing {0} in {1}", sp.Name, m_scene.Name); + sp.StandUp(); + } + } + } + + private List GetScenePresences(string[] cmdParams) + { + bool useRegex = false; + OptionSet options = new OptionSet().Add("regex", v=> useRegex = v != null ); + + List mainParams = options.Parse(cmdParams); + + string firstName = mainParams[3]; + string lastName = mainParams[4]; + + List scenePresencesMatched = new List(); + + if (useRegex) + { + Regex nameRegex = new Regex(string.Format("{0} {1}", firstName, lastName)); + List scenePresences = m_scene.GetScenePresences(); + + foreach (ScenePresence sp in scenePresences) + { + if (!sp.IsChildAgent && nameRegex.IsMatch(sp.Name)) + scenePresencesMatched.Add(sp); + } + } + else + { + ScenePresence sp = m_scene.GetScenePresence(firstName, lastName); + + if (sp != null && !sp.IsChildAgent) + scenePresencesMatched.Add(sp); + } + + return scenePresencesMatched; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs index 37ab35a..45af212 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs @@ -65,7 +65,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice // Capability string prefixes private static readonly string m_parcelVoiceInfoRequestPath = "0207/"; private static readonly string m_provisionVoiceAccountRequestPath = "0208/"; - private static readonly string m_chatSessionRequestPath = "0209/"; + //private static readonly string m_chatSessionRequestPath = "0209/"; // Control info private static bool m_Enabled = false; @@ -326,15 +326,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice "ParcelVoiceInfoRequest", agentID.ToString())); - caps.RegisterHandler( - "ChatSessionRequest", - new RestStreamHandler( - "POST", - capsBase + m_chatSessionRequestPath, - (request, path, param, httpRequest, httpResponse) - => ChatSessionRequest(scene, request, path, param, agentID, caps), - "ChatSessionRequest", - agentID.ToString())); + //caps.RegisterHandler( + // "ChatSessionRequest", + // new RestStreamHandler( + // "POST", + // capsBase + m_chatSessionRequestPath, + // (request, path, param, httpRequest, httpResponse) + // => ChatSessionRequest(scene, request, path, param, agentID, caps), + // "ChatSessionRequest", + // agentID.ToString())); } /// @@ -551,13 +551,20 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice reqStream.Close(); } - HttpWebResponse fwdrsp = (HttpWebResponse)forwardreq.GetResponse(); - Encoding encoding = Util.UTF8; - StreamReader fwdresponsestream = new StreamReader(fwdrsp.GetResponseStream(), encoding); - fwdresponsestr = fwdresponsestream.ReadToEnd(); - fwdresponsecontenttype = fwdrsp.ContentType; - fwdresponsecode = (int)fwdrsp.StatusCode; - fwdresponsestream.Close(); + using (HttpWebResponse fwdrsp = (HttpWebResponse)forwardreq.GetResponse()) + { + Encoding encoding = Util.UTF8; + + using (Stream s = fwdrsp.GetResponseStream()) + { + using (StreamReader fwdresponsestream = new StreamReader(s)) + { + fwdresponsestr = fwdresponsestream.ReadToEnd(); + fwdresponsecontenttype = fwdrsp.ContentType; + fwdresponsecode = (int)fwdrsp.StatusCode; + } + } + } response["content_type"] = fwdresponsecontenttype; response["str_response_string"] = fwdresponsestr; diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs index 881807a..dd44564 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs @@ -92,7 +92,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice // Capability strings private static readonly string m_parcelVoiceInfoRequestPath = "0107/"; private static readonly string m_provisionVoiceAccountRequestPath = "0108/"; - private static readonly string m_chatSessionRequestPath = "0109/"; + //private static readonly string m_chatSessionRequestPath = "0109/"; // Control info, e.g. vivox server, admin user, admin password private static bool m_pluginEnabled = false; @@ -117,6 +117,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice private IConfig m_config; + private object m_Lock; + public void Initialise(IConfigSource config) { @@ -128,6 +130,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice if (!m_config.GetBoolean("enabled", false)) return; + m_Lock = new object(); + try { // retrieve configuration variables @@ -429,15 +433,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice "ParcelVoiceInfoRequest", agentID.ToString())); - caps.RegisterHandler( - "ChatSessionRequest", - new RestStreamHandler( - "POST", - capsBase + m_chatSessionRequestPath, - (request, path, param, httpRequest, httpResponse) - => ChatSessionRequest(scene, request, path, param, agentID, caps), - "ChatSessionRequest", - agentID.ToString())); + //caps.RegisterHandler( + // "ChatSessionRequest", + // new RestStreamHandler( + // "POST", + // capsBase + m_chatSessionRequestPath, + // (request, path, param, httpRequest, httpResponse) + // => ChatSessionRequest(scene, request, path, param, agentID, caps), + // "ChatSessionRequest", + // agentID.ToString())); } /// @@ -818,11 +822,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice { string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", channelId, m_authToken); - if (parent != null && parent != String.Empty) + if (!string.IsNullOrEmpty(parent)) { requrl = String.Format("{0}&chan_parent={1}", requrl, parent); } - if (description != null && description != String.Empty) + if (!string.IsNullOrEmpty(description)) { requrl = String.Format("{0}&chan_desc={1}", requrl, description); } @@ -832,7 +836,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice requrl = String.Format("{0}&chan_roll_off={1}", requrl, m_vivoxChannelRollOff); requrl = String.Format("{0}&chan_dist_model={1}", requrl, m_vivoxChannelDistanceModel); requrl = String.Format("{0}&chan_max_range={1}", requrl, m_vivoxChannelMaximumRange); - requrl = String.Format("{0}&chan_ckamping_distance={1}", requrl, m_vivoxChannelClampingDistance); + requrl = String.Format("{0}&chan_clamping_distance={1}", requrl, m_vivoxChannelClampingDistance); XmlElement resp = VivoxCall(requrl, true); if (XmlFind(resp, "response.level0.body.chan_uri", out channelUri)) @@ -858,7 +862,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice // requrl = String.Format("{0}&chan_parent={1}", requrl, parent); // } - if (description != null && description != String.Empty) + if (!string.IsNullOrEmpty(description)) { requrl = String.Format("{0}&chan_desc={1}", requrl, description); } @@ -1043,7 +1047,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice private XmlElement VivoxDeleteChannel(string parent, string channelid) { string requrl = String.Format(m_vivoxChannelDel, m_vivoxServer, "delete", channelid, m_authToken); - if (parent != null && parent != String.Empty) + if (!string.IsNullOrEmpty(parent)) { requrl = String.Format("{0}&chan_parent={1}", requrl, parent); } @@ -1111,27 +1115,32 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice doc = new XmlDocument(); - try + // Let's serialize all calls to Vivox. Most of these are driven by + // the clients (CAPs), when the user arrives at the region. We don't + // want to issue many simultaneous http requests to Vivox, because mono + // doesn't like that + lock (m_Lock) { - // Otherwise prepare the request - m_log.DebugFormat("[VivoxVoice] Sending request <{0}>", requrl); - - HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requrl); - HttpWebResponse rsp = null; + try + { + // Otherwise prepare the request + m_log.DebugFormat("[VivoxVoice] Sending request <{0}>", requrl); - // We are sending just parameters, no content - req.ContentLength = 0; + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requrl); - // Send request and retrieve the response - rsp = (HttpWebResponse)req.GetResponse(); + // We are sending just parameters, no content + req.ContentLength = 0; - XmlTextReader rdr = new XmlTextReader(rsp.GetResponseStream()); - doc.Load(rdr); - rdr.Close(); - } - catch (Exception e) - { - m_log.ErrorFormat("[VivoxVoice] Error in admin call : {0}", e.Message); + // Send request and retrieve the response + using (HttpWebResponse rsp = (HttpWebResponse)req.GetResponse()) + using (Stream s = rsp.GetResponseStream()) + using (XmlTextReader rdr = new XmlTextReader(s)) + doc.Load(rdr); + } + catch (Exception e) + { + m_log.ErrorFormat("[VivoxVoice] Error in admin call : {0}", e.Message); + } } // If we're debugging server responses, dump the whole @@ -1316,4 +1325,4 @@ namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice return false; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs index 2802e2f..e1b6abb 100644 --- a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs @@ -55,8 +55,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups private IGroupsServicesConnector m_groupData = null; // Config Options - private bool m_groupMessagingEnabled = false; - private bool m_debugEnabled = true; + private bool m_groupMessagingEnabled; + private bool m_debugEnabled; /// /// If enabled, module only tries to send group IMs to online users by querying cached presence information. @@ -113,7 +113,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups if (m_messageOnlineAgentsOnly) m_usersOnlineCache = new ExpiringCache(); - m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true); + m_debugEnabled = groupsConfig.GetBoolean("MessagingDebugEnabled", m_debugEnabled); } m_log.InfoFormat( @@ -127,6 +127,14 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups return; scene.RegisterModuleInterface(this); + + scene.AddCommand( + "Debug", + this, + "debug groups messaging verbose", + "debug groups messaging verbose ", + "This setting turns on very verbose groups messaging debugging", + HandleDebugGroupsMessagingVerbose); } public void RegionLoaded(Scene scene) @@ -218,6 +226,26 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups #endregion + private void HandleDebugGroupsMessagingVerbose(object modules, string[] args) + { + if (args.Length < 5) + { + MainConsole.Instance.Output("Usage: debug groups messaging verbose "); + return; + } + + bool verbose = false; + if (!bool.TryParse(args[4], out verbose)) + { + MainConsole.Instance.Output("Usage: debug groups messaging verbose "); + return; + } + + m_debugEnabled = verbose; + + MainConsole.Instance.OutputFormat("{0} verbose logging set to {1}", Name, m_debugEnabled); + } + /// /// Not really needed, but does confirm that the group exists. /// @@ -237,11 +265,20 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups return false; } } - + public void SendMessageToGroup(GridInstantMessage im, UUID groupID) { - List groupMembers = m_groupData.GetGroupMembers(new UUID(im.fromAgentID), groupID); + SendMessageToGroup(im, groupID, new UUID(im.fromAgentID), null); + } + + public void SendMessageToGroup( + GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func sendCondition) + { + int requestStartTick = Environment.TickCount; + + List groupMembers = m_groupData.GetGroupMembers(sendingAgentForGroupCalls, groupID); int groupMembersCount = groupMembers.Count; + HashSet attemptDeliveryUuidSet = null; if (m_messageOnlineAgentsOnly) { @@ -257,10 +294,12 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds); } - HashSet onlineAgentsUuidSet = new HashSet(); - Array.ForEach(onlineAgents, pi => onlineAgentsUuidSet.Add(pi.UserID)); + attemptDeliveryUuidSet + = new HashSet(Array.ConvertAll(onlineAgents, pi => pi.UserID)); + + //Array.ForEach(onlineAgents, pi => attemptDeliveryUuidSet.Add(pi.UserID)); - groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList(); + //groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList(); // if (m_debugEnabled) // m_log.DebugFormat( @@ -269,26 +308,42 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups } else { + attemptDeliveryUuidSet + = new HashSet(groupMembers.ConvertAll(gmd => gmd.AgentID.ToString())); + if (m_debugEnabled) m_log.DebugFormat( "[GROUPS-MESSAGING]: SendMessageToGroup called for group {0} with {1} visible members", groupID, groupMembers.Count); - } - - int requestStartTick = Environment.TickCount; + } foreach (GroupMembersData member in groupMembers) { - if (m_groupData.hasAgentDroppedGroupChatSession(member.AgentID, groupID)) + if (sendCondition != null) + { + if (!sendCondition(member)) + { + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: Not sending to {0} as they do not fulfill send condition", + member.AgentID); + + continue; + } + } + else if (m_groupData.hasAgentDroppedGroupChatSession(member.AgentID, groupID)) { // Don't deliver messages to people who have dropped this session - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} has dropped session, not delivering to them", member.AgentID); + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: {0} has dropped session, not delivering to them", member.AgentID); + continue; } // Copy Message GridInstantMessage msg = new GridInstantMessage(); - msg.imSessionID = groupID.Guid; + msg.imSessionID = im.imSessionID; msg.fromAgentName = im.fromAgentName; msg.message = im.message; msg.dialog = im.dialog; @@ -304,26 +359,51 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups msg.toAgentID = member.AgentID.Guid; - IClientAPI client = GetActiveClient(member.AgentID); - if (client == null) + if (attemptDeliveryUuidSet.Contains(member.AgentID.ToString())) { - // If they're not local, forward across the grid - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} via Grid", member.AgentID); - m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { }); + IClientAPI client = GetActiveClient(member.AgentID); + if (client == null) + { + int startTick = Environment.TickCount; + + // If they're not local, forward across the grid + m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { }); + + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: Delivering to {0} via grid took {1} ms", + member.AgentID, Environment.TickCount - startTick); + } + else + { + int startTick = Environment.TickCount; + + ProcessMessageFromGroupSession(msg, client); + + // Deliver locally, directly + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: Delivering to {0} locally took {1} ms", + member.AgentID, Environment.TickCount - startTick); + } } else { - // Deliver locally, directly - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name); - ProcessMessageFromGroupSession(msg); + int startTick = Environment.TickCount; + + m_msgTransferModule.HandleUndeliverableMessage(msg, delegate(bool success) { }); + + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: Handling undeliverable message for {0} took {1} ms", + member.AgentID, Environment.TickCount - startTick); } } - // Temporary for assessing how long it still takes to send messages to large online groups. - if (m_messageOnlineAgentsOnly) + if (m_debugEnabled) m_log.DebugFormat( - "[GROUPS-MESSAGING]: SendMessageToGroup for group {0} with {1} visible members, {2} online took {3}ms", - groupID, groupMembersCount, groupMembers.Count(), Environment.TickCount - requestStartTick); + "[GROUPS-MESSAGING]: Total SendMessageToGroup for group {0} with {1} members, {2} candidates for delivery took {3} ms", + groupID, groupMembersCount, attemptDeliveryUuidSet.Count(), Environment.TickCount - requestStartTick); } #region SimGridEventHandlers @@ -348,7 +428,6 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups // Any other message type will not be delivered to a client by the // Instant Message Module - if (m_debugEnabled) { m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); @@ -362,13 +441,35 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups || (msg.dialog == (byte)InstantMessageDialog.SessionAdd) || (msg.dialog == (byte)InstantMessageDialog.SessionDrop))) { - ProcessMessageFromGroupSession(msg); + IClientAPI client = null; + + if (msg.dialog == (byte)InstantMessageDialog.SessionSend) + { + client = GetActiveClient(new UUID(msg.toAgentID)); + + if (client != null) + { + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} locally", client.Name); + } + else + { + m_log.WarnFormat("[GROUPS-MESSAGING]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID); + + return; + } + } + + ProcessMessageFromGroupSession(msg, client); } } - private void ProcessMessageFromGroupSession(GridInstantMessage msg) + private void ProcessMessageFromGroupSession(GridInstantMessage msg, IClientAPI client) { - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID); + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS-MESSAGING]: Session message from {0} going to agent {1}, sessionID {2}, type {3}", + msg.fromAgentName, msg.toAgentID, msg.imSessionID, (InstantMessageDialog)msg.dialog); UUID AgentID = new UUID(msg.fromAgentID); UUID GroupID = new UUID(msg.imSessionID); @@ -392,74 +493,62 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups // Add them to the session for now, and Invite them m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID); - UUID toAgentID = new UUID(msg.toAgentID); - IClientAPI activeClient = GetActiveClient(toAgentID); - if (activeClient != null) + GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null); + if (groupInfo != null) { - GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null); - if (groupInfo != null) - { - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Sending chatterbox invite instant message"); - - // Force? open the group session dialog??? - // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg); - IEventQueue eq = activeClient.Scene.RequestModuleInterface(); - eq.ChatterboxInvitation( - GroupID - , groupInfo.GroupName - , new UUID(msg.fromAgentID) - , msg.message - , new UUID(msg.toAgentID) - , msg.fromAgentName - , msg.dialog - , msg.timestamp - , msg.offline == 1 - , (int)msg.ParentEstateID - , msg.Position - , 1 - , new UUID(msg.imSessionID) - , msg.fromGroup - , Utils.StringToBytes(groupInfo.GroupName) - ); - - eq.ChatterBoxSessionAgentListUpdates( - new UUID(GroupID) - , new UUID(msg.fromAgentID) - , new UUID(msg.toAgentID) - , false //canVoiceChat - , false //isModerator - , false //text mute - ); - } + if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Sending chatterbox invite instant message"); + + // Force? open the group session dialog??? + // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg); + IEventQueue eq = client.Scene.RequestModuleInterface(); + eq.ChatterboxInvitation( + GroupID + , groupInfo.GroupName + , new UUID(msg.fromAgentID) + , msg.message + , new UUID(msg.toAgentID) + , msg.fromAgentName + , msg.dialog + , msg.timestamp + , msg.offline == 1 + , (int)msg.ParentEstateID + , msg.Position + , 1 + , new UUID(msg.imSessionID) + , msg.fromGroup + , Utils.StringToBytes(groupInfo.GroupName) + ); + + eq.ChatterBoxSessionAgentListUpdates( + new UUID(GroupID) + , new UUID(msg.fromAgentID) + , new UUID(msg.toAgentID) + , false //canVoiceChat + , false //isModerator + , false //text mute + ); } + + break; } else if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID, GroupID)) { // User hasn't dropped, so they're in the session, // maybe we should deliver it. - IClientAPI client = GetActiveClient(new UUID(msg.toAgentID)); - if (client != null) - { - // Deliver locally, directly - if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} locally", client.Name); - client.SendInstantMessage(msg); - } - else - { - m_log.WarnFormat("[GROUPS-MESSAGING]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID); - } + client.SendInstantMessage(msg); } + break; default: - m_log.WarnFormat("[GROUPS-MESSAGING]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString()); - break; + client.SendInstantMessage(msg); + + break;; } } #endregion - #region ClientEvents private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { @@ -548,15 +637,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups // Don't log any normal IMs (privacy!) if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent) { - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromGroup({0})", im.fromGroup ? "True" : "False"); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentID({0})", im.fromAgentID.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentName({0})", im.fromAgentName.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: imSessionID({0})", im.imSessionID.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: message({0})", im.message.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: offline({0})", im.offline.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: toAgentID({0})", im.toAgentID.ToString()); - m_log.WarnFormat("[GROUPS-MESSAGING]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket")); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromGroup({0})", im.fromGroup ? "True" : "False"); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: Dialog({0})", (InstantMessageDialog)im.dialog); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromAgentID({0})", im.fromAgentID); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromAgentName({0})", im.fromAgentName); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: imSessionID({0})", im.imSessionID); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: message({0})", im.message); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: offline({0})", im.offline); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: toAgentID({0})", im.toAgentID); + m_log.DebugFormat("[GROUPS-MESSAGING]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket")); } } @@ -567,7 +656,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups /// private IClientAPI GetActiveClient(UUID agentID) { - if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Looking for local client {0}", agentID); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Looking for local client {0}", agentID); IClientAPI child = null; @@ -579,12 +669,16 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups { if (!sp.IsChildAgent) { - if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Found root agent for client : {0}", sp.ControllingClient.Name); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Found root agent for client : {0}", sp.ControllingClient.Name); + return sp.ControllingClient; } else { - if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Found child agent for client : {0}", sp.ControllingClient.Name); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Found child agent for client : {0}", sp.ControllingClient.Name); + child = sp.ControllingClient; } } @@ -593,12 +687,15 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups // If we didn't find a root, then just return whichever child we found, or null if none if (child == null) { - if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Could not find local client for agent : {0}", agentID); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Could not find local client for agent : {0}", agentID); } else { - if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Returning child agent for client : {0}", child.Name); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS-MESSAGING]: Returning child agent for client : {0}", child.Name); } + return child; } diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs index 29f9591..1565da9 100644 --- a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs @@ -35,10 +35,10 @@ using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; +using System.Text; using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags; namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups @@ -76,9 +76,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups private List m_sceneList = new List(); - private IMessageTransferModule m_msgTransferModule = null; + private IMessageTransferModule m_msgTransferModule; + + private IGroupsMessagingModule m_groupsMessagingModule; - private IGroupsServicesConnector m_groupData = null; + private IGroupsServicesConnector m_groupData; // Configuration settings private bool m_groupsEnabled = false; @@ -184,10 +186,19 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups if (m_msgTransferModule == null) { m_groupsEnabled = false; - m_log.Warn("[GROUPS]: Could not get MessageTransferModule"); + m_log.Warn("[GROUPS]: Could not get IMessageTransferModule"); } } + if (m_groupsMessagingModule == null) + { + m_groupsMessagingModule = scene.RequestModuleInterface(); + + // No message transfer module, no notices, group invites, rejects, ejects, etc + if (m_groupsMessagingModule == null) + m_log.Warn("[GROUPS]: Could not get IGroupsMessagingModule"); + } + lock (m_sceneList) { m_sceneList.Add(scene); @@ -250,7 +261,6 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups client.OnUUIDGroupNameRequest += HandleUUIDGroupNameRequest; client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest; - client.OnDirFindQuery += OnDirFindQuery; client.OnRequestAvatarProperties += OnRequestAvatarProperties; // Used for Notices and Group Invites/Accept/Reject @@ -303,21 +313,6 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups } */ - void OnDirFindQuery(IClientAPI remoteClient, UUID queryID, string queryText, uint queryFlags, int queryStart) - { - if (((DirFindFlags)queryFlags & DirFindFlags.Groups) == DirFindFlags.Groups) - { - if (m_debugEnabled) - m_log.DebugFormat( - "[GROUPS]: {0} called with queryText({1}) queryFlags({2}) queryStart({3})", - System.Reflection.MethodBase.GetCurrentMethod().Name, queryText, (DirFindFlags)queryFlags, queryStart); - - // TODO: This currently ignores pretty much all the query flags including Mature and sort order - remoteClient.SendDirGroupsReply(queryID, m_groupData.FindGroups(GetRequestingAgentID(remoteClient), queryText).ToArray()); - } - - } - private void OnAgentDataUpdateRequest(IClientAPI remoteClient, UUID dataForAgentID, UUID sessionID) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); @@ -361,7 +356,10 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { - if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS]: {0} called for {1}, message type {2}", + System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name, (InstantMessageDialog)im.dialog); // Group invitations if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) @@ -437,81 +435,160 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups string Subject = im.message.Substring(0, im.message.IndexOf('|')); string Message = im.message.Substring(Subject.Length + 1); + InventoryItemBase item = null; + bool hasAttachment = false; + UUID itemID = UUID.Zero; //Assignment to quiet compiler + UUID ownerID = UUID.Zero; //Assignment to quiet compiler byte[] bucket; - if ((im.binaryBucket.Length == 1) && (im.binaryBucket[0] == 0)) - { - bucket = new byte[19]; - bucket[0] = 0; //dunno - bucket[1] = 0; //dunno - GroupID.ToBytes(bucket, 2); - bucket[18] = 0; //dunno - } - else + if (im.binaryBucket.Length >= 1 && im.binaryBucket[0] > 0) { string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); binBucket = binBucket.Remove(0, 14).Trim(); - if (m_debugEnabled) + + OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); + if (binBucketOSD is OSD) { - m_log.WarnFormat("I don't understand a group notice binary bucket of: {0}", binBucket); + OSDMap binBucketMap = (OSDMap)binBucketOSD; + + itemID = binBucketMap["item_id"].AsUUID(); + ownerID = binBucketMap["owner_id"].AsUUID(); + + //Attempt to get the details of the attached item. + //If sender doesn't own the attachment, the item + //variable will be set to null and attachment will + //not be included with the group notice. + Scene scene = (Scene)remoteClient.Scene; + item = new InventoryItemBase(itemID, ownerID); + item = scene.InventoryService.GetItem(item); - OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); - - foreach (string key in binBucketOSD.Keys) + if (item != null) { - if (binBucketOSD.ContainsKey(key)) - { - m_log.WarnFormat("{0}: {1}", key, binBucketOSD[key].ToString()); - } + //Got item details so include the attachment. + hasAttachment = true; } } - - // treat as if no attachment + else + { + m_log.DebugFormat("[Groups]: Received OSD with unexpected type: {0}", binBucketOSD.GetType()); + } + } + + if (hasAttachment) + { + //Bucket contains information about attachment. + // + //Byte offset and description of bucket data: + //0: 1 byte indicating if attachment is present + //1: 1 byte indicating the type of attachment + //2: 16 bytes - Group UUID + //18: 16 bytes - UUID of the attachment owner + //34: 16 bytes - UUID of the attachment + //50: variable - Name of the attachment + //??: NUL byte to terminate the attachment name + byte[] name = Encoding.UTF8.GetBytes(item.Name); + bucket = new byte[51 + name.Length];//3 bytes, 3 UUIDs, and name + bucket[0] = 1; //Has attachment flag + bucket[1] = (byte)item.InvType; //Type of Attachment + GroupID.ToBytes(bucket, 2); + ownerID.ToBytes(bucket, 18); + itemID.ToBytes(bucket, 34); + name.CopyTo(bucket, 50); + } + else + { bucket = new byte[19]; - bucket[0] = 0; //dunno - bucket[1] = 0; //dunno + bucket[0] = 0; //Has attachment flag + bucket[1] = 0; //Type of attachment GroupID.ToBytes(bucket, 2); - bucket[18] = 0; //dunno + bucket[18] = 0; //NUL terminate name of attachment } - m_groupData.AddGroupNotice(GetRequestingAgentID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket); if (OnNewGroupNotice != null) { OnNewGroupNotice(GroupID, NoticeID); } - // Send notice out to everyone that wants notices - foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID)) + if (m_debugEnabled) { - if (m_debugEnabled) + foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID)) { - UserAccount targetUser = m_sceneList[0].UserAccountService.GetUserAccount(remoteClient.Scene.RegionInfo.ScopeID, member.AgentID); - if (targetUser != null) + if (m_debugEnabled) { - m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices); - } - else - { - m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, member.AgentID, member.AcceptNotices); + UserAccount targetUser + = m_sceneList[0].UserAccountService.GetUserAccount( + remoteClient.Scene.RegionInfo.ScopeID, member.AgentID); + + if (targetUser != null) + { + m_log.DebugFormat( + "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", + NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices); + } + else + { + m_log.DebugFormat( + "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", + NoticeID, member.AgentID, member.AcceptNotices); + } } } + } - if (member.AcceptNotices) - { - // Build notice IIM - GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); + GridInstantMessage msg + = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); - msg.toAgentID = member.AgentID.Guid; - OutgoingInstantMessage(msg, member.AgentID); - } + if (m_groupsMessagingModule != null) + m_groupsMessagingModule.SendMessageToGroup( + msg, GroupID, remoteClient.AgentId, gmd => gmd.AcceptNotices); + } + } + + if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted) + { + //Is bucket large enough to hold UUID of the attachment? + if (im.binaryBucket.Length < 16) + return; + + UUID noticeID = new UUID(im.imSessionID); + + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS]: Requesting notice {0} for {1}", noticeID, remoteClient.AgentId); + + GroupNoticeInfo notice = m_groupData.GetGroupNotice(GetRequestingAgentID(remoteClient), noticeID); + if (notice != null) + { + UUID giver = new UUID(notice.BinaryBucket, 18); + UUID attachmentUUID = new UUID(notice.BinaryBucket, 34); + + if (m_debugEnabled) + m_log.DebugFormat("[Groups]: Giving inventory from {0} to {1}", giver, remoteClient.AgentId); + + string message; + InventoryItemBase itemCopy = ((Scene)(remoteClient.Scene)).GiveInventoryItem(remoteClient.AgentId, + giver, attachmentUUID, out message); + + if (itemCopy == null) + { + remoteClient.SendAgentAlertMessage(message, false); + return; } + + remoteClient.SendInventoryItemCreateUpdate(itemCopy, 0); + } + else + { + if (m_debugEnabled) + m_log.DebugFormat( + "[GROUPS]: Could not find notice {0} for {1} on GroupNoticeInventoryAccepted.", + noticeID, remoteClient.AgentId); } } - + // Interop, received special 210 code for ejecting a group member // this only works within the comms servers domain, and won't work hypergrid - // TODO:FIXME: Use a presense server of some kind to find out where the + // TODO:FIXME: Use a presence server of some kind to find out where the // client actually is, and try contacting that region directly to notify them, // or provide the notification via xmlrpc update queue if ((im.dialog == 210)) @@ -764,7 +841,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups remoteClient.SendCreateGroupReply(UUID.Zero, false, "You have got insufficient funds to create a group."); return UUID.Zero; } - money.ApplyCharge(GetRequestingAgentID(remoteClient), money.GroupCreationCharge, "Group Creation"); + money.ApplyCharge(GetRequestingAgentID(remoteClient), money.GroupCreationCharge, MoneyTransactionType.GroupCreate); } UUID groupID = m_groupData.CreateGroup(GetRequestingAgentID(remoteClient), name, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish, GetRequestingAgentID(remoteClient)); @@ -889,26 +966,10 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups if (data != null) { - GroupRecord groupInfo = m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), data.GroupID, null); - - GridInstantMessage msg = new GridInstantMessage(); - msg.imSessionID = UUID.Zero.Guid; - msg.fromAgentID = data.GroupID.Guid; - msg.toAgentID = GetRequestingAgentID(remoteClient).Guid; - msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); - msg.fromAgentName = "Group Notice : " + groupInfo == null ? "Unknown" : groupInfo.GroupName; - msg.message = data.noticeData.Subject + "|" + data.Message; - msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNoticeRequested; - msg.fromGroup = true; - msg.offline = (byte)0; - msg.ParentEstateID = 0; - msg.Position = Vector3.Zero; - msg.RegionID = UUID.Zero.Guid; - msg.binaryBucket = data.BinaryBucket; + GridInstantMessage msg = CreateGroupNoticeIM(remoteClient.AgentId, groupNoticeID, (byte)InstantMessageDialog.GroupNoticeRequested); OutgoingInstantMessage(msg, GetRequestingAgentID(remoteClient)); } - } public GridInstantMessage CreateGroupNoticeIM(UUID agentID, UUID groupNoticeID, byte dialog) @@ -916,10 +977,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); GridInstantMessage msg = new GridInstantMessage(); - msg.imSessionID = UUID.Zero.Guid; + byte[] bucket; + + msg.imSessionID = groupNoticeID.Guid; msg.toAgentID = agentID.Guid; msg.dialog = dialog; - // msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNotice; msg.fromGroup = true; msg.offline = (byte)0; msg.ParentEstateID = 0; @@ -933,13 +995,38 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups msg.timestamp = info.noticeData.Timestamp; msg.fromAgentName = info.noticeData.FromName; msg.message = info.noticeData.Subject + "|" + info.Message; - msg.binaryBucket = info.BinaryBucket; + + if (info.BinaryBucket[0] > 0) + { + //32 is due to not needing space for two of the UUIDs. + //(Don't need UUID of attachment or its owner in IM) + //50 offset gets us to start of attachment name. + //We are skipping the attachment flag, type, and + //the three UUID fields at the start of the bucket. + bucket = new byte[info.BinaryBucket.Length-32]; + bucket[0] = 1; //Has attachment + bucket[1] = info.BinaryBucket[1]; + Array.Copy(info.BinaryBucket, 50, + bucket, 18, info.BinaryBucket.Length-50); + } + else + { + bucket = new byte[19]; + bucket[0] = 0; //No attachment + bucket[1] = 0; //Attachment type + bucket[18] = 0; //NUL terminate name + } + + info.GroupID.ToBytes(bucket, 2); + msg.binaryBucket = bucket; } else { - if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Group Notice {0} not found, composing empty message.", groupNoticeID); + if (m_debugEnabled) + m_log.DebugFormat("[GROUPS]: Group Notice {0} not found, composing empty message.", groupNoticeID); + msg.fromAgentID = UUID.Zero.Guid; - msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); ; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); msg.fromAgentName = string.Empty; msg.message = string.Empty; msg.binaryBucket = new byte[0]; @@ -1063,7 +1150,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups // Message to ejector // Interop, received special 210 code for ejecting a group member // this only works within the comms servers domain, and won't work hypergrid - // TODO:FIXME: Use a presense server of some kind to find out where the + // TODO:FIXME: Use a presence server of some kind to find out where the // client actually is, and try contacting that region directly to notify them, // or provide the notification via xmlrpc update queue @@ -1178,6 +1265,12 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups } } + public List FindGroups(IClientAPI remoteClient, string query) + { + return m_groupData.FindGroups(GetRequestingAgentID(remoteClient), query); + } + + #endregion #region Client/Update Tools @@ -1222,7 +1315,6 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups AgentDataMap.Add("AgentID", OSD.FromUUID(dataForAgentID)); AgentData.Add(AgentDataMap); - OSDArray GroupData = new OSDArray(data.Length); OSDArray NewGroupData = new OSDArray(data.Length); @@ -1288,7 +1380,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups presence.Grouptitle = Title; if (! presence.IsChildAgent) - presence.SendAvatarDataToAllAgents(); + presence.SendAvatarDataToAllClients(); } } } diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs index 7bae8f7..1cb4747 100644 --- a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs @@ -42,7 +42,6 @@ using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Services.Interfaces; @@ -212,8 +211,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR]: Initializing {0}", this.Name); m_groupsServerURI = groupsConfig.GetString("GroupsServerURI", string.Empty); - if ((m_groupsServerURI == null) || - (m_groupsServerURI == string.Empty)) + if (string.IsNullOrEmpty(m_groupsServerURI)) { m_log.ErrorFormat("Please specify a valid Simian Server for GroupsServerURI in OpenSim.ini, [Groups]"); m_connectorEnabled = false; @@ -438,7 +436,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups return null; } } - else if ((groupName != null) && (groupName != string.Empty)) + else if (!string.IsNullOrEmpty(groupName)) { if (!SimianGetFirstGenericEntry("Group", groupName, out groupID, out GroupInfoMap)) { diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs index c1bdacb..9a42bac 100644 --- a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs @@ -26,15 +26,25 @@ */ using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; using System.Reflection; using Nini.Config; using NUnit.Framework; using OpenMetaverse; +using OpenMetaverse.Messages.Linden; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Avatar.InstantMessage; +using OpenSim.Region.CoreModules.Framework; using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups.Tests { @@ -44,11 +54,28 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups.Tests [TestFixture] public class GroupsModuleTests : OpenSimTestCase { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + uint port = 9999; + uint sslPort = 9998; + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, sslPort, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + } + [Test] - public void TestBasic() + public void TestSendAgentGroupDataUpdate() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); TestScene scene = new SceneHelpers().SetupScene(); IConfigSource configSource = new IniConfigSource(); @@ -56,8 +83,185 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups.Tests config.Set("Enabled", true); config.Set("Module", "GroupsModule"); config.Set("DebugEnabled", true); + + GroupsModule gm = new GroupsModule(); + EventQueueGetModule eqgm = new EventQueueGetModule(); + + // We need a capabilities module active so that adding the scene presence creates an event queue in the + // EventQueueGetModule SceneHelpers.SetupSceneModules( - scene, configSource, new object[] { new MockGroupsServicesConnector() }); + scene, configSource, gm, new MockGroupsServicesConnector(), new CapabilitiesModule(), eqgm); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseStem("1")); + + gm.SendAgentGroupDataUpdate(sp.ControllingClient); + + Hashtable eventsResponse = eqgm.GetEvents(UUID.Zero, sp.UUID); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.OK)); + +// Console.WriteLine("Response [{0}]", (string)eventsResponse["str_response_string"]); + + OSDMap rawOsd = (OSDMap)OSDParser.DeserializeLLSDXml((string)eventsResponse["str_response_string"]); + OSDArray eventsOsd = (OSDArray)rawOsd["events"]; + + bool foundUpdate = false; + foreach (OSD osd in eventsOsd) + { + OSDMap eventOsd = (OSDMap)osd; + + if (eventOsd["message"] == "AgentGroupDataUpdate") + foundUpdate = true; + } + + Assert.That(foundUpdate, Is.True, "Did not find AgentGroupDataUpdate in response"); + + // TODO: More checking of more actual event data. + } + + [Test] + public void TestSendGroupNotice() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + MessageTransferModule mtm = new MessageTransferModule(); + GroupsModule gm = new GroupsModule(); + GroupsMessagingModule gmm = new GroupsMessagingModule(); + MockGroupsServicesConnector mgsc = new MockGroupsServicesConnector(); + + IConfigSource configSource = new IniConfigSource(); + + { + IConfig config = configSource.AddConfig("Messaging"); + config.Set("MessageTransferModule", mtm.Name); + } + + { + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", gm.Name); + config.Set("DebugEnabled", true); + config.Set("MessagingModule", gmm.Name); + config.Set("MessagingEnabled", true); + } + + SceneHelpers.SetupSceneModules(scene, configSource, mgsc, mtm, gm, gmm); + + UUID userId = TestHelpers.ParseTail(0x1); + string subjectText = "newman"; + string messageText = "Hello"; + string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + TestClient tc = (TestClient)sp.ControllingClient; + + UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true); + gm.JoinGroupRequest(tc, groupID); + + // Create a second user who doesn't want to receive notices + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2)); + TestClient tc2 = (TestClient)sp2.ControllingClient; + gm.JoinGroupRequest(tc2, groupID); + gm.SetGroupAcceptNotices(tc2, groupID, false, true); + + List spReceivedMessages = new List(); + tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im); + + List sp2ReceivedMessages = new List(); + tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im); + + GridInstantMessage noticeIm = new GridInstantMessage(); + noticeIm.fromAgentID = userId.Guid; + noticeIm.toAgentID = groupID.Guid; + noticeIm.message = combinedSubjectMessage; + noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice; + + tc.HandleImprovedInstantMessage(noticeIm); + + Assert.That(spReceivedMessages.Count, Is.EqualTo(1)); + Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage)); + + List notices = mgsc.GetGroupNotices(UUID.Zero, groupID); + Assert.AreEqual(1, notices.Count); + + // OpenSimulator (possibly also SL) transport the notice ID as the session ID! + Assert.AreEqual(notices[0].NoticeID.Guid, spReceivedMessages[0].imSessionID); + + Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0)); + } + + /// + /// Run test with the MessageOnlineUsersOnly flag set. + /// + [Test] + public void TestSendGroupNoticeOnlineOnly() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + MessageTransferModule mtm = new MessageTransferModule(); + GroupsModule gm = new GroupsModule(); + GroupsMessagingModule gmm = new GroupsMessagingModule(); + + IConfigSource configSource = new IniConfigSource(); + + { + IConfig config = configSource.AddConfig("Messaging"); + config.Set("MessageTransferModule", mtm.Name); + } + + { + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", gm.Name); + config.Set("DebugEnabled", true); + config.Set("MessagingModule", gmm.Name); + config.Set("MessagingEnabled", true); + config.Set("MessageOnlineUsersOnly", true); + } + + SceneHelpers.SetupSceneModules(scene, configSource, new MockGroupsServicesConnector(), mtm, gm, gmm); + + UUID userId = TestHelpers.ParseTail(0x1); + string subjectText = "newman"; + string messageText = "Hello"; + string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + TestClient tc = (TestClient)sp.ControllingClient; + + UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true); + gm.JoinGroupRequest(tc, groupID); + + // Create a second user who doesn't want to receive notices + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2)); + TestClient tc2 = (TestClient)sp2.ControllingClient; + gm.JoinGroupRequest(tc2, groupID); + gm.SetGroupAcceptNotices(tc2, groupID, false, true); + + List spReceivedMessages = new List(); + tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im); + + List sp2ReceivedMessages = new List(); + tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im); + + GridInstantMessage noticeIm = new GridInstantMessage(); + noticeIm.fromAgentID = userId.Guid; + noticeIm.toAgentID = groupID.Guid; + noticeIm.message = combinedSubjectMessage; + noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice; + + tc.HandleImprovedInstantMessage(noticeIm); + + Assert.That(spReceivedMessages.Count, Is.EqualTo(1)); + Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage)); + + Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0)); } } } \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs index 1101851..20555e4 100644 --- a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs @@ -41,7 +41,6 @@ using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Services.Interfaces; @@ -168,8 +167,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups m_log.DebugFormat("[XMLRPC-GROUPS-CONNECTOR]: Initializing {0}", this.Name); m_groupsServerURI = groupsConfig.GetString("GroupsServerURI", string.Empty); - if ((m_groupsServerURI == null) || - (m_groupsServerURI == string.Empty)) + if (string.IsNullOrEmpty(m_groupsServerURI)) { m_log.ErrorFormat("Please specify a valid URL for GroupsServerURI in OpenSim.ini, [Groups]"); m_connectorEnabled = false; @@ -354,7 +352,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups { param["GroupID"] = GroupID.ToString(); } - if ((GroupName != null) && (GroupName != string.Empty)) + if (!string.IsNullOrEmpty(GroupName)) { param["Name"] = GroupName.ToString(); } @@ -1013,7 +1011,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups Hashtable respData = (Hashtable)resp.Value; if (respData.Contains("error") && !respData.Contains("succeed")) { - LogRespDataToConsoleError(respData); + LogRespDataToConsoleError(requestingAgentID, function, param, respData); } return respData; @@ -1041,20 +1039,11 @@ namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups return error; } - private void LogRespDataToConsoleError(Hashtable respData) + private void LogRespDataToConsoleError(UUID requestingAgentID, string function, Hashtable param, Hashtable respData) { - m_log.Error("[XMLRPC-GROUPS-CONNECTOR]: Error:"); - - foreach (string key in respData.Keys) - { - m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: Key: {0}", key); - - string[] lines = respData[key].ToString().Split(new char[] { '\n' }); - foreach (string line in lines) - { - m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: {0}", line); - } - } + m_log.ErrorFormat( + "[XMLRPC-GROUPS-CONNECTOR]: Error when calling {0} for {1} with params {2}. Response params are {3}", + function, requestingAgentID, Util.PrettyFormatToSingleLine(param), Util.PrettyFormatToSingleLine(respData)); } /// @@ -1146,28 +1135,38 @@ namespace Nwc.XmlRpc request.AllowWriteStreamBuffering = true; request.KeepAlive = !_disableKeepAlive; - Stream stream = request.GetRequestStream(); - XmlTextWriter xml = new XmlTextWriter(stream, Encoding.ASCII); - _serializer.Serialize(xml, this); - xml.Flush(); - xml.Close(); - - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - StreamReader input = new StreamReader(response.GetResponseStream()); - - string inputXml = input.ReadToEnd(); - XmlRpcResponse resp; - try + using (Stream stream = request.GetRequestStream()) { - resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml); + using (XmlTextWriter xml = new XmlTextWriter(stream, Encoding.ASCII)) + { + _serializer.Serialize(xml, this); + xml.Flush(); + } } - catch (Exception e) + + XmlRpcResponse resp; + + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { - RequestResponse = inputXml; - throw e; + using (Stream s = response.GetResponseStream()) + { + using (StreamReader input = new StreamReader(s)) + { + string inputXml = input.ReadToEnd(); + + try + { + resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml); + } + catch (Exception e) + { + RequestResponse = inputXml; + throw e; + } + } + } } - input.Close(); - response.Close(); + return resp; } } diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/DataRequestHandler.cs b/OpenSim/Region/OptionalModules/DataSnapshot/DataRequestHandler.cs new file mode 100644 index 0000000..50276ae --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/DataRequestHandler.cs @@ -0,0 +1,99 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ + +using System.Collections; +using System.Reflection; +using System.Xml; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Scenes; +using Caps = OpenSim.Framework.Capabilities.Caps; + +namespace OpenSim.Region.DataSnapshot +{ + public class DataRequestHandler + { +// private Scene m_scene = null; + private DataSnapshotManager m_externalData = null; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public DataRequestHandler(Scene scene, DataSnapshotManager externalData) + { +// m_scene = scene; + m_externalData = externalData; + + //Register HTTP handler + if (MainServer.Instance.AddHTTPHandler("collector", OnGetSnapshot)) + { + m_log.Info("[DATASNAPSHOT]: Set up snapshot service"); + } + // Register validation callback handler + MainServer.Instance.AddHTTPHandler("validate", OnValidate); + + } + + public Hashtable OnGetSnapshot(Hashtable keysvals) + { + m_log.Debug("[DATASNAPSHOT] Received collection request"); + Hashtable reply = new Hashtable(); + int statuscode = 200; + + string snapObj = (string)keysvals["region"]; + + XmlDocument response = m_externalData.GetSnapshot(snapObj); + + reply["str_response_string"] = response.OuterXml; + reply["int_response_code"] = statuscode; + reply["content_type"] = "text/xml"; + + return reply; + } + + public Hashtable OnValidate(Hashtable keysvals) + { + m_log.Debug("[DATASNAPSHOT] Received validation request"); + Hashtable reply = new Hashtable(); + int statuscode = 200; + + string secret = (string)keysvals["secret"]; + if (secret == m_externalData.Secret.ToString()) + statuscode = 403; + + reply["str_response_string"] = string.Empty; + reply["int_response_code"] = statuscode; + reply["content_type"] = "text/plain"; + + return reply; + } + + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/DataSnapshotManager.cs b/OpenSim/Region/OptionalModules/DataSnapshot/DataSnapshotManager.cs new file mode 100644 index 0000000..0c3446d --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/DataSnapshotManager.cs @@ -0,0 +1,487 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Xml; +using log4net; +using Nini.Config; +using OpenMetaverse; +using Mono.Addins; +using OpenSim.Framework; +using OpenSim.Region.DataSnapshot.Interfaces; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.DataSnapshot +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DataSnapshotManager")] + public class DataSnapshotManager : ISharedRegionModule, IDataSnapshot + { + #region Class members + //Information from config + private bool m_enabled = false; + private bool m_configLoaded = false; + private List m_disabledModules = new List(); + private Dictionary m_gridinfo = new Dictionary(); + private string m_snapsDir = "DataSnapshot"; + private string m_exposure_level = "minimum"; + + //Lists of stuff we need + private List m_scenes = new List(); + private List m_dataproviders = new List(); + + //Various internal objects + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + internal object m_syncInit = new object(); + + //DataServices and networking + private string m_dataServices = "noservices"; + public string m_listener_port = ConfigSettings.DefaultRegionHttpPort.ToString(); + public string m_hostname = "127.0.0.1"; + private UUID m_Secret = UUID.Random(); + private bool m_servicesNotified = false; + + //Update timers + private int m_period = 20; // in seconds + private int m_maxStales = 500; + private int m_stales = 0; + private int m_lastUpdate = 0; + + //Program objects + private SnapshotStore m_snapStore = null; + + #endregion + + #region Properties + + public string ExposureLevel + { + get { return m_exposure_level; } + } + + public UUID Secret + { + get { return m_Secret; } + } + + #endregion + + #region Region Module interface + + public void Initialise(IConfigSource config) + { + if (!m_configLoaded) + { + m_configLoaded = true; + //m_log.Debug("[DATASNAPSHOT]: Loading configuration"); + //Read from the config for options + lock (m_syncInit) + { + try + { + m_enabled = config.Configs["DataSnapshot"].GetBoolean("index_sims", m_enabled); + string gatekeeper = Util.GetConfigVarFromSections(config, "GatekeeperURI", + new string[] { "Startup", "Hypergrid", "GridService" }, String.Empty); + // Legacy. Remove soon! + if (string.IsNullOrEmpty(gatekeeper)) + { + IConfig conf = config.Configs["GridService"]; + if (conf != null) + gatekeeper = conf.GetString("Gatekeeper", gatekeeper); + } + if (!string.IsNullOrEmpty(gatekeeper)) + m_gridinfo.Add("gatekeeperURL", gatekeeper); + + m_gridinfo.Add( + "name", config.Configs["DataSnapshot"].GetString("gridname", "the lost continent of hippo")); + m_exposure_level = config.Configs["DataSnapshot"].GetString("data_exposure", m_exposure_level); + m_period = config.Configs["DataSnapshot"].GetInt("default_snapshot_period", m_period); + m_maxStales = config.Configs["DataSnapshot"].GetInt("max_changes_before_update", m_maxStales); + m_snapsDir = config.Configs["DataSnapshot"].GetString("snapshot_cache_directory", m_snapsDir); + m_listener_port = config.Configs["Network"].GetString("http_listener_port", m_listener_port); + + m_dataServices = config.Configs["DataSnapshot"].GetString("data_services", m_dataServices); + // New way of spec'ing data services, one per line + AddDataServicesVars(config.Configs["DataSnapshot"]); + + String[] annoying_string_array = config.Configs["DataSnapshot"].GetString("disable_modules", "").Split(".".ToCharArray()); + foreach (String bloody_wanker in annoying_string_array) + { + m_disabledModules.Add(bloody_wanker); + } + m_lastUpdate = Environment.TickCount; + } + catch (Exception) + { + m_log.Warn("[DATASNAPSHOT]: Could not load configuration. DataSnapshot will be disabled."); + m_enabled = false; + return; + } + + } + + } + + } + + public void AddRegion(Scene scene) + { + if (!m_enabled) + return; + + m_log.DebugFormat("[DATASNAPSHOT]: Module added to Scene {0}.", scene.RegionInfo.RegionName); + + if (!m_servicesNotified) + { + m_hostname = scene.RegionInfo.ExternalHostName; + m_snapStore = new SnapshotStore(m_snapsDir, m_gridinfo, m_listener_port, m_hostname); + + //Hand it the first scene, assuming that all scenes have the same BaseHTTPServer + new DataRequestHandler(scene, this); + + if (m_dataServices != "" && m_dataServices != "noservices") + NotifyDataServices(m_dataServices, "online"); + + m_servicesNotified = true; + } + + m_scenes.Add(scene); + m_snapStore.AddScene(scene); + + Assembly currentasm = Assembly.GetExecutingAssembly(); + + foreach (Type pluginType in currentasm.GetTypes()) + { + if (pluginType.IsPublic) + { + if (!pluginType.IsAbstract) + { + if (pluginType.GetInterface("IDataSnapshotProvider") != null) + { + IDataSnapshotProvider module = (IDataSnapshotProvider)Activator.CreateInstance(pluginType); + module.Initialize(scene, this); + module.OnStale += MarkDataStale; + + m_dataproviders.Add(module); + m_snapStore.AddProvider(module); + + m_log.Debug("[DATASNAPSHOT]: Added new data provider type: " + pluginType.Name); + } + } + } + } + + } + + public void RemoveRegion(Scene scene) + { + if (!m_enabled) + return; + + m_log.Info("[DATASNAPSHOT]: Region " + scene.RegionInfo.RegionName + " is being removed, removing from indexing"); + Scene restartedScene = SceneForUUID(scene.RegionInfo.RegionID); + + m_scenes.Remove(restartedScene); + m_snapStore.RemoveScene(restartedScene); + + //Getting around the fact that we can't remove objects from a collection we are enumerating over + List providersToRemove = new List(); + + foreach (IDataSnapshotProvider provider in m_dataproviders) + { + if (provider.GetParentScene == restartedScene) + { + providersToRemove.Add(provider); + } + } + + foreach (IDataSnapshotProvider provider in providersToRemove) + { + m_dataproviders.Remove(provider); + m_snapStore.RemoveProvider(provider); + } + + m_snapStore.RemoveScene(restartedScene); + } + + public void PostInitialise() + { + } + + public void RegionLoaded(Scene scene) + { + if (!m_enabled) + return; + + m_log.DebugFormat("[DATASNAPSHOT]: Marking scene {0} as stale.", scene.RegionInfo.RegionName); + m_snapStore.ForceSceneStale(scene); + } + + public void Close() + { + if (!m_enabled) + return; + + if (m_enabled && m_dataServices != "" && m_dataServices != "noservices") + NotifyDataServices(m_dataServices, "offline"); + } + + + public string Name + { + get { return "External Data Generator"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion + + #region Associated helper functions + + public Scene SceneForName(string name) + { + foreach (Scene scene in m_scenes) + if (scene.RegionInfo.RegionName == name) + return scene; + + return null; + } + + public Scene SceneForUUID(UUID id) + { + foreach (Scene scene in m_scenes) + if (scene.RegionInfo.RegionID == id) + return scene; + + return null; + } + + private void AddDataServicesVars(IConfig config) + { + // Make sure the services given this way aren't in m_dataServices already + List servs = new List(m_dataServices.Split(new char[] { ';' })); + + StringBuilder sb = new StringBuilder(); + string[] keys = config.GetKeys(); + + if (keys.Length > 0) + { + IEnumerable serviceKeys = keys.Where(value => value.StartsWith("DATA_SRV_")); + foreach (string serviceKey in serviceKeys) + { + string keyValue = config.GetString(serviceKey, string.Empty).Trim(); + if (!servs.Contains(keyValue)) + sb.Append(keyValue).Append(";"); + } + } + + m_dataServices = (m_dataServices == "noservices") ? sb.ToString() : sb.Append(m_dataServices).ToString(); + } + + #endregion + + #region [Public] Snapshot storage functions + + /** + * Reply to the http request + */ + public XmlDocument GetSnapshot(string regionName) + { + CheckStale(); + + XmlDocument requestedSnap = new XmlDocument(); + requestedSnap.AppendChild(requestedSnap.CreateXmlDeclaration("1.0", null, null)); + requestedSnap.AppendChild(requestedSnap.CreateWhitespace("\r\n")); + + XmlNode regiondata = requestedSnap.CreateNode(XmlNodeType.Element, "regiondata", ""); + try + { + if (string.IsNullOrEmpty(regionName)) + { + XmlNode timerblock = requestedSnap.CreateNode(XmlNodeType.Element, "expire", ""); + timerblock.InnerText = m_period.ToString(); + regiondata.AppendChild(timerblock); + + regiondata.AppendChild(requestedSnap.CreateWhitespace("\r\n")); + foreach (Scene scene in m_scenes) + { + regiondata.AppendChild(m_snapStore.GetScene(scene, requestedSnap)); + } + } + else + { + Scene scene = SceneForName(regionName); + regiondata.AppendChild(m_snapStore.GetScene(scene, requestedSnap)); + } + requestedSnap.AppendChild(regiondata); + regiondata.AppendChild(requestedSnap.CreateWhitespace("\r\n")); + } + catch (XmlException e) + { + m_log.Warn("[DATASNAPSHOT]: XmlException while trying to load snapshot: " + e.ToString()); + requestedSnap = GetErrorMessage(regionName, e); + } + catch (Exception e) + { + m_log.Warn("[DATASNAPSHOT]: Caught unknown exception while trying to load snapshot: " + e.StackTrace); + requestedSnap = GetErrorMessage(regionName, e); + } + + + return requestedSnap; + } + + private XmlDocument GetErrorMessage(string regionName, Exception e) + { + XmlDocument errorMessage = new XmlDocument(); + XmlNode error = errorMessage.CreateNode(XmlNodeType.Element, "error", ""); + XmlNode region = errorMessage.CreateNode(XmlNodeType.Element, "region", ""); + region.InnerText = regionName; + + XmlNode exception = errorMessage.CreateNode(XmlNodeType.Element, "exception", ""); + exception.InnerText = e.ToString(); + + error.AppendChild(region); + error.AppendChild(exception); + errorMessage.AppendChild(error); + + return errorMessage; + } + + #endregion + + #region External data services + private void NotifyDataServices(string servicesStr, string serviceName) + { + Stream reply = null; + string delimStr = ";"; + char [] delimiter = delimStr.ToCharArray(); + + string[] services = servicesStr.Split(delimiter, StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < services.Length; i++) + { + string url = services[i].Trim(); + using (RestClient cli = new RestClient(url)) + { + cli.AddQueryParameter("service", serviceName); + cli.AddQueryParameter("host", m_hostname); + cli.AddQueryParameter("port", m_listener_port); + cli.AddQueryParameter("secret", m_Secret.ToString()); + cli.RequestMethod = "GET"; + try + { + reply = cli.Request(null); + } + catch (WebException) + { + m_log.Warn("[DATASNAPSHOT]: Unable to notify " + url); + } + catch (Exception e) + { + m_log.Warn("[DATASNAPSHOT]: Ignoring unknown exception " + e.ToString()); + } + + byte[] response = new byte[1024]; + // int n = 0; + try + { + // n = reply.Read(response, 0, 1024); + reply.Read(response, 0, 1024); + } + catch (Exception e) + { + m_log.WarnFormat("[DATASNAPSHOT]: Unable to decode reply from data service. Ignoring. {0}", e.StackTrace); + } + // This is not quite working, so... + // string responseStr = Util.UTF8.GetString(response); + m_log.Info("[DATASNAPSHOT]: data service " + url + " notified. Secret: " + m_Secret); + } + } + + } + #endregion + + #region Latency-based update functions + + public void MarkDataStale(IDataSnapshotProvider provider) + { + //Behavior here: Wait m_period seconds, then update if there has not been a request in m_period seconds + //or m_maxStales has been exceeded + m_stales++; + } + + private void CheckStale() + { + // Wrap check + if (Environment.TickCount < m_lastUpdate) + { + m_lastUpdate = Environment.TickCount; + } + + if (m_stales >= m_maxStales) + { + if (Environment.TickCount - m_lastUpdate >= 20000) + { + m_stales = 0; + m_lastUpdate = Environment.TickCount; + MakeEverythingStale(); + } + } + else + { + if (m_lastUpdate + 1000 * m_period < Environment.TickCount) + { + m_stales = 0; + m_lastUpdate = Environment.TickCount; + MakeEverythingStale(); + } + } + } + + public void MakeEverythingStale() + { + m_log.Debug("[DATASNAPSHOT]: Marking all scenes as stale."); + foreach (Scene scene in m_scenes) + { + m_snapStore.ForceSceneStale(scene); + } + } + #endregion + + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/EstateSnapshot.cs b/OpenSim/Region/OptionalModules/DataSnapshot/EstateSnapshot.cs new file mode 100644 index 0000000..8da9e8c --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/EstateSnapshot.cs @@ -0,0 +1,149 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Xml; +using OpenMetaverse; +using OpenSim.Framework; + +using OpenSim.Region.DataSnapshot.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Region.DataSnapshot.Providers +{ + public class EstateSnapshot : IDataSnapshotProvider + { + /* This module doesn't check for changes, since it's *assumed* there are none. + * Nevertheless, it's possible to have changes, since all the fields are public. + * There's no event to subscribe to. :/ + * + * I don't think anything changes the fields beyond RegionModule PostInit, however. + */ + private Scene m_scene = null; + // private DataSnapshotManager m_parent = null; + private bool m_stale = true; + + #region IDataSnapshotProvider Members + + public XmlNode RequestSnapshotData(XmlDocument factory) + { + //Estate data section - contains who owns a set of sims and the name of the set. + //Now in DataSnapshotProvider module form! + XmlNode estatedata = factory.CreateNode(XmlNodeType.Element, "estate", ""); + + UUID ownerid = m_scene.RegionInfo.EstateSettings.EstateOwner; + + UserAccount userInfo = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, ownerid); + //TODO: Change to query userserver about the master avatar UUID ? + String firstname; + String lastname; + + if (userInfo != null) + { + firstname = userInfo.FirstName; + lastname = userInfo.LastName; + + //TODO: Fix the marshalling system to have less copypasta gruntwork + XmlNode user = factory.CreateNode(XmlNodeType.Element, "user", ""); +// XmlAttribute type = (XmlAttribute)factory.CreateNode(XmlNodeType.Attribute, "type", ""); +// type.Value = "owner"; +// user.Attributes.Append(type); + + //TODO: Create more TODOs + XmlNode username = factory.CreateNode(XmlNodeType.Element, "name", ""); + username.InnerText = firstname + " " + lastname; + user.AppendChild(username); + + XmlNode useruuid = factory.CreateNode(XmlNodeType.Element, "uuid", ""); + useruuid.InnerText = ownerid.ToString(); + user.AppendChild(useruuid); + + estatedata.AppendChild(user); + } + + XmlNode estatename = factory.CreateNode(XmlNodeType.Element, "name", ""); + estatename.InnerText = m_scene.RegionInfo.EstateSettings.EstateName.ToString(); + estatedata.AppendChild(estatename); + + XmlNode estateid = factory.CreateNode(XmlNodeType.Element, "id", ""); + estateid.InnerText = m_scene.RegionInfo.EstateSettings.EstateID.ToString(); + estatedata.AppendChild(estateid); + + XmlNode parentid = factory.CreateNode(XmlNodeType.Element, "parentid", ""); + parentid.InnerText = m_scene.RegionInfo.EstateSettings.ParentEstateID.ToString(); + estatedata.AppendChild(parentid); + + XmlNode flags = factory.CreateNode(XmlNodeType.Element, "flags", ""); + + XmlAttribute teleport = (XmlAttribute)factory.CreateNode(XmlNodeType.Attribute, "teleport", ""); + teleport.Value = m_scene.RegionInfo.EstateSettings.AllowDirectTeleport.ToString(); + flags.Attributes.Append(teleport); + + XmlAttribute publicaccess = (XmlAttribute)factory.CreateNode(XmlNodeType.Attribute, "public", ""); + publicaccess.Value = m_scene.RegionInfo.EstateSettings.PublicAccess.ToString(); + flags.Attributes.Append(publicaccess); + + estatedata.AppendChild(flags); + + this.Stale = false; + return estatedata; + } + + public void Initialize(Scene scene, DataSnapshotManager parent) + { + m_scene = scene; + // m_parent = parent; + } + + public Scene GetParentScene + { + get { return m_scene; } + } + + public String Name { + get { return "EstateSnapshot"; } + } + + public bool Stale + { + get { + return m_stale; + } + set { + m_stale = value; + + if (m_stale) + OnStale(this); + } + } + + public event ProviderStale OnStale; + + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshot.cs b/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshot.cs new file mode 100644 index 0000000..3b3db65 --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshot.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Xml; + +namespace OpenSim.Region.DataSnapshot.Interfaces +{ + public interface IDataSnapshot + { + XmlDocument GetSnapshot(string regionName); + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshotProvider.cs b/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshotProvider.cs new file mode 100644 index 0000000..daea373 --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/Interfaces/IDataSnapshotProvider.cs @@ -0,0 +1,46 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ + +using System; +using System.Xml; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.DataSnapshot.Interfaces +{ + public delegate void ProviderStale(IDataSnapshotProvider provider); + + public interface IDataSnapshotProvider + { + XmlNode RequestSnapshotData(XmlDocument document); + void Initialize(Scene scene, DataSnapshotManager parent); + Scene GetParentScene { get; } + String Name { get; } + bool Stale { get; set; } + event ProviderStale OnStale; + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/LLSDDiscovery.cs b/OpenSim/Region/OptionalModules/DataSnapshot/LLSDDiscovery.cs new file mode 100644 index 0000000..54a87f9 --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/LLSDDiscovery.cs @@ -0,0 +1,44 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using OpenSim.Framework.Capabilities; + +namespace OpenSim.Region.DataSnapshot +{ + [OSDMap] + public class LLSDDiscoveryResponse + { + public OSDArray snapshot_resources; + } + + [OSDMap] + public class LLSDDiscoveryDataURL + { + public string snapshot_format; + public string snapshot_url; + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/LandSnapshot.cs b/OpenSim/Region/OptionalModules/DataSnapshot/LandSnapshot.cs new file mode 100644 index 0000000..b8c90cd --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/LandSnapshot.cs @@ -0,0 +1,433 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; + +using OpenSim.Region.CoreModules.World.Land; +using OpenSim.Region.DataSnapshot.Interfaces; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Region.DataSnapshot.Providers +{ + public class LandSnapshot : IDataSnapshotProvider + { + private Scene m_scene = null; + private DataSnapshotManager m_parent = null; + //private Dictionary m_landIndexed = new Dictionary(); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private bool m_stale = true; + + #region Dead code + + /* + * David, I don't think we need this at all. When we do the snapshot, we can + * simply look into the parcels that are marked for ShowDirectory -- see + * conditional in RequestSnapshotData + * + //Revise this, look for more direct way of checking for change in land + #region Client hooks + + public void OnNewClient(IClientAPI client) + { + //Land hooks + client.OnParcelDivideRequest += ParcelSplitHook; + client.OnParcelJoinRequest += ParcelSplitHook; + client.OnParcelPropertiesUpdateRequest += ParcelPropsHook; + } + + public void ParcelSplitHook(int west, int south, int east, int north, IClientAPI remote_client) + { + PrepareData(); + } + + public void ParcelPropsHook(ParcelPropertiesUpdatePacket packet, IClientAPI remote_client) + { + PrepareData(); + } + + #endregion + + public void PrepareData() + { + m_log.Info("[EXTERNALDATA]: Generating land data."); + + m_landIndexed.Clear(); + + //Index sim land + foreach (KeyValuePair curLand in m_scene.LandManager.landList) + { + //if ((curLand.Value.LandData.landFlags & (uint)ParcelFlags.ShowDirectory) == (uint)ParcelFlags.ShowDirectory) + //{ + m_landIndexed.Add(curLand.Key, curLand.Value.Copy()); + //} + } + } + + public Dictionary IndexedLand { + get { return m_landIndexed; } + } + */ + + #endregion + + #region IDataSnapshotProvider members + + public void Initialize(Scene scene, DataSnapshotManager parent) + { + m_scene = scene; + m_parent = parent; + + //Brought back from the dead for staleness checks. + m_scene.EventManager.OnNewClient += OnNewClient; + } + + public Scene GetParentScene + { + get { return m_scene; } + } + + public XmlNode RequestSnapshotData(XmlDocument nodeFactory) + { + ILandChannel landChannel = m_scene.LandChannel; + List parcels = landChannel.AllParcels(); + + IDwellModule dwellModule = m_scene.RequestModuleInterface(); + + XmlNode parent = nodeFactory.CreateNode(XmlNodeType.Element, "parceldata", ""); + if (parcels != null) + { + + //foreach (KeyValuePair curParcel in m_landIndexed) + foreach (ILandObject parcel_interface in parcels) + { + // Play it safe + if (!(parcel_interface is LandObject)) + continue; + + LandObject land = (LandObject)parcel_interface; + + LandData parcel = land.LandData; + if (m_parent.ExposureLevel.Equals("all") || + (m_parent.ExposureLevel.Equals("minimum") && + (parcel.Flags & (uint)ParcelFlags.ShowDirectory) == (uint)ParcelFlags.ShowDirectory)) + { + + //TODO: make better method of marshalling data from LandData to XmlNode + XmlNode xmlparcel = nodeFactory.CreateNode(XmlNodeType.Element, "parcel", ""); + + // Attributes of the parcel node + XmlAttribute scripts_attr = nodeFactory.CreateAttribute("scripts"); + scripts_attr.Value = GetScriptsPermissions(parcel); + XmlAttribute build_attr = nodeFactory.CreateAttribute("build"); + build_attr.Value = GetBuildPermissions(parcel); + XmlAttribute public_attr = nodeFactory.CreateAttribute("public"); + public_attr.Value = GetPublicPermissions(parcel); + // Check the category of the Parcel + XmlAttribute category_attr = nodeFactory.CreateAttribute("category"); + category_attr.Value = ((int)parcel.Category).ToString(); + // Check if the parcel is for sale + XmlAttribute forsale_attr = nodeFactory.CreateAttribute("forsale"); + forsale_attr.Value = CheckForSale(parcel); + XmlAttribute sales_attr = nodeFactory.CreateAttribute("salesprice"); + sales_attr.Value = parcel.SalePrice.ToString(); + + XmlAttribute directory_attr = nodeFactory.CreateAttribute("showinsearch"); + directory_attr.Value = GetShowInSearch(parcel); + //XmlAttribute entities_attr = nodeFactory.CreateAttribute("entities"); + //entities_attr.Value = land.primsOverMe.Count.ToString(); + xmlparcel.Attributes.Append(directory_attr); + xmlparcel.Attributes.Append(scripts_attr); + xmlparcel.Attributes.Append(build_attr); + xmlparcel.Attributes.Append(public_attr); + xmlparcel.Attributes.Append(category_attr); + xmlparcel.Attributes.Append(forsale_attr); + xmlparcel.Attributes.Append(sales_attr); + //xmlparcel.Attributes.Append(entities_attr); + + + //name, description, area, and UUID + XmlNode name = nodeFactory.CreateNode(XmlNodeType.Element, "name", ""); + name.InnerText = parcel.Name; + xmlparcel.AppendChild(name); + + XmlNode desc = nodeFactory.CreateNode(XmlNodeType.Element, "description", ""); + desc.InnerText = parcel.Description; + xmlparcel.AppendChild(desc); + + XmlNode uuid = nodeFactory.CreateNode(XmlNodeType.Element, "uuid", ""); + uuid.InnerText = parcel.GlobalID.ToString(); + xmlparcel.AppendChild(uuid); + + XmlNode area = nodeFactory.CreateNode(XmlNodeType.Element, "area", ""); + area.InnerText = parcel.Area.ToString(); + xmlparcel.AppendChild(area); + + //default location + XmlNode tpLocation = nodeFactory.CreateNode(XmlNodeType.Element, "location", ""); + Vector3 loc = parcel.UserLocation; + if (loc.Equals(Vector3.Zero)) // This test is moot at this point: the location is wrong by default + loc = new Vector3((parcel.AABBMax.X + parcel.AABBMin.X) / 2, (parcel.AABBMax.Y + parcel.AABBMin.Y) / 2, (parcel.AABBMax.Z + parcel.AABBMin.Z) / 2); + tpLocation.InnerText = loc.X.ToString() + "/" + loc.Y.ToString() + "/" + loc.Z.ToString(); + xmlparcel.AppendChild(tpLocation); + + XmlNode infouuid = nodeFactory.CreateNode(XmlNodeType.Element, "infouuid", ""); + uint x = (uint)loc.X, y = (uint)loc.Y; + findPointInParcel(land, ref x, ref y); // find a suitable spot + infouuid.InnerText = Util.BuildFakeParcelID( + m_scene.RegionInfo.RegionHandle, x, y).ToString(); + xmlparcel.AppendChild(infouuid); + + XmlNode dwell = nodeFactory.CreateNode(XmlNodeType.Element, "dwell", ""); + if (dwellModule != null) + dwell.InnerText = dwellModule.GetDwell(parcel.GlobalID).ToString(); + else + dwell.InnerText = "0"; + xmlparcel.AppendChild(dwell); + + //TODO: figure how to figure out teleport system landData.landingType + + //land texture snapshot uuid + if (parcel.SnapshotID != UUID.Zero) + { + XmlNode textureuuid = nodeFactory.CreateNode(XmlNodeType.Element, "image", ""); + textureuuid.InnerText = parcel.SnapshotID.ToString(); + xmlparcel.AppendChild(textureuuid); + } + + string groupName = String.Empty; + + //attached user and group + if (parcel.GroupID != UUID.Zero) + { + XmlNode groupblock = nodeFactory.CreateNode(XmlNodeType.Element, "group", ""); + XmlNode groupuuid = nodeFactory.CreateNode(XmlNodeType.Element, "groupuuid", ""); + groupuuid.InnerText = parcel.GroupID.ToString(); + groupblock.AppendChild(groupuuid); + + IGroupsModule gm = m_scene.RequestModuleInterface(); + if (gm != null) + { + GroupRecord g = gm.GetGroupRecord(parcel.GroupID); + if (g != null) + groupName = g.GroupName; + } + + XmlNode groupname = nodeFactory.CreateNode(XmlNodeType.Element, "groupname", ""); + groupname.InnerText = groupName; + groupblock.AppendChild(groupname); + + xmlparcel.AppendChild(groupblock); + } + + XmlNode userblock = nodeFactory.CreateNode(XmlNodeType.Element, "owner", ""); + + UUID userOwnerUUID = parcel.OwnerID; + + XmlNode useruuid = nodeFactory.CreateNode(XmlNodeType.Element, "uuid", ""); + useruuid.InnerText = userOwnerUUID.ToString(); + userblock.AppendChild(useruuid); + + if (!parcel.IsGroupOwned) + { + try + { + XmlNode username = nodeFactory.CreateNode(XmlNodeType.Element, "name", ""); + UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, userOwnerUUID); + username.InnerText = account.FirstName + " " + account.LastName; + userblock.AppendChild(username); + } + catch (Exception) + { + //m_log.Info("[DATASNAPSHOT]: Cannot find owner name; ignoring this parcel"); + } + + } + else + { + XmlNode username = nodeFactory.CreateNode(XmlNodeType.Element, "name", ""); + username.InnerText = groupName; + userblock.AppendChild(username); + } + + xmlparcel.AppendChild(userblock); + + parent.AppendChild(xmlparcel); + } + } + //snap.AppendChild(parent); + } + + this.Stale = false; + return parent; + } + + public String Name + { + get { return "LandSnapshot"; } + } + + public bool Stale + { + get + { + return m_stale; + } + set + { + m_stale = value; + + if (m_stale) + OnStale(this); + } + } + + public event ProviderStale OnStale; + + #endregion + + #region Helper functions + + private string GetScriptsPermissions(LandData parcel) + { + if ((parcel.Flags & (uint)ParcelFlags.AllowOtherScripts) == (uint)ParcelFlags.AllowOtherScripts) + return "true"; + else + return "false"; + + } + + private string GetPublicPermissions(LandData parcel) + { + if ((parcel.Flags & (uint)ParcelFlags.UseAccessList) == (uint)ParcelFlags.UseAccessList) + return "false"; + else + return "true"; + + } + + private string GetBuildPermissions(LandData parcel) + { + if ((parcel.Flags & (uint)ParcelFlags.CreateObjects) == (uint)ParcelFlags.CreateObjects) + return "true"; + else + return "false"; + + } + + private string CheckForSale(LandData parcel) + { + if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale) + return "true"; + else + return "false"; + } + + private string GetShowInSearch(LandData parcel) + { + if ((parcel.Flags & (uint)ParcelFlags.ShowDirectory) == (uint)ParcelFlags.ShowDirectory) + return "true"; + else + return "false"; + + } + + #endregion + + #region Change detection hooks + + public void OnNewClient(IClientAPI client) + { + //Land hooks + client.OnParcelDivideRequest += delegate(int west, int south, int east, int north, + IClientAPI remote_client) { this.Stale = true; }; + client.OnParcelJoinRequest += delegate(int west, int south, int east, int north, + IClientAPI remote_client) { this.Stale = true; }; + client.OnParcelPropertiesUpdateRequest += delegate(LandUpdateArgs args, int local_id, + IClientAPI remote_client) { this.Stale = true; }; + client.OnParcelBuy += delegate(UUID agentId, UUID groupId, bool final, bool groupOwned, + bool removeContribution, int parcelLocalID, int parcelArea, int parcelPrice, bool authenticated) + { this.Stale = true; }; + } + + public void ParcelSplitHook(int west, int south, int east, int north, IClientAPI remote_client) + { + this.Stale = true; + } + + public void ParcelPropsHook(LandUpdateArgs args, int local_id, IClientAPI remote_client) + { + this.Stale = true; + } + + #endregion + + // this is needed for non-convex parcels (example: rectangular parcel, and in the exact center + // another, smaller rectangular parcel). Both will have the same initial coordinates. + private void findPointInParcel(ILandObject land, ref uint refX, ref uint refY) + { + m_log.DebugFormat("[DATASNAPSHOT] trying {0}, {1}", refX, refY); + // the point we started with already is in the parcel + if (land.ContainsPoint((int)refX, (int)refY)) return; + + // ... otherwise, we have to search for a point within the parcel + uint startX = (uint)land.LandData.AABBMin.X; + uint startY = (uint)land.LandData.AABBMin.Y; + uint endX = (uint)land.LandData.AABBMax.X; + uint endY = (uint)land.LandData.AABBMax.Y; + + // default: center of the parcel + refX = (startX + endX) / 2; + refY = (startY + endY) / 2; + // If the center point is within the parcel, take that one + if (land.ContainsPoint((int)refX, (int)refY)) return; + + // otherwise, go the long way. + for (uint y = startY; y <= endY; ++y) + { + for (uint x = startX; x <= endX; ++x) + { + if (land.ContainsPoint((int)x, (int)y)) + { + // found a point + refX = x; + refY = y; + return; + } + } + } + } + } +} + diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/ObjectSnapshot.cs b/OpenSim/Region/OptionalModules/DataSnapshot/ObjectSnapshot.cs new file mode 100644 index 0000000..0bb4044 --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/ObjectSnapshot.cs @@ -0,0 +1,264 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.DataSnapshot.Interfaces; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.DataSnapshot.Providers +{ + public class ObjectSnapshot : IDataSnapshotProvider + { + private Scene m_scene = null; + // private DataSnapshotManager m_parent = null; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private bool m_stale = true; + + private static UUID m_DefaultImage = new UUID("89556747-24cb-43ed-920b-47caed15465f"); + private static UUID m_BlankImage = new UUID("5748decc-f629-461c-9a36-a35a221fe21f"); + + + public void Initialize(Scene scene, DataSnapshotManager parent) + { + m_scene = scene; + // m_parent = parent; + + //To check for staleness, we must catch all incoming client packets. + m_scene.EventManager.OnNewClient += OnNewClient; + m_scene.EventManager.OnParcelPrimCountAdd += delegate(SceneObjectGroup obj) { this.Stale = true; }; + } + + public void OnNewClient(IClientAPI client) + { + //Detect object data changes by hooking into the IClientAPI. + //Very dirty, and breaks whenever someone changes the client API. + + client.OnAddPrim += delegate (UUID ownerID, UUID groupID, Vector3 RayEnd, Quaternion rot, + PrimitiveBaseShape shape, byte bypassRaycast, Vector3 RayStart, UUID RayTargetID, + byte RayEndIsIntersection) { this.Stale = true; }; + client.OnLinkObjects += delegate (IClientAPI remoteClient, uint parent, List children) + { this.Stale = true; }; + client.OnDelinkObjects += delegate(List primIds, IClientAPI clientApi) { this.Stale = true; }; + client.OnGrabUpdate += delegate(UUID objectID, Vector3 offset, Vector3 grapPos, + IClientAPI remoteClient, List surfaceArgs) { this.Stale = true; }; + client.OnObjectAttach += delegate(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, + bool silent) { this.Stale = true; }; + client.OnObjectDuplicate += delegate(uint localID, Vector3 offset, uint dupeFlags, UUID AgentID, + UUID GroupID) { this.Stale = true; }; + client.OnObjectDuplicateOnRay += delegate(uint localID, uint dupeFlags, UUID AgentID, UUID GroupID, + UUID RayTargetObj, Vector3 RayEnd, Vector3 RayStart, bool BypassRaycast, + bool RayEndIsIntersection, bool CopyCenters, bool CopyRotates) { this.Stale = true; }; + client.OnObjectIncludeInSearch += delegate(IClientAPI remoteClient, bool IncludeInSearch, uint localID) + { this.Stale = true; }; + client.OnObjectPermissions += delegate(IClientAPI controller, UUID agentID, UUID sessionID, + byte field, uint localId, uint mask, byte set) { this.Stale = true; }; + client.OnRezObject += delegate(IClientAPI remoteClient, UUID itemID, Vector3 RayEnd, + Vector3 RayStart, UUID RayTargetID, byte BypassRayCast, bool RayEndIsIntersection, + bool RezSelected, + bool RemoveItem, UUID fromTaskID) { this.Stale = true; }; + } + + public Scene GetParentScene + { + get { return m_scene; } + } + + public XmlNode RequestSnapshotData(XmlDocument nodeFactory) + { + m_log.Debug("[DATASNAPSHOT]: Generating object data for scene " + m_scene.RegionInfo.RegionName); + + XmlNode parent = nodeFactory.CreateNode(XmlNodeType.Element, "objectdata", ""); + XmlNode node; + + EntityBase[] entities = m_scene.Entities.GetEntities(); + foreach (EntityBase entity in entities) + { + // only objects, not avatars + if (entity is SceneObjectGroup) + { + SceneObjectGroup obj = (SceneObjectGroup)entity; + +// m_log.Debug("[DATASNAPSHOT]: Found object " + obj.Name + " in scene"); + + // libomv will complain about PrimFlags.JointWheel + // being obsolete, so we... + #pragma warning disable 0612 + if ((obj.RootPart.Flags & PrimFlags.JointWheel) == PrimFlags.JointWheel) + { + SceneObjectPart m_rootPart = obj.RootPart; + + ILandObject land = m_scene.LandChannel.GetLandObject(m_rootPart.AbsolutePosition.X, m_rootPart.AbsolutePosition.Y); + + XmlNode xmlobject = nodeFactory.CreateNode(XmlNodeType.Element, "object", ""); + node = nodeFactory.CreateNode(XmlNodeType.Element, "uuid", ""); + node.InnerText = obj.UUID.ToString(); + xmlobject.AppendChild(node); + + node = nodeFactory.CreateNode(XmlNodeType.Element, "title", ""); + node.InnerText = m_rootPart.Name; + xmlobject.AppendChild(node); + + node = nodeFactory.CreateNode(XmlNodeType.Element, "description", ""); + node.InnerText = m_rootPart.Description; + xmlobject.AppendChild(node); + + node = nodeFactory.CreateNode(XmlNodeType.Element, "flags", ""); + node.InnerText = String.Format("{0:x}", (uint)m_rootPart.Flags); + xmlobject.AppendChild(node); + + node = nodeFactory.CreateNode(XmlNodeType.Element, "regionuuid", ""); + node.InnerText = m_scene.RegionInfo.RegionSettings.RegionUUID.ToString(); + xmlobject.AppendChild(node); + + if (land != null && land.LandData != null) + { + node = nodeFactory.CreateNode(XmlNodeType.Element, "parceluuid", ""); + node.InnerText = land.LandData.GlobalID.ToString(); + xmlobject.AppendChild(node); + } + else + { + // Something is wrong with this object. Let's not list it. + m_log.WarnFormat("[DATASNAPSHOT]: Bad data for object {0} ({1}) in region {2}", obj.Name, obj.UUID, m_scene.RegionInfo.RegionName); + continue; + } + + node = nodeFactory.CreateNode(XmlNodeType.Element, "location", ""); + Vector3 loc = obj.AbsolutePosition; + node.InnerText = loc.X.ToString() + "/" + loc.Y.ToString() + "/" + loc.Z.ToString(); + xmlobject.AppendChild(node); + + string bestImage = GuessImage(obj); + if (bestImage != string.Empty) + { + node = nodeFactory.CreateNode(XmlNodeType.Element, "image", ""); + node.InnerText = bestImage; + xmlobject.AppendChild(node); + } + + parent.AppendChild(xmlobject); + } + #pragma warning disable 0612 + } + } + this.Stale = false; + return parent; + } + + public String Name + { + get { return "ObjectSnapshot"; } + } + + public bool Stale + { + get + { + return m_stale; + } + set + { + m_stale = value; + + if (m_stale) + OnStale(this); + } + } + + public event ProviderStale OnStale; + + /// + /// Guesses the best image, based on a simple heuristic. It guesses only for boxes. + /// We're optimizing for boxes, because those are the most common objects + /// marked "Show in search" -- boxes with content inside.For other shapes, + /// it's really hard to tell which texture should be grabbed. + /// + /// + /// + private string GuessImage(SceneObjectGroup sog) + { + string bestguess = string.Empty; + Dictionary counts = new Dictionary(); + + PrimitiveBaseShape shape = sog.RootPart.Shape; + if (shape != null && shape.ProfileShape == ProfileShape.Square) + { + Primitive.TextureEntry textures = shape.Textures; + if (textures != null) + { + if (textures.DefaultTexture != null && + textures.DefaultTexture.TextureID != UUID.Zero && + textures.DefaultTexture.TextureID != m_DefaultImage && + textures.DefaultTexture.TextureID != m_BlankImage && + textures.DefaultTexture.RGBA.A < 50f) + { + counts[textures.DefaultTexture.TextureID] = 8; + } + + if (textures.FaceTextures != null) + { + foreach (Primitive.TextureEntryFace tentry in textures.FaceTextures) + { + if (tentry != null) + { + if (tentry.TextureID != UUID.Zero && tentry.TextureID != m_DefaultImage && tentry.TextureID != m_BlankImage && tentry.RGBA.A < 50) + { + int c = 0; + counts.TryGetValue(tentry.TextureID, out c); + counts[tentry.TextureID] = c + 1; + // decrease the default texture count + if (counts.ContainsKey(textures.DefaultTexture.TextureID)) + counts[textures.DefaultTexture.TextureID] = counts[textures.DefaultTexture.TextureID] - 1; + } + } + } + } + + // Let's pick the most unique texture + int min = 9999; + foreach (KeyValuePair kv in counts) + { + if (kv.Value < min && kv.Value >= 1) + { + bestguess = kv.Key.ToString(); + min = kv.Value; + } + } + } + } + + return bestguess; + } + } +} diff --git a/OpenSim/Region/OptionalModules/DataSnapshot/SnapshotStore.cs b/OpenSim/Region/OptionalModules/DataSnapshot/SnapshotStore.cs new file mode 100644 index 0000000..480aaaf --- /dev/null +++ b/OpenSim/Region/OptionalModules/DataSnapshot/SnapshotStore.cs @@ -0,0 +1,337 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSimulator Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using log4net; +using OpenSim.Region.DataSnapshot.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.DataSnapshot +{ + public class SnapshotStore + { + #region Class Members + private String m_directory = "unyuu"; //not an attempt at adding RM references to core SVN, honest + private Dictionary m_scenes = null; + private List m_providers = null; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Dictionary m_gridinfo = null; + private bool m_cacheEnabled = true; + private string m_listener_port = "9000"; //TODO: Set default port over 9000 + private string m_hostname = "127.0.0.1"; + #endregion + + public SnapshotStore(string directory, Dictionary gridinfo, string port, string hostname) { + m_directory = directory; + m_scenes = new Dictionary(); + m_providers = new List(); + m_gridinfo = gridinfo; + m_listener_port = port; + m_hostname = hostname; + + if (Directory.Exists(m_directory)) + { + m_log.Info("[DATASNAPSHOT]: Response and fragment cache directory already exists."); + } + else + { + // Try to create the directory. + m_log.Info("[DATASNAPSHOT]: Creating directory " + m_directory); + try + { + Directory.CreateDirectory(m_directory); + } + catch (Exception e) + { + m_log.Error("[DATASNAPSHOT]: Failed to create directory " + m_directory, e); + + //This isn't a horrible problem, just disable cacheing. + m_cacheEnabled = false; + m_log.Error("[DATASNAPSHOT]: Could not create directory, response cache has been disabled."); + } + } + } + + public void ForceSceneStale(Scene scene) { + m_scenes[scene] = true; + } + + #region Fragment storage + public XmlNode GetFragment(IDataSnapshotProvider provider, XmlDocument factory) + { + XmlNode data = null; + + if (provider.Stale || !m_cacheEnabled) + { + data = provider.RequestSnapshotData(factory); + + if (m_cacheEnabled) + { + String path = DataFileNameFragment(provider.GetParentScene, provider.Name); + + try + { + using (XmlTextWriter snapXWriter = new XmlTextWriter(path, Encoding.Default)) + { + snapXWriter.Formatting = Formatting.Indented; + snapXWriter.WriteStartDocument(); + data.WriteTo(snapXWriter); + snapXWriter.WriteEndDocument(); + } + } + catch (Exception e) + { + m_log.WarnFormat("[DATASNAPSHOT]: Exception on writing to file {0}: {1}", path, e.Message); + } + + } + + //mark provider as not stale, parent scene as stale + provider.Stale = false; + m_scenes[provider.GetParentScene] = true; + + m_log.Debug("[DATASNAPSHOT]: Generated fragment response for provider type " + provider.Name); + } + else + { + String path = DataFileNameFragment(provider.GetParentScene, provider.Name); + + XmlDocument fragDocument = new XmlDocument(); + fragDocument.PreserveWhitespace = true; + fragDocument.Load(path); + foreach (XmlNode node in fragDocument) + { + data = factory.ImportNode(node, true); + } + + m_log.Debug("[DATASNAPSHOT]: Retrieved fragment response for provider type " + provider.Name); + } + + return data; + } + #endregion + + #region Response storage + public XmlNode GetScene(Scene scene, XmlDocument factory) + { + m_log.Debug("[DATASNAPSHOT]: Data requested for scene " + scene.RegionInfo.RegionName); + + if (!m_scenes.ContainsKey(scene)) { + m_scenes.Add(scene, true); //stale by default + } + + XmlNode regionElement = null; + + if (!m_scenes[scene]) + { + m_log.Debug("[DATASNAPSHOT]: Attempting to retrieve snapshot from cache."); + //get snapshot from cache + String path = DataFileNameScene(scene); + + XmlDocument fragDocument = new XmlDocument(); + fragDocument.PreserveWhitespace = true; + + fragDocument.Load(path); + + foreach (XmlNode node in fragDocument) + { + regionElement = factory.ImportNode(node, true); + } + + m_log.Debug("[DATASNAPSHOT]: Obtained snapshot from cache for " + scene.RegionInfo.RegionName); + } + else + { + m_log.Debug("[DATASNAPSHOT]: Attempting to generate snapshot."); + //make snapshot + regionElement = MakeRegionNode(scene, factory); + + regionElement.AppendChild(GetGridSnapshotData(factory)); + XmlNode regionData = factory.CreateNode(XmlNodeType.Element, "data", ""); + + foreach (IDataSnapshotProvider dataprovider in m_providers) + { + if (dataprovider.GetParentScene == scene) + { + regionData.AppendChild(GetFragment(dataprovider, factory)); + } + } + + regionElement.AppendChild(regionData); + + factory.AppendChild(regionElement); + + //save snapshot + String path = DataFileNameScene(scene); + + try + { + using (XmlTextWriter snapXWriter = new XmlTextWriter(path, Encoding.Default)) + { + snapXWriter.Formatting = Formatting.Indented; + snapXWriter.WriteStartDocument(); + regionElement.WriteTo(snapXWriter); + snapXWriter.WriteEndDocument(); + } + } + catch (Exception e) + { + m_log.WarnFormat("[DATASNAPSHOT]: Exception on writing to file {0}: {1}", path, e.Message); + } + + m_scenes[scene] = false; + + m_log.Debug("[DATASNAPSHOT]: Generated new snapshot for " + scene.RegionInfo.RegionName); + } + + return regionElement; + } + + #endregion + + #region Helpers + private string DataFileNameFragment(Scene scene, String fragmentName) + { + return Path.Combine(m_directory, Path.ChangeExtension(Sanitize(scene.RegionInfo.RegionName + "_" + fragmentName), "xml")); + } + + private string DataFileNameScene(Scene scene) + { + return Path.Combine(m_directory, Path.ChangeExtension(Sanitize(scene.RegionInfo.RegionName), "xml")); + //return (m_snapsDir + Path.DirectorySeparatorChar + scene.RegionInfo.RegionName + ".xml"); + } + + private static string Sanitize(string name) + { + string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); + string invalidReStr = string.Format(@"[{0}]", invalidChars); + string newname = Regex.Replace(name, invalidReStr, "_"); + return newname.Replace('.', '_'); + } + + private XmlNode MakeRegionNode(Scene scene, XmlDocument basedoc) + { + XmlNode docElement = basedoc.CreateNode(XmlNodeType.Element, "region", ""); + + XmlAttribute attr = basedoc.CreateAttribute("category"); + attr.Value = GetRegionCategory(scene); + docElement.Attributes.Append(attr); + + attr = basedoc.CreateAttribute("entities"); + attr.Value = scene.Entities.Count.ToString(); + docElement.Attributes.Append(attr); + + //attr = basedoc.CreateAttribute("parcels"); + //attr.Value = scene.LandManager.landList.Count.ToString(); + //docElement.Attributes.Append(attr); + + + XmlNode infoblock = basedoc.CreateNode(XmlNodeType.Element, "info", ""); + + XmlNode infopiece = basedoc.CreateNode(XmlNodeType.Element, "uuid", ""); + infopiece.InnerText = scene.RegionInfo.RegionID.ToString(); + infoblock.AppendChild(infopiece); + + infopiece = basedoc.CreateNode(XmlNodeType.Element, "url", ""); + infopiece.InnerText = "http://" + m_hostname + ":" + m_listener_port; + infoblock.AppendChild(infopiece); + + infopiece = basedoc.CreateNode(XmlNodeType.Element, "name", ""); + infopiece.InnerText = scene.RegionInfo.RegionName; + infoblock.AppendChild(infopiece); + + infopiece = basedoc.CreateNode(XmlNodeType.Element, "handle", ""); + infopiece.InnerText = scene.RegionInfo.RegionHandle.ToString(); + infoblock.AppendChild(infopiece); + + docElement.AppendChild(infoblock); + + m_log.Debug("[DATASNAPSHOT]: Generated region node"); + return docElement; + } + + private String GetRegionCategory(Scene scene) + { + if (scene.RegionInfo.RegionSettings.Maturity == 0) + return "PG"; + + if (scene.RegionInfo.RegionSettings.Maturity == 1) + return "Mature"; + + if (scene.RegionInfo.RegionSettings.Maturity == 2) + return "Adult"; + + return "Unknown"; + } + + private XmlNode GetGridSnapshotData(XmlDocument factory) + { + XmlNode griddata = factory.CreateNode(XmlNodeType.Element, "grid", ""); + + foreach (KeyValuePair GridData in m_gridinfo) + { + //TODO: make it lowercase tag names for diva + XmlNode childnode = factory.CreateNode(XmlNodeType.Element, GridData.Key, ""); + childnode.InnerText = GridData.Value; + griddata.AppendChild(childnode); + } + + m_log.Debug("[DATASNAPSHOT]: Got grid snapshot data"); + + return griddata; + } + #endregion + + #region Manage internal collections + public void AddScene(Scene newScene) + { + m_scenes.Add(newScene, true); + } + + public void RemoveScene(Scene deadScene) + { + m_scenes.Remove(deadScene); + } + + public void AddProvider(IDataSnapshotProvider newProvider) + { + m_providers.Add(newProvider); + } + + public void RemoveProvider(IDataSnapshotProvider deadProvider) + { + m_providers.Remove(deadProvider); + } + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs b/OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs index 7d37135..bbf7168 100644 --- a/OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs +++ b/OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs @@ -33,6 +33,12 @@ using Nini.Config; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +// You will need to uncomment these lines if you are adding a region module to some other assembly which does not already +// specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans +// the available DLLs +//[assembly: Addin("MyModule", "1.0")] +//[assembly: AddinDependency("OpenSim", "0.8.1")] + namespace OpenSim.Region.OptionalModules.Example.BareBonesNonShared { /// diff --git a/OpenSim/Region/OptionalModules/Example/BareBonesShared/BareBonesSharedModule.cs b/OpenSim/Region/OptionalModules/Example/BareBonesShared/BareBonesSharedModule.cs index 781fe95..46fea3e 100644 --- a/OpenSim/Region/OptionalModules/Example/BareBonesShared/BareBonesSharedModule.cs +++ b/OpenSim/Region/OptionalModules/Example/BareBonesShared/BareBonesSharedModule.cs @@ -33,6 +33,12 @@ using Nini.Config; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +// You will need to uncomment these lines if you are adding a region module to some other assembly which does not already +// specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans +// the available DLLs +//[assembly: Addin("MyModule", "1.0")] +//[assembly: AddinDependency("OpenSim", "0.8.1")] + namespace OpenSim.Region.OptionalModules.Example.BareBonesShared { /// diff --git a/OpenSim/Region/OptionalModules/Example/WebSocketEchoTest/WebSocketEchoModule.cs b/OpenSim/Region/OptionalModules/Example/WebSocketEchoTest/WebSocketEchoModule.cs new file mode 100644 index 0000000..5bf0ed4 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Example/WebSocketEchoTest/WebSocketEchoModule.cs @@ -0,0 +1,175 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenSim.Framework.Servers; +using Mono.Addins; +using log4net; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Framework.Servers.HttpServer; + + +namespace OpenSim.Region.OptionalModules.WebSocketEchoModule +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WebSocketEchoModule")] + public class WebSocketEchoModule : ISharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool enabled; + public string Name { get { return "WebSocketEchoModule"; } } + + public Type ReplaceableInterface { get { return null; } } + + + private HashSet _activeHandlers = new HashSet(); + + public void Initialise(IConfigSource pConfig) + { + enabled = (pConfig.Configs["WebSocketEcho"] != null); +// if (enabled) +// m_log.DebugFormat("[WebSocketEchoModule]: INITIALIZED MODULE"); + } + + /// + /// This method sets up the callback to WebSocketHandlerCallback below when a HTTPRequest comes in for /echo + /// + public void PostInitialise() + { + if (enabled) + MainServer.Instance.AddWebSocketHandler("/echo", WebSocketHandlerCallback); + } + + // This gets called by BaseHttpServer and gives us an opportunity to set things on the WebSocket handler before we turn it on + public void WebSocketHandlerCallback(string path, WebSocketHttpServerHandler handler) + { + SubscribeToEvents(handler); + handler.SetChunksize(8192); + handler.NoDelay_TCP_Nagle = true; + handler.HandshakeAndUpgrade(); + } + + //These are our normal events + public void SubscribeToEvents(WebSocketHttpServerHandler handler) + { + handler.OnClose += HandlerOnOnClose; + handler.OnText += HandlerOnOnText; + handler.OnUpgradeCompleted += HandlerOnOnUpgradeCompleted; + handler.OnData += HandlerOnOnData; + handler.OnPong += HandlerOnOnPong; + } + + public void UnSubscribeToEvents(WebSocketHttpServerHandler handler) + { + handler.OnClose -= HandlerOnOnClose; + handler.OnText -= HandlerOnOnText; + handler.OnUpgradeCompleted -= HandlerOnOnUpgradeCompleted; + handler.OnData -= HandlerOnOnData; + handler.OnPong -= HandlerOnOnPong; + } + + private void HandlerOnOnPong(object sender, PongEventArgs pongdata) + { + m_log.Info("[WebSocketEchoModule]: Got a pong.. ping time: " + pongdata.PingResponseMS); + } + + private void HandlerOnOnData(object sender, WebsocketDataEventArgs data) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + obj.SendData(data.Data); + m_log.Info("[WebSocketEchoModule]: We received a bunch of ugly non-printable bytes"); + obj.SendPingCheck(); + } + + + private void HandlerOnOnUpgradeCompleted(object sender, UpgradeCompletedEventArgs completeddata) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + _activeHandlers.Add(obj); + } + + private void HandlerOnOnText(object sender, WebsocketTextEventArgs text) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + obj.SendMessage(text.Data); + m_log.Info("[WebSocketEchoModule]: We received this: " + text.Data); + } + + // Remove the references to our handler + private void HandlerOnOnClose(object sender, CloseEventArgs closedata) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + UnSubscribeToEvents(obj); + + lock (_activeHandlers) + _activeHandlers.Remove(obj); + obj.Dispose(); + } + + // Shutting down.. so shut down all sockets. + // Note.. this should be done outside of an ienumerable if you're also hook to the close event. + public void Close() + { + if (!enabled) + return; + + // We convert this to a for loop so we're not in in an IEnumerable when the close + //call triggers an event which then removes item from _activeHandlers that we're enumerating + WebSocketHttpServerHandler[] items = new WebSocketHttpServerHandler[_activeHandlers.Count]; + _activeHandlers.CopyTo(items); + + for (int i = 0; i < items.Length; i++) + { + items[i].Close(string.Empty); + items[i].Dispose(); + } + _activeHandlers.Clear(); + MainServer.Instance.RemoveWebSocketHandler("/echo"); + } + + public void AddRegion(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName); + } + + public void RemoveRegion(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + } + + public void RegionLoaded(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} LOADED", scene.RegionInfo.RegionName); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs new file mode 100644 index 0000000..e95889d --- /dev/null +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -0,0 +1,608 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; // for computing md5 hash +using log4net; +using Mono.Addins; +using Nini.Config; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType; + +using Ionic.Zlib; + +// You will need to uncomment these lines if you are adding a region module to some other assembly which does not already +// specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans +// the available DLLs +//[assembly: Addin("MaterialsModule", "1.0")] +//[assembly: AddinDependency("OpenSim", "0.8.1")] + +namespace OpenSim.Region.OptionalModules.Materials +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsModule")] + public class MaterialsModule : INonSharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public string Name { get { return "MaterialsModule"; } } + + public Type ReplaceableInterface { get { return null; } } + + private Scene m_scene = null; + private bool m_enabled = false; + private int m_maxMaterialsPerTransaction = 50; + + public Dictionary m_regionMaterials = new Dictionary(); + + public void Initialise(IConfigSource source) + { + m_enabled = true; // default is enabled + + IConfig config = source.Configs["Materials"]; + if (config != null) + { + m_enabled = config.GetBoolean("enable_materials", m_enabled); + m_maxMaterialsPerTransaction = config.GetInt("MaxMaterialsPerTransaction", m_maxMaterialsPerTransaction); + } + + if (m_enabled) + m_log.DebugFormat("[Materials]: Initialized"); + } + + public void Close() + { + if (!m_enabled) + return; + } + + public void AddRegion(Scene scene) + { + if (!m_enabled) + return; + + m_scene = scene; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; + m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene; + } + + private void EventManager_OnObjectAddedToScene(SceneObjectGroup obj) + { + foreach (var part in obj.Parts) + if (part != null) + GetStoredMaterialsInPart(part); + } + + private void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps) + { + string capsBase = "/CAPS/" + caps.CapsObjectPath; + + IRequestHandler renderMaterialsPostHandler + = new RestStreamHandler("POST", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsPostCap(request, agentID), + "RenderMaterials", null); + caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler); + + // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET + // and POST handlers, (at least at the time this was originally written), so we first set up a POST + // handler normally and then add a GET handler via MainServer + + IRequestHandler renderMaterialsGetHandler + = new RestStreamHandler("GET", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsGetCap(request), + "RenderMaterials", null); + MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler); + + // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well + IRequestHandler renderMaterialsPutHandler + = new RestStreamHandler("PUT", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsPostCap(request, agentID), + "RenderMaterials", null); + MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler); + } + + public void RemoveRegion(Scene scene) + { + if (!m_enabled) + return; + + m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; + m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene; + } + + public void RegionLoaded(Scene scene) + { + if (!m_enabled) return; + + ISimulatorFeaturesModule featuresModule = scene.RequestModuleInterface(); + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + } + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + features["MaxMaterialsPerTransaction"] = m_maxMaterialsPerTransaction; + } + + /// + /// Finds any legacy materials stored in DynAttrs that may exist for this part and add them to 'm_regionMaterials'. + /// + /// + private void GetLegacyStoredMaterialsInPart(SceneObjectPart part) + { + if (part.DynAttrs == null) + return; + + OSD OSMaterials = null; + OSDArray matsArr = null; + + lock (part.DynAttrs) + { + if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) + { + OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); + + if (materialsStore == null) + return; + + materialsStore.TryGetValue("Materials", out OSMaterials); + } + + if (OSMaterials != null && OSMaterials is OSDArray) + matsArr = OSMaterials as OSDArray; + else + return; + } + + if (matsArr == null) + return; + + foreach (OSD elemOsd in matsArr) + { + if (elemOsd != null && elemOsd is OSDMap) + { + OSDMap matMap = elemOsd as OSDMap; + if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material")) + { + try + { + lock (m_regionMaterials) + m_regionMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"]; + } + catch (Exception e) + { + m_log.Warn("[Materials]: exception decoding persisted legacy material: " + e.ToString()); + } + } + } + } + } + + /// + /// Find the materials used in the SOP, and add them to 'm_regionMaterials'. + /// + private void GetStoredMaterialsInPart(SceneObjectPart part) + { + if (part.Shape == null) + return; + + var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); + if (te == null) + return; + + GetLegacyStoredMaterialsInPart(part); + + if (te.DefaultTexture != null) + GetStoredMaterialInFace(part, te.DefaultTexture); + else + m_log.WarnFormat( + "[Materials]: Default texture for part {0} (part of object {1}) in {2} unexpectedly null. Ignoring.", + part.Name, part.ParentGroup.Name, m_scene.Name); + + foreach (Primitive.TextureEntryFace face in te.FaceTextures) + { + if (face != null) + GetStoredMaterialInFace(part, face); + } + } + + /// + /// Find the materials used in one Face, and add them to 'm_regionMaterials'. + /// + private void GetStoredMaterialInFace(SceneObjectPart part, Primitive.TextureEntryFace face) + { + UUID id = face.MaterialID; + if (id == UUID.Zero) + return; + + lock (m_regionMaterials) + { + if (m_regionMaterials.ContainsKey(id)) + return; + + byte[] data = m_scene.AssetService.GetData(id.ToString()); + if (data == null) + { + m_log.WarnFormat("[Materials]: Prim \"{0}\" ({1}) contains unknown material ID {2}", part.Name, part.UUID, id); + return; + } + + OSDMap mat; + try + { + mat = (OSDMap)OSDParser.DeserializeLLSDXml(data); + } + catch (Exception e) + { + m_log.WarnFormat("[Materials]: cannot decode material asset {0}: {1}", id, e.Message); + return; + } + + m_regionMaterials[id] = mat; + } + } + + public string RenderMaterialsPostCap(string request, UUID agentID) + { + OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); + OSDMap resp = new OSDMap(); + + OSDMap materialsFromViewer = null; + + OSDArray respArr = new OSDArray(); + + if (req.ContainsKey("Zipped")) + { + OSD osd = null; + + byte[] inBytes = req["Zipped"].AsBinary(); + + try + { + osd = ZDecompressBytesToOsd(inBytes); + + if (osd != null) + { + if (osd is OSDArray) // assume array of MaterialIDs designating requested material entries + { + foreach (OSD elem in (OSDArray)osd) + { + try + { + UUID id = new UUID(elem.AsBinary(), 0); + + lock (m_regionMaterials) + { + if (m_regionMaterials.ContainsKey(id)) + { + OSDMap matMap = new OSDMap(); + matMap["ID"] = OSD.FromBinary(id.GetBytes()); + matMap["Material"] = m_regionMaterials[id]; + respArr.Add(matMap); + } + else + { + m_log.Warn("[Materials]: request for unknown material ID: " + id.ToString()); + + // Theoretically we could try to load the material from the assets service, + // but that shouldn't be necessary because the viewer should only request + // materials that exist in a prim on the region, and all of these materials + // are already stored in m_regionMaterials. + } + } + } + catch (Exception e) + { + m_log.Error("Error getting materials in response to viewer request", e); + continue; + } + } + } + else if (osd is OSDMap) // request to assign a material + { + materialsFromViewer = osd as OSDMap; + + if (materialsFromViewer.ContainsKey("FullMaterialsPerFace")) + { + OSD matsOsd = materialsFromViewer["FullMaterialsPerFace"]; + if (matsOsd is OSDArray) + { + OSDArray matsArr = matsOsd as OSDArray; + + try + { + foreach (OSDMap matsMap in matsArr) + { + uint primLocalID = 0; + try { + primLocalID = matsMap["ID"].AsUInteger(); + } + catch (Exception e) { + m_log.Warn("[Materials]: cannot decode \"ID\" from matsMap: " + e.Message); + continue; + } + + OSDMap mat = null; + try + { + mat = matsMap["Material"] as OSDMap; + } + catch (Exception e) + { + m_log.Warn("[Materials]: cannot decode \"Material\" from matsMap: " + e.Message); + continue; + } + + SceneObjectPart sop = m_scene.GetSceneObjectPart(primLocalID); + if (sop == null) + { + m_log.WarnFormat("[Materials]: SOP not found for localId: {0}", primLocalID.ToString()); + continue; + } + + if (!m_scene.Permissions.CanEditObject(sop.UUID, agentID)) + { + m_log.WarnFormat("User {0} can't edit object {1} {2}", agentID, sop.Name, sop.UUID); + continue; + } + + Primitive.TextureEntry te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length); + if (te == null) + { + m_log.WarnFormat("[Materials]: Error in TextureEntry for SOP {0} {1}", sop.Name, sop.UUID); + continue; + } + + + UUID id; + if (mat == null) + { + // This happens then the user removes a material from a prim + id = UUID.Zero; + } + else + { + id = StoreMaterialAsAsset(agentID, mat, sop); + } + + + int face = -1; + + if (matsMap.ContainsKey("Face")) + { + face = matsMap["Face"].AsInteger(); + Primitive.TextureEntryFace faceEntry = te.CreateFace((uint)face); + faceEntry.MaterialID = id; + } + else + { + if (te.DefaultTexture == null) + m_log.WarnFormat("[Materials]: TextureEntry.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID); + else + te.DefaultTexture.MaterialID = id; + } + + //m_log.DebugFormat("[Materials]: in \"{0}\" {1}, setting material ID for face {2} to {3}", sop.Name, sop.UUID, face, id); + + // We can't use sop.UpdateTextureEntry(te) because it filters, so do it manually + sop.Shape.TextureEntry = te.GetBytes(); + + if (sop.ParentGroup != null) + { + sop.TriggerScriptChangedEvent(Changed.TEXTURE); + sop.UpdateFlag = UpdateRequired.FULL; + sop.ParentGroup.HasGroupChanged = true; + sop.ScheduleFullUpdate(); + } + } + } + catch (Exception e) + { + m_log.Warn("[Materials]: exception processing received material ", e); + } + } + } + } + } + + } + catch (Exception e) + { + m_log.Warn("[Materials]: exception decoding zipped CAP payload ", e); + //return ""; + } + } + + + resp["Zipped"] = ZCompressOSD(respArr, false); + string response = OSDParser.SerializeLLSDXmlString(resp); + + //m_log.Debug("[Materials]: cap request: " + request); + //m_log.Debug("[Materials]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary())); + //m_log.Debug("[Materials]: cap response: " + response); + return response; + } + + private UUID StoreMaterialAsAsset(UUID agentID, OSDMap mat, SceneObjectPart sop) + { + UUID id; + // Material UUID = hash of the material's data. + // This makes materials deduplicate across the entire grid (but isn't otherwise required). + byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat)); + using (var md5 = MD5.Create()) + id = new UUID(md5.ComputeHash(data), 0); + + lock (m_regionMaterials) + { + if (!m_regionMaterials.ContainsKey(id)) + { + m_regionMaterials[id] = mat; + + // This asset might exist already, but it's ok to try to store it again + string name = "Material " + ChooseMaterialName(mat, sop); + name = name.Substring(0, Math.Min(64, name.Length)).Trim(); + AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString()); + asset.Data = data; + m_scene.AssetService.Store(asset); + } + } + return id; + } + + /// + /// Use heuristics to choose a good name for the material. + /// + private string ChooseMaterialName(OSDMap mat, SceneObjectPart sop) + { + UUID normMap = mat["NormMap"].AsUUID(); + if (normMap != UUID.Zero) + { + AssetBase asset = m_scene.AssetService.GetCached(normMap.ToString()); + if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR")) + return asset.Name; + } + + UUID specMap = mat["SpecMap"].AsUUID(); + if (specMap != UUID.Zero) + { + AssetBase asset = m_scene.AssetService.GetCached(specMap.ToString()); + if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR")) + return asset.Name; + } + + if (sop.Name != "Primitive") + return sop.Name; + + if ((sop.ParentGroup != null) && (sop.ParentGroup.Name != "Primitive")) + return sop.ParentGroup.Name; + + return ""; + } + + + public string RenderMaterialsGetCap(string request) + { + OSDMap resp = new OSDMap(); + int matsCount = 0; + OSDArray allOsd = new OSDArray(); + + lock (m_regionMaterials) + { + foreach (KeyValuePair kvp in m_regionMaterials) + { + OSDMap matMap = new OSDMap(); + + matMap["ID"] = OSD.FromBinary(kvp.Key.GetBytes()); + matMap["Material"] = kvp.Value; + allOsd.Add(matMap); + matsCount++; + } + } + + resp["Zipped"] = ZCompressOSD(allOsd, false); + + return OSDParser.SerializeLLSDXmlString(resp); + } + + private static string ZippedOsdBytesToString(byte[] bytes) + { + try + { + return OSDParser.SerializeJsonString(ZDecompressBytesToOsd(bytes)); + } + catch (Exception e) + { + return "ZippedOsdBytesToString caught an exception: " + e.ToString(); + } + } + + /// + /// computes a UUID by hashing a OSD object + /// + /// + /// + private static UUID HashOsd(OSD osd) + { + byte[] data = OSDParser.SerializeLLSDBinary(osd, false); + using (var md5 = MD5.Create()) + return new UUID(md5.ComputeHash(data), 0); + } + + public static OSD ZCompressOSD(OSD inOsd, bool useHeader) + { + OSD osd = null; + + byte[] data = OSDParser.SerializeLLSDBinary(inOsd, useHeader); + + using (MemoryStream msSinkCompressed = new MemoryStream()) + { + using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed, + Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true)) + { + zOut.Write(data, 0, data.Length); + } + + msSinkCompressed.Seek(0L, SeekOrigin.Begin); + osd = OSD.FromBinary(msSinkCompressed.ToArray()); + } + + return osd; + } + + + public static OSD ZDecompressBytesToOsd(byte[] input) + { + OSD osd = null; + + using (MemoryStream msSinkUnCompressed = new MemoryStream()) + { + using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true)) + { + zOut.Write(input, 0, input.Length); + } + + msSinkUnCompressed.Seek(0L, SeekOrigin.Begin); + osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray()); + } + + return osd; + } + } +} diff --git a/OpenSim/Region/OptionalModules/PhysicsParameters/PhysicsParameters.cs b/OpenSim/Region/OptionalModules/PhysicsParameters/PhysicsParameters.cs index 40f7fbc..1d9179c 100755 --- a/OpenSim/Region/OptionalModules/PhysicsParameters/PhysicsParameters.cs +++ b/OpenSim/Region/OptionalModules/PhysicsParameters/PhysicsParameters.cs @@ -36,7 +36,7 @@ using OpenSim.Framework.Console; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; namespace OpenSim.Region.OptionalModules.PhysicsParameters { @@ -146,7 +146,7 @@ namespace OpenSim.Region.OptionalModules.PhysicsParameters { foreach (PhysParameterEntry ppe in physScene.GetParameterList()) { - float val = 0.0f; + string val = string.Empty; if (physScene.GetPhysicsParameter(ppe.name, out val)) { WriteOut(" {0}/{1} = {2}", scene.RegionInfo.RegionName, ppe.name, val); @@ -159,7 +159,7 @@ namespace OpenSim.Region.OptionalModules.PhysicsParameters } else { - float val = 0.0f; + string val = string.Empty; if (physScene.GetPhysicsParameter(parm, out val)) { WriteOut(" {0}/{1} = {2}", scene.RegionInfo.RegionName, parm, val); @@ -185,21 +185,12 @@ namespace OpenSim.Region.OptionalModules.PhysicsParameters return; } string parm = "xxx"; - float val = 0f; + string valparm = String.Empty; uint localID = (uint)PhysParameterEntry.APPLY_TO_NONE; // set default value try { parm = cmdparms[2]; - string valparm = cmdparms[3].ToLower(); - if (valparm == "true") - val = PhysParameterEntry.NUMERIC_TRUE; - else - { - if (valparm == "false") - val = PhysParameterEntry.NUMERIC_FALSE; - else - val = float.Parse(valparm, Culture.NumberFormatInfo); - } + valparm = cmdparms[3].ToLower(); if (cmdparms.Length > 4) { if (cmdparms[4].ToLower() == "all") @@ -224,7 +215,7 @@ namespace OpenSim.Region.OptionalModules.PhysicsParameters IPhysicsParameters physScene = scene.PhysicsScene as IPhysicsParameters; if (physScene != null) { - if (!physScene.SetPhysicsParameter(parm, val, localID)) + if (!physScene.SetPhysicsParameter(parm, valparm, localID)) { WriteError("Failed set of parameter '{0}' for region '{1}'", parm, scene.RegionInfo.RegionName); } diff --git a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs index c1957e2..395bbf1 100644 --- a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs +++ b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs @@ -26,8 +26,9 @@ */ using System; -using System.Reflection; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using log4net; using Mono.Addins; using Nini.Config; @@ -57,11 +58,10 @@ namespace OpenSim.Region.OptionalModules public void Initialise(IConfigSource config) { - IConfig myConfig = config.Configs["Startup"]; + string permissionModules = Util.GetConfigVarFromSections(config, "permissionmodules", + new string[] { "Startup", "Permissions" }, "DefaultPermissionsModule"); - string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); - - List modules=new List(permissionModules.Split(',')); + List modules = new List(permissionModules.Split(',').Select(m => m.Trim())); if(!modules.Contains("PrimLimitsModule")) return; @@ -102,20 +102,34 @@ namespace OpenSim.Region.OptionalModules public void RegionLoaded(Scene scene) { m_dialogModule = scene.RequestModuleInterface(); - } + } - private bool CanRezObject(int objectCount, UUID owner, Vector3 objectPosition, Scene scene) + private bool CanRezObject(int objectCount, UUID ownerID, Vector3 objectPosition, Scene scene) { ILandObject lo = scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); - int usedPrims = lo.PrimCounts.Total; - int simulatorCapacity = lo.GetSimulatorMaxPrimCount(); - if (objectCount + usedPrims > simulatorCapacity) + string response = DoCommonChecks(objectCount, ownerID, lo, scene); + + if (response != null) { - m_dialogModule.SendAlertToUser(owner, "Unable to rez object because the parcel is too full"); + m_dialogModule.SendAlertToUser(ownerID, response); return false; } + return true; + } + //OnDuplicateObject + private bool CanDuplicateObject(int objectCount, UUID objectID, UUID ownerID, Scene scene, Vector3 objectPosition) + { + ILandObject lo = scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); + + string response = DoCommonChecks(objectCount, ownerID, lo, scene); + + if (response != null) + { + m_dialogModule.SendAlertToUser(ownerID, response); + return false; + } return true; } @@ -127,10 +141,12 @@ namespace OpenSim.Region.OptionalModules ILandObject oldParcel = scene.LandChannel.GetLandObject(oldPoint.X, oldPoint.Y); ILandObject newParcel = scene.LandChannel.GetLandObject(newPoint.X, newPoint.Y); - int usedPrims = newParcel.PrimCounts.Total; - int simulatorCapacity = newParcel.GetSimulatorMaxPrimCount(); - - // The prim hasn't crossed a region boundry so we don't need to worry + // newParcel will be null only if it outside of our current region. If this is the case, then the + // receiving permissions will perform the check. + if (newParcel == null) + return true; + + // The prim hasn't crossed a region boundary so we don't need to worry // about prim counts here if(oldParcel.Equals(newParcel)) { @@ -145,30 +161,53 @@ namespace OpenSim.Region.OptionalModules } // TODO: Add Special Case here for temporary prims - - if(objectCount + usedPrims > simulatorCapacity) + + string response = DoCommonChecks(objectCount, obj.OwnerID, newParcel, scene); + + if (response != null) { - m_dialogModule.SendAlertToUser(obj.OwnerID, "Unable to move object because the destination parcel is too full"); + m_dialogModule.SendAlertToUser(obj.OwnerID, response); return false; } - return true; } - //OnDuplicateObject - private bool CanDuplicateObject(int objectCount, UUID objectID, UUID owner, Scene scene, Vector3 objectPosition) + private string DoCommonChecks(int objectCount, UUID ownerID, ILandObject lo, Scene scene) { - ILandObject lo = scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); - int usedPrims = lo.PrimCounts.Total; - int simulatorCapacity = lo.GetSimulatorMaxPrimCount(); + string response = null; - if(objectCount + usedPrims > simulatorCapacity) + int simulatorCapacity = lo.GetSimulatorMaxPrimCount(); + if ((objectCount + lo.PrimCounts.Total) > simulatorCapacity) { - m_dialogModule.SendAlertToUser(owner, "Unable to duplicate object because the parcel is too full"); - return false; + response = "Unable to rez object because the parcel is too full"; } - - return true; + else + { + int maxPrimsPerUser = scene.RegionInfo.MaxPrimsPerUser; + if (maxPrimsPerUser >= 0) + { + // per-user prim limit is set + if (ownerID != lo.LandData.OwnerID || lo.LandData.IsGroupOwned) + { + // caller is not the sole Parcel owner + EstateSettings estateSettings = scene.RegionInfo.EstateSettings; + if (ownerID != estateSettings.EstateOwner) + { + // caller is NOT the Estate owner + List mgrs = new List(estateSettings.EstateManagers); + if (!mgrs.Contains(ownerID)) + { + // caller is not an Estate Manager + if ((lo.PrimCounts.Users[ownerID] + objectCount) > maxPrimsPerUser) + { + response = "Unable to rez object because you have reached your limit"; + } + } + } + } + } + } + return response; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/OptionalModules/Properties/AssemblyInfo.cs b/OpenSim/Region/OptionalModules/Properties/AssemblyInfo.cs index 217b2d5..dc6ca6f 100644 --- a/OpenSim/Region/OptionalModules/Properties/AssemblyInfo.cs +++ b/OpenSim/Region/OptionalModules/Properties/AssemblyInfo.cs @@ -30,8 +30,8 @@ using Mono.Addins; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.8.3.*")] -[assembly: Addin("OpenSim.Region.OptionalModules", "0.1")] -[assembly: AddinDependency("OpenSim", "0.5")] + +[assembly: Addin("OpenSim.Region.OptionalModules", OpenSim.VersionInfo.VersionNumber)] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerClientEventForwarder.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerClientEventForwarder.cs new file mode 100644 index 0000000..721d396 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerClientEventForwarder.cs @@ -0,0 +1,94 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.RegionCombinerModule +{ +public class RegionCombinerClientEventForwarder + { + private Scene m_rootScene; + private Dictionary m_virtScene = new Dictionary(); + private Dictionary m_forwarders = new Dictionary(); + + public RegionCombinerClientEventForwarder(RegionConnections rootScene) + { + m_rootScene = rootScene.RegionScene; + } + + public void AddSceneToEventForwarding(Scene virtualScene) + { + lock (m_virtScene) + { + if (m_virtScene.ContainsKey(virtualScene.RegionInfo.originRegionID)) + { + m_virtScene[virtualScene.RegionInfo.originRegionID] = virtualScene; + } + else + { + m_virtScene.Add(virtualScene.RegionInfo.originRegionID, virtualScene); + } + } + + lock (m_forwarders) + { + // TODO: Fix this to unregister if this happens + if (m_forwarders.ContainsKey(virtualScene.RegionInfo.originRegionID)) + m_forwarders.Remove(virtualScene.RegionInfo.originRegionID); + + RegionCombinerIndividualEventForwarder forwarder = + new RegionCombinerIndividualEventForwarder(m_rootScene, virtualScene); + m_forwarders.Add(virtualScene.RegionInfo.originRegionID, forwarder); + + virtualScene.EventManager.OnNewClient += forwarder.ClientConnect; + virtualScene.EventManager.OnClientClosed += forwarder.ClientClosed; + } + } + + public void RemoveSceneFromEventForwarding (Scene virtualScene) + { + lock (m_forwarders) + { + RegionCombinerIndividualEventForwarder forwarder = m_forwarders[virtualScene.RegionInfo.originRegionID]; + virtualScene.EventManager.OnNewClient -= forwarder.ClientConnect; + virtualScene.EventManager.OnClientClosed -= forwarder.ClientClosed; + m_forwarders.Remove(virtualScene.RegionInfo.originRegionID); + } + lock (m_virtScene) + { + if (m_virtScene.ContainsKey(virtualScene.RegionInfo.originRegionID)) + { + m_virtScene.Remove(virtualScene.RegionInfo.originRegionID); + } + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerIndividualEventForwarder.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerIndividualEventForwarder.cs new file mode 100644 index 0000000..83732e2 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerIndividualEventForwarder.cs @@ -0,0 +1,139 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Attachments; +using OpenSim.Region.CoreModules.Avatar.Gods; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.RegionCombinerModule +{ + public class RegionCombinerIndividualEventForwarder + { + private Scene m_rootScene; + private Scene m_virtScene; + + public RegionCombinerIndividualEventForwarder(Scene rootScene, Scene virtScene) + { + m_rootScene = rootScene; + m_virtScene = virtScene; + } + + public void ClientConnect(IClientAPI client) + { + m_virtScene.UnSubscribeToClientPrimEvents(client); + m_virtScene.UnSubscribeToClientPrimRezEvents(client); + m_virtScene.UnSubscribeToClientInventoryEvents(client); + if(m_virtScene.AttachmentsModule != null) + ((AttachmentsModule)m_virtScene.AttachmentsModule).UnsubscribeFromClientEvents(client); + //m_virtScene.UnSubscribeToClientTeleportEvents(client); + m_virtScene.UnSubscribeToClientScriptEvents(client); + + IGodsModule virtGodsModule = m_virtScene.RequestModuleInterface(); + if (virtGodsModule != null) + ((GodsModule)virtGodsModule).UnsubscribeFromClientEvents(client); + + m_virtScene.UnSubscribeToClientNetworkEvents(client); + + m_rootScene.SubscribeToClientPrimEvents(client); + client.OnAddPrim += LocalAddNewPrim; + client.OnRezObject += LocalRezObject; + + m_rootScene.SubscribeToClientInventoryEvents(client); + if (m_rootScene.AttachmentsModule != null) + ((AttachmentsModule)m_rootScene.AttachmentsModule).SubscribeToClientEvents(client); + //m_rootScene.SubscribeToClientTeleportEvents(client); + m_rootScene.SubscribeToClientScriptEvents(client); + + IGodsModule rootGodsModule = m_virtScene.RequestModuleInterface(); + if (rootGodsModule != null) + ((GodsModule)rootGodsModule).UnsubscribeFromClientEvents(client); + + m_rootScene.SubscribeToClientNetworkEvents(client); + } + + public void ClientClosed(UUID clientid, Scene scene) + { + } + + /// + /// Fixes position based on the region the Rez event came in on + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private void LocalRezObject(IClientAPI remoteclient, UUID itemid, Vector3 rayend, Vector3 raystart, + UUID raytargetid, byte bypassraycast, bool rayendisintersection, bool rezselected, bool removeitem, + UUID fromtaskid) + { + int differenceX = (int)m_virtScene.RegionInfo.RegionLocX - (int)m_rootScene.RegionInfo.RegionLocX; + int differenceY = (int)m_virtScene.RegionInfo.RegionLocY - (int)m_rootScene.RegionInfo.RegionLocY; + rayend.X += differenceX * (int)Constants.RegionSize; + rayend.Y += differenceY * (int)Constants.RegionSize; + raystart.X += differenceX * (int)Constants.RegionSize; + raystart.Y += differenceY * (int)Constants.RegionSize; + + m_rootScene.RezObject(remoteclient, itemid, rayend, raystart, raytargetid, bypassraycast, + rayendisintersection, rezselected, removeitem, fromtaskid); + } + /// + /// Fixes position based on the region the AddPrimShape event came in on + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private void LocalAddNewPrim(UUID ownerid, UUID groupid, Vector3 rayend, Quaternion rot, + PrimitiveBaseShape shape, byte bypassraycast, Vector3 raystart, UUID raytargetid, + byte rayendisintersection) + { + int differenceX = (int)m_virtScene.RegionInfo.RegionLocX - (int)m_rootScene.RegionInfo.RegionLocX; + int differenceY = (int)m_virtScene.RegionInfo.RegionLocY - (int)m_rootScene.RegionInfo.RegionLocY; + rayend.X += differenceX * (int)Constants.RegionSize; + rayend.Y += differenceY * (int)Constants.RegionSize; + raystart.X += differenceX * (int)Constants.RegionSize; + raystart.Y += differenceY * (int)Constants.RegionSize; + m_rootScene.AddNewPrim(ownerid, groupid, rayend, rot, shape, bypassraycast, raystart, raytargetid, + rayendisintersection); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerLargeLandChannel.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerLargeLandChannel.cs new file mode 100644 index 0000000..4bf2a82 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerLargeLandChannel.cs @@ -0,0 +1,201 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.World.Land; + +namespace OpenSim.Region.RegionCombinerModule +{ + public class RegionCombinerLargeLandChannel : ILandChannel + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private RegionData RegData; + private ILandChannel RootRegionLandChannel; + private readonly List RegionConnections; + + #region ILandChannel Members + + public RegionCombinerLargeLandChannel(RegionData regData, ILandChannel rootRegionLandChannel, + List regionConnections) + { + RegData = regData; + RootRegionLandChannel = rootRegionLandChannel; + RegionConnections = regionConnections; + } + + public List ParcelsNearPoint(Vector3 position) + { + //m_log.DebugFormat("[LANDPARCELNEARPOINT]: {0}>", position); + return RootRegionLandChannel.ParcelsNearPoint(position - RegData.Offset); + } + + public List AllParcels() + { + return RootRegionLandChannel.AllParcels(); + } + + public void Clear(bool setupDefaultParcel) + { + RootRegionLandChannel.Clear(setupDefaultParcel); + } + + public ILandObject GetLandObject(Vector3 position) + { + return GetLandObject(position.X, position.Y); + } + + public ILandObject GetLandObject(int x, int y) + { + return GetLandObject((float)x, (float)y); + +// m_log.DebugFormat("[BIGLANDTESTINT]: <{0},{1}>", x, y); +// +// if (x > 0 && x <= (int)Constants.RegionSize && y > 0 && y <= (int)Constants.RegionSize) +// { +// return RootRegionLandChannel.GetLandObject(x, y); +// } +// else +// { +// int offsetX = (x / (int)Constants.RegionSize); +// int offsetY = (y / (int)Constants.RegionSize); +// offsetX *= (int)Constants.RegionSize; +// offsetY *= (int)Constants.RegionSize; +// +// foreach (RegionData regionData in RegionConnections) +// { +// if (regionData.Offset.X == offsetX && regionData.Offset.Y == offsetY) +// { +// m_log.DebugFormat( +// "[REGION COMBINER LARGE LAND CHANNEL]: Found region {0} at offset {1},{2}", +// regionData.RegionScene.Name, offsetX, offsetY); +// +// return regionData.RegionScene.LandChannel.GetLandObject(x - offsetX, y - offsetY); +// } +// } +// //ILandObject obj = new LandObject(UUID.Zero, false, RegData.RegionScene); +// //obj.LandData.Name = "NO LAND"; +// //return obj; +// } +// +// m_log.DebugFormat("[REGION COMBINER LARGE LAND CHANNEL]: No region found at {0},{1}, returning null", x, y); +// +// return null; + } + + public ILandObject GetLandObject(int localID) + { + // XXX: Possibly should be looking in every land channel, not just the root. + return RootRegionLandChannel.GetLandObject(localID); + } + + public ILandObject GetLandObject(float x, float y) + { +// m_log.DebugFormat("[BIGLANDTESTFLOAT]: <{0},{1}>", x, y); + + if (x > 0 && x <= (int)Constants.RegionSize && y > 0 && y <= (int)Constants.RegionSize) + { + return RootRegionLandChannel.GetLandObject(x, y); + } + else + { + int offsetX = (int)(x/(int) Constants.RegionSize); + int offsetY = (int)(y/(int) Constants.RegionSize); + offsetX *= (int) Constants.RegionSize; + offsetY *= (int) Constants.RegionSize; + + foreach (RegionData regionData in RegionConnections) + { + if (regionData.Offset.X == offsetX && regionData.Offset.Y == offsetY) + { +// m_log.DebugFormat( +// "[REGION COMBINER LARGE LAND CHANNEL]: Found region {0} at offset {1},{2}", +// regionData.RegionScene.Name, offsetX, offsetY); + + return regionData.RegionScene.LandChannel.GetLandObject(x - offsetX, y - offsetY); + } + } + +// ILandObject obj = new LandObject(UUID.Zero, false, RegData.RegionScene); +// obj.LandData.Name = "NO LAND"; +// return obj; + } + +// m_log.DebugFormat("[REGION COMBINER LARGE LAND CHANNEL]: No region found at {0},{1}, returning null", x, y); + + return null; + } + + public bool IsForcefulBansAllowed() + { + return RootRegionLandChannel.IsForcefulBansAllowed(); + } + + public void UpdateLandObject(int localID, LandData data) + { + RootRegionLandChannel.UpdateLandObject(localID, data); + } + + public void Join(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) + { + RootRegionLandChannel.Join(start_x, start_y, end_x, end_y, attempting_user_id); + } + + public void Subdivide(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) + { + RootRegionLandChannel.Subdivide(start_x, start_y, end_x, end_y, attempting_user_id); + } + + public void ReturnObjectsInParcel(int localID, uint returnType, UUID[] agentIDs, UUID[] taskIDs, IClientAPI remoteClient) + { + RootRegionLandChannel.ReturnObjectsInParcel(localID, returnType, agentIDs, taskIDs, remoteClient); + } + + public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel) + { + RootRegionLandChannel.setParcelObjectMaxOverride(overrideDel); + } + + public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel) + { + RootRegionLandChannel.setSimulatorObjectMaxOverride(overrideDel); + } + + public void SetParcelOtherCleanTime(IClientAPI remoteClient, int localID, int otherCleanTime) + { + RootRegionLandChannel.SetParcelOtherCleanTime(remoteClient, localID, otherCleanTime); + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerModule.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerModule.cs new file mode 100644 index 0000000..32eead0 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerModule.cs @@ -0,0 +1,880 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Console; +using OpenSim.Region.PhysicsModules.SharedBase; +using Mono.Addins; + +namespace OpenSim.Region.RegionCombinerModule +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RegionCombinerModule")] + public class RegionCombinerModule : ISharedRegionModule, IRegionCombinerModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static string LogHeader = "[REGION COMBINER MODULE]"; + + public string Name + { + get { return "RegionCombinerModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + /// + /// Is this module enabled? + /// + private bool m_combineContiguousRegions = false; + + /// + /// This holds the root regions for the megaregions. + /// + /// + /// Usually there is only ever one megaregion (and hence only one entry here). + /// + private Dictionary m_regions = new Dictionary(); + + /// + /// The scenes that comprise the megaregion. + /// + private Dictionary m_startingScenes = new Dictionary(); + + public void Initialise(IConfigSource source) + { + IConfig myConfig = source.Configs["Startup"]; + m_combineContiguousRegions = myConfig.GetBoolean("CombineContiguousRegions", false); + if (m_combineContiguousRegions) + m_log.ErrorFormat("[REGION COMBINER MODULE]: THIS MODULE IS BEING MARKED OBSOLETE AND MAY SOON BE REMOVED. PLEASE USE VARREGIONS INSTEAD."); + + MainConsole.Instance.Commands.AddCommand( + "RegionCombinerModule", false, "fix-phantoms", "fix-phantoms", + "Fixes phantom objects after an import to a megaregion or a change from a megaregion back to normal regions", + FixPhantoms); + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + if (m_combineContiguousRegions) + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + lock (m_startingScenes) + m_startingScenes.Remove(scene.RegionInfo.originRegionID); + } + + public void RegionLoaded(Scene scene) + { + lock (m_startingScenes) + m_startingScenes.Add(scene.RegionInfo.originRegionID, scene); + + if (m_combineContiguousRegions) + { + RegionLoadedDoWork(scene); + + scene.EventManager.OnNewPresence += NewPresence; + } + } + + public bool IsRootForMegaregion(UUID regionId) + { + lock (m_regions) + return m_regions.ContainsKey(regionId); + } + + public Vector2 GetSizeOfMegaregion(UUID regionId) + { + lock (m_regions) + { + if (m_regions.ContainsKey(regionId)) + { + RegionConnections rootConn = m_regions[regionId]; + + return new Vector2((float)rootConn.XEnd, (float)rootConn.YEnd); + } + } + + throw new Exception(string.Format("Region with id {0} not found", regionId)); + } + + // Test to see if this postiion (relative to the region) is within the area covered + // by this megaregion. + public bool PositionIsInMegaregion(UUID currentRegion, int xx, int yy) + { + bool ret = false; + if (xx < 0 || yy < 0) + return ret; + + foreach (RegionConnections rootRegion in m_regions.Values) + { + if (currentRegion == rootRegion.RegionId) + { + // The caller is in the root region so this is an easy test + if (xx < rootRegion.XEnd && yy < rootRegion.YEnd) + { + ret = true; + } + break; + } + else + { + // Maybe the caller is in one of the sub-regions + foreach (RegionData childRegion in rootRegion.ConnectedRegions) + { + if (currentRegion == childRegion.RegionId) + { + // This is a child. Diddle the offsets and check if in + Vector3 positionInMegaregion = childRegion.Offset; + positionInMegaregion.X += xx; + positionInMegaregion.Y += yy; + if (positionInMegaregion.X < rootRegion.XEnd && positionInMegaregion.Y < rootRegion.YEnd) + { + ret = true; + } + break; + } + } + } + } + + return ret; + } + + private void NewPresence(ScenePresence presence) + { + if (presence.IsChildAgent) + { + byte[] throttleData; + + try + { + throttleData = presence.ControllingClient.GetThrottlesPacked(1); + } + catch (NotImplementedException) + { + return; + } + + if (throttleData == null) + return; + + if (throttleData.Length == 0) + return; + + if (throttleData.Length != 28) + return; + + byte[] adjData; + int pos = 0; + + if (!BitConverter.IsLittleEndian) + { + byte[] newData = new byte[7 * 4]; + Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4); + + for (int i = 0; i < 7; i++) + Array.Reverse(newData, i * 4, 4); + + adjData = newData; + } + else + { + adjData = throttleData; + } + + // 0.125f converts from bits to bytes + int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); + // State is a subcategory of task that we allocate a percentage to + + + //int total = resend + land + wind + cloud + task + texture + asset; + + byte[] data = new byte[7 * 4]; + int ii = 0; + + Buffer.BlockCopy(Utils.FloatToBytes(resend), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(land * 50), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(wind), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(cloud), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(task), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(texture), 0, data, ii, 4); ii += 4; + Buffer.BlockCopy(Utils.FloatToBytes(asset), 0, data, ii, 4); + + try + { + presence.ControllingClient.SetChildAgentThrottle(data); + } + catch (NotImplementedException) + { + return; + } + } + } + + private void RegionLoadedDoWork(Scene scene) + { +/* + // For testing on a single instance + if (scene.RegionInfo.RegionLocX == 1004 && scene.RegionInfo.RegionLocY == 1000) + return; + // +*/ + + RegionConnections newConn = new RegionConnections(); + newConn.ConnectedRegions = new List(); + newConn.RegionScene = scene; + newConn.RegionLandChannel = scene.LandChannel; + newConn.RegionId = scene.RegionInfo.originRegionID; + newConn.X = scene.RegionInfo.RegionLocX; + newConn.Y = scene.RegionInfo.RegionLocY; + newConn.XEnd = scene.RegionInfo.RegionSizeX; + newConn.YEnd = scene.RegionInfo.RegionSizeX; + + lock (m_regions) + { + bool connectedYN = false; + + foreach (RegionConnections rootConn in m_regions.Values) + { + #region commented + /* + // If we're one region over +x +y + //xxy + //xxx + //xxx + if ((((int)conn.X * (int)Constants.RegionSize) + conn.XEnd + == (regionConnections.X * (int)Constants.RegionSize)) + && (((int)conn.Y * (int)Constants.RegionSize) - conn.YEnd + == (regionConnections.Y * (int)Constants.RegionSize))) + { + Vector3 offset = Vector3.Zero; + offset.X = (((regionConnections.X * (int) Constants.RegionSize)) - + ((conn.X * (int) Constants.RegionSize))); + offset.Y = (((regionConnections.Y * (int) Constants.RegionSize)) - + ((conn.Y * (int) Constants.RegionSize))); + + Vector3 extents = Vector3.Zero; + extents.Y = regionConnections.YEnd + conn.YEnd; + extents.X = conn.XEnd + conn.XEnd; + + m_log.DebugFormat("Scene: {0} to the northwest of Scene{1}. Offset: {2}. Extents:{3}", + conn.RegionScene.RegionInfo.RegionName, + regionConnections.RegionScene.RegionInfo.RegionName, + offset, extents); + + scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset, extents); + + connectedYN = true; + break; + } + */ + + /* + //If we're one region over x +y + //xxx + //xxx + //xyx + if ((((int)conn.X * (int)Constants.RegionSize) + == (regionConnections.X * (int)Constants.RegionSize)) + && (((int)conn.Y * (int)Constants.RegionSize) - conn.YEnd + == (regionConnections.Y * (int)Constants.RegionSize))) + { + Vector3 offset = Vector3.Zero; + offset.X = (((regionConnections.X * (int)Constants.RegionSize)) - + ((conn.X * (int)Constants.RegionSize))); + offset.Y = (((regionConnections.Y * (int)Constants.RegionSize)) - + ((conn.Y * (int)Constants.RegionSize))); + + Vector3 extents = Vector3.Zero; + extents.Y = regionConnections.YEnd + conn.YEnd; + extents.X = conn.XEnd; + + m_log.DebugFormat("Scene: {0} to the north of Scene{1}. Offset: {2}. Extents:{3}", + conn.RegionScene.RegionInfo.RegionName, + regionConnections.RegionScene.RegionInfo.RegionName, offset, extents); + + scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset, extents); + connectedYN = true; + break; + } + */ + + /* + // If we're one region over -x +y + //xxx + //xxx + //yxx + if ((((int)conn.X * (int)Constants.RegionSize) - conn.XEnd + == (regionConnections.X * (int)Constants.RegionSize)) + && (((int)conn.Y * (int)Constants.RegionSize) - conn.YEnd + == (regionConnections.Y * (int)Constants.RegionSize))) + { + Vector3 offset = Vector3.Zero; + offset.X = (((regionConnections.X * (int)Constants.RegionSize)) - + ((conn.X * (int)Constants.RegionSize))); + offset.Y = (((regionConnections.Y * (int)Constants.RegionSize)) - + ((conn.Y * (int)Constants.RegionSize))); + + Vector3 extents = Vector3.Zero; + extents.Y = regionConnections.YEnd + conn.YEnd; + extents.X = conn.XEnd + conn.XEnd; + + m_log.DebugFormat("Scene: {0} to the northeast of Scene. Offset: {2}. Extents:{3}", + conn.RegionScene.RegionInfo.RegionName, + regionConnections.RegionScene.RegionInfo.RegionName, offset, extents); + + scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset, extents); + + + connectedYN = true; + break; + } + */ + + /* + // If we're one region over -x y + //xxx + //yxx + //xxx + if ((((int)conn.X * (int)Constants.RegionSize) - conn.XEnd + == (regionConnections.X * (int)Constants.RegionSize)) + && (((int)conn.Y * (int)Constants.RegionSize) + == (regionConnections.Y * (int)Constants.RegionSize))) + { + Vector3 offset = Vector3.Zero; + offset.X = (((regionConnections.X * (int)Constants.RegionSize)) - + ((conn.X * (int)Constants.RegionSize))); + offset.Y = (((regionConnections.Y * (int)Constants.RegionSize)) - + ((conn.Y * (int)Constants.RegionSize))); + + Vector3 extents = Vector3.Zero; + extents.Y = regionConnections.YEnd; + extents.X = conn.XEnd + conn.XEnd; + + m_log.DebugFormat("Scene: {0} to the east of Scene{1} Offset: {2}. Extents:{3}", + conn.RegionScene.RegionInfo.RegionName, + regionConnections.RegionScene.RegionInfo.RegionName, offset, extents); + + scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset, extents); + + connectedYN = true; + break; + } + */ + + /* + // If we're one region over -x -y + //yxx + //xxx + //xxx + if ((((int)conn.X * (int)Constants.RegionSize) - conn.XEnd + == (regionConnections.X * (int)Constants.RegionSize)) + && (((int)conn.Y * (int)Constants.RegionSize) + conn.YEnd + == (regionConnections.Y * (int)Constants.RegionSize))) + { + Vector3 offset = Vector3.Zero; + offset.X = (((regionConnections.X * (int)Constants.RegionSize)) - + ((conn.X * (int)Constants.RegionSize))); + offset.Y = (((regionConnections.Y * (int)Constants.RegionSize)) - + ((conn.Y * (int)Constants.RegionSize))); + + Vector3 extents = Vector3.Zero; + extents.Y = regionConnections.YEnd + conn.YEnd; + extents.X = conn.XEnd + conn.XEnd; + + m_log.DebugFormat("Scene: {0} to the northeast of Scene{1} Offset: {2}. Extents:{3}", + conn.RegionScene.RegionInfo.RegionName, + regionConnections.RegionScene.RegionInfo.RegionName, offset, extents); + + scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset, extents); + + connectedYN = true; + break; + } + */ + #endregion + + + // Check to see if this new region is adjacent to the root region. + // Note that we expect the regions to be combined from the root region outward + // thus the requirement for the ordering in the configuration files. + + // If we're one region over +x y (i.e. root region is to the west) + //xxx + //xxy + //xxx + if (rootConn.PosX + rootConn.XEnd >= newConn.PosX && rootConn.PosY >= newConn.PosY) + { + connectedYN = DoWorkForOneRegionOverPlusXY(rootConn, newConn, scene); + break; + } + + // If we're one region over x +y (i.e. root region is to the south) + //xyx + //xxx + //xxx + if (rootConn.PosX >= newConn.PosX && rootConn.PosY + rootConn.YEnd >= newConn.PosY) + { + connectedYN = DoWorkForOneRegionOverPlusXY(rootConn, newConn, scene); + break; + } + + // If we're one region over +x +y (i.e. root region is to the south-west) + //xxy + //xxx + //xxx + if (rootConn.PosX + rootConn.XEnd >= newConn.PosX && rootConn.PosY + rootConn.YEnd >= newConn.PosY) + { + connectedYN = DoWorkForOneRegionOverPlusXY(rootConn, newConn, scene); + break; + } + } + + // If !connectYN means that this region is a root region + if (!connectedYN) + { + DoWorkForRootRegion(newConn, scene); + } + } + } + + private bool DoWorkForOneRegionOverPlusXY(RegionConnections rootConn, RegionConnections newConn, Scene scene) + { + // Offset (in meters) from the base of this region to the base of the root region. + Vector3 offset = Vector3.Zero; + offset.X = newConn.PosX - rootConn.PosX; + offset.Y = newConn.PosY - rootConn.PosY; + + // The new total size of the region (in meters) + // We just extend the X and Y dimensions so the extent might temporarily include areas without regions. + Vector3 extents = Vector3.Zero; + extents.X = Math.Max(rootConn.XEnd, offset.X + newConn.RegionScene.RegionInfo.RegionSizeX); + extents.Y = Math.Max(rootConn.YEnd, offset.Y + newConn.RegionScene.RegionInfo.RegionSizeY); + + rootConn.UpdateExtents(extents); + + m_log.DebugFormat( + "[REGION COMBINER MODULE]: Root region {0} is to the west of region {1}, Offset: {2}, Extents: {3}", + rootConn.RegionScene.RegionInfo.RegionName, + newConn.RegionScene.RegionInfo.RegionName, offset, extents); + + RegionData ConnectedRegion = new RegionData(); + ConnectedRegion.Offset = offset; + ConnectedRegion.RegionId = scene.RegionInfo.originRegionID; + ConnectedRegion.RegionScene = scene; + rootConn.ConnectedRegions.Add(ConnectedRegion); + + // Inform root region Physics about the extents of this region + rootConn.RegionScene.PhysicsScene.Combine(null, Vector3.Zero, extents); + + // Inform Child region that it needs to forward it's terrain to the root region + scene.PhysicsScene.Combine(rootConn.RegionScene.PhysicsScene, offset, Vector3.Zero); + + // Reset Terrain.. since terrain loads before we get here, we need to load + // it again so it loads in the root region + scene.PhysicsScene.SetTerrain(scene.Heightmap.GetFloatsSerialised()); + + // Create a client event forwarder and add this region's events to the root region. + if (rootConn.ClientEventForwarder != null) + rootConn.ClientEventForwarder.AddSceneToEventForwarding(scene); + + return true; + } + + /* + * 20140215 radams1: The border stuff was removed and the addition of regions to the mega-regions + * was generalized. These functions are not needed for the generalized solution but left for reference. + private bool DoWorkForOneRegionOverXPlusY(RegionConnections rootConn, RegionConnections newConn, Scene scene) + { + Vector3 offset = Vector3.Zero; + offset.X = newConn.PosX - rootConn.PosX; + offset.Y = newConn.PosY - rootConn.PosY; + + Vector3 extents = Vector3.Zero; + extents.Y = newConn.YEnd + rootConn.YEnd; + extents.X = rootConn.XEnd; + rootConn.UpdateExtents(extents); + + RegionData ConnectedRegion = new RegionData(); + ConnectedRegion.Offset = offset; + ConnectedRegion.RegionId = scene.RegionInfo.originRegionID; + ConnectedRegion.RegionScene = scene; + rootConn.ConnectedRegions.Add(ConnectedRegion); + + m_log.DebugFormat( + "[REGION COMBINER MODULE]: Root region {0} is to the south of region {1}, Offset: {2}, Extents: {3}", + rootConn.RegionScene.RegionInfo.RegionName, + newConn.RegionScene.RegionInfo.RegionName, offset, extents); + + rootConn.RegionScene.PhysicsScene.Combine(null, Vector3.Zero, extents); + scene.PhysicsScene.Combine(rootConn.RegionScene.PhysicsScene, offset, Vector3.Zero); + + // Reset Terrain.. since terrain normally loads first. + //conn.RegionScene.PhysicsScene.SetTerrain(conn.RegionScene.Heightmap.GetFloatsSerialised()); + scene.PhysicsScene.SetTerrain(scene.Heightmap.GetFloatsSerialised()); + //conn.RegionScene.PhysicsScene.SetTerrain(conn.RegionScene.Heightmap.GetFloatsSerialised()); + + if (rootConn.ClientEventForwarder != null) + rootConn.ClientEventForwarder.AddSceneToEventForwarding(scene); + + return true; + } + + private bool DoWorkForOneRegionOverPlusXPlusY(RegionConnections rootConn, RegionConnections newConn, Scene scene) + { + Vector3 offset = Vector3.Zero; + offset.X = newConn.PosX - rootConn.PosX; + offset.Y = newConn.PosY - rootConn.PosY; + + Vector3 extents = Vector3.Zero; + + // We do not want to inflate the extents for regions strictly to the NE of the root region, since this + // would double count regions strictly to the north and east that have already been added. +// extents.Y = regionConnections.YEnd + conn.YEnd; +// extents.X = regionConnections.XEnd + conn.XEnd; +// conn.UpdateExtents(extents); + + extents.Y = rootConn.YEnd; + extents.X = rootConn.XEnd; + + RegionData ConnectedRegion = new RegionData(); + ConnectedRegion.Offset = offset; + ConnectedRegion.RegionId = scene.RegionInfo.originRegionID; + ConnectedRegion.RegionScene = scene; + + rootConn.ConnectedRegions.Add(ConnectedRegion); + + m_log.DebugFormat( + "[REGION COMBINER MODULE]: Region {0} is to the southwest of Scene {1}, Offset: {2}, Extents: {3}", + rootConn.RegionScene.RegionInfo.RegionName, + newConn.RegionScene.RegionInfo.RegionName, offset, extents); + + rootConn.RegionScene.PhysicsScene.Combine(null, Vector3.Zero, extents); + scene.PhysicsScene.Combine(rootConn.RegionScene.PhysicsScene, offset, Vector3.Zero); + + // Reset Terrain.. since terrain normally loads first. + //conn.RegionScene.PhysicsScene.SetTerrain(conn.RegionScene.Heightmap.GetFloatsSerialised()); + scene.PhysicsScene.SetTerrain(scene.Heightmap.GetFloatsSerialised()); + //conn.RegionScene.PhysicsScene.SetTerrain(conn.RegionScene.Heightmap.GetFloatsSerialised()); + + if (rootConn.ClientEventForwarder != null) + rootConn.ClientEventForwarder.AddSceneToEventForwarding(scene); + + return true; + + //scene.PhysicsScene.Combine(conn.RegionScene.PhysicsScene, offset,extents); + } + */ + + private void DoWorkForRootRegion(RegionConnections rootConn, Scene scene) + { + m_log.DebugFormat("[REGION COMBINER MODULE]: Adding root region {0}", scene.RegionInfo.RegionName); + + RegionData rdata = new RegionData(); + rdata.Offset = Vector3.Zero; + rdata.RegionId = scene.RegionInfo.originRegionID; + rdata.RegionScene = scene; + // save it's land channel + rootConn.RegionLandChannel = scene.LandChannel; + + // Substitue our landchannel + RegionCombinerLargeLandChannel lnd = new RegionCombinerLargeLandChannel(rdata, scene.LandChannel, + rootConn.ConnectedRegions); + + scene.LandChannel = lnd; + + // Forward the permissions modules of each of the connected regions to the root region + lock (m_regions) + { + foreach (RegionData r in rootConn.ConnectedRegions) + { + ForwardPermissionRequests(rootConn, r.RegionScene); + } + + // Create the root region's Client Event Forwarder + rootConn.ClientEventForwarder = new RegionCombinerClientEventForwarder(rootConn); + + // Sets up the CoarseLocationUpdate forwarder for this root region + scene.EventManager.OnNewPresence += SetCoarseLocationDelegate; + + // Adds this root region to a dictionary of regions that are connectable + m_regions.Add(scene.RegionInfo.originRegionID, rootConn); + } + } + + private void SetCoarseLocationDelegate(ScenePresence presence) + { + presence.SetSendCoarseLocationMethod(SendCoarseLocationUpdates); + } + + // This delegate was refactored for non-combined regions. + // This combined region version will not use the pre-compiled lists of locations and ids + private void SendCoarseLocationUpdates(UUID sceneId, ScenePresence presence, List coarseLocations, List avatarUUIDs) + { + RegionConnections connectiondata = null; + lock (m_regions) + { + if (m_regions.ContainsKey(sceneId)) + connectiondata = m_regions[sceneId]; + else + return; + } + + List CoarseLocations = new List(); + List AvatarUUIDs = new List(); + + connectiondata.RegionScene.ForEachRootScenePresence(delegate(ScenePresence sp) + { + if (sp.UUID != presence.UUID) + { + CoarseLocations.Add(sp.AbsolutePosition); + AvatarUUIDs.Add(sp.UUID); + } + }); + + DistributeCoarseLocationUpdates(CoarseLocations, AvatarUUIDs, connectiondata, presence); + } + + private void DistributeCoarseLocationUpdates(List locations, List uuids, + RegionConnections connectiondata, ScenePresence rootPresence) + { + RegionData[] rdata = connectiondata.ConnectedRegions.ToArray(); + //List clients = new List(); + Dictionary updates = new Dictionary(); + + // Root Region entry + RegionCoarseLocationStruct rootupdatedata = new RegionCoarseLocationStruct(); + rootupdatedata.Locations = new List(); + rootupdatedata.Uuids = new List(); + rootupdatedata.Offset = Vector2.Zero; + + rootupdatedata.UserAPI = rootPresence.ControllingClient; + + if (rootupdatedata.UserAPI != null) + updates.Add(Vector2.Zero, rootupdatedata); + + //Each Region needs an entry or we will end up with dead minimap dots + foreach (RegionData regiondata in rdata) + { + Vector2 offset = new Vector2(regiondata.Offset.X, regiondata.Offset.Y); + RegionCoarseLocationStruct updatedata = new RegionCoarseLocationStruct(); + updatedata.Locations = new List(); + updatedata.Uuids = new List(); + updatedata.Offset = offset; + + if (offset == Vector2.Zero) + updatedata.UserAPI = rootPresence.ControllingClient; + else + updatedata.UserAPI = LocateUsersChildAgentIClientAPI(offset, rootPresence.UUID, rdata); + + if (updatedata.UserAPI != null) + updates.Add(offset, updatedata); + } + + // go over the locations and assign them to an IClientAPI + for (int i = 0; i < locations.Count; i++) + //{locations[i]/(int) Constants.RegionSize; + { + Vector3 pPosition = new Vector3((int)locations[i].X / (int)Constants.RegionSize, + (int)locations[i].Y / (int)Constants.RegionSize, locations[i].Z); + Vector2 offset = new Vector2(pPosition.X*(int) Constants.RegionSize, + pPosition.Y*(int) Constants.RegionSize); + + if (!updates.ContainsKey(offset)) + { + // This shouldn't happen + RegionCoarseLocationStruct updatedata = new RegionCoarseLocationStruct(); + updatedata.Locations = new List(); + updatedata.Uuids = new List(); + updatedata.Offset = offset; + + if (offset == Vector2.Zero) + updatedata.UserAPI = rootPresence.ControllingClient; + else + updatedata.UserAPI = LocateUsersChildAgentIClientAPI(offset, rootPresence.UUID, rdata); + + updates.Add(offset,updatedata); + } + + updates[offset].Locations.Add(locations[i]); + updates[offset].Uuids.Add(uuids[i]); + } + + // Send out the CoarseLocationupdates from their respective client connection based on where the avatar is + foreach (Vector2 offset in updates.Keys) + { + if (updates[offset].UserAPI != null) + { + updates[offset].UserAPI.SendCoarseLocationUpdate(updates[offset].Uuids,updates[offset].Locations); + } + } + } + + /// + /// Locates a the Client of a particular region in an Array of RegionData based on offset + /// + /// + /// + /// + /// IClientAPI or null + private IClientAPI LocateUsersChildAgentIClientAPI(Vector2 offset, UUID uUID, RegionData[] rdata) + { + IClientAPI returnclient = null; + foreach (RegionData r in rdata) + { + if (r.Offset.X == offset.X && r.Offset.Y == offset.Y) + { + return r.RegionScene.SceneGraph.GetControllingClient(uUID); + } + } + + return returnclient; + } + + public void PostInitialise() + { + } + +// /// +// /// TODO: +// /// +// /// +// public void UnCombineRegion(RegionData rdata) +// { +// lock (m_regions) +// { +// if (m_regions.ContainsKey(rdata.RegionId)) +// { +// // uncombine root region and virtual regions +// } +// else +// { +// foreach (RegionConnections r in m_regions.Values) +// { +// foreach (RegionData rd in r.ConnectedRegions) +// { +// if (rd.RegionId == rdata.RegionId) +// { +// // uncombine virtual region +// } +// } +// } +// } +// } +// } + + public void ForwardPermissionRequests(RegionConnections BigRegion, Scene VirtualRegion) + { + if (BigRegion.PermissionModule == null) + BigRegion.PermissionModule = new RegionCombinerPermissionModule(BigRegion.RegionScene); + + VirtualRegion.Permissions.OnBypassPermissions += BigRegion.PermissionModule.BypassPermissions; + VirtualRegion.Permissions.OnSetBypassPermissions += BigRegion.PermissionModule.SetBypassPermissions; + VirtualRegion.Permissions.OnPropagatePermissions += BigRegion.PermissionModule.PropagatePermissions; + VirtualRegion.Permissions.OnGenerateClientFlags += BigRegion.PermissionModule.GenerateClientFlags; + VirtualRegion.Permissions.OnAbandonParcel += BigRegion.PermissionModule.CanAbandonParcel; + VirtualRegion.Permissions.OnReclaimParcel += BigRegion.PermissionModule.CanReclaimParcel; + VirtualRegion.Permissions.OnDeedParcel += BigRegion.PermissionModule.CanDeedParcel; + VirtualRegion.Permissions.OnDeedObject += BigRegion.PermissionModule.CanDeedObject; + VirtualRegion.Permissions.OnIsGod += BigRegion.PermissionModule.IsGod; + VirtualRegion.Permissions.OnDuplicateObject += BigRegion.PermissionModule.CanDuplicateObject; + VirtualRegion.Permissions.OnDeleteObject += BigRegion.PermissionModule.CanDeleteObject; //MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnEditObject += BigRegion.PermissionModule.CanEditObject; //MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnEditParcelProperties += BigRegion.PermissionModule.CanEditParcelProperties; //MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnInstantMessage += BigRegion.PermissionModule.CanInstantMessage; + VirtualRegion.Permissions.OnInventoryTransfer += BigRegion.PermissionModule.CanInventoryTransfer; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnIssueEstateCommand += BigRegion.PermissionModule.CanIssueEstateCommand; //FULLY IMPLEMENTED + VirtualRegion.Permissions.OnMoveObject += BigRegion.PermissionModule.CanMoveObject; //MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnObjectEntry += BigRegion.PermissionModule.CanObjectEntry; + VirtualRegion.Permissions.OnReturnObjects += BigRegion.PermissionModule.CanReturnObjects; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnRezObject += BigRegion.PermissionModule.CanRezObject; //MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnRunConsoleCommand += BigRegion.PermissionModule.CanRunConsoleCommand; + VirtualRegion.Permissions.OnRunScript += BigRegion.PermissionModule.CanRunScript; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnCompileScript += BigRegion.PermissionModule.CanCompileScript; + VirtualRegion.Permissions.OnSellParcel += BigRegion.PermissionModule.CanSellParcel; + VirtualRegion.Permissions.OnTakeObject += BigRegion.PermissionModule.CanTakeObject; + VirtualRegion.Permissions.OnTakeCopyObject += BigRegion.PermissionModule.CanTakeCopyObject; + VirtualRegion.Permissions.OnTerraformLand += BigRegion.PermissionModule.CanTerraformLand; + VirtualRegion.Permissions.OnLinkObject += BigRegion.PermissionModule.CanLinkObject; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnDelinkObject += BigRegion.PermissionModule.CanDelinkObject; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnBuyLand += BigRegion.PermissionModule.CanBuyLand; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnViewNotecard += BigRegion.PermissionModule.CanViewNotecard; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnViewScript += BigRegion.PermissionModule.CanViewScript; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnEditNotecard += BigRegion.PermissionModule.CanEditNotecard; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnEditScript += BigRegion.PermissionModule.CanEditScript; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnCreateObjectInventory += BigRegion.PermissionModule.CanCreateObjectInventory; //NOT IMPLEMENTED HERE + VirtualRegion.Permissions.OnEditObjectInventory += BigRegion.PermissionModule.CanEditObjectInventory;//MAYBE FULLY IMPLEMENTED + VirtualRegion.Permissions.OnCopyObjectInventory += BigRegion.PermissionModule.CanCopyObjectInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnDeleteObjectInventory += BigRegion.PermissionModule.CanDeleteObjectInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnResetScript += BigRegion.PermissionModule.CanResetScript; + VirtualRegion.Permissions.OnCreateUserInventory += BigRegion.PermissionModule.CanCreateUserInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnCopyUserInventory += BigRegion.PermissionModule.CanCopyUserInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnEditUserInventory += BigRegion.PermissionModule.CanEditUserInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnDeleteUserInventory += BigRegion.PermissionModule.CanDeleteUserInventory; //NOT YET IMPLEMENTED + VirtualRegion.Permissions.OnTeleport += BigRegion.PermissionModule.CanTeleport; //NOT YET IMPLEMENTED + } + + #region console commands + + public void FixPhantoms(string module, string[] cmdparams) + { + List scenes = new List(m_startingScenes.Values); + + foreach (Scene s in scenes) + { + MainConsole.Instance.OutputFormat("Fixing phantoms for {0}", s.RegionInfo.RegionName); + + s.ForEachSOG(so => so.AbsolutePosition = so.AbsolutePosition); + } + } + + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerPermissionModule.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerPermissionModule.cs new file mode 100644 index 0000000..07dd68b --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCombinerPermissionModule.cs @@ -0,0 +1,270 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.RegionCombinerModule +{ + public class RegionCombinerPermissionModule + { + private Scene m_rootScene; + + public RegionCombinerPermissionModule(Scene RootScene) + { + m_rootScene = RootScene; + } + + #region Permission Override + + public bool BypassPermissions() + { + return m_rootScene.Permissions.BypassPermissions(); + } + + public void SetBypassPermissions(bool value) + { + m_rootScene.Permissions.SetBypassPermissions(value); + } + + public bool PropagatePermissions() + { + return m_rootScene.Permissions.PropagatePermissions(); + } + + public uint GenerateClientFlags(UUID userid, UUID objectidid) + { + return m_rootScene.Permissions.GenerateClientFlags(userid,objectidid); + } + + public bool CanAbandonParcel(UUID user, ILandObject parcel, Scene scene) + { + return m_rootScene.Permissions.CanAbandonParcel(user,parcel); + } + + public bool CanReclaimParcel(UUID user, ILandObject parcel, Scene scene) + { + return m_rootScene.Permissions.CanReclaimParcel(user, parcel); + } + + public bool CanDeedParcel(UUID user, ILandObject parcel, Scene scene) + { + return m_rootScene.Permissions.CanDeedParcel(user, parcel); + } + + public bool CanDeedObject(UUID user, UUID @group, Scene scene) + { + return m_rootScene.Permissions.CanDeedObject(user,@group); + } + + public bool IsGod(UUID user, Scene requestfromscene) + { + return m_rootScene.Permissions.IsGod(user); + } + + public bool CanDuplicateObject(int objectcount, UUID objectid, UUID owner, Scene scene, Vector3 objectposition) + { + return m_rootScene.Permissions.CanDuplicateObject(objectcount, objectid, owner, objectposition); + } + + public bool CanDeleteObject(UUID objectid, UUID deleter, Scene scene) + { + return m_rootScene.Permissions.CanDeleteObject(objectid, deleter); + } + + public bool CanEditObject(UUID objectid, UUID editorid, Scene scene) + { + return m_rootScene.Permissions.CanEditObject(objectid, editorid); + } + + public bool CanEditParcelProperties(UUID user, ILandObject parcel, GroupPowers g, Scene scene) + { + return m_rootScene.Permissions.CanEditParcelProperties(user, parcel, g); + } + + public bool CanInstantMessage(UUID user, UUID target, Scene startscene) + { + return m_rootScene.Permissions.CanInstantMessage(user, target); + } + + public bool CanInventoryTransfer(UUID user, UUID target, Scene startscene) + { + return m_rootScene.Permissions.CanInventoryTransfer(user, target); + } + + public bool CanIssueEstateCommand(UUID user, Scene requestfromscene, bool ownercommand) + { + return m_rootScene.Permissions.CanIssueEstateCommand(user, ownercommand); + } + + public bool CanMoveObject(UUID objectid, UUID moverid, Scene scene) + { + return m_rootScene.Permissions.CanMoveObject(objectid, moverid); + } + + public bool CanObjectEntry(UUID objectid, bool enteringregion, Vector3 newpoint, Scene scene) + { + return m_rootScene.Permissions.CanObjectEntry(objectid, enteringregion, newpoint); + } + + public bool CanReturnObjects(ILandObject land, UUID user, List objects, Scene scene) + { + return m_rootScene.Permissions.CanReturnObjects(land, user, objects); + } + + public bool CanRezObject(int objectcount, UUID owner, Vector3 objectposition, Scene scene) + { + return m_rootScene.Permissions.CanRezObject(objectcount, owner, objectposition); + } + + public bool CanRunConsoleCommand(UUID user, Scene requestfromscene) + { + return m_rootScene.Permissions.CanRunConsoleCommand(user); + } + + public bool CanRunScript(UUID script, UUID objectid, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanRunScript(script, objectid, user); + } + + public bool CanCompileScript(UUID owneruuid, int scripttype, Scene scene) + { + return m_rootScene.Permissions.CanCompileScript(owneruuid, scripttype); + } + + public bool CanSellParcel(UUID user, ILandObject parcel, Scene scene) + { + return m_rootScene.Permissions.CanSellParcel(user, parcel); + } + + public bool CanTakeObject(UUID objectid, UUID stealer, Scene scene) + { + return m_rootScene.Permissions.CanTakeObject(objectid, stealer); + } + + public bool CanTakeCopyObject(UUID objectid, UUID userid, Scene inscene) + { + return m_rootScene.Permissions.CanTakeObject(objectid, userid); + } + + public bool CanTerraformLand(UUID user, Vector3 position, Scene requestfromscene) + { + return m_rootScene.Permissions.CanTerraformLand(user, position); + } + + public bool CanLinkObject(UUID user, UUID objectid) + { + return m_rootScene.Permissions.CanLinkObject(user, objectid); + } + + public bool CanDelinkObject(UUID user, UUID objectid) + { + return m_rootScene.Permissions.CanDelinkObject(user, objectid); + } + + public bool CanBuyLand(UUID user, ILandObject parcel, Scene scene) + { + return m_rootScene.Permissions.CanBuyLand(user, parcel); + } + + public bool CanViewNotecard(UUID script, UUID objectid, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanViewNotecard(script, objectid, user); + } + + public bool CanViewScript(UUID script, UUID objectid, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanViewScript(script, objectid, user); + } + + public bool CanEditNotecard(UUID notecard, UUID objectid, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanEditNotecard(notecard, objectid, user); + } + + public bool CanEditScript(UUID script, UUID objectid, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanEditScript(script, objectid, user); + } + + public bool CanCreateObjectInventory(int invtype, UUID objectid, UUID userid) + { + return m_rootScene.Permissions.CanCreateObjectInventory(invtype, objectid, userid); + } + + public bool CanEditObjectInventory(UUID objectid, UUID editorid, Scene scene) + { + return m_rootScene.Permissions.CanEditObjectInventory(objectid, editorid); + } + + public bool CanCopyObjectInventory(UUID itemid, UUID objectid, UUID userid) + { + return m_rootScene.Permissions.CanCopyObjectInventory(itemid, objectid, userid); + } + + public bool CanDeleteObjectInventory(UUID itemid, UUID objectid, UUID userid) + { + return m_rootScene.Permissions.CanDeleteObjectInventory(itemid, objectid, userid); + } + + public bool CanResetScript(UUID prim, UUID script, UUID user, Scene scene) + { + return m_rootScene.Permissions.CanResetScript(prim, script, user); + } + + public bool CanCreateUserInventory(int invtype, UUID userid) + { + return m_rootScene.Permissions.CanCreateUserInventory(invtype, userid); + } + + public bool CanCopyUserInventory(UUID itemid, UUID userid) + { + return m_rootScene.Permissions.CanCopyUserInventory(itemid, userid); + } + + public bool CanEditUserInventory(UUID itemid, UUID userid) + { + return m_rootScene.Permissions.CanEditUserInventory(itemid, userid); + } + + public bool CanDeleteUserInventory(UUID itemid, UUID userid) + { + return m_rootScene.Permissions.CanDeleteUserInventory(itemid, userid); + } + + public bool CanTeleport(UUID userid, Scene scene) + { + return m_rootScene.Permissions.CanTeleport(userid); + } + + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionConnections.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionConnections.cs new file mode 100644 index 0000000..62a3a91 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionConnections.cs @@ -0,0 +1,94 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.RegionCombinerModule +{ + public class RegionConnections + { + /// + /// Root Region ID + /// + public UUID RegionId; + + /// + /// Root Region Scene + /// + public Scene RegionScene; + + /// + /// LargeLandChannel for combined region + /// + public ILandChannel RegionLandChannel; + + /// + /// The x map co-ordinate for this region (where each co-ordinate is a Constants.RegionSize block). + /// + public uint X; + + /// + /// The y co-ordinate for this region (where each cor-odinate is a Constants.RegionSize block). + /// + public uint Y; + + /// + /// The X meters position of this connection. + /// + public uint PosX { get { return Util.RegionToWorldLoc(X); } } + + /// + /// The Y meters co-ordinate of this connection. + /// + public uint PosY { get { return Util.RegionToWorldLoc(Y); } } + + /// + /// The size of the megaregion in meters. + /// + public uint XEnd; + + /// + /// The size of the megaregion in meters. + /// + public uint YEnd; + + public List ConnectedRegions; + public RegionCombinerPermissionModule PermissionModule; + public RegionCombinerClientEventForwarder ClientEventForwarder; + + public void UpdateExtents(Vector3 extents) + { + XEnd = (uint)extents.X; + YEnd = (uint)extents.Y; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCourseLocation.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCourseLocation.cs new file mode 100644 index 0000000..224ac99 --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionCourseLocation.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.RegionCombinerModule +{ + + struct RegionCoarseLocationStruct + { + public List Locations; + public List Uuids; + public IClientAPI UserAPI; + public Vector2 Offset; + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionData.cs b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionData.cs new file mode 100644 index 0000000..42fca9f --- /dev/null +++ b/OpenSim/Region/OptionalModules/RegionCombinerModule/RegionData.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.RegionCombinerModule +{ + public class RegionData + { + public UUID RegionId; + public Scene RegionScene; + // Offset of this region from the base of the root region. + public Vector3 Offset; + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs index 34894ba..c38bb3e 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs @@ -49,7 +49,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private OSD m_ValueStore; + protected virtual OSD ValueStore { get; set; } protected class TakeValueCallbackClass { @@ -68,42 +68,141 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore protected List m_TakeStore; protected List m_ReadStore; + // add separators for quoted paths and array references + protected static Regex m_ParsePassOne = new Regex("({[^}]+}|\\[[0-9]+\\]|\\[\\+\\])"); + // add quotes to bare identifiers which are limited to alphabetic characters + protected static Regex m_ParsePassThree = new Regex("(? + /// This is a simple estimator for the size of the stored data, it + /// is not precise, but should be close enough to implement reasonable + /// limits on the storage space used + /// + // ----------------------------------------------------------------- + public int StringSpace { get; set; } + // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - public JsonStore() : this("") {} + public static bool CanonicalPathExpression(string ipath, out string opath) + { + Stack path; + if (! ParsePathExpression(ipath,out path)) + { + opath = ""; + return false; + } + + opath = PathExpressionToKey(path); + return true; + } - public JsonStore(string value) + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStore() { + StringSpace = 0; m_TakeStore = new List(); m_ReadStore = new List(); - + } + + public JsonStore(string value) : this() + { + // This is going to throw an exception if the value is not + // a valid JSON chunk. Calling routines should catch the + // exception and handle it appropriately if (String.IsNullOrEmpty(value)) - m_ValueStore = new OSDMap(); + ValueStore = new OSDMap(); else - m_ValueStore = OSDParser.DeserializeJson(value); + ValueStore = OSDParser.DeserializeJson(value); } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStoreNodeType GetNodeType(string expr) + { + Stack path; + if (! ParsePathExpression(expr,out path)) + return JsonStoreNodeType.Undefined; + + OSD result = ProcessPathExpression(ValueStore,path); + if (result == null) + return JsonStoreNodeType.Undefined; + + if (result is OSDMap) + return JsonStoreNodeType.Object; + + if (result is OSDArray) + return JsonStoreNodeType.Array; + + if (OSDBaseType(result.Type)) + return JsonStoreNodeType.Value; + + return JsonStoreNodeType.Undefined; + } + // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - public bool TestPath(string expr, bool useJson) + public JsonStoreValueType GetValueType(string expr) { - Stack path = ParsePathExpression(expr); - OSD result = ProcessPathExpression(m_ValueStore,path); + Stack path; + if (! ParsePathExpression(expr,out path)) + return JsonStoreValueType.Undefined; + + OSD result = ProcessPathExpression(ValueStore,path); if (result == null) - return false; + return JsonStoreValueType.Undefined; - if (useJson || result.Type == OSDType.String) - return true; + if (result is OSDMap) + return JsonStoreValueType.Undefined; - return false; + if (result is OSDArray) + return JsonStoreValueType.Undefined; + + if (result is OSDBoolean) + return JsonStoreValueType.Boolean; + + if (result is OSDInteger) + return JsonStoreValueType.Integer; + + if (result is OSDReal) + return JsonStoreValueType.Float; + + if (result is OSDString) + return JsonStoreValueType.String; + + return JsonStoreValueType.Undefined; } // ----------------------------------------------------------------- @@ -111,10 +210,37 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- + public int ArrayLength(string expr) + { + Stack path; + if (! ParsePathExpression(expr,out path)) + return -1; + + OSD result = ProcessPathExpression(ValueStore,path); + if (result != null && result.Type == OSDType.Array) + { + OSDArray arr = result as OSDArray; + return arr.Count; + } + + return -1; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- public bool GetValue(string expr, out string value, bool useJson) { - Stack path = ParsePathExpression(expr); - OSD result = ProcessPathExpression(m_ValueStore,path); + Stack path; + if (! ParsePathExpression(expr,out path)) + { + value = ""; + return false; + } + + OSD result = ProcessPathExpression(ValueStore,path); return ConvertOutputValue(result,out value,useJson); } @@ -136,7 +262,37 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public bool SetValue(string expr, string value, bool useJson) { - OSD ovalue = useJson ? OSDParser.DeserializeJson(value) : new OSDString(value); + OSD ovalue; + + // One note of caution... if you use an empty string in the + // structure it will be assumed to be a default value and will + // not be seialized in the json + + if (useJson) + { + // There doesn't appear to be a good way to determine if the + // value is valid Json other than to let the parser crash + try + { + ovalue = OSDParser.DeserializeJson(value); + } + catch (Exception) + { + if (value.StartsWith("'") && value.EndsWith("'")) + { + ovalue = new OSDString(value.Substring(1,value.Length - 2)); + } + else + { + return false; + } + } + } + else + { + ovalue = new OSDString(value); + } + return SetValueFromExpression(expr,ovalue); } @@ -147,10 +303,13 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public bool TakeValue(string expr, bool useJson, TakeValueCallback cback) { - Stack path = ParsePathExpression(expr); + Stack path; + if (! ParsePathExpression(expr,out path)) + return false; + string pexpr = PathExpressionToKey(path); - OSD result = ProcessPathExpression(m_ValueStore,path); + OSD result = ProcessPathExpression(ValueStore,path); if (result == null) { m_TakeStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); @@ -178,10 +337,13 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public bool ReadValue(string expr, bool useJson, TakeValueCallback cback) { - Stack path = ParsePathExpression(expr); + Stack path; + if (! ParsePathExpression(expr,out path)) + return false; + string pexpr = PathExpressionToKey(path); - OSD result = ProcessPathExpression(m_ValueStore,path); + OSD result = ProcessPathExpression(ValueStore,path); if (result == null) { m_ReadStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); @@ -208,25 +370,30 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- protected bool SetValueFromExpression(string expr, OSD ovalue) { - Stack path = ParsePathExpression(expr); + Stack path; + if (! ParsePathExpression(expr,out path)) + return false; + if (path.Count == 0) { - m_ValueStore = ovalue; + ValueStore = ovalue; + StringSpace = 0; return true; } + // pkey will be the final element in the path, we pull it out here to make sure + // that the assignment works correctly string pkey = path.Pop(); string pexpr = PathExpressionToKey(path); if (pexpr != "") pexpr += "."; - OSD result = ProcessPathExpression(m_ValueStore,path); + OSD result = ProcessPathExpression(ValueStore,path); if (result == null) return false; - Regex aPattern = new Regex("\\[([0-9]+|\\+)\\]"); - MatchCollection amatches = aPattern.Matches(pkey,0); - + // Check pkey, the last element in the path, for and extract array references + MatchCollection amatches = m_ArrayPattern.Matches(pkey,0); if (amatches.Count > 0) { if (result.Type != OSDType.Array) @@ -242,8 +409,13 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore { string npkey = String.Format("[{0}]",amap.Count); - amap.Add(ovalue); - InvokeNextCallback(pexpr + npkey); + if (ovalue != null) + { + StringSpace += ComputeSizeOf(ovalue); + + amap.Add(ovalue); + InvokeNextCallback(pexpr + npkey); + } return true; } @@ -251,9 +423,14 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore if (0 <= aval && aval < amap.Count) { if (ovalue == null) + { + StringSpace -= ComputeSizeOf(amap[aval]); amap.RemoveAt(aval); + } else { + StringSpace -= ComputeSizeOf(amap[aval]); + StringSpace += ComputeSizeOf(ovalue); amap[aval] = ovalue; InvokeNextCallback(pexpr + pkey); } @@ -263,9 +440,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore return false; } - Regex hPattern = new Regex("{([^}]+)}"); - MatchCollection hmatches = hPattern.Matches(pkey,0); - + // Check for and extract hash references + MatchCollection hmatches = m_HashPattern.Matches(pkey,0); if (hmatches.Count > 0) { Match match = hmatches[0]; @@ -274,16 +450,27 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore if (result is OSDMap) { + // this is the assignment case OSDMap hmap = result as OSDMap; if (ovalue != null) { + StringSpace -= ComputeSizeOf(hmap[hkey]); + StringSpace += ComputeSizeOf(ovalue); + hmap[hkey] = ovalue; InvokeNextCallback(pexpr + pkey); + return true; } - else if (hmap.ContainsKey(hkey)) + + // this is the remove case + if (hmap.ContainsKey(hkey)) + { + StringSpace -= ComputeSizeOf(hmap[hkey]); hmap.Remove(hkey); - - return true; + return true; + } + + return false; } return false; @@ -332,39 +519,33 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// use a stack because we process the path in inverse order later /// // ----------------------------------------------------------------- - protected static Stack ParsePathExpression(string path) + protected static bool ParsePathExpression(string expr, out Stack path) { - Stack m_path = new Stack(); + path = new Stack(); // add front and rear separators - path = "." + path + "."; + expr = "." + expr + "."; - // add separators for quoted paths - Regex pass1 = new Regex("{[^}]+}"); - path = pass1.Replace(path,".$0.",-1,0); - - // add separators for array references - Regex pass2 = new Regex("(\\[[0-9]+\\]|\\[\\+\\])"); - path = pass2.Replace(path,".$0.",-1,0); + // add separators for quoted exprs and array references + expr = m_ParsePassOne.Replace(expr,".$1.",-1,0); // add quotes to bare identifier - Regex pass3 = new Regex("\\.([a-zA-Z]+)"); - path = pass3.Replace(path,".{$1}",-1,0); + expr = m_ParsePassThree.Replace(expr,".{$1}",-1,0); // remove extra separators - Regex pass4 = new Regex("\\.+"); - path = pass4.Replace(path,".",-1,0); + expr = m_ParsePassFour.Replace(expr,".",-1,0); - Regex validate = new Regex("^\\.(({[^}]+}|\\[[0-9]+\\]|\\[\\+\\])\\.)+$"); - if (validate.IsMatch(path)) + // validate the results (catches extra quote characters for example) + if (m_ValidatePath.IsMatch(expr)) { - Regex parser = new Regex("\\.({[^}]+}|\\[[0-9]+\\]|\\[\\+\\]+)"); - MatchCollection matches = parser.Matches(path,0); + MatchCollection matches = m_PathComponent.Matches(expr,0); foreach (Match match in matches) - m_path.Push(match.Groups[1].Value); + path.Push(match.Groups[1].Value); + + return true; } - return m_path; + return false; } // ----------------------------------------------------------------- @@ -385,9 +566,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore return null; // ---------- Check for an array index ---------- - Regex aPattern = new Regex("\\[([0-9]+)\\]"); - MatchCollection amatches = aPattern.Matches(pkey,0); - + MatchCollection amatches = m_SimpleArrayPattern.Matches(pkey,0); + if (amatches.Count > 0) { if (rmap.Type != OSDType.Array) @@ -410,9 +590,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } // ---------- Check for a hash index ---------- - Regex hPattern = new Regex("{([^}]+)}"); - MatchCollection hmatches = hPattern.Matches(pkey,0); - + MatchCollection hmatches = m_HashPattern.Matches(pkey,0); + if (hmatches.Count > 0) { if (rmap.Type != OSDType.Map) @@ -456,14 +635,14 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // The path pointed to an intermediate hash structure if (result.Type == OSDType.Map) { - value = OSDParser.SerializeJsonString(result as OSDMap); + value = OSDParser.SerializeJsonString(result as OSDMap,true); return true; } // The path pointed to an intermediate hash structure if (result.Type == OSDType.Array) { - value = OSDParser.SerializeJsonString(result as OSDArray); + value = OSDParser.SerializeJsonString(result as OSDArray,true); return true; } @@ -471,7 +650,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore return true; } - if (result.Type == OSDType.String) + if (OSDBaseType(result.Type)) { value = result.AsString(); return true; @@ -496,5 +675,91 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore return pkey; } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected static bool OSDBaseType(OSDType type) + { + // Should be the list of base types for which AsString() returns + // something useful + if (type == OSDType.Boolean) + return true; + if (type == OSDType.Integer) + return true; + if (type == OSDType.Real) + return true; + if (type == OSDType.String) + return true; + if (type == OSDType.UUID) + return true; + if (type == OSDType.Date) + return true; + if (type == OSDType.URI) + return true; + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected static int ComputeSizeOf(OSD value) + { + string sval; + + if (ConvertOutputValue(value,out sval,true)) + return sval.Length; + + return 0; + } + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public class JsonObjectStore : JsonStore + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private UUID m_objectID; + + protected override OSD ValueStore + { + get + { + SceneObjectPart sop = m_scene.GetSceneObjectPart(m_objectID); + if (sop == null) + { + // This is bad + return null; + } + + return sop.DynAttrs.TopLevelMap; + } + + // cannot set the top level + set + { + m_log.InfoFormat("[JsonStore] cannot set top level value in object store"); + } + } + + public JsonObjectStore(Scene scene, UUID oid) : base() + { + m_scene = scene; + m_objectID = oid; + + // the size limit is imposed on whatever is already in the store + StringSpace = ComputeSizeOf(ValueStore); + } } + } diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs new file mode 100644 index 0000000..d4b19dd --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using Mono.Addins; + +using System; +using System.Reflection; +using System.Threading; +using System.Text; +using System.Net; +using System.Net.Sockets; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreCommandsModule")] + + public class JsonStoreCommandsModule : INonSharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IConfig m_config = null; + private bool m_enabled = false; + + private Scene m_scene = null; + //private IJsonStoreModule m_store; + private JsonStoreModule m_store; + +#region Region Module interface + + // ----------------------------------------------------------------- + /// + /// Name of this shared module is it's class name + /// + // ----------------------------------------------------------------- + public string Name + { + get { return this.GetType().Name; } + } + + // ----------------------------------------------------------------- + /// + /// Initialise this shared module + /// + /// this region is getting initialised + /// nini config, we are not using this + // ----------------------------------------------------------------- + public void Initialise(IConfigSource config) + { + try + { + if ((m_config = config.Configs["JsonStore"]) == null) + { + // There is no configuration, the module is disabled + // m_log.InfoFormat("[JsonStore] no configuration info"); + return; + } + + m_enabled = m_config.GetBoolean("Enabled", m_enabled); + } + catch (Exception e) + { + m_log.Error("[JsonStore]: initialization error: {0}", e); + return; + } + + if (m_enabled) + m_log.DebugFormat("[JsonStore]: module is enabled"); + } + + // ----------------------------------------------------------------- + /// + /// everything is loaded, perform post load configuration + /// + // ----------------------------------------------------------------- + public void PostInitialise() + { + } + + // ----------------------------------------------------------------- + /// + /// Nothing to do on close + /// + // ----------------------------------------------------------------- + public void Close() + { + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void AddRegion(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + + } + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void RemoveRegion(Scene scene) + { + // need to remove all references to the scene in the subscription + // list to enable full garbage collection of the scene object + } + + // ----------------------------------------------------------------- + /// + /// Called when all modules have been added for a region. This is + /// where we hook up events + /// + // ----------------------------------------------------------------- + public void RegionLoaded(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + + m_store = (JsonStoreModule) m_scene.RequestModuleInterface(); + if (m_store == null) + { + m_log.ErrorFormat("[JsonStoreCommands]: JsonModule interface not defined"); + m_enabled = false; + return; + } + + scene.AddCommand("JsonStore", this, "jsonstore stats", "jsonstore stats", + "Display statistics about the state of the JsonStore module", "", + CmdStats); + } + } + + /// ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public Type ReplaceableInterface + { + get { return null; } + } + +#endregion + +#region Commands + + private void CmdStats(string module, string[] cmd) + { + if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null) + return; + + JsonStoreStats stats = m_store.GetStoreStats(); + MainConsole.Instance.OutputFormat("{0}\t{1}",m_scene.RegionInfo.RegionName,stats.StoreCount); + } + +#endregion + + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs index e68764a..26044f0 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs @@ -42,7 +42,6 @@ using OpenSim.Region.Framework.Scenes; using System.Collections.Generic; using System.Text.RegularExpressions; - namespace OpenSim.Region.OptionalModules.Scripting.JsonStore { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreModule")] @@ -54,9 +53,13 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore private IConfig m_config = null; private bool m_enabled = false; + private bool m_enableObjectStore = false; + private int m_maxStringSpace = Int32.MaxValue; + private Scene m_scene = null; private Dictionary m_JsonValueStore; + private UUID m_sharedStore; #region Region Module interface @@ -90,15 +93,19 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } m_enabled = m_config.GetBoolean("Enabled", m_enabled); + m_enableObjectStore = m_config.GetBoolean("EnableObjectStore", m_enableObjectStore); + m_maxStringSpace = m_config.GetInt("MaxStringSpace", m_maxStringSpace); + if (m_maxStringSpace == 0) + m_maxStringSpace = Int32.MaxValue; } catch (Exception e) { - m_log.ErrorFormat("[JsonStore] initialization error: {0}",e.Message); + m_log.Error("[JsonStore]: initialization error: {0}", e); return; } if (m_enabled) - m_log.DebugFormat("[JsonStore] module is enabled"); + m_log.DebugFormat("[JsonStore]: module is enabled"); } // ----------------------------------------------------------------- @@ -133,6 +140,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore m_sharedStore = UUID.Zero; m_JsonValueStore = new Dictionary(); m_JsonValueStore.Add(m_sharedStore,new JsonStore("")); + + scene.EventManager.OnObjectBeingRemovedFromScene += EventManagerOnObjectBeingRemovedFromScene; } } @@ -142,6 +151,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RemoveRegion(Scene scene) { + scene.EventManager.OnObjectBeingRemovedFromScene -= EventManagerOnObjectBeingRemovedFromScene; + // need to remove all references to the scene in the subscription // list to enable full garbage collection of the scene object } @@ -154,7 +165,9 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RegionLoaded(Scene scene) { - if (m_enabled) {} + if (m_enabled) + { + } } /// ----------------------------------------------------------------- @@ -168,8 +181,68 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore #endregion +#region SceneEvents + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public void EventManagerOnObjectBeingRemovedFromScene(SceneObjectGroup obj) + { + obj.ForEachPart(delegate(SceneObjectPart sop) { DestroyStore(sop.UUID); } ); + } + +#endregion + #region ScriptInvocationInteface + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStoreStats GetStoreStats() + { + JsonStoreStats stats; + + lock (m_JsonValueStore) + { + stats.StoreCount = m_JsonValueStore.Count; + } + + return stats; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool AttachObjectStore(UUID objectID) + { + if (! m_enabled) return false; + if (! m_enableObjectStore) return false; + + SceneObjectPart sop = m_scene.GetSceneObjectPart(objectID); + if (sop == null) + { + m_log.ErrorFormat("[JsonStore] unable to attach to unknown object; {0}", objectID); + return false; + } + + lock (m_JsonValueStore) + { + if (m_JsonValueStore.ContainsKey(objectID)) + return true; + + JsonStore map = new JsonObjectStore(m_scene,objectID); + m_JsonValueStore.Add(objectID,map); + } + + return true; + } + // ----------------------------------------------------------------- /// /// @@ -189,9 +262,9 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore { map = new JsonStore(value); } - catch (Exception e) + catch (Exception) { - m_log.InfoFormat("[JsonStore] Unable to initialize store from {0}; {1}",value,e.Message); + m_log.ErrorFormat("[JsonStore]: Unable to initialize store from {0}", value); return false; } @@ -211,9 +284,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore if (! m_enabled) return false; lock (m_JsonValueStore) - m_JsonValueStore.Remove(storeID); - - return true; + return m_JsonValueStore.Remove(storeID); } // ----------------------------------------------------------------- @@ -221,31 +292,76 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - public bool TestPath(UUID storeID, string path, bool useJson) + public bool TestStore(UUID storeID) { if (! m_enabled) return false; + lock (m_JsonValueStore) + return m_JsonValueStore.ContainsKey(storeID); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStoreNodeType GetNodeType(UUID storeID, string path) + { + if (! m_enabled) return JsonStoreNodeType.Undefined; + JsonStore map = null; lock (m_JsonValueStore) { if (! m_JsonValueStore.TryGetValue(storeID,out map)) { m_log.InfoFormat("[JsonStore] Missing store {0}",storeID); - return false; + return JsonStoreNodeType.Undefined; } } try { lock (map) - return map.TestPath(path,useJson); + return map.GetNodeType(path); } catch (Exception e) { - m_log.InfoFormat("[JsonStore] Path test failed for {0} in {1}; {2}",path,storeID,e.Message); + m_log.Error(string.Format("[JsonStore]: Path test failed for {0} in {1}", path, storeID), e); } - return false; + return JsonStoreNodeType.Undefined; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStoreValueType GetValueType(UUID storeID, string path) + { + if (! m_enabled) return JsonStoreValueType.Undefined; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + m_log.InfoFormat("[JsonStore] Missing store {0}",storeID); + return JsonStoreValueType.Undefined; + } + } + + try + { + lock (map) + return map.GetValueType(path); + } + catch (Exception e) + { + m_log.Error(string.Format("[JsonStore]: Path test failed for {0} in {1}", path, storeID), e); + } + + return JsonStoreValueType.Undefined; } // ----------------------------------------------------------------- @@ -270,12 +386,20 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore try { lock (map) - if (map.SetValue(path,value,useJson)) - return true; + { + if (map.StringSpace > m_maxStringSpace) + { + m_log.WarnFormat("[JsonStore] {0} exceeded string size; {1} bytes used of {2} limit", + storeID,map.StringSpace,m_maxStringSpace); + return false; + } + + return map.SetValue(path,value,useJson); + } } catch (Exception e) { - m_log.InfoFormat("[JsonStore] Unable to assign {0} to {1} in {2}; {3}",value,path,storeID,e.Message); + m_log.Error(string.Format("[JsonStore]: Unable to assign {0} to {1} in {2}", value, path, storeID), e); } return false; @@ -303,12 +427,11 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore try { lock (map) - if (map.RemoveValue(path)) - return true; + return map.RemoveValue(path); } catch (Exception e) { - m_log.InfoFormat("[JsonStore] Unable to remove {0} in {1}; {2}",path,storeID,e.Message); + m_log.Error(string.Format("[JsonStore]: Unable to remove {0} in {1}", path, storeID), e); } return false; @@ -319,6 +442,37 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- + public int GetArrayLength(UUID storeID, string path) + { + if (! m_enabled) return -1; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + return -1; + } + + try + { + lock (map) + { + return map.ArrayLength(path); + } + } + catch (Exception e) + { + m_log.Error("[JsonStore]: unable to retrieve value", e); + } + + return -1; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- public bool GetValue(UUID storeID, string path, bool useJson, out string value) { value = String.Empty; @@ -341,7 +495,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } catch (Exception e) { - m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.Message); + m_log.Error("[JsonStore]: unable to retrieve value", e); } return false; @@ -380,7 +534,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } catch (Exception e) { - m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.ToString()); + m_log.Error("[JsonStore] unable to retrieve value", e); } cback(String.Empty); @@ -419,7 +573,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } catch (Exception e) { - m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.ToString()); + m_log.Error("[JsonStore]: unable to retrieve value", e); } cback(String.Empty); diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs index 0c175ca..edf51a2 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs @@ -39,8 +39,10 @@ using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Scripting; using System.Collections.Generic; using System.Text.RegularExpressions; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.OptionalModules.Scripting.JsonStore { @@ -57,7 +59,9 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore private IScriptModuleComms m_comms; private IJsonStoreModule m_store; - + + private Dictionary> m_scriptStores = new Dictionary>(); + #region Region Module interface // ----------------------------------------------------------------- @@ -92,12 +96,12 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore } catch (Exception e) { - m_log.ErrorFormat("[JsonStoreScripts] initialization error: {0}",e.Message); + m_log.ErrorFormat("[JsonStoreScripts]: initialization error: {0}", e.Message); return; } if (m_enabled) - m_log.DebugFormat("[JsonStoreScripts] module is enabled"); + m_log.DebugFormat("[JsonStoreScripts]: module is enabled"); } // ----------------------------------------------------------------- @@ -124,6 +128,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void AddRegion(Scene scene) { + scene.EventManager.OnScriptReset += HandleScriptReset; + scene.EventManager.OnRemoveScript += HandleScriptReset; } // ----------------------------------------------------------------- @@ -132,12 +138,34 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RemoveRegion(Scene scene) { + scene.EventManager.OnScriptReset -= HandleScriptReset; + scene.EventManager.OnRemoveScript -= HandleScriptReset; + // need to remove all references to the scene in the subscription // list to enable full garbage collection of the scene object } // ----------------------------------------------------------------- /// + /// + // ----------------------------------------------------------------- + private void HandleScriptReset(uint localID, UUID itemID) + { + HashSet stores; + + lock (m_scriptStores) + { + if (! m_scriptStores.TryGetValue(itemID, out stores)) + return; + m_scriptStores.Remove(itemID); + } + + foreach (UUID id in stores) + m_store.DestroyStore(id); + } + + // ----------------------------------------------------------------- + /// /// Called when all modules have been added for a region. This is /// where we hook up events /// @@ -150,7 +178,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore m_comms = m_scene.RequestModuleInterface(); if (m_comms == null) { - m_log.ErrorFormat("[JsonStoreScripts] ScriptModuleComms interface not defined"); + m_log.ErrorFormat("[JsonStoreScripts]: ScriptModuleComms interface not defined"); m_enabled = false; return; } @@ -158,40 +186,20 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore m_store = m_scene.RequestModuleInterface(); if (m_store == null) { - m_log.ErrorFormat("[JsonStoreScripts] JsonModule interface not defined"); + m_log.ErrorFormat("[JsonStoreScripts]: JsonModule interface not defined"); m_enabled = false; return; } - + try { - m_comms.RegisterScriptInvocation(this,"JsonCreateStore"); - m_comms.RegisterScriptInvocation(this,"JsonDestroyStore"); - - m_comms.RegisterScriptInvocation(this,"JsonReadNotecard"); - m_comms.RegisterScriptInvocation(this,"JsonWriteNotecard"); - - m_comms.RegisterScriptInvocation(this,"JsonTestPath"); - m_comms.RegisterScriptInvocation(this,"JsonTestPathJson"); - - m_comms.RegisterScriptInvocation(this,"JsonGetValue"); - m_comms.RegisterScriptInvocation(this,"JsonGetValueJson"); - - m_comms.RegisterScriptInvocation(this,"JsonTakeValue"); - m_comms.RegisterScriptInvocation(this,"JsonTakeValueJson"); - - m_comms.RegisterScriptInvocation(this,"JsonReadValue"); - m_comms.RegisterScriptInvocation(this,"JsonReadValueJson"); - - m_comms.RegisterScriptInvocation(this,"JsonSetValue"); - m_comms.RegisterScriptInvocation(this,"JsonSetValueJson"); - - m_comms.RegisterScriptInvocation(this,"JsonRemoveValue"); + m_comms.RegisterScriptInvocations(this); + m_comms.RegisterConstants(this); } catch (Exception e) { // See http://opensimulator.org/mantis/view.php?id=5971 for more information - m_log.WarnFormat("[JsonStroreScripts] script method registration failed; {0}",e.Message); + m_log.WarnFormat("[JsonStoreScripts]: script method registration failed; {0}", e.Message); m_enabled = false; } } @@ -208,28 +216,73 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore #endregion +#region ScriptConstantsInterface + + [ScriptConstant] + public static readonly int JSON_NODETYPE_UNDEF = (int)JsonStoreNodeType.Undefined; + + [ScriptConstant] + public static readonly int JSON_NODETYPE_OBJECT = (int)JsonStoreNodeType.Object; + + [ScriptConstant] + public static readonly int JSON_NODETYPE_ARRAY = (int)JsonStoreNodeType.Array; + + [ScriptConstant] + public static readonly int JSON_NODETYPE_VALUE = (int)JsonStoreNodeType.Value; + + [ScriptConstant] + public static readonly int JSON_VALUETYPE_UNDEF = (int)JsonStoreValueType.Undefined; + + [ScriptConstant] + public static readonly int JSON_VALUETYPE_BOOLEAN = (int)JsonStoreValueType.Boolean; + + [ScriptConstant] + public static readonly int JSON_VALUETYPE_INTEGER = (int)JsonStoreValueType.Integer; + + [ScriptConstant] + public static readonly int JSON_VALUETYPE_FLOAT = (int)JsonStoreValueType.Float; + + [ScriptConstant] + public static readonly int JSON_VALUETYPE_STRING = (int)JsonStoreValueType.String; + + +#endregion + #region ScriptInvocationInteface // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - protected void GenerateRuntimeError(string msg) + [ScriptInvocation] + public UUID JsonAttachObjectStore(UUID hostID, UUID scriptID) { - throw new Exception("JsonStore Runtime Error: " + msg); + UUID uuid = UUID.Zero; + if (! m_store.AttachObjectStore(hostID)) + GenerateRuntimeError("Failed to create Json store"); + + return hostID; } - + // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - protected UUID JsonCreateStore(UUID hostID, UUID scriptID, string value) + [ScriptInvocation] + public UUID JsonCreateStore(UUID hostID, UUID scriptID, string value) { UUID uuid = UUID.Zero; if (! m_store.CreateStore(value, ref uuid)) GenerateRuntimeError("Failed to create Json store"); + lock (m_scriptStores) + { + if (! m_scriptStores.ContainsKey(scriptID)) + m_scriptStores[scriptID] = new HashSet(); + + m_scriptStores[scriptID].Add(uuid); + } return uuid; } @@ -238,8 +291,15 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected int JsonDestroyStore(UUID hostID, UUID scriptID, UUID storeID) + [ScriptInvocation] + public int JsonDestroyStore(UUID hostID, UUID scriptID, UUID storeID) { + lock(m_scriptStores) + { + if (m_scriptStores.ContainsKey(scriptID)) + m_scriptStores[scriptID].Remove(storeID); + } + return m_store.DestroyStore(storeID) ? 1 : 0; } @@ -248,10 +308,37 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected UUID JsonReadNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, UUID assetID) + [ScriptInvocation] + public int JsonTestStore(UUID hostID, UUID scriptID, UUID storeID) + { + return m_store.TestStore(storeID) ? 1 : 0; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + [ScriptInvocation] + public UUID JsonRezAtRoot(UUID hostID, UUID scriptID, string item, Vector3 pos, Vector3 vel, Quaternion rot, string param) + { + UUID reqID = UUID.Random(); + Util.FireAndForget( + o => DoJsonRezObject(hostID, scriptID, reqID, item, pos, vel, rot, param), null, "JsonStoreScriptModule.DoJsonRezObject"); + return reqID; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + [ScriptInvocation] + public UUID JsonReadNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, string notecardIdentifier) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonReadNotecard(reqID,hostID,scriptID,storeID,path,assetID); }); + Util.FireAndForget( + o => DoJsonReadNotecard(reqID, hostID, scriptID, storeID, path, notecardIdentifier), null, "JsonStoreScriptModule.JsonReadNotecard"); return reqID; } @@ -260,10 +347,12 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected UUID JsonWriteNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, string name) + [ScriptInvocation] + public UUID JsonWriteNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, string name) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonWriteNotecard(reqID,hostID,scriptID,storeID,path,name); }); + Util.FireAndForget( + o => DoJsonWriteNotecard(reqID,hostID,scriptID,storeID,path,name), null, "JsonStoreScriptModule.DoJsonWriteNotecard"); return reqID; } @@ -272,14 +361,41 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected int JsonTestPath(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public string JsonList2Path(UUID hostID, UUID scriptID, object[] pathlist) + { + string ipath = ConvertList2Path(pathlist); + string opath; + + if (JsonStore.CanonicalPathExpression(ipath,out opath)) + return opath; + + // This won't parse if passed to the other routines as opposed to + // returning an empty string which is a valid path and would overwrite + // the entire store + return "**INVALID**"; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + [ScriptInvocation] + public int JsonGetNodeType(UUID hostID, UUID scriptID, UUID storeID, string path) { - return m_store.TestPath(storeID,path,false) ? 1 : 0; + return (int)m_store.GetNodeType(storeID,path); } - protected int JsonTestPathJson(UUID hostID, UUID scriptID, UUID storeID, string path) + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + [ScriptInvocation] + public int JsonGetValueType(UUID hostID, UUID scriptID, UUID storeID, string path) { - return m_store.TestPath(storeID,path,true) ? 1 : 0; + return (int)m_store.GetValueType(storeID,path); } // ----------------------------------------------------------------- @@ -287,12 +403,14 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected int JsonSetValue(UUID hostID, UUID scriptID, UUID storeID, string path, string value) + [ScriptInvocation] + public int JsonSetValue(UUID hostID, UUID scriptID, UUID storeID, string path, string value) { return m_store.SetValue(storeID,path,value,false) ? 1 : 0; } - protected int JsonSetValueJson(UUID hostID, UUID scriptID, UUID storeID, string path, string value) + [ScriptInvocation] + public int JsonSetJson(UUID hostID, UUID scriptID, UUID storeID, string path, string value) { return m_store.SetValue(storeID,path,value,true) ? 1 : 0; } @@ -302,7 +420,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected int JsonRemoveValue(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public int JsonRemoveValue(UUID hostID, UUID scriptID, UUID storeID, string path) { return m_store.RemoveValue(storeID,path) ? 1 : 0; } @@ -312,14 +431,27 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected string JsonGetValue(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public int JsonGetArrayLength(UUID hostID, UUID scriptID, UUID storeID, string path) + { + return m_store.GetArrayLength(storeID,path); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + [ScriptInvocation] + public string JsonGetValue(UUID hostID, UUID scriptID, UUID storeID, string path) { string value = String.Empty; m_store.GetValue(storeID,path,false,out value); return value; } - protected string JsonGetValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public string JsonGetJson(UUID hostID, UUID scriptID, UUID storeID, string path) { string value = String.Empty; m_store.GetValue(storeID,path,true, out value); @@ -331,80 +463,109 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - protected UUID JsonTakeValue(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public UUID JsonTakeValue(UUID hostID, UUID scriptID, UUID storeID, string path) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonTakeValue(scriptID,reqID,storeID,path,false); }); + Util.FireAndForget( + o => DoJsonTakeValue(scriptID,reqID,storeID,path,false), null, "JsonStoreScriptModule.DoJsonTakeValue"); return reqID; } - protected UUID JsonTakeValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public UUID JsonTakeValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonTakeValue(scriptID,reqID,storeID,path,true); }); + Util.FireAndForget( + o => DoJsonTakeValue(scriptID,reqID,storeID,path,true), null, "JsonStoreScriptModule.DoJsonTakeValueJson"); return reqID; } - private void DoJsonTakeValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) - { - try - { - m_store.TakeValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); - return; - } - catch (Exception e) - { - m_log.InfoFormat("[JsonStoreScripts] unable to retrieve value; {0}",e.ToString()); - } - - DispatchValue(scriptID,reqID,String.Empty); - } - - // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - protected UUID JsonReadValue(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public UUID JsonReadValue(UUID hostID, UUID scriptID, UUID storeID, string path) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonReadValue(scriptID,reqID,storeID,path,false); }); + Util.FireAndForget( + o => DoJsonReadValue(scriptID,reqID,storeID,path,false), null, "JsonStoreScriptModule.DoJsonReadValue"); return reqID; } - protected UUID JsonReadValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + [ScriptInvocation] + public UUID JsonReadValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) { UUID reqID = UUID.Random(); - Util.FireAndForget(delegate(object o) { DoJsonReadValue(scriptID,reqID,storeID,path,true); }); + Util.FireAndForget( + o => DoJsonReadValue(scriptID,reqID,storeID,path,true), null, "JsonStoreScriptModule.DoJsonReadValueJson"); return reqID; } - private void DoJsonReadValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) +#endregion + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected void GenerateRuntimeError(string msg) + { + m_log.InfoFormat("[JsonStore] runtime error: {0}",msg); + throw new Exception("JsonStore Runtime Error: " + msg); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected void DispatchValue(UUID scriptID, UUID reqID, string value) + { + m_comms.DispatchReply(scriptID,1,value,reqID.ToString()); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + private void DoJsonTakeValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) { try { - m_store.ReadValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); + m_store.TakeValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); return; } catch (Exception e) { - m_log.InfoFormat("[JsonStoreScripts] unable to retrieve value; {0}",e.ToString()); + m_log.InfoFormat("[JsonStoreScripts]: unable to retrieve value; {0}",e.ToString()); } DispatchValue(scriptID,reqID,String.Empty); } -#endregion // ----------------------------------------------------------------- /// /// /// // ----------------------------------------------------------------- - protected void DispatchValue(UUID scriptID, UUID reqID, string value) + private void DoJsonReadValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) { - m_comms.DispatchReply(scriptID,1,value,reqID.ToString()); + try + { + m_store.ReadValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); + return; + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStoreScripts]: unable to retrieve value; {0}",e.ToString()); + } + + DispatchValue(scriptID,reqID,String.Empty); } // ----------------------------------------------------------------- @@ -412,31 +573,44 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore /// /// // ----------------------------------------------------------------- - private void DoJsonReadNotecard(UUID reqID, UUID hostID, UUID scriptID, UUID storeID, string path, UUID assetID) + private void DoJsonReadNotecard( + UUID reqID, UUID hostID, UUID scriptID, UUID storeID, string path, string notecardIdentifier) { + UUID assetID; + + if (!UUID.TryParse(notecardIdentifier, out assetID)) + { + SceneObjectPart part = m_scene.GetSceneObjectPart(hostID); + assetID = ScriptUtils.GetAssetIdFromItemName(part, notecardIdentifier, (int)AssetType.Notecard); + } + AssetBase a = m_scene.AssetService.Get(assetID.ToString()); if (a == null) - GenerateRuntimeError(String.Format("Unable to find notecard asset {0}",assetID)); + GenerateRuntimeError(String.Format("Unable to find notecard asset {0}", assetID)); if (a.Type != (sbyte)AssetType.Notecard) - GenerateRuntimeError(String.Format("Invalid notecard asset {0}",assetID)); + GenerateRuntimeError(String.Format("Invalid notecard asset {0}", assetID)); - m_log.DebugFormat("[JsonStoreScripts] read notecard in context {0}",storeID); + m_log.DebugFormat("[JsonStoreScripts]: read notecard in context {0}",storeID); try { - string jsondata = SLUtil.ParseNotecardToString(Encoding.UTF8.GetString(a.Data)); + string jsondata = SLUtil.ParseNotecardToString(a.Data); int result = m_store.SetValue(storeID, path, jsondata,true) ? 1 : 0; - m_comms.DispatchReply(scriptID,result, "", reqID.ToString()); + m_comms.DispatchReply(scriptID, result, "", reqID.ToString()); return; } + catch(SLUtil.NotANotecardFormatException e) + { + m_log.WarnFormat("[JsonStoreScripts]: Notecard parsing failed; assetId {0} at line number {1}", assetID.ToString(), e.lineNumber); + } catch (Exception e) { - m_log.WarnFormat("[JsonStoreScripts] Json parsing failed; {0}",e.Message); + m_log.WarnFormat("[JsonStoreScripts]: Json parsing failed; {0}", e.Message); } - GenerateRuntimeError(String.Format("Json parsing failed for {0}",assetID.ToString())); - m_comms.DispatchReply(scriptID,0,"",reqID.ToString()); + GenerateRuntimeError(String.Format("Json parsing failed for {0}", assetID)); + m_comms.DispatchReply(scriptID, 0, "", reqID.ToString()); } // ----------------------------------------------------------------- @@ -494,5 +668,141 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore m_comms.DispatchReply(scriptID,1,assetID.ToString(),reqID.ToString()); } + + // ----------------------------------------------------------------- + /// + /// Convert a list of values that are path components to a single string path + /// + // ----------------------------------------------------------------- + protected static Regex m_ArrayPattern = new Regex("^([0-9]+|\\+)$"); + private string ConvertList2Path(object[] pathlist) + { + string path = ""; + for (int i = 0; i < pathlist.Length; i++) + { + string token = ""; + + if (pathlist[i] is string) + { + token = pathlist[i].ToString(); + + // Check to see if this is a bare number which would not be a valid + // identifier otherwise + if (m_ArrayPattern.IsMatch(token)) + token = '[' + token + ']'; + } + else if (pathlist[i] is int) + { + token = "[" + pathlist[i].ToString() + "]"; + } + else + { + token = "." + pathlist[i].ToString() + "."; + } + + path += token + "."; + } + + return path; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + private void DoJsonRezObject(UUID hostID, UUID scriptID, UUID reqID, string name, Vector3 pos, Vector3 vel, Quaternion rot, string param) + { + if (Double.IsNaN(rot.X) || Double.IsNaN(rot.Y) || Double.IsNaN(rot.Z) || Double.IsNaN(rot.W)) + { + GenerateRuntimeError("Invalid rez rotation"); + return; + } + + SceneObjectGroup host = m_scene.GetSceneObjectGroup(hostID); + if (host == null) + { + GenerateRuntimeError(String.Format("Unable to find rezzing host '{0}'",hostID)); + return; + } + + // hpos = host.RootPart.GetWorldPosition() + // float dist = (float)llVecDist(hpos, pos); + // if (dist > m_ScriptDistanceFactor * 10.0f) + // return; + + TaskInventoryItem item = host.RootPart.Inventory.GetInventoryItem(name); + if (item == null) + { + GenerateRuntimeError(String.Format("Unable to find object to rez '{0}'",name)); + return; + } + + if (item.InvType != (int)InventoryType.Object) + { + GenerateRuntimeError("Can't create requested object; object is missing from database"); + return; + } + + List objlist; + List veclist; + + bool success = host.RootPart.Inventory.GetRezReadySceneObjects(item, out objlist, out veclist); + if (! success) + { + GenerateRuntimeError("Failed to create object"); + return; + } + + int totalPrims = 0; + foreach (SceneObjectGroup group in objlist) + totalPrims += group.PrimCount; + + if (! m_scene.Permissions.CanRezObject(totalPrims, item.OwnerID, pos)) + { + GenerateRuntimeError("Not allowed to create the object"); + return; + } + + if (! m_scene.Permissions.BypassPermissions()) + { + if ((item.CurrentPermissions & (uint)PermissionMask.Copy) == 0) + host.RootPart.Inventory.RemoveInventoryItem(item.ItemID); + } + + for (int i = 0; i < objlist.Count; i++) + { + SceneObjectGroup group = objlist[i]; + Vector3 curpos = pos + veclist[i]; + + if (group.IsAttachment == false && group.RootPart.Shape.State != 0) + { + group.RootPart.AttachedPos = group.AbsolutePosition; + group.RootPart.Shape.LastAttachPoint = (byte)group.AttachmentPoint; + } + + group.FromPartID = host.RootPart.UUID; + m_scene.AddNewSceneObject(group, true, curpos, rot, vel); + + UUID storeID = group.UUID; + if (! m_store.CreateStore(param, ref storeID)) + { + GenerateRuntimeError("Unable to create jsonstore for new object"); + continue; + } + + // We can only call this after adding the scene object, since the scene object references the scene + // to find out if scripts should be activated at all. + group.RootPart.SetDieAtEdge(true); + group.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 3); + group.ResumeScripts(); + + group.ScheduleGroupForFullUpdate(); + + // send the reply back to the host object, use the integer param to indicate the number + // of remaining objects + m_comms.DispatchReply(scriptID, objlist.Count-i-1, group.RootPart.UUID.ToString(), reqID.ToString()); + } + } } } diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs new file mode 100644 index 0000000..99a7076 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs @@ -0,0 +1,900 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using log4net; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Scripting.ScriptModuleComms; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore.Tests +{ + /// + /// Tests for inventory functions in LSL + /// + [TestFixture] + public class JsonStoreScriptModuleTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + private ScriptModuleCommsModule m_smcm; + private JsonStoreScriptModule m_jssm; + + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource configSource = new IniConfigSource(); + IConfig jsonStoreConfig = configSource.AddConfig("JsonStore"); + jsonStoreConfig.Set("Enabled", "true"); + + m_engine = new MockScriptEngine(); + m_smcm = new ScriptModuleCommsModule(); + JsonStoreModule jsm = new JsonStoreModule(); + m_jssm = new JsonStoreScriptModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, configSource, m_engine, m_smcm, jsm, m_jssm); + + try + { + m_smcm.RegisterScriptInvocation(this, "DummyTestMethod"); + } + catch (ArgumentException) + { + Assert.Ignore("Ignoring test since running on .NET 3.5 or earlier."); + } + + // XXX: Unfortunately, ICommsModule currently has no way of deregistering methods. + } + + private object InvokeOp(string name, params object[] args) + { + return InvokeOpOnHost(name, UUID.Zero, args); + } + + private object InvokeOpOnHost(string name, UUID hostId, params object[] args) + { + return m_smcm.InvokeOperation(hostId, UUID.Zero, name, args); + } + + [Test] + public void TestJsonCreateStore() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Test blank store + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + } + + // Test single element store + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + } + + // Test with an integer value + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 42.15 }"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(value, Is.EqualTo("42.15")); + } + + // Test with an array as the root node + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "[ 'one', 'two', 'three' ]"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "[1]"); + Assert.That(value, Is.EqualTo("two")); + } + } + + [Test] + public void TestJsonDestroyStore() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + int dsrv = (int)InvokeOp("JsonDestroyStore", storeId); + + Assert.That(dsrv, Is.EqualTo(1)); + + int tprv = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(tprv, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + + [Test] + public void TestJsonDestroyStoreNotExists() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + + int dsrv = (int)InvokeOp("JsonDestroyStore", fakeStoreId); + + Assert.That(dsrv, Is.EqualTo(0)); + } + + [Test] + public void TestJsonGetValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Two' } }"); + + { + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello.World"); + Assert.That(value, Is.EqualTo("Two")); + } + + // Test get of path section instead of leaf + { + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + + // Test get of non-existing value + { + string fakeValueGet = (string)InvokeOp("JsonGetValue", storeId, "foo"); + Assert.That(fakeValueGet, Is.EqualTo("")); + } + + // Test get from non-existing store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + string fakeStoreValueGet = (string)InvokeOp("JsonGetValue", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueGet, Is.EqualTo("")); + } + } + + [Test] + public void TestJsonGetJson() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Two' } }"); + + { + string value = (string)InvokeOp("JsonGetJson", storeId, "Hello.World"); + Assert.That(value, Is.EqualTo("'Two'")); + } + + // Test get of path section instead of leaf + { + string value = (string)InvokeOp("JsonGetJson", storeId, "Hello"); + Assert.That(value, Is.EqualTo("{\"World\":\"Two\"}")); + } + + // Test get of non-existing value + { + string fakeValueGet = (string)InvokeOp("JsonGetJson", storeId, "foo"); + Assert.That(fakeValueGet, Is.EqualTo("")); + } + + // Test get from non-existing store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + string fakeStoreValueGet = (string)InvokeOp("JsonGetJson", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueGet, Is.EqualTo("")); + } + } + +// [Test] +// public void TestJsonTakeValue() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId +// = (UUID)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonCreateStore", new object[] { "{ 'Hello' : 'World' }" }); +// +// string value +// = (string)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonTakeValue", new object[] { storeId, "Hello" }); +// +// Assert.That(value, Is.EqualTo("World")); +// +// string value2 +// = (string)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonGetValue", new object[] { storeId, "Hello" }); +// +// Assert.That(value, Is.Null); +// } + + [Test] + public void TestJsonRemoveValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Test remove of node in object pointing to a string + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string returnValue2 = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(returnValue2, Is.EqualTo("")); + } + + // Test remove of node in object pointing to another object + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Wally' } }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string returnValue2 = (string)InvokeOp("JsonGetJson", storeId, "Hello"); + Assert.That(returnValue2, Is.EqualTo("")); + } + + // Test remove of node in an array + { + UUID storeId + = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : [ 'value1', 'value2' ] }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello[0]"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello[0]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + + result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello[1]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string stringReturnValue = (string)InvokeOp("JsonGetValue", storeId, "Hello[0]"); + Assert.That(stringReturnValue, Is.EqualTo("value2")); + + stringReturnValue = (string)InvokeOp("JsonGetJson", storeId, "Hello[1]"); + Assert.That(stringReturnValue, Is.EqualTo("")); + } + + // Test remove of non-existing value + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + + int fakeValueRemove = (int)InvokeOp("JsonRemoveValue", storeId, "Cheese"); + Assert.That(fakeValueRemove, Is.EqualTo(0)); + } + + { + // Test get from non-existing store + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueRemove = (int)InvokeOp("JsonRemoveValue", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); + } + } + +// [Test] +// public void TestJsonTestPath() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'One' } }"); +// +// { +// int result = (int)InvokeOp("JsonTestPath", storeId, "Hello.World"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// // Test for path which does not resolve to a value. +// { +// int result = (int)InvokeOp("JsonTestPath", storeId, "Hello"); +// Assert.That(result, Is.EqualTo(0)); +// } +// +// { +// int result2 = (int)InvokeOp("JsonTestPath", storeId, "foo"); +// Assert.That(result2, Is.EqualTo(0)); +// } +// +// // Test with fake store +// { +// UUID fakeStoreId = TestHelpers.ParseTail(0x500); +// int fakeStoreValueRemove = (int)InvokeOp("JsonTestPath", fakeStoreId, "Hello"); +// Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); +// } +// } + +// [Test] +// public void TestJsonTestPathJson() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'One' } }"); +// +// { +// int result = (int)InvokeOp("JsonTestPathJson", storeId, "Hello.World"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// // Test for path which does not resolve to a value. +// { +// int result = (int)InvokeOp("JsonTestPathJson", storeId, "Hello"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// { +// int result2 = (int)InvokeOp("JsonTestPathJson", storeId, "foo"); +// Assert.That(result2, Is.EqualTo(0)); +// } +// +// // Test with fake store +// { +// UUID fakeStoreId = TestHelpers.ParseTail(0x500); +// int fakeStoreValueRemove = (int)InvokeOp("JsonTestPathJson", fakeStoreId, "Hello"); +// Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); +// } +// } + + [Test] + public void TestJsonGetArrayLength() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : [ 'one', 2 ] } }"); + + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "Hello.World"); + Assert.That(result, Is.EqualTo(2)); + } + + // Test path which is not an array + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "Hello"); + Assert.That(result, Is.EqualTo(-1)); + } + + // Test fake path + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "foo"); + Assert.That(result, Is.EqualTo(-1)); + } + + // Test fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int result = (int)InvokeOp("JsonGetArrayLength", fakeStoreId, "Hello.World"); + Assert.That(result, Is.EqualTo(-1)); + } + } + + [Test] + public void TestJsonGetNodeType() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : [ 'one', 2 ] } }"); + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "."); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_OBJECT)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_OBJECT)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_ARRAY)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World[0]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World[1]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + } + + // Test for non-existent path + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "foo"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + + // Test for non-existent store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int result = (int)InvokeOp("JsonGetNodeType", fakeStoreId, "."); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + } + + [Test] + public void TestJsonList2Path() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Invoking these methods directly since I just couldn't get comms module invocation to work for some reason + // - some confusion with the methods that take a params object[] invocation. + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo" }); + Assert.That(result, Is.EqualTo("{foo}")); + } + + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo", "bar" }); + Assert.That(result, Is.EqualTo("{foo}.{bar}")); + } + + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo", 1, "bar" }); + Assert.That(result, Is.EqualTo("{foo}.[1].{bar}")); + } + } + + [Test] + public void TestJsonSetValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing periods with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun.Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun.Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // *** Test [] *** + + // Test setting a key containing unbalanced ] without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun]Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun]Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced [ without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun[Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun[Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced [] without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun[]Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun[]Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced ] with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun]Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun]Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing unbalanced [ with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing empty balanced [] with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[]Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[]Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + +// // Commented out as this currently unexpectedly fails. +// // Test setting a key containing brackets around an integer with delineation +// { +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); +// +// int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[0]Circus}", "Times"); +// Assert.That(result, Is.EqualTo(1)); +// +// string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[0]Circus}"); +// Assert.That(value, Is.EqualTo("Times")); +// } + + // *** Test {} *** + + // Test setting a key containing unbalanced } without delineation. Expecting failure (?) + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun}Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun}Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced { without delineation. Expecting failure (?) + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun{Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun}Circus"); + Assert.That(value, Is.EqualTo("")); + } + +// // Commented out as this currently unexpectedly fails. +// // Test setting a key containing unbalanced } +// { +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); +// +// int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun}Circus}", "Times"); +// Assert.That(result, Is.EqualTo(0)); +// } + + // Test setting a key containing unbalanced { with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun{Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun{Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing balanced {} with delineation. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun{Filled}Circus}", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun{Filled}Circus}"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting to location that does not exist. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun.Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test with fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueSet = (int)InvokeOp("JsonSetValue", fakeStoreId, "Hello", "World"); + Assert.That(fakeStoreValueSet, Is.EqualTo(0)); + } + } + + [Test] + public void TestJsonSetJson() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Single quoted token case + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "'Times'"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Sub-tree case + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "{ 'Filled' : 'Times' }"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Filled"); + Assert.That(value, Is.EqualTo("Times")); + } + + // If setting single strings in JsonSetValueJson, these must be single quoted tokens, not bare strings. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting to location that does not exist. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun.Circus", "'Times'"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test with fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueSet = (int)InvokeOp("JsonSetJson", fakeStoreId, "Hello", "'World'"); + Assert.That(fakeStoreValueSet, Is.EqualTo(0)); + } + } + + /// + /// Test for writing json to a notecard + /// + /// + /// TODO: Really needs to test correct receipt of the link_message event. Could do this by directly fetching + /// it via the MockScriptEngine or perhaps by a dummy script instance. + /// + [Test] + public void TestJsonWriteNotecard() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, TestHelpers.ParseTail(0x1)); + m_scene.AddSceneObject(so); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello':'World' }"); + + { + string notecardName = "nc1"; + + // Write notecard + UUID writeNotecardRequestId = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, storeId, "", notecardName); + Assert.That(writeNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem nc1Item = so.RootPart.Inventory.GetInventoryItem(notecardName); + Assert.That(nc1Item, Is.Not.Null); + + // TODO: Should independently check the contents. + } + + // TODO: Write partial test + + { + // Try to write notecard for a bad path + // In this case we do get a request id but no notecard is written. + string badPathNotecardName = "badPathNotecardName"; + + UUID writeNotecardBadPathRequestId + = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, storeId, "flibble", badPathNotecardName); + Assert.That(writeNotecardBadPathRequestId, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem badPathItem = so.RootPart.Inventory.GetInventoryItem(badPathNotecardName); + Assert.That(badPathItem, Is.Null); + } + + { + // Test with fake store + // In this case we do get a request id but no notecard is written. + string fakeStoreNotecardName = "fakeStoreNotecardName"; + + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + UUID fakeStoreWriteNotecardValue + = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, fakeStoreId, "", fakeStoreNotecardName); + Assert.That(fakeStoreWriteNotecardValue, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem fakeStoreItem = so.RootPart.Inventory.GetInventoryItem(fakeStoreNotecardName); + Assert.That(fakeStoreItem, Is.Null); + } + } + + /// + /// Test for reading json from a notecard + /// + /// + /// TODO: Really needs to test correct receipt of the link_message event. Could do this by directly fetching + /// it via the MockScriptEngine or perhaps by a dummy script instance. + /// + [Test] + public void TestJsonReadNotecard() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string notecardName = "nc1"; + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, TestHelpers.ParseTail(0x1)); + m_scene.AddSceneObject(so); + + UUID creatingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello':'World' }"); + + // Write notecard + InvokeOpOnHost("JsonWriteNotecard", so.UUID, creatingStoreId, "", notecardName); + + { + // Read notecard + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to new single component path + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to new multi-component path. This should not work. + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make.it", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.it.Hello"); + Assert.That(value, Is.EqualTo("")); + } + + { + // Read notecard to existing multi-component path. This should work + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'make' : { 'it' : 'so' } }"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make.it", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.it.Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to invalid path. This should not work. + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'make' : { 'it' : 'so' } }"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "/", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + + { + // Try read notecard to fake store. + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, fakeStoreId, "", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", fakeStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + } + + public object DummyTestMethod(object o1, object o2, object o3, object o4, object o5) { return null; } + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs b/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs index 6fb28e2..4bafe2f 100644 --- a/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs @@ -180,6 +180,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.Minimodule /// * Internet /// * Everything /// +#pragma warning disable 0618 public static AppDomain CreateRestrictedDomain(string permissionSetName, string appDomainName) { if (permissionSetName == null) @@ -240,6 +241,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.Minimodule return restrictedDomain; } +#pragma warning restore 0618 void EventManager_OnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) diff --git a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SOPObject.cs b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SOPObject.cs index 5ed1514..47b9c09 100644 --- a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SOPObject.cs +++ b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SOPObject.cs @@ -34,7 +34,7 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.OptionalModules.Scripting.Minimodule.Object; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using PrimType=OpenSim.Region.OptionalModules.Scripting.Minimodule.Object.PrimType; using SculptType=OpenSim.Region.OptionalModules.Scripting.Minimodule.Object.SculptType; diff --git a/OpenSim/Region/OptionalModules/Scripting/RegionReadyModule/RegionReadyModule.cs b/OpenSim/Region/OptionalModules/Scripting/RegionReadyModule/RegionReadyModule.cs index c550c44..870c0bb 100644 --- a/OpenSim/Region/OptionalModules/Scripting/RegionReadyModule/RegionReadyModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/RegionReadyModule/RegionReadyModule.cs @@ -105,7 +105,10 @@ namespace OpenSim.Region.OptionalModules.Scripting.RegionReady m_scene.LoginLock = true; m_scene.EventManager.OnEmptyScriptCompileQueue += OnEmptyScriptCompileQueue; - m_log.InfoFormat("[RegionReady]: Region {0} - LOGINS DISABLED DURING INITIALIZATION.", m_scene.Name); + // This should always show up to the user but should not trigger warn/errors as these messages are + // expected and are not simulator problems. Ideally, there would be a status level in log4net but + // failing that, we will print out to console instead. + MainConsole.Instance.OutputFormat("Region {0} - LOGINS DISABLED DURING INITIALIZATION.", m_scene.Name); if (m_uri != string.Empty) { @@ -169,7 +172,10 @@ namespace OpenSim.Region.OptionalModules.Scripting.RegionReady c.Channel = m_channelNotify; c.Message += numScriptsFailed.ToString() + "," + message; c.Type = ChatTypeEnum.Region; - c.Position = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 30); + if (m_scene != null) + c.Position = new Vector3((m_scene.RegionInfo.RegionSizeX * 0.5f), (m_scene.RegionInfo.RegionSizeY * 0.5f), 30); + else + c.Position = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 30); c.Sender = null; c.SenderUUID = UUID.Zero; c.Scene = m_scene; @@ -215,8 +221,11 @@ namespace OpenSim.Region.OptionalModules.Scripting.RegionReady // m_log.InfoFormat("[RegionReady]: Logins enabled for {0}, Oar {1}", // m_scene.RegionInfo.RegionName, m_oarFileLoading.ToString()); - m_log.InfoFormat( - "[RegionReady]: INITIALIZATION COMPLETE FOR {0} - LOGINS ENABLED", m_scene.Name); + // Putting this out to console to make it eye-catching for people who are running OpenSimulator + // without info log messages enabled. Making this a warning is arguably misleading since it isn't a + // warning, and monitor scripts looking for warn/error/fatal messages will received false positives. + // Arguably, log4net needs a status log level (like Apache). + MainConsole.Instance.OutputFormat("INITIALIZATION COMPLETE FOR {0} - LOGINS ENABLED", m_scene.Name); } m_scene.SceneGridService.InformNeighborsThatRegionisUp( @@ -297,7 +306,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.RegionReady finally { if (os != null) - os.Close(); + os.Dispose(); } } } diff --git a/OpenSim/Region/OptionalModules/Scripting/XmlRpcRouterModule/XmlRpcGridRouterModule.cs b/OpenSim/Region/OptionalModules/Scripting/XmlRpcRouterModule/XmlRpcGridRouterModule.cs index 709d389..744d1e3 100644 --- a/OpenSim/Region/OptionalModules/Scripting/XmlRpcRouterModule/XmlRpcGridRouterModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/XmlRpcRouterModule/XmlRpcGridRouterModule.cs @@ -35,7 +35,6 @@ using OpenMetaverse; using Mono.Addins; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Framework.Client; diff --git a/OpenSim/Region/OptionalModules/UserStatistics/ActiveConnectionsAJAX.cs b/OpenSim/Region/OptionalModules/UserStatistics/ActiveConnectionsAJAX.cs new file mode 100644 index 0000000..6a1112c --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/ActiveConnectionsAJAX.cs @@ -0,0 +1,308 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Monitoring; + +namespace OpenSim.Region.UserStatistics +{ + public class ActiveConnectionsAJAX : IStatsController + { + private Vector3 DefaultNeighborPosition = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 70); + + #region IStatsController Members + + public string ReportName + { + get { return ""; } + } + + public Hashtable ProcessModel(Hashtable pParams) + { + List m_scene = (List)pParams["Scenes"]; + + Hashtable nh = new Hashtable(); + nh.Add("hdata", m_scene); + + return nh; + } + + public string RenderView(Hashtable pModelResult) + { + List all_scenes = (List) pModelResult["hdata"]; + + StringBuilder output = new StringBuilder(); + HTMLUtil.OL_O(ref output, ""); + foreach (Scene scene in all_scenes) + { + HTMLUtil.LI_O(ref output, String.Empty); + output.Append(scene.RegionInfo.RegionName); + HTMLUtil.OL_O(ref output, String.Empty); + scene.ForEachScenePresence(delegate(ScenePresence av) + { + Dictionary queues = new Dictionary(); + if (av.ControllingClient is IStatsCollector) + { + IStatsCollector isClient = (IStatsCollector)av.ControllingClient; + queues = decodeQueueReport(isClient.Report()); + } + HTMLUtil.LI_O(ref output, String.Empty); + output.Append(av.Name); + output.Append("      "); + output.Append((av.IsChildAgent ? "Child" : "Root")); + if (av.AbsolutePosition == DefaultNeighborPosition) + { + output.Append("
Position: ?"); + } + else + { + output.Append(string.Format("
Position: <{0},{1},{2}>", (int)av.AbsolutePosition.X, + (int)av.AbsolutePosition.Y, + (int)av.AbsolutePosition.Z)); + } + Dictionary throttles = DecodeClientThrottles(av.ControllingClient.GetThrottlesPacked(1)); + + HTMLUtil.UL_O(ref output, String.Empty); + + foreach (string throttlename in throttles.Keys) + { + HTMLUtil.LI_O(ref output, String.Empty); + output.Append(throttlename); + output.Append(":"); + output.Append(throttles[throttlename].ToString()); + if (queues.ContainsKey(throttlename)) + { + output.Append("/"); + output.Append(queues[throttlename]); + } + HTMLUtil.LI_C(ref output); + } + if (queues.ContainsKey("Incoming") && queues.ContainsKey("Outgoing")) + { + HTMLUtil.LI_O(ref output, "red"); + output.Append("SEND:"); + output.Append(queues["Outgoing"]); + output.Append("/"); + output.Append(queues["Incoming"]); + HTMLUtil.LI_C(ref output); + } + + HTMLUtil.UL_C(ref output); + HTMLUtil.LI_C(ref output); + }); + HTMLUtil.OL_C(ref output); + } + HTMLUtil.OL_C(ref output); + return output.ToString(); + } + + /// + /// Convert active connections information to JSON string. Returns a structure: + ///
+        /// {"regionName": {
+        ///     "presenceName": {
+        ///         "name": "presenceName",
+        ///         "position": "",
+        ///         "isRoot": "false",
+        ///         "throttle": {
+        ///         },
+        ///         "queue": {
+        ///         }
+        ///     },
+        ///     ... // multiple presences in the scene
+        ///   },
+        ///   ...   // multiple regions in the sim
+        /// }
+        ///
+        /// 
+ ///
+ /// + /// + public string RenderJson(Hashtable pModelResult) + { + List all_scenes = (List) pModelResult["hdata"]; + + OSDMap regionInfo = new OSDMap(); + foreach (Scene scene in all_scenes) + { + OSDMap sceneInfo = new OpenMetaverse.StructuredData.OSDMap(); + List avatarInScene = scene.GetScenePresences(); + foreach (ScenePresence av in avatarInScene) + { + OSDMap presenceInfo = new OSDMap(); + presenceInfo.Add("Name", new OSDString(av.Name)); + + Dictionary queues = new Dictionary(); + if (av.ControllingClient is IStatsCollector) + { + IStatsCollector isClient = (IStatsCollector) av.ControllingClient; + queues = decodeQueueReport(isClient.Report()); + } + OSDMap queueInfo = new OpenMetaverse.StructuredData.OSDMap(); + foreach (KeyValuePair kvp in queues) { + queueInfo.Add(kvp.Key, new OSDString(kvp.Value)); + } + sceneInfo.Add("queues", queueInfo); + + if (av.IsChildAgent) + presenceInfo.Add("isRoot", new OSDString("false")); + else + presenceInfo.Add("isRoot", new OSDString("true")); + + if (av.AbsolutePosition == DefaultNeighborPosition) + { + presenceInfo.Add("position", new OSDString("<0, 0, 0>")); + } + else + { + presenceInfo.Add("position", new OSDString(string.Format("<{0},{1},{2}>", + (int)av.AbsolutePosition.X, + (int) av.AbsolutePosition.Y, + (int) av.AbsolutePosition.Z)) ); + } + + Dictionary throttles = DecodeClientThrottles(av.ControllingClient.GetThrottlesPacked(1)); + OSDMap throttleInfo = new OpenMetaverse.StructuredData.OSDMap(); + foreach (string throttlename in throttles.Keys) + { + throttleInfo.Add(throttlename, new OSDString(throttles[throttlename].ToString())); + } + presenceInfo.Add("throttle", throttleInfo); + + sceneInfo.Add(av.Name, presenceInfo); + } + regionInfo.Add(scene.RegionInfo.RegionName, sceneInfo); + } + return regionInfo.ToString(); + } + + public Dictionary DecodeClientThrottles(byte[] throttle) + { + Dictionary returndict = new Dictionary(); + // From mantis http://opensimulator.org/mantis/view.php?id=1374 + // it appears that sometimes we are receiving empty throttle byte arrays. + // TODO: Investigate this behaviour + if (throttle.Length == 0) + { + return new Dictionary(); + } + + int tResend = -1; + int tLand = -1; + int tWind = -1; + int tCloud = -1; + int tTask = -1; + int tTexture = -1; + int tAsset = -1; + int tall = -1; + const int singlefloat = 4; + + //Agent Throttle Block contains 7 single floatingpoint values. + int j = 0; + + // Some Systems may be big endian... + // it might be smart to do this check more often... + if (!BitConverter.IsLittleEndian) + for (int i = 0; i < 7; i++) + Array.Reverse(throttle, j + i * singlefloat, singlefloat); + + // values gotten from OpenMetaverse.org/wiki/Throttle. Thanks MW_ + // bytes + // Convert to integer, since.. the full fp space isn't used. + tResend = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Resend", tResend); + j += singlefloat; + tLand = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Land", tLand); + j += singlefloat; + tWind = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Wind", tWind); + j += singlefloat; + tCloud = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Cloud", tCloud); + j += singlefloat; + tTask = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Task", tTask); + j += singlefloat; + tTexture = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Texture", tTexture); + j += singlefloat; + tAsset = (int)BitConverter.ToSingle(throttle, j); + returndict.Add("Asset", tAsset); + + tall = tResend + tLand + tWind + tCloud + tTask + tTexture + tAsset; + returndict.Add("All", tall); + + return returndict; + } + public Dictionary decodeQueueReport(string rep) + { + Dictionary returndic = new Dictionary(); + if (rep.Length == 79) + { + int pos = 1; + returndic.Add("All", rep.Substring((6 * pos), 8)); pos++; + returndic.Add("Incoming", rep.Substring((7 * pos), 8)); pos++; + returndic.Add("Outgoing", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Resend", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Land", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Wind", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Cloud", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Task", rep.Substring((7 * pos) , 8)); pos++; + returndic.Add("Texture", rep.Substring((7 * pos), 8)); pos++; + returndic.Add("Asset", rep.Substring((7 * pos), 8)); + /* + * return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}", + SendQueue.Count(), + IncomingPacketQueue.Count, + OutgoingPacketQueue.Count, + ResendOutgoingPacketQueue.Count, + LandOutgoingPacketQueue.Count, + WindOutgoingPacketQueue.Count, + CloudOutgoingPacketQueue.Count, + TaskOutgoingPacketQueue.Count, + TextureOutgoingPacketQueue.Count, + AssetOutgoingPacketQueue.Count); + */ + } + + + + return returndic; + } + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/Clients_report.cs b/OpenSim/Region/OptionalModules/UserStatistics/Clients_report.cs new file mode 100644 index 0000000..4a6f7be --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/Clients_report.cs @@ -0,0 +1,329 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.UserStatistics +{ + public class Clients_report : IStatsController + { + #region IStatsController Members + + public string ReportName + { + get { return "Client"; } + } + + /// + /// Return summar information in the form: + ///
+        /// {"totalUsers": "34",
+        ///  "totalSessions": "233",
+        ///  ...
+        /// }
+        /// 
+ ///
+ /// + /// + public string RenderJson(Hashtable pModelResult) { + stats_default_page_values values = (stats_default_page_values) pModelResult["hdata"]; + + OSDMap summaryInfo = new OpenMetaverse.StructuredData.OSDMap(); + summaryInfo.Add("totalUsers", new OSDString(values.total_num_users.ToString())); + summaryInfo.Add("totalSessions", new OSDString(values.total_num_sessions.ToString())); + summaryInfo.Add("averageClientFPS", new OSDString(values.avg_client_fps.ToString())); + summaryInfo.Add("averageClientMem", new OSDString(values.avg_client_mem_use.ToString())); + summaryInfo.Add("averageSimFPS", new OSDString(values.avg_sim_fps.ToString())); + summaryInfo.Add("averagePingTime", new OSDString(values.avg_ping.ToString())); + summaryInfo.Add("totalKBOut", new OSDString(values.total_kb_out.ToString())); + summaryInfo.Add("totalKBIn", new OSDString(values.total_kb_in.ToString())); + return summaryInfo.ToString(); + } + + public Hashtable ProcessModel(Hashtable pParams) + { + SqliteConnection dbConn = (SqliteConnection)pParams["DatabaseConnection"]; + + + List clidata = new List(); + List cliRegData = new List(); + Hashtable regionTotals = new Hashtable(); + + Hashtable modeldata = new Hashtable(); + modeldata.Add("Scenes", pParams["Scenes"]); + modeldata.Add("Reports", pParams["Reports"]); + int totalclients = 0; + int totalregions = 0; + + lock (dbConn) + { + string sql = "select count(distinct region_id) as regcnt from stats_session_data"; + + SqliteCommand cmd = new SqliteCommand(sql, dbConn); + SqliteDataReader sdr = cmd.ExecuteReader(); + if (sdr.HasRows) + { + sdr.Read(); + totalregions = Convert.ToInt32(sdr["regcnt"]); + } + + sdr.Close(); + sdr.Dispose(); + + sql = + "select client_version, count(*) as cnt, avg(avg_sim_fps) as simfps from stats_session_data group by client_version order by count(*) desc LIMIT 10;"; + + cmd = new SqliteCommand(sql, dbConn); + sdr = cmd.ExecuteReader(); + if (sdr.HasRows) + { + while (sdr.Read()) + { + ClientVersionData udata = new ClientVersionData(); + udata.version = sdr["client_version"].ToString(); + udata.count = Convert.ToInt32(sdr["cnt"]); + udata.fps = Convert.ToSingle(sdr["simfps"]); + clidata.Add(udata); + totalclients += udata.count; + + } + } + sdr.Close(); + sdr.Dispose(); + + if (totalregions > 1) + { + sql = + "select region_id, client_version, count(*) as cnt, avg(avg_sim_fps) as simfps from stats_session_data group by region_id, client_version order by region_id, count(*) desc;"; + cmd = new SqliteCommand(sql, dbConn); + + sdr = cmd.ExecuteReader(); + + if (sdr.HasRows) + { + while (sdr.Read()) + { + ClientVersionData udata = new ClientVersionData(); + udata.version = sdr["client_version"].ToString(); + udata.count = Convert.ToInt32(sdr["cnt"]); + udata.fps = Convert.ToSingle(sdr["simfps"]); + udata.region_id = UUID.Parse(sdr["region_id"].ToString()); + cliRegData.Add(udata); + } + } + sdr.Close(); + sdr.Dispose(); + + + } + + } + + foreach (ClientVersionData cvd in cliRegData) + { + + if (regionTotals.ContainsKey(cvd.region_id)) + { + int regiontotal = (int)regionTotals[cvd.region_id]; + regiontotal += cvd.count; + regionTotals[cvd.region_id] = regiontotal; + } + else + { + regionTotals.Add(cvd.region_id, cvd.count); + } + + + + } + + modeldata["ClientData"] = clidata; + modeldata["ClientRegionData"] = cliRegData; + modeldata["RegionTotals"] = regionTotals; + modeldata["Total"] = totalclients; + + return modeldata; + } + + public string RenderView(Hashtable pModelResult) + { + List clidata = (List) pModelResult["ClientData"]; + int totalclients = (int)pModelResult["Total"]; + Hashtable regionTotals = (Hashtable) pModelResult["RegionTotals"]; + List cliRegData = (List) pModelResult["ClientRegionData"]; + List m_scenes = (List)pModelResult["Scenes"]; + Dictionary reports = (Dictionary)pModelResult["Reports"]; + + const string STYLESHEET = + @" + +"; + + StringBuilder output = new StringBuilder(); + HTMLUtil.HtmlHeaders_O(ref output); + output.Append(STYLESHEET); + HTMLUtil.HtmlHeaders_C(ref output); + + HTMLUtil.AddReportLinks(ref output, reports, ""); + + HTMLUtil.TABLE_O(ref output, "defaultr"); + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "header"); + output.Append("ClientVersion"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("Count/%"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("SimFPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + + foreach (ClientVersionData cvd in clidata) + { + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "content"); + string linkhref = "sessions.report?VersionString=" + cvd.version; + HTMLUtil.A(ref output, cvd.version, linkhref, ""); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(cvd.count); + output.Append("/"); + if (totalclients > 0) + output.Append((((float)cvd.count / (float)totalclients)*100).ToString()); + else + output.Append(0); + + output.Append("%"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(cvd.fps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + } + HTMLUtil.TABLE_C(ref output); + + if (cliRegData.Count > 0) + { + HTMLUtil.TABLE_O(ref output, "defaultr"); + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "header"); + output.Append("Region"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("ClientVersion"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("Count/%"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("SimFPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + + foreach (ClientVersionData cvd in cliRegData) + { + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "content"); + output.Append(regionNamefromUUID(m_scenes, cvd.region_id)); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(cvd.version); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(cvd.count); + output.Append("/"); + if ((int)regionTotals[cvd.region_id] > 0) + output.Append((((float)cvd.count / (float)((int)regionTotals[cvd.region_id])) * 100).ToString()); + else + output.Append(0); + + output.Append("%"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(cvd.fps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + } + HTMLUtil.TABLE_C(ref output); + + } + + output.Append(""); + output.Append(""); + return output.ToString(); + } + public string regionNamefromUUID(List scenes, UUID region_id) + { + string returnstring = string.Empty; + foreach (Scene sn in scenes) + { + if (region_id == sn.RegionInfo.originRegionID) + { + returnstring = sn.RegionInfo.RegionName; + break; + } + } + + if (returnstring.Length == 0) + { + returnstring = region_id.ToString(); + } + + return returnstring; + } + + #endregion + } + + public struct ClientVersionData + { + public UUID region_id; + public string version; + public int count; + public float fps; + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/Default_Report.cs b/OpenSim/Region/OptionalModules/UserStatistics/Default_Report.cs new file mode 100644 index 0000000..fabe3d4 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/Default_Report.cs @@ -0,0 +1,277 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Monitoring; + + +namespace OpenSim.Region.UserStatistics +{ + public class Default_Report : IStatsController + { + + public string ReportName + { + get { return "Home"; } + } + + #region IStatsController Members + + public Hashtable ProcessModel(Hashtable pParams) + { + SqliteConnection conn = (SqliteConnection)pParams["DatabaseConnection"]; + List m_scene = (List)pParams["Scenes"]; + + stats_default_page_values mData = rep_DefaultReport_data(conn, m_scene); + mData.sim_stat_data = (Dictionary)pParams["SimStats"]; + mData.stats_reports = (Dictionary) pParams["Reports"]; + + Hashtable nh = new Hashtable(); + nh.Add("hdata", mData); + nh.Add("Reports", pParams["Reports"]); + + return nh; + } + + public string RenderView(Hashtable pModelResult) + { + stats_default_page_values mData = (stats_default_page_values) pModelResult["hdata"]; + return rep_Default_report_view(mData); + } + + #endregion + + public string rep_Default_report_view(stats_default_page_values values) + { + + + StringBuilder output = new StringBuilder(); + + + + const string TableClass = "defaultr"; + const string TRClass = "defaultr"; + const string TDHeaderClass = "header"; + const string TDDataClass = "content"; + //const string TDDataClassRight = "contentright"; + const string TDDataClassCenter = "contentcenter"; + + const string STYLESHEET = + @" + +"; + HTMLUtil.HtmlHeaders_O(ref output); + + HTMLUtil.InsertProtoTypeAJAX(ref output); + string[] ajaxUpdaterDivs = new string[3]; + int[] ajaxUpdaterSeconds = new int[3]; + string[] ajaxUpdaterReportFragments = new string[3]; + + ajaxUpdaterDivs[0] = "activeconnections"; + ajaxUpdaterSeconds[0] = 10; + ajaxUpdaterReportFragments[0] = "activeconnectionsajax.html"; + + ajaxUpdaterDivs[1] = "activesimstats"; + ajaxUpdaterSeconds[1] = 20; + ajaxUpdaterReportFragments[1] = "simstatsajax.html"; + + ajaxUpdaterDivs[2] = "activelog"; + ajaxUpdaterSeconds[2] = 5; + ajaxUpdaterReportFragments[2] = "activelogajax.html"; + + HTMLUtil.InsertPeriodicUpdaters(ref output, ajaxUpdaterDivs, ajaxUpdaterSeconds, ajaxUpdaterReportFragments); + + output.Append(STYLESHEET); + HTMLUtil.HtmlHeaders_C(ref output); + HTMLUtil.AddReportLinks(ref output, values.stats_reports, ""); + HTMLUtil.TABLE_O(ref output, TableClass); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("# Users Total"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("# Sessions Total"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Avg Client FPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Avg Client Mem Use"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Avg Sim FPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Avg Ping"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("KB Out Total"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("KB In Total"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(values.total_num_users); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(values.total_num_sessions); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.avg_client_fps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.avg_client_mem_use); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.avg_sim_fps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.avg_ping); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.total_kb_out); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(values.total_kb_in); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TABLE_C(ref output); + + HTMLUtil.HR(ref output, ""); + HTMLUtil.TABLE_O(ref output, ""); + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "align_top"); + output.Append("
Active Connections loading...
"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "align_top"); + output.Append("
SimStats loading...
"); + output.Append("
ActiveLog loading...
"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TABLE_C(ref output); + output.Append(""); + // TODO: FIXME: template + return output.ToString(); + } + + + + public stats_default_page_values rep_DefaultReport_data(SqliteConnection db, List m_scene) + { + stats_default_page_values returnstruct = new stats_default_page_values(); + returnstruct.all_scenes = m_scene.ToArray(); + lock (db) + { + string SQL = @"SELECT COUNT(DISTINCT agent_id) as agents, COUNT(*) as sessions, AVG(avg_fps) as client_fps, + AVG(avg_sim_fps) as savg_sim_fps, AVG(avg_ping) as sav_ping, SUM(n_out_kb) as num_in_kb, + SUM(n_out_pk) as num_in_packets, SUM(n_in_kb) as num_out_kb, SUM(n_in_pk) as num_out_packets, AVG(mem_use) as sav_mem_use + FROM stats_session_data;"; + SqliteCommand cmd = new SqliteCommand(SQL, db); + SqliteDataReader sdr = cmd.ExecuteReader(); + if (sdr.HasRows) + { + sdr.Read(); + returnstruct.total_num_users = Convert.ToInt32(sdr["agents"]); + returnstruct.total_num_sessions = Convert.ToInt32(sdr["sessions"]); + returnstruct.avg_client_fps = Convert.ToSingle(sdr["client_fps"]); + returnstruct.avg_sim_fps = Convert.ToSingle(sdr["savg_sim_fps"]); + returnstruct.avg_ping = Convert.ToSingle(sdr["sav_ping"]); + returnstruct.total_kb_out = Convert.ToSingle(sdr["num_out_kb"]); + returnstruct.total_kb_in = Convert.ToSingle(sdr["num_in_kb"]); + returnstruct.avg_client_mem_use = Convert.ToSingle(sdr["sav_mem_use"]); + + } + } + return returnstruct; + } + + /// + /// Return summar information in the form: + ///
+        /// {"totalUsers": "34",
+        ///  "totalSessions": "233",
+        ///  ...
+        /// }
+        /// 
+ ///
+ /// + /// + public string RenderJson(Hashtable pModelResult) { + stats_default_page_values values = (stats_default_page_values) pModelResult["hdata"]; + + OSDMap summaryInfo = new OSDMap(); + summaryInfo.Add("totalUsers", new OSDString(values.total_num_users.ToString())); + summaryInfo.Add("totalSessions", new OSDString(values.total_num_sessions.ToString())); + summaryInfo.Add("averageClientFPS", new OSDString(values.avg_client_fps.ToString())); + summaryInfo.Add("averageClientMem", new OSDString(values.avg_client_mem_use.ToString())); + summaryInfo.Add("averageSimFPS", new OSDString(values.avg_sim_fps.ToString())); + summaryInfo.Add("averagePingTime", new OSDString(values.avg_ping.ToString())); + summaryInfo.Add("totalKBOut", new OSDString(values.total_kb_out.ToString())); + summaryInfo.Add("totalKBIn", new OSDString(values.total_kb_in.ToString())); + return summaryInfo.ToString(); + } + } + + public struct stats_default_page_values + { + public int total_num_users; + public int total_num_sessions; + public float avg_client_fps; + public float avg_client_mem_use; + public float avg_sim_fps; + public float avg_ping; + public float total_kb_out; + public float total_kb_in; + public float avg_client_resends; + public Scene[] all_scenes; + public Dictionary sim_stat_data; + public Dictionary stats_reports; + } + +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/HTMLUtil.cs b/OpenSim/Region/OptionalModules/UserStatistics/HTMLUtil.cs new file mode 100644 index 0000000..c07619f --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/HTMLUtil.cs @@ -0,0 +1,263 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenSim.Region.UserStatistics +{ + public static class HTMLUtil + { + + public static void TR_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(">\n\t"); + } + + public static void TR_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void TD_O(ref StringBuilder o, string pclass) + { + TD_O(ref o, pclass, 0, 0); + } + + public static void TD_O(ref StringBuilder o, string pclass, int rowspan, int colspan) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + if (rowspan > 1) + { + o.Append(" rowspan=\""); + o.Append(rowspan); + o.Append("\""); + } + if (colspan > 1) + { + o.Append(" colspan=\""); + o.Append(colspan); + o.Append("\""); + } + o.Append(">"); + } + + public static void TD_C(ref StringBuilder o) + { + o.Append(""); + } + + public static void TABLE_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(">\n\t"); + } + + public static void TABLE_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void BLOCKQUOTE_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(" />\n"); + } + + public static void BLOCKQUOTE_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void BR(ref StringBuilder o) + { + o.Append("
\n"); + } + + public static void HR(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(" />\n"); + } + + public static void UL_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(" />\n"); + } + + public static void UL_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void OL_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(" />\n"); + } + + public static void OL_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void LI_O(ref StringBuilder o, string pclass) + { + o.Append(" 0) + { + GenericClass(ref o, pclass); + } + o.Append(" />\n"); + } + + public static void LI_C(ref StringBuilder o) + { + o.Append("\n"); + } + + public static void GenericClass(ref StringBuilder o, string pclass) + { + o.Append(" class=\""); + o.Append(pclass); + o.Append("\""); + } + + public static void InsertProtoTypeAJAX(ref StringBuilder o) + { + o.Append("\n"); + o.Append("\n"); + } + + public static void InsertPeriodicUpdaters(ref StringBuilder o, string[] divID, int[] seconds, string[] reportfrag) + { + o.Append(""); + } + + public static void HtmlHeaders_O(ref StringBuilder o) + { + o.Append("\n"); + o.Append(""); + o.Append(""); + } + + public static void HtmlHeaders_C(ref StringBuilder o) + { + o.Append(""); + o.Append(""); + } + + public static void AddReportLinks(ref StringBuilder o, Dictionary reports, string pClass) + { + int repcount = 0; + foreach (string str in reports.Keys) + { + if (reports[str].ReportName.Length > 0) + { + if (repcount > 0) + { + o.Append("|  "); + } + A(ref o, reports[str].ReportName, str, pClass); + o.Append("  "); + repcount++; + } + } + } + + public static void A(ref StringBuilder o, string linktext, string linkhref, string pClass) + { + o.Append(" 0) + { + GenericClass(ref o, pClass); + } + o.Append(" href=\""); + o.Append(linkhref); + o.Append("\">"); + o.Append(linktext); + o.Append(""); + } + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/IStatsReport.cs b/OpenSim/Region/OptionalModules/UserStatistics/IStatsReport.cs new file mode 100644 index 0000000..80c4487 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/IStatsReport.cs @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections; + +namespace OpenSim.Region.UserStatistics +{ + public interface IStatsController + { + string ReportName { get; } + Hashtable ProcessModel(Hashtable pParams); + string RenderView(Hashtable pModelResult); + string RenderJson(Hashtable pModelResult); + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/LogLinesAJAX.cs b/OpenSim/Region/OptionalModules/UserStatistics/LogLinesAJAX.cs new file mode 100644 index 0000000..4d45b80 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/LogLinesAJAX.cs @@ -0,0 +1,159 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Monitoring; + +namespace OpenSim.Region.UserStatistics +{ + public class LogLinesAJAX : IStatsController + { + private Regex normalizeEndLines = new Regex(@"\r\n", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.Multiline); + + private Regex webFormat = new Regex(@"[^\s]*\s([^,]*),[^\s]*\s([A-Z]*)[^\s-][^\[]*\[([^\]]*)\]([^\n]*)", + RegexOptions.Singleline | RegexOptions.Compiled); + private Regex TitleColor = new Regex(@"[^\s]*\s(?:[^,]*),[^\s]*\s(?:[A-Z]*)[^\s-][^\[]*\[([^\]]*)\](?:[^\n]*)", + RegexOptions.Singleline | RegexOptions.Compiled); + + + #region IStatsController Members + + public string ReportName + { + get { return ""; } + } + + public Hashtable ProcessModel(Hashtable pParams) + { + Hashtable nh = new Hashtable(); + nh.Add("loglines", pParams["LogLines"]); + return nh; + } + + public string RenderView(Hashtable pModelResult) + { + StringBuilder output = new StringBuilder(); + + HTMLUtil.HR(ref output, ""); + output.Append("

ActiveLog

\n"); + + string tmp = normalizeEndLines.Replace(pModelResult["loglines"].ToString(), "\n"); + + string[] result = Regex.Split(tmp, "\n"); + + string formatopen = ""; + string formatclose = ""; + + for (int i = 0; i < result.Length; i++) + { + if (result[i].Length >= 30) + { + string logtype = result[i].Substring(24, 6); + switch (logtype) + { + case "WARN ": + formatopen = ""; + formatclose = ""; + break; + + case "ERROR ": + formatopen = ""; + formatclose = ""; + break; + + default: + formatopen = ""; + formatclose = ""; + break; + + } + } + StringBuilder replaceStr = new StringBuilder(); + //string titlecolorresults = + + string formatresult = Regex.Replace(TitleColor.Replace(result[i], "$1"), "[^ABCDEFabcdef0-9]", ""); + if (formatresult.Length > 6) + { + formatresult = formatresult.Substring(0, 6); + + } + for (int j = formatresult.Length; j <= 5; j++) + formatresult += "0"; + replaceStr.Append("$1 - [$3] $4
"); + string repstr = replaceStr.ToString(); + + output.Append(formatopen); + output.Append(webFormat.Replace(result[i], repstr)); + output.Append(formatclose); + } + + + return output.ToString(); + } + + /// + /// Return the last log lines. Output in the format: + ///
+        /// {"logLines": [
+        /// "line1",
+        /// "line2",
+        /// ...
+        /// ]
+        /// }
+        /// 
+ ///
+ /// + /// + public string RenderJson(Hashtable pModelResult) + { + OSDMap logInfo = new OpenMetaverse.StructuredData.OSDMap(); + + OSDArray logLines = new OpenMetaverse.StructuredData.OSDArray(); + string tmp = normalizeEndLines.Replace(pModelResult["loglines"].ToString(), "\n"); + string[] result = Regex.Split(tmp, "\n"); + for (int i = 0; i < result.Length; i++) + { + logLines.Add(new OSDString(result[i])); + } + logInfo.Add("logLines", logLines); + return logInfo.ToString(); + } + + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/Prototype_distributor.cs b/OpenSim/Region/OptionalModules/UserStatistics/Prototype_distributor.cs new file mode 100644 index 0000000..6f8b2aa --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/Prototype_distributor.cs @@ -0,0 +1,80 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using OpenSim.Framework; + +namespace OpenSim.Region.UserStatistics +{ + public class Prototype_distributor : IStatsController + { + private string jsFileName = "prototype.js"; + private string prototypejs = string.Empty; + + public Prototype_distributor() + { + jsFileName = "prototype.js"; + } + + public Prototype_distributor(string jsName) + { + jsFileName = jsName; + } + + public string ReportName + { + get { return ""; } + } + public Hashtable ProcessModel(Hashtable pParams) + { + Hashtable pResult = new Hashtable(); + pResult["js"] = jsFileName; + return pResult; + } + + public string RenderView(Hashtable pModelResult) + { + string fileName = (string)pModelResult["js"]; + using (StreamReader fs = new StreamReader(new FileStream(Util.dataDir() + "/data/" + fileName, FileMode.Open))) + { + prototypejs = fs.ReadToEnd(); + fs.Close(); + } + return prototypejs; + } + + public string RenderJson(Hashtable pModelResult) + { + return "{}"; + } + + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/Sessions_Report.cs b/OpenSim/Region/OptionalModules/UserStatistics/Sessions_Report.cs new file mode 100644 index 0000000..0e94912 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/Sessions_Report.cs @@ -0,0 +1,288 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.UserStatistics +{ + public class Sessions_Report : IStatsController + { + #region IStatsController Members + + public string ReportName + { + get { return "Sessions"; } + } + + public Hashtable ProcessModel(Hashtable pParams) + { + Hashtable modeldata = new Hashtable(); + modeldata.Add("Scenes", pParams["Scenes"]); + modeldata.Add("Reports", pParams["Reports"]); + SqliteConnection dbConn = (SqliteConnection)pParams["DatabaseConnection"]; + List lstSessions = new List(); + Hashtable requestvars = (Hashtable) pParams["RequestVars"]; + + + string puserUUID = string.Empty; + string clientVersionString = string.Empty; + int queryparams = 0; + + if (requestvars != null) + { + if (requestvars.ContainsKey("UserID")) + { + UUID testUUID = UUID.Zero; + if (UUID.TryParse(requestvars["UserID"].ToString(), out testUUID)) + { + puserUUID = requestvars["UserID"].ToString(); + + } + } + + if (requestvars.ContainsKey("VersionString")) + { + clientVersionString = requestvars["VersionString"].ToString(); + } + } + + lock (dbConn) + { + string sql = + "SELECT distinct a.name_f, a.name_l, a.Agent_ID, b.Session_ID, b.client_version, b.last_updated, b.start_time FROM stats_session_data a LEFT OUTER JOIN stats_session_data b ON a.Agent_ID = b.Agent_ID"; + + if (puserUUID.Length > 0) + { + if (queryparams == 0) + sql += " WHERE"; + else + sql += " AND"; + + sql += " b.agent_id=:agent_id"; + queryparams++; + } + + if (clientVersionString.Length > 0) + { + if (queryparams == 0) + sql += " WHERE"; + else + sql += " AND"; + + sql += " b.client_version=:client_version"; + queryparams++; + } + + sql += " ORDER BY a.name_f, a.name_l, b.last_updated;"; + + SqliteCommand cmd = new SqliteCommand(sql, dbConn); + + if (puserUUID.Length > 0) + cmd.Parameters.Add(new SqliteParameter(":agent_id", puserUUID)); + if (clientVersionString.Length > 0) + cmd.Parameters.Add(new SqliteParameter(":client_version", clientVersionString)); + + SqliteDataReader sdr = cmd.ExecuteReader(); + + if (sdr.HasRows) + { + UUID userUUID = UUID.Zero; + + SessionList activeSessionList = new SessionList(); + activeSessionList.user_id=UUID.Random(); + while (sdr.Read()) + { + UUID readUUID = UUID.Parse(sdr["agent_id"].ToString()); + if (readUUID != userUUID) + { + activeSessionList = new SessionList(); + activeSessionList.user_id = readUUID; + activeSessionList.firstname = sdr["name_f"].ToString(); + activeSessionList.lastname = sdr["name_l"].ToString(); + activeSessionList.sessions = new List(); + lstSessions.Add(activeSessionList); + } + + ShortSessionData ssd = new ShortSessionData(); + + ssd.last_update = Utils.UnixTimeToDateTime((uint)Convert.ToInt32(sdr["last_updated"])); + ssd.start_time = Utils.UnixTimeToDateTime((uint)Convert.ToInt32(sdr["start_time"])); + ssd.session_id = UUID.Parse(sdr["session_id"].ToString()); + ssd.client_version = sdr["client_version"].ToString(); + activeSessionList.sessions.Add(ssd); + + userUUID = activeSessionList.user_id; + } + } + sdr.Close(); + sdr.Dispose(); + + } + modeldata["SessionData"] = lstSessions; + return modeldata; + } + + public string RenderView(Hashtable pModelResult) + { + List lstSession = (List) pModelResult["SessionData"]; + Dictionary reports = (Dictionary)pModelResult["Reports"]; + + const string STYLESHEET = + @" + +"; + + StringBuilder output = new StringBuilder(); + HTMLUtil.HtmlHeaders_O(ref output); + output.Append(STYLESHEET); + HTMLUtil.HtmlHeaders_C(ref output); + + HTMLUtil.AddReportLinks(ref output, reports, ""); + + HTMLUtil.TABLE_O(ref output, "defaultr"); + HTMLUtil.TR_O(ref output, "defaultr"); + HTMLUtil.TD_O(ref output, "header"); + output.Append("FirstName"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("LastName"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("SessionEnd"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("SessionLength"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "header"); + output.Append("Client"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + if (lstSession.Count == 0) + { + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "align_top", 1, 5); + output.Append("No results for that query"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + } + foreach (SessionList ssnlst in lstSession) + { + int cnt = 0; + foreach (ShortSessionData sesdata in ssnlst.sessions) + { + HTMLUtil.TR_O(ref output, ""); + if (cnt++ == 0) + { + HTMLUtil.TD_O(ref output, "align_top", ssnlst.sessions.Count, 1); + output.Append(ssnlst.firstname); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "align_top", ssnlst.sessions.Count, 1); + output.Append(ssnlst.lastname); + HTMLUtil.TD_C(ref output); + } + HTMLUtil.TD_O(ref output, "content"); + output.Append(sesdata.last_update.ToShortDateString()); + output.Append(" - "); + output.Append(sesdata.last_update.ToShortTimeString()); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + TimeSpan dtlength = sesdata.last_update.Subtract(sesdata.start_time); + if (dtlength.Days > 0) + { + output.Append(dtlength.Days); + output.Append(" Days "); + } + if (dtlength.Hours > 0) + { + output.Append(dtlength.Hours); + output.Append(" Hours "); + } + if (dtlength.Minutes > 0) + { + output.Append(dtlength.Minutes); + output.Append(" Minutes"); + } + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, "content"); + output.Append(sesdata.client_version); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + + } + HTMLUtil.TR_O(ref output, ""); + HTMLUtil.TD_O(ref output, "align_top", 1, 5); + HTMLUtil.HR(ref output, ""); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + } + HTMLUtil.TABLE_C(ref output); + output.Append("\n"); + return output.ToString(); + } + + public class SessionList + { + public string firstname; + public string lastname; + public UUID user_id; + public List sessions; + } + + public struct ShortSessionData + { + public UUID session_id; + public string client_version; + public DateTime last_update; + public DateTime start_time; + } + + public string RenderJson(Hashtable pModelResult) + { + return "{}"; + } + #endregion + } + +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/SimStatsAJAX.cs b/OpenSim/Region/OptionalModules/UserStatistics/SimStatsAJAX.cs new file mode 100644 index 0000000..06d9e91 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/SimStatsAJAX.cs @@ -0,0 +1,276 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Mono.Data.SqliteClient; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Monitoring; + +namespace OpenSim.Region.UserStatistics +{ + public class SimStatsAJAX : IStatsController + { + #region IStatsController Members + + public string ReportName + { + get { return ""; } + } + + public Hashtable ProcessModel(Hashtable pParams) + { + List m_scene = (List)pParams["Scenes"]; + + Hashtable nh = new Hashtable(); + nh.Add("hdata", m_scene); + nh.Add("simstats", pParams["SimStats"]); + return nh; + } + + public string RenderView(Hashtable pModelResult) + { + StringBuilder output = new StringBuilder(); + List all_scenes = (List) pModelResult["hdata"]; + Dictionary sdatadic = (Dictionary)pModelResult["simstats"]; + + const string TableClass = "defaultr"; + const string TRClass = "defaultr"; + const string TDHeaderClass = "header"; + const string TDDataClass = "content"; + //const string TDDataClassRight = "contentright"; + const string TDDataClassCenter = "contentcenter"; + + foreach (USimStatsData sdata in sdatadic.Values) + { + + + foreach (Scene sn in all_scenes) + { + if (sn.RegionInfo.RegionID == sdata.RegionId) + { + output.Append("

"); + output.Append(sn.RegionInfo.RegionName); + output.Append("

"); + } + } + HTMLUtil.TABLE_O(ref output, TableClass); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Dilatn"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("SimFPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("PhysFPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("AgntUp"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("RootAg"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("ChldAg"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("Prims"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("ATvPrm"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("AtvScr"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("ScrLPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(sdata.TimeDilation); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(sdata.SimFps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.PhysicsFps); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.AgentUpdates); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.RootAgents); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.ChildAgents); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.TotalPrims); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.ActivePrims); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.ActiveScripts); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.ScriptLinesPerSecond); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("FrmMS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("AgtMS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("PhysMS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("OthrMS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("OutPPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("InPPS"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("NoAckKB"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("PndDWN"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDHeaderClass); + output.Append("PndUP"); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TR_O(ref output, TRClass); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(sdata.TotalFrameTime); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClass); + output.Append(sdata.AgentFrameTime); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.PhysicsFrameTime); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.OtherFrameTime); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.OutPacketsPerSecond); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.InPacketsPerSecond); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.UnackedBytes); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.PendingDownloads); + HTMLUtil.TD_C(ref output); + HTMLUtil.TD_O(ref output, TDDataClassCenter); + output.Append(sdata.PendingUploads); + HTMLUtil.TD_C(ref output); + HTMLUtil.TR_C(ref output); + HTMLUtil.TABLE_C(ref output); + + } + + return output.ToString(); + } + + /// + /// Return stat information for all regions in the sim. Returns data of the form: + ///
+        /// {"REGIONNAME": {
+        ///     "region": "REGIONNAME",
+        ///     "timeDilation": "101", 
+        ///     ...     // the rest of the stat info
+        ///     },
+        ///  ...    // entries for each region
+        ///  }
+        /// 
+ ///
+ /// + /// + public string RenderJson(Hashtable pModelResult) + { + List all_scenes = (List) pModelResult["hdata"]; + Dictionary sdatadic = (Dictionary)pModelResult["simstats"]; + + OSDMap allStatsInfo = new OpenMetaverse.StructuredData.OSDMap(); + foreach (USimStatsData sdata in sdatadic.Values) + { + OSDMap statsInfo = new OpenMetaverse.StructuredData.OSDMap(); + string regionName = "unknown"; + foreach (Scene sn in all_scenes) + { + if (sn.RegionInfo.RegionID == sdata.RegionId) + { + regionName = sn.RegionInfo.RegionName; + break; + } + } + statsInfo.Add("region", new OSDString(regionName)); + statsInfo.Add("timeDilation", new OSDString(sdata.TimeDilation.ToString())); + statsInfo.Add("simFPS", new OSDString(sdata.SimFps.ToString())); + statsInfo.Add("physicsFPS", new OSDString(sdata.PhysicsFps.ToString())); + statsInfo.Add("agentUpdates", new OSDString(sdata.AgentUpdates.ToString())); + statsInfo.Add("rootAgents", new OSDString(sdata.RootAgents.ToString())); + statsInfo.Add("childAgents", new OSDString(sdata.ChildAgents.ToString())); + statsInfo.Add("totalPrims", new OSDString(sdata.TotalPrims.ToString())); + statsInfo.Add("activePrims", new OSDString(sdata.ActivePrims.ToString())); + statsInfo.Add("activeScripts", new OSDString(sdata.ActiveScripts.ToString())); + statsInfo.Add("scriptLinesPerSec", new OSDString(sdata.ScriptLinesPerSecond.ToString())); + statsInfo.Add("totalFrameTime", new OSDString(sdata.TotalFrameTime.ToString())); + statsInfo.Add("agentFrameTime", new OSDString(sdata.AgentFrameTime.ToString())); + statsInfo.Add("physicsFrameTime", new OSDString(sdata.PhysicsFrameTime.ToString())); + statsInfo.Add("otherFrameTime", new OSDString(sdata.OtherFrameTime.ToString())); + statsInfo.Add("outPacketsPerSec", new OSDString(sdata.OutPacketsPerSecond.ToString())); + statsInfo.Add("inPacketsPerSec", new OSDString(sdata.InPacketsPerSecond.ToString())); + statsInfo.Add("unackedByptes", new OSDString(sdata.UnackedBytes.ToString())); + statsInfo.Add("pendingDownloads", new OSDString(sdata.PendingDownloads.ToString())); + statsInfo.Add("pendingUploads", new OSDString(sdata.PendingUploads.ToString())); + + allStatsInfo.Add(regionName, statsInfo); + } + return allStatsInfo.ToString(); + } + + #endregion + } +} diff --git a/OpenSim/Region/OptionalModules/UserStatistics/Updater_distributor.cs b/OpenSim/Region/OptionalModules/UserStatistics/Updater_distributor.cs new file mode 100644 index 0000000..601e06b --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/Updater_distributor.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using OpenSim.Framework; + +namespace OpenSim.Region.UserStatistics +{ + public class Updater_distributor : IStatsController + { + private string updaterjs = string.Empty; + + public string ReportName + { + get { return ""; } + } + + public Hashtable ProcessModel(Hashtable pParams) + { + Hashtable pResult = new Hashtable(); + if (updaterjs.Length == 0) + { + StreamReader fs = new StreamReader(new FileStream(Util.dataDir() + "/data/updater.js", FileMode.Open)); + updaterjs = fs.ReadToEnd(); + fs.Close(); + fs.Dispose(); + } + pResult["js"] = updaterjs; + return pResult; + } + + public string RenderView(Hashtable pModelResult) + { + return pModelResult["js"].ToString(); + } + + public string RenderJson(Hashtable pModelResult) { + return "{}"; + } + + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs b/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs new file mode 100644 index 0000000..bcb6361 --- /dev/null +++ b/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs @@ -0,0 +1,1203 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; // to be used for REST-->Grid shortly +using System.Reflection; +using System.Text; +using System.Threading; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Mono.Data.SqliteClient; +using Mono.Addins; + +using Caps = OpenSim.Framework.Capabilities.Caps; + +using OSD = OpenMetaverse.StructuredData.OSD; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.UserStatistics +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WebStatsModule")] + public class WebStatsModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static SqliteConnection dbConn; + + /// + /// User statistics sessions keyed by agent ID + /// + private Dictionary m_sessions = new Dictionary(); + + private List m_scenes = new List(); + private Dictionary reports = new Dictionary(); + private Dictionary m_simstatsCounters = new Dictionary(); + private const int updateStatsMod = 6; + private int updateLogMod = 1; + private volatile int updateLogCounter = 0; + private volatile int concurrencyCounter = 0; + private bool enabled = false; + private string m_loglines = String.Empty; + private volatile int lastHit = 12000; + + #region ISharedRegionModule + + public virtual void Initialise(IConfigSource config) + { + IConfig cnfg = config.Configs["WebStats"]; + + if (cnfg != null) + enabled = cnfg.GetBoolean("enabled", false); + } + + public virtual void PostInitialise() + { + if (!enabled) + return; + + if (Util.IsWindows()) + Util.LoadArchSpecificWindowsDll("sqlite3.dll"); + + //IConfig startupConfig = config.Configs["Startup"]; + + dbConn = new SqliteConnection("URI=file:LocalUserStatistics.db,version=3"); + dbConn.Open(); + CreateTables(dbConn); + + Prototype_distributor protodep = new Prototype_distributor(); + Updater_distributor updatedep = new Updater_distributor(); + ActiveConnectionsAJAX ajConnections = new ActiveConnectionsAJAX(); + SimStatsAJAX ajSimStats = new SimStatsAJAX(); + LogLinesAJAX ajLogLines = new LogLinesAJAX(); + Default_Report defaultReport = new Default_Report(); + Clients_report clientReport = new Clients_report(); + Sessions_Report sessionsReport = new Sessions_Report(); + + reports.Add("prototype.js", protodep); + reports.Add("updater.js", updatedep); + reports.Add("activeconnectionsajax.html", ajConnections); + reports.Add("simstatsajax.html", ajSimStats); + reports.Add("activelogajax.html", ajLogLines); + reports.Add("default.report", defaultReport); + reports.Add("clients.report", clientReport); + reports.Add("sessions.report", sessionsReport); + + reports.Add("sim.css", new Prototype_distributor("sim.css")); + reports.Add("sim.html", new Prototype_distributor("sim.html")); + reports.Add("jquery.js", new Prototype_distributor("jquery.js")); + + //// + // Add Your own Reports here (Do Not Modify Lines here Devs!) + //// + + //// + // End Own reports section + //// + + MainServer.Instance.AddHTTPHandler("/SStats/", HandleStatsRequest); + MainServer.Instance.AddHTTPHandler("/CAPS/VS/", HandleUnknownCAPSRequest); + } + + public virtual void AddRegion(Scene scene) + { + if (!enabled) + return; + + lock (m_scenes) + { + m_scenes.Add(scene); + updateLogMod = m_scenes.Count * 2; + + m_simstatsCounters.Add(scene.RegionInfo.RegionID, new USimStatsData(scene.RegionInfo.RegionID)); + + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + scene.EventManager.OnDeregisterCaps += OnDeRegisterCaps; + scene.EventManager.OnClientClosed += OnClientClosed; + scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; + scene.StatsReporter.OnSendStatsResult += ReceiveClassicSimStatsPacket; + } + } + + public void RegionLoaded(Scene scene) + { + } + + public void RemoveRegion(Scene scene) + { + if (!enabled) + return; + + lock (m_scenes) + { + m_scenes.Remove(scene); + updateLogMod = m_scenes.Count * 2; + m_simstatsCounters.Remove(scene.RegionInfo.RegionID); + } + } + + public virtual void Close() + { + if (!enabled) + return; + + dbConn.Close(); + dbConn.Dispose(); + m_sessions.Clear(); + m_scenes.Clear(); + reports.Clear(); + m_simstatsCounters.Clear(); + } + + public virtual string Name + { + get { return "ViewerStatsModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion + + private void ReceiveClassicSimStatsPacket(SimStats stats) + { + if (!enabled) + return; + + try + { + // Ignore the update if there's a report running right now + // ignore the update if there hasn't been a hit in 30 seconds. + if (concurrencyCounter > 0 || System.Environment.TickCount - lastHit > 30000) + return; + + // We will conduct this under lock so that fields such as updateLogCounter do not potentially get + // confused if a scene is removed. + // XXX: Possibly the scope of this lock could be reduced though it's not critical. + lock (m_scenes) + { + if (updateLogMod != 0 && updateLogCounter++ % updateLogMod == 0) + { + m_loglines = readLogLines(10); + + if (updateLogCounter > 10000) + updateLogCounter = 1; + } + + USimStatsData ss = m_simstatsCounters[stats.RegionUUID]; + + if ((++ss.StatsCounter % updateStatsMod) == 0) + { + ss.ConsumeSimStats(stats); + } + } + } + catch (KeyNotFoundException) + { + } + } + + private Hashtable HandleUnknownCAPSRequest(Hashtable request) + { + //string regpath = request["uri"].ToString(); + int response_code = 200; + string contenttype = "text/html"; + UpdateUserStats(ParseViewerStats(request["body"].ToString(), UUID.Zero), dbConn); + Hashtable responsedata = new Hashtable(); + + responsedata["int_response_code"] = response_code; + responsedata["content_type"] = contenttype; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = string.Empty; + return responsedata; + } + + private Hashtable HandleStatsRequest(Hashtable request) + { + lastHit = System.Environment.TickCount; + Hashtable responsedata = new Hashtable(); + string regpath = request["uri"].ToString(); + int response_code = 404; + string contenttype = "text/html"; + bool jsonFormatOutput = false; + + string strOut = string.Empty; + + // The request patch should be "/SStats/reportName" where 'reportName' + // is one of the names added to the 'reports' hashmap. + regpath = regpath.Remove(0, 8); + if (regpath.Length == 0) regpath = "default.report"; + if (reports.ContainsKey(regpath)) + { + IStatsController rep = reports[regpath]; + Hashtable repParams = new Hashtable(); + + if (request.ContainsKey("json")) + jsonFormatOutput = true; + + if (request.ContainsKey("requestvars")) + repParams["RequestVars"] = request["requestvars"]; + else + repParams["RequestVars"] = new Hashtable(); + + if (request.ContainsKey("querystringkeys")) + repParams["QueryStringKeys"] = request["querystringkeys"]; + else + repParams["QueryStringKeys"] = new string[0]; + + + repParams["DatabaseConnection"] = dbConn; + repParams["Scenes"] = m_scenes; + repParams["SimStats"] = m_simstatsCounters; + repParams["LogLines"] = m_loglines; + repParams["Reports"] = reports; + + concurrencyCounter++; + + if (jsonFormatOutput) + { + strOut = rep.RenderJson(rep.ProcessModel(repParams)); + contenttype = "text/json"; + } + else + { + strOut = rep.RenderView(rep.ProcessModel(repParams)); + } + + if (regpath.EndsWith("js")) + { + contenttype = "text/javascript"; + } + + if (regpath.EndsWith("css")) + { + contenttype = "text/css"; + } + + concurrencyCounter--; + + response_code = 200; + } + else + { + strOut = MainServer.Instance.GetHTTP404(""); + } + + responsedata["int_response_code"] = response_code; + responsedata["content_type"] = contenttype; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = strOut; + + return responsedata; + } + + private void CreateTables(SqliteConnection db) + { + using (SqliteCommand createcmd = new SqliteCommand(SQL_STATS_TABLE_CREATE, db)) + { + createcmd.ExecuteNonQuery(); + } + } + + private void OnRegisterCaps(UUID agentID, Caps caps) + { +// m_log.DebugFormat("[WEB STATS MODULE]: OnRegisterCaps: agentID {0} caps {1}", agentID, caps); + + string capsPath = "/CAPS/VS/" + UUID.Random(); + caps.RegisterHandler( + "ViewerStats", + new RestStreamHandler( + "POST", + capsPath, + (request, path, param, httpRequest, httpResponse) + => ViewerStatsReport(request, path, param, agentID, caps), + "ViewerStats", + agentID.ToString())); + } + + private void OnDeRegisterCaps(UUID agentID, Caps caps) + { + } + + protected virtual void AddEventHandlers() + { + lock (m_scenes) + { + updateLogMod = m_scenes.Count * 2; + foreach (Scene scene in m_scenes) + { + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + scene.EventManager.OnDeregisterCaps += OnDeRegisterCaps; + scene.EventManager.OnClientClosed += OnClientClosed; + scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; + } + } + } + + private void OnMakeRootAgent(ScenePresence agent) + { +// m_log.DebugFormat( +// "[WEB STATS MODULE]: Looking for session {0} for {1} in {2}", +// agent.ControllingClient.SessionId, agent.Name, agent.Scene.Name); + + lock (m_sessions) + { + UserSession uid; + + if (!m_sessions.ContainsKey(agent.UUID)) + { + UserSessionData usd = UserSessionUtil.newUserSessionData(); + uid = new UserSession(); + uid.name_f = agent.Firstname; + uid.name_l = agent.Lastname; + uid.session_data = usd; + + m_sessions.Add(agent.UUID, uid); + } + else + { + uid = m_sessions[agent.UUID]; + } + + uid.region_id = agent.Scene.RegionInfo.RegionID; + uid.session_id = agent.ControllingClient.SessionId; + } + } + + private void OnClientClosed(UUID agentID, Scene scene) + { + lock (m_sessions) + { + if (m_sessions.ContainsKey(agentID) && m_sessions[agentID].region_id == scene.RegionInfo.RegionID) + { + m_sessions.Remove(agentID); + } + } + } + + private string readLogLines(int amount) + { + Encoding encoding = Encoding.ASCII; + int sizeOfChar = encoding.GetByteCount("\n"); + byte[] buffer = encoding.GetBytes("\n"); + string logfile = Util.logFile(); + FileStream fs = new FileStream(logfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + Int64 tokenCount = 0; + Int64 endPosition = fs.Length / sizeOfChar; + + for (Int64 position = sizeOfChar; position < endPosition; position += sizeOfChar) + { + fs.Seek(-position, SeekOrigin.End); + fs.Read(buffer, 0, buffer.Length); + + if (encoding.GetString(buffer) == "\n") + { + tokenCount++; + if (tokenCount == amount) + { + byte[] returnBuffer = new byte[fs.Length - fs.Position]; + fs.Read(returnBuffer, 0, returnBuffer.Length); + fs.Close(); + fs.Dispose(); + return encoding.GetString(returnBuffer); + } + } + } + + // handle case where number of tokens in file is less than numberOfTokens + fs.Seek(0, SeekOrigin.Begin); + buffer = new byte[fs.Length]; + fs.Read(buffer, 0, buffer.Length); + fs.Close(); + fs.Dispose(); + return encoding.GetString(buffer); + } + + /// + /// Callback for a viewerstats cap + /// + /// + /// + /// + /// + /// + /// + private string ViewerStatsReport(string request, string path, string param, + UUID agentID, Caps caps) + { +// m_log.DebugFormat("[WEB STATS MODULE]: Received viewer starts report from {0}", agentID); + + UpdateUserStats(ParseViewerStats(request, agentID), dbConn); + + return String.Empty; + } + + private UserSession ParseViewerStats(string request, UUID agentID) + { + UserSession uid = new UserSession(); + UserSessionData usd; + OSD message = OSDParser.DeserializeLLSDXml(request); + OSDMap mmap; + + lock (m_sessions) + { + if (agentID != UUID.Zero) + { + if (!m_sessions.ContainsKey(agentID)) + { + m_log.WarnFormat("[WEB STATS MODULE]: no session for stat disclosure for agent {0}", agentID); + return new UserSession(); + } + + uid = m_sessions[agentID]; + +// m_log.DebugFormat("[WEB STATS MODULE]: Got session {0} for {1}", uid.session_id, agentID); + } + else + { + // parse through the beginning to locate the session + if (message.Type != OSDType.Map) + return new UserSession(); + + mmap = (OSDMap)message; + { + UUID sessionID = mmap["session_id"].AsUUID(); + + if (sessionID == UUID.Zero) + return new UserSession(); + + + // search through each session looking for the owner + foreach (UUID usersessionid in m_sessions.Keys) + { + // got it! + if (m_sessions[usersessionid].session_id == sessionID) + { + agentID = usersessionid; + uid = m_sessions[usersessionid]; + break; + } + + } + + // can't find a session + if (agentID == UUID.Zero) + { + return new UserSession(); + } + } + } + } + + usd = uid.session_data; + + if (message.Type != OSDType.Map) + return new UserSession(); + + mmap = (OSDMap)message; + { + if (mmap["agent"].Type != OSDType.Map) + return new UserSession(); + OSDMap agent_map = (OSDMap)mmap["agent"]; + usd.agent_id = agentID; + usd.name_f = uid.name_f; + usd.name_l = uid.name_l; + usd.region_id = uid.region_id; + usd.a_language = agent_map["language"].AsString(); + usd.mem_use = (float)agent_map["mem_use"].AsReal(); + usd.meters_traveled = (float)agent_map["meters_traveled"].AsReal(); + usd.regions_visited = agent_map["regions_visited"].AsInteger(); + usd.run_time = (float)agent_map["run_time"].AsReal(); + usd.start_time = (float)agent_map["start_time"].AsReal(); + usd.client_version = agent_map["version"].AsString(); + + UserSessionUtil.UpdateMultiItems(ref usd, agent_map["agents_in_view"].AsInteger(), + (float)agent_map["ping"].AsReal(), + (float)agent_map["sim_fps"].AsReal(), + (float)agent_map["fps"].AsReal()); + + if (mmap["downloads"].Type != OSDType.Map) + return new UserSession(); + OSDMap downloads_map = (OSDMap)mmap["downloads"]; + usd.d_object_kb = (float)downloads_map["object_kbytes"].AsReal(); + usd.d_texture_kb = (float)downloads_map["texture_kbytes"].AsReal(); + usd.d_world_kb = (float)downloads_map["workd_kbytes"].AsReal(); + +// m_log.DebugFormat("[WEB STATS MODULE]: mmap[\"session_id\"] = [{0}]", mmap["session_id"].AsUUID()); + + usd.session_id = mmap["session_id"].AsUUID(); + + if (mmap["system"].Type != OSDType.Map) + return new UserSession(); + OSDMap system_map = (OSDMap)mmap["system"]; + + usd.s_cpu = system_map["cpu"].AsString(); + usd.s_gpu = system_map["gpu"].AsString(); + usd.s_os = system_map["os"].AsString(); + usd.s_ram = system_map["ram"].AsInteger(); + + if (mmap["stats"].Type != OSDType.Map) + return new UserSession(); + + OSDMap stats_map = (OSDMap)mmap["stats"]; + { + + if (stats_map["failures"].Type != OSDType.Map) + return new UserSession(); + OSDMap stats_failures = (OSDMap)stats_map["failures"]; + usd.f_dropped = stats_failures["dropped"].AsInteger(); + usd.f_failed_resends = stats_failures["failed_resends"].AsInteger(); + usd.f_invalid = stats_failures["invalid"].AsInteger(); + usd.f_resent = stats_failures["resent"].AsInteger(); + usd.f_send_packet = stats_failures["send_packet"].AsInteger(); + + if (stats_map["net"].Type != OSDType.Map) + return new UserSession(); + OSDMap stats_net = (OSDMap)stats_map["net"]; + { + if (stats_net["in"].Type != OSDType.Map) + return new UserSession(); + + OSDMap net_in = (OSDMap)stats_net["in"]; + usd.n_in_kb = (float)net_in["kbytes"].AsReal(); + usd.n_in_pk = net_in["packets"].AsInteger(); + + if (stats_net["out"].Type != OSDType.Map) + return new UserSession(); + OSDMap net_out = (OSDMap)stats_net["out"]; + + usd.n_out_kb = (float)net_out["kbytes"].AsReal(); + usd.n_out_pk = net_out["packets"].AsInteger(); + } + } + } + + uid.session_data = usd; + m_sessions[agentID] = uid; + +// m_log.DebugFormat( +// "[WEB STATS MODULE]: Parse data for {0} {1}, session {2}", uid.name_f, uid.name_l, uid.session_id); + + return uid; + } + + private void UpdateUserStats(UserSession uid, SqliteConnection db) + { +// m_log.DebugFormat( +// "[WEB STATS MODULE]: Updating user stats for {0} {1}, session {2}", uid.name_f, uid.name_l, uid.session_id); + + if (uid.session_id == UUID.Zero) + return; + + lock (db) + { + using (SqliteCommand updatecmd = new SqliteCommand(SQL_STATS_TABLE_INSERT, db)) + { + updatecmd.Parameters.Add(new SqliteParameter(":session_id", uid.session_data.session_id.ToString())); + updatecmd.Parameters.Add(new SqliteParameter(":agent_id", uid.session_data.agent_id.ToString())); + updatecmd.Parameters.Add(new SqliteParameter(":region_id", uid.session_data.region_id.ToString())); + updatecmd.Parameters.Add(new SqliteParameter(":last_updated", (int) uid.session_data.last_updated)); + updatecmd.Parameters.Add(new SqliteParameter(":remote_ip", uid.session_data.remote_ip)); + updatecmd.Parameters.Add(new SqliteParameter(":name_f", uid.session_data.name_f)); + updatecmd.Parameters.Add(new SqliteParameter(":name_l", uid.session_data.name_l)); + updatecmd.Parameters.Add(new SqliteParameter(":avg_agents_in_view", uid.session_data.avg_agents_in_view)); + updatecmd.Parameters.Add(new SqliteParameter(":min_agents_in_view", + (int) uid.session_data.min_agents_in_view)); + updatecmd.Parameters.Add(new SqliteParameter(":max_agents_in_view", + (int) uid.session_data.max_agents_in_view)); + updatecmd.Parameters.Add(new SqliteParameter(":mode_agents_in_view", + (int) uid.session_data.mode_agents_in_view)); + updatecmd.Parameters.Add(new SqliteParameter(":avg_fps", uid.session_data.avg_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":min_fps", uid.session_data.min_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":max_fps", uid.session_data.max_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":mode_fps", uid.session_data.mode_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":a_language", uid.session_data.a_language)); + updatecmd.Parameters.Add(new SqliteParameter(":mem_use", uid.session_data.mem_use)); + updatecmd.Parameters.Add(new SqliteParameter(":meters_traveled", uid.session_data.meters_traveled)); + updatecmd.Parameters.Add(new SqliteParameter(":avg_ping", uid.session_data.avg_ping)); + updatecmd.Parameters.Add(new SqliteParameter(":min_ping", uid.session_data.min_ping)); + updatecmd.Parameters.Add(new SqliteParameter(":max_ping", uid.session_data.max_ping)); + updatecmd.Parameters.Add(new SqliteParameter(":mode_ping", uid.session_data.mode_ping)); + updatecmd.Parameters.Add(new SqliteParameter(":regions_visited", uid.session_data.regions_visited)); + updatecmd.Parameters.Add(new SqliteParameter(":run_time", uid.session_data.run_time)); + updatecmd.Parameters.Add(new SqliteParameter(":avg_sim_fps", uid.session_data.avg_sim_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":min_sim_fps", uid.session_data.min_sim_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":max_sim_fps", uid.session_data.max_sim_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":mode_sim_fps", uid.session_data.mode_sim_fps)); + updatecmd.Parameters.Add(new SqliteParameter(":start_time", uid.session_data.start_time)); + updatecmd.Parameters.Add(new SqliteParameter(":client_version", uid.session_data.client_version)); + updatecmd.Parameters.Add(new SqliteParameter(":s_cpu", uid.session_data.s_cpu)); + updatecmd.Parameters.Add(new SqliteParameter(":s_gpu", uid.session_data.s_gpu)); + updatecmd.Parameters.Add(new SqliteParameter(":s_os", uid.session_data.s_os)); + updatecmd.Parameters.Add(new SqliteParameter(":s_ram", uid.session_data.s_ram)); + updatecmd.Parameters.Add(new SqliteParameter(":d_object_kb", uid.session_data.d_object_kb)); + updatecmd.Parameters.Add(new SqliteParameter(":d_texture_kb", uid.session_data.d_texture_kb)); + updatecmd.Parameters.Add(new SqliteParameter(":d_world_kb", uid.session_data.d_world_kb)); + updatecmd.Parameters.Add(new SqliteParameter(":n_in_kb", uid.session_data.n_in_kb)); + updatecmd.Parameters.Add(new SqliteParameter(":n_in_pk", uid.session_data.n_in_pk)); + updatecmd.Parameters.Add(new SqliteParameter(":n_out_kb", uid.session_data.n_out_kb)); + updatecmd.Parameters.Add(new SqliteParameter(":n_out_pk", uid.session_data.n_out_pk)); + updatecmd.Parameters.Add(new SqliteParameter(":f_dropped", uid.session_data.f_dropped)); + updatecmd.Parameters.Add(new SqliteParameter(":f_failed_resends", uid.session_data.f_failed_resends)); + updatecmd.Parameters.Add(new SqliteParameter(":f_invalid", uid.session_data.f_invalid)); + updatecmd.Parameters.Add(new SqliteParameter(":f_off_circuit", uid.session_data.f_off_circuit)); + updatecmd.Parameters.Add(new SqliteParameter(":f_resent", uid.session_data.f_resent)); + updatecmd.Parameters.Add(new SqliteParameter(":f_send_packet", uid.session_data.f_send_packet)); + +// StringBuilder parameters = new StringBuilder(); +// SqliteParameterCollection spc = updatecmd.Parameters; +// foreach (SqliteParameter sp in spc) +// parameters.AppendFormat("{0}={1},", sp.ParameterName, sp.Value); +// +// m_log.DebugFormat("[WEB STATS MODULE]: Parameters {0}", parameters); + +// m_log.DebugFormat("[WEB STATS MODULE]: Database stats update for {0}", uid.session_data.agent_id); + + updatecmd.ExecuteNonQuery(); + } + } + } + + #region SQL + private const string SQL_STATS_TABLE_CREATE = @"CREATE TABLE IF NOT EXISTS stats_session_data ( + session_id VARCHAR(36) NOT NULL PRIMARY KEY, + agent_id VARCHAR(36) NOT NULL DEFAULT '', + region_id VARCHAR(36) NOT NULL DEFAULT '', + last_updated INT NOT NULL DEFAULT '0', + remote_ip VARCHAR(16) NOT NULL DEFAULT '', + name_f VARCHAR(50) NOT NULL DEFAULT '', + name_l VARCHAR(50) NOT NULL DEFAULT '', + avg_agents_in_view FLOAT NOT NULL DEFAULT '0', + min_agents_in_view INT NOT NULL DEFAULT '0', + max_agents_in_view INT NOT NULL DEFAULT '0', + mode_agents_in_view INT NOT NULL DEFAULT '0', + avg_fps FLOAT NOT NULL DEFAULT '0', + min_fps FLOAT NOT NULL DEFAULT '0', + max_fps FLOAT NOT NULL DEFAULT '0', + mode_fps FLOAT NOT NULL DEFAULT '0', + a_language VARCHAR(25) NOT NULL DEFAULT '', + mem_use FLOAT NOT NULL DEFAULT '0', + meters_traveled FLOAT NOT NULL DEFAULT '0', + avg_ping FLOAT NOT NULL DEFAULT '0', + min_ping FLOAT NOT NULL DEFAULT '0', + max_ping FLOAT NOT NULL DEFAULT '0', + mode_ping FLOAT NOT NULL DEFAULT '0', + regions_visited INT NOT NULL DEFAULT '0', + run_time FLOAT NOT NULL DEFAULT '0', + avg_sim_fps FLOAT NOT NULL DEFAULT '0', + min_sim_fps FLOAT NOT NULL DEFAULT '0', + max_sim_fps FLOAT NOT NULL DEFAULT '0', + mode_sim_fps FLOAT NOT NULL DEFAULT '0', + start_time FLOAT NOT NULL DEFAULT '0', + client_version VARCHAR(255) NOT NULL DEFAULT '', + s_cpu VARCHAR(255) NOT NULL DEFAULT '', + s_gpu VARCHAR(255) NOT NULL DEFAULT '', + s_os VARCHAR(2255) NOT NULL DEFAULT '', + s_ram INT NOT NULL DEFAULT '0', + d_object_kb FLOAT NOT NULL DEFAULT '0', + d_texture_kb FLOAT NOT NULL DEFAULT '0', + d_world_kb FLOAT NOT NULL DEFAULT '0', + n_in_kb FLOAT NOT NULL DEFAULT '0', + n_in_pk INT NOT NULL DEFAULT '0', + n_out_kb FLOAT NOT NULL DEFAULT '0', + n_out_pk INT NOT NULL DEFAULT '0', + f_dropped INT NOT NULL DEFAULT '0', + f_failed_resends INT NOT NULL DEFAULT '0', + f_invalid INT NOT NULL DEFAULT '0', + f_off_circuit INT NOT NULL DEFAULT '0', + f_resent INT NOT NULL DEFAULT '0', + f_send_packet INT NOT NULL DEFAULT '0' + );"; + + private const string SQL_STATS_TABLE_INSERT = @"INSERT OR REPLACE INTO stats_session_data ( +session_id, agent_id, region_id, last_updated, remote_ip, name_f, name_l, avg_agents_in_view, min_agents_in_view, max_agents_in_view, +mode_agents_in_view, avg_fps, min_fps, max_fps, mode_fps, a_language, mem_use, meters_traveled, avg_ping, min_ping, max_ping, mode_ping, +regions_visited, run_time, avg_sim_fps, min_sim_fps, max_sim_fps, mode_sim_fps, start_time, client_version, s_cpu, s_gpu, s_os, s_ram, +d_object_kb, d_texture_kb, d_world_kb, n_in_kb, n_in_pk, n_out_kb, n_out_pk, f_dropped, f_failed_resends, f_invalid, f_off_circuit, +f_resent, f_send_packet +) +VALUES +( +:session_id, :agent_id, :region_id, :last_updated, :remote_ip, :name_f, :name_l, :avg_agents_in_view, :min_agents_in_view, :max_agents_in_view, +:mode_agents_in_view, :avg_fps, :min_fps, :max_fps, :mode_fps, :a_language, :mem_use, :meters_traveled, :avg_ping, :min_ping, :max_ping, :mode_ping, +:regions_visited, :run_time, :avg_sim_fps, :min_sim_fps, :max_sim_fps, :mode_sim_fps, :start_time, :client_version, :s_cpu, :s_gpu, :s_os, :s_ram, +:d_object_kb, :d_texture_kb, :d_world_kb, :n_in_kb, :n_in_pk, :n_out_kb, :n_out_pk, :f_dropped, :f_failed_resends, :f_invalid, :f_off_circuit, +:f_resent, :f_send_packet +) +"; + + #endregion + + } + + public static class UserSessionUtil + { + public static UserSessionData newUserSessionData() + { + UserSessionData obj = ZeroSession(new UserSessionData()); + return obj; + } + + public static void UpdateMultiItems(ref UserSessionData s, int agents_in_view, float ping, float sim_fps, float fps) + { + // don't insert zero values here or it'll skew the statistics. + if (agents_in_view == 0 && fps == 0 && sim_fps == 0 && ping == 0) + return; + s._agents_in_view.Add(agents_in_view); + s._fps.Add(fps); + s._sim_fps.Add(sim_fps); + s._ping.Add(ping); + + int[] __agents_in_view = s._agents_in_view.ToArray(); + + s.avg_agents_in_view = ArrayAvg_i(__agents_in_view); + s.min_agents_in_view = ArrayMin_i(__agents_in_view); + s.max_agents_in_view = ArrayMax_i(__agents_in_view); + s.mode_agents_in_view = ArrayMode_i(__agents_in_view); + + float[] __fps = s._fps.ToArray(); + s.avg_fps = ArrayAvg_f(__fps); + s.min_fps = ArrayMin_f(__fps); + s.max_fps = ArrayMax_f(__fps); + s.mode_fps = ArrayMode_f(__fps); + + float[] __sim_fps = s._sim_fps.ToArray(); + s.avg_sim_fps = ArrayAvg_f(__sim_fps); + s.min_sim_fps = ArrayMin_f(__sim_fps); + s.max_sim_fps = ArrayMax_f(__sim_fps); + s.mode_sim_fps = ArrayMode_f(__sim_fps); + + float[] __ping = s._ping.ToArray(); + s.avg_ping = ArrayAvg_f(__ping); + s.min_ping = ArrayMin_f(__ping); + s.max_ping = ArrayMax_f(__ping); + s.mode_ping = ArrayMode_f(__ping); + } + + #region Statistics + + public static int ArrayMin_i(int[] arr) + { + int cnt = arr.Length; + if (cnt == 0) + return 0; + + Array.Sort(arr); + return arr[0]; + } + + public static int ArrayMax_i(int[] arr) + { + int cnt = arr.Length; + if (cnt == 0) + return 0; + + Array.Sort(arr); + return arr[cnt-1]; + } + + public static float ArrayMin_f(float[] arr) + { + int cnt = arr.Length; + if (cnt == 0) + return 0; + + Array.Sort(arr); + return arr[0]; + } + + public static float ArrayMax_f(float[] arr) + { + int cnt = arr.Length; + if (cnt == 0) + return 0; + + Array.Sort(arr); + return arr[cnt - 1]; + } + + public static float ArrayAvg_i(int[] arr) + { + int cnt = arr.Length; + + if (cnt == 0) + return 0; + + float result = arr[0]; + + for (int i = 1; i < cnt; i++) + result += arr[i]; + + return result / cnt; + } + + public static float ArrayAvg_f(float[] arr) + { + int cnt = arr.Length; + + if (cnt == 0) + return 0; + + float result = arr[0]; + + for (int i = 1; i < cnt; i++) + result += arr[i]; + + return result / cnt; + } + + public static float ArrayMode_f(float[] arr) + { + List mode = new List(); + + float[] srtArr = new float[arr.Length]; + float[,] freq = new float[arr.Length, 2]; + Array.Copy(arr, srtArr, arr.Length); + Array.Sort(srtArr); + + float tmp = srtArr[0]; + int index = 0; + int i = 0; + while (i < srtArr.Length) + { + freq[index, 0] = tmp; + + while (tmp == srtArr[i]) + { + freq[index, 1]++; + i++; + + if (i > srtArr.Length - 1) + break; + } + + if (i < srtArr.Length) + { + tmp = srtArr[i]; + index++; + } + + } + + Array.Clear(srtArr, 0, srtArr.Length); + + for (i = 0; i < srtArr.Length; i++) + srtArr[i] = freq[i, 1]; + + Array.Sort(srtArr); + + if ((srtArr[srtArr.Length - 1]) == 0 || (srtArr[srtArr.Length - 1]) == 1) + return 0; + + float freqtest = (float)freq.Length / freq.Rank; + + for (i = 0; i < freqtest; i++) + { + if (freq[i, 1] == srtArr[index]) + mode.Add(freq[i, 0]); + + } + + return mode.ToArray()[0]; + } + + public static int ArrayMode_i(int[] arr) + { + List mode = new List(); + + int[] srtArr = new int[arr.Length]; + int[,] freq = new int[arr.Length, 2]; + Array.Copy(arr, srtArr, arr.Length); + Array.Sort(srtArr); + + int tmp = srtArr[0]; + int index = 0; + int i = 0; + while (i < srtArr.Length) + { + freq[index, 0] = tmp; + + while (tmp == srtArr[i]) + { + freq[index, 1]++; + i++; + + if (i > srtArr.Length - 1) + break; + } + + if (i < srtArr.Length) + { + tmp = srtArr[i]; + index++; + } + + } + + Array.Clear(srtArr, 0, srtArr.Length); + + for (i = 0; i < srtArr.Length; i++) + srtArr[i] = freq[i, 1]; + + Array.Sort(srtArr); + + if ((srtArr[srtArr.Length - 1]) == 0 || (srtArr[srtArr.Length - 1]) == 1) + return 0; + + float freqtest = (float)freq.Length / freq.Rank; + + for (i = 0; i < freqtest; i++) + { + if (freq[i, 1] == srtArr[index]) + mode.Add(freq[i, 0]); + + } + + return mode.ToArray()[0]; + } + + #endregion + + private static UserSessionData ZeroSession(UserSessionData s) + { + s.session_id = UUID.Zero; + s.agent_id = UUID.Zero; + s.region_id = UUID.Zero; + s.last_updated = Util.UnixTimeSinceEpoch(); + s.remote_ip = ""; + s.name_f = ""; + s.name_l = ""; + s.avg_agents_in_view = 0; + s.min_agents_in_view = 0; + s.max_agents_in_view = 0; + s.mode_agents_in_view = 0; + s.avg_fps = 0; + s.min_fps = 0; + s.max_fps = 0; + s.mode_fps = 0; + s.a_language = ""; + s.mem_use = 0; + s.meters_traveled = 0; + s.avg_ping = 0; + s.min_ping = 0; + s.max_ping = 0; + s.mode_ping = 0; + s.regions_visited = 0; + s.run_time = 0; + s.avg_sim_fps = 0; + s.min_sim_fps = 0; + s.max_sim_fps = 0; + s.mode_sim_fps = 0; + s.start_time = 0; + s.client_version = ""; + s.s_cpu = ""; + s.s_gpu = ""; + s.s_os = ""; + s.s_ram = 0; + s.d_object_kb = 0; + s.d_texture_kb = 0; + s.d_world_kb = 0; + s.n_in_kb = 0; + s.n_in_pk = 0; + s.n_out_kb = 0; + s.n_out_pk = 0; + s.f_dropped = 0; + s.f_failed_resends = 0; + s.f_invalid = 0; + s.f_off_circuit = 0; + s.f_resent = 0; + s.f_send_packet = 0; + s._ping = new List(); + s._fps = new List(); + s._sim_fps = new List(); + s._agents_in_view = new List(); + return s; + } + } + #region structs + + public class UserSession + { + public UUID session_id; + public UUID region_id; + public string name_f; + public string name_l; + public UserSessionData session_data; + } + + public struct UserSessionData + { + public UUID session_id; + public UUID agent_id; + public UUID region_id; + public float last_updated; + public string remote_ip; + public string name_f; + public string name_l; + public float avg_agents_in_view; + public float min_agents_in_view; + public float max_agents_in_view; + public float mode_agents_in_view; + public float avg_fps; + public float min_fps; + public float max_fps; + public float mode_fps; + public string a_language; + public float mem_use; + public float meters_traveled; + public float avg_ping; + public float min_ping; + public float max_ping; + public float mode_ping; + public int regions_visited; + public float run_time; + public float avg_sim_fps; + public float min_sim_fps; + public float max_sim_fps; + public float mode_sim_fps; + public float start_time; + public string client_version; + public string s_cpu; + public string s_gpu; + public string s_os; + public int s_ram; + public float d_object_kb; + public float d_texture_kb; + public float d_world_kb; + public float n_in_kb; + public int n_in_pk; + public float n_out_kb; + public int n_out_pk; + public int f_dropped; + public int f_failed_resends; + public int f_invalid; + public int f_off_circuit; + public int f_resent; + public int f_send_packet; + public List _ping; + public List _fps; + public List _sim_fps; + public List _agents_in_view; + } + + #endregion + + public class USimStatsData + { + private UUID m_regionID = UUID.Zero; + private volatile int m_statcounter = 0; + private volatile float m_timeDilation; + private volatile float m_simFps; + private volatile float m_physicsFps; + private volatile float m_agentUpdates; + private volatile float m_rootAgents; + private volatile float m_childAgents; + private volatile float m_totalPrims; + private volatile float m_activePrims; + private volatile float m_totalFrameTime; + private volatile float m_netFrameTime; + private volatile float m_physicsFrameTime; + private volatile float m_otherFrameTime; + private volatile float m_imageFrameTime; + private volatile float m_inPacketsPerSecond; + private volatile float m_outPacketsPerSecond; + private volatile float m_unackedBytes; + private volatile float m_agentFrameTime; + private volatile float m_pendingDownloads; + private volatile float m_pendingUploads; + private volatile float m_activeScripts; + private volatile float m_scriptLinesPerSecond; + + public UUID RegionId { get { return m_regionID; } } + public int StatsCounter { get { return m_statcounter; } set { m_statcounter = value;}} + public float TimeDilation { get { return m_timeDilation; } } + public float SimFps { get { return m_simFps; } } + public float PhysicsFps { get { return m_physicsFps; } } + public float AgentUpdates { get { return m_agentUpdates; } } + public float RootAgents { get { return m_rootAgents; } } + public float ChildAgents { get { return m_childAgents; } } + public float TotalPrims { get { return m_totalPrims; } } + public float ActivePrims { get { return m_activePrims; } } + public float TotalFrameTime { get { return m_totalFrameTime; } } + public float NetFrameTime { get { return m_netFrameTime; } } + public float PhysicsFrameTime { get { return m_physicsFrameTime; } } + public float OtherFrameTime { get { return m_otherFrameTime; } } + public float ImageFrameTime { get { return m_imageFrameTime; } } + public float InPacketsPerSecond { get { return m_inPacketsPerSecond; } } + public float OutPacketsPerSecond { get { return m_outPacketsPerSecond; } } + public float UnackedBytes { get { return m_unackedBytes; } } + public float AgentFrameTime { get { return m_agentFrameTime; } } + public float PendingDownloads { get { return m_pendingDownloads; } } + public float PendingUploads { get { return m_pendingUploads; } } + public float ActiveScripts { get { return m_activeScripts; } } + public float ScriptLinesPerSecond { get { return m_scriptLinesPerSecond; } } + + public USimStatsData(UUID pRegionID) + { + m_regionID = pRegionID; + } + + public void ConsumeSimStats(SimStats stats) + { + m_regionID = stats.RegionUUID; + m_timeDilation = stats.StatsBlock[0].StatValue; + m_simFps = stats.StatsBlock[1].StatValue; + m_physicsFps = stats.StatsBlock[2].StatValue; + m_agentUpdates = stats.StatsBlock[3].StatValue; + m_rootAgents = stats.StatsBlock[4].StatValue; + m_childAgents = stats.StatsBlock[5].StatValue; + m_totalPrims = stats.StatsBlock[6].StatValue; + m_activePrims = stats.StatsBlock[7].StatValue; + m_totalFrameTime = stats.StatsBlock[8].StatValue; + m_netFrameTime = stats.StatsBlock[9].StatValue; + m_physicsFrameTime = stats.StatsBlock[10].StatValue; + m_otherFrameTime = stats.StatsBlock[11].StatValue; + m_imageFrameTime = stats.StatsBlock[12].StatValue; + m_inPacketsPerSecond = stats.StatsBlock[13].StatValue; + m_outPacketsPerSecond = stats.StatsBlock[14].StatValue; + m_unackedBytes = stats.StatsBlock[15].StatValue; + m_agentFrameTime = stats.StatsBlock[16].StatValue; + m_pendingDownloads = stats.StatsBlock[17].StatValue; + m_pendingUploads = stats.StatsBlock[18].StatValue; + m_activeScripts = stats.StatsBlock[19].StatValue; + m_scriptLinesPerSecond = stats.StatsBlock[20].StatValue; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/CameraOnlyModeModule.cs b/OpenSim/Region/OptionalModules/ViewerSupport/CameraOnlyModeModule.cs new file mode 100644 index 0000000..7ae4223 --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/CameraOnlyModeModule.cs @@ -0,0 +1,176 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using System.Threading; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim; +using OpenSim.Region; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework; +//using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using Mono.Addins; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using TeleportFlags = OpenSim.Framework.Constants.TeleportFlags; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "CameraOnlyMode")] + public class CameraOnlyModeModule : INonSharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private SimulatorFeaturesHelper m_Helper; + private bool m_Enabled; + private int m_UserLevel; + + public string Name + { + get { return "CameraOnlyModeModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource config) + { + IConfig moduleConfig = config.Configs["CameraOnlyModeModule"]; + if (moduleConfig != null) + { + m_Enabled = moduleConfig.GetBoolean("enabled", false); + if (m_Enabled) + { + m_UserLevel = moduleConfig.GetInt("UserLevel", 0); + m_log.Info("[CAMERA-ONLY MODE]: CameraOnlyModeModule enabled"); + } + + } + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + if (m_Enabled) + { + m_scene = scene; + //m_scene.EventManager.OnMakeRootAgent += (OnMakeRootAgent); + } + } + + //private void OnMakeRootAgent(ScenePresence obj) + //{ + // throw new NotImplementedException(); + //} + + public void RegionLoaded(Scene scene) + { + if (m_Enabled) + { + IEntityTransferModule et = m_scene.RequestModuleInterface(); + m_Helper = new SimulatorFeaturesHelper(scene, et); + + ISimulatorFeaturesModule featuresModule = m_scene.RequestModuleInterface(); + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + } + } + + public void RemoveRegion(Scene scene) + { + } + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + m_log.DebugFormat("[CAMERA-ONLY MODE]: OnSimulatorFeaturesRequest in {0}", m_scene.RegionInfo.RegionName); + if (m_Helper.ShouldSend(agentID) && m_Helper.UserLevel(agentID) <= m_UserLevel) + { + OSDMap extrasMap; + if (features.ContainsKey("OpenSimExtras")) + { + extrasMap = (OSDMap)features["OpenSimExtras"]; + } + else + { + extrasMap = new OSDMap(); + features["OpenSimExtras"] = extrasMap; + } + extrasMap["camera-only-mode"] = OSDMap.FromString("true"); + m_log.DebugFormat("[CAMERA-ONLY MODE]: Sent in {0}", m_scene.RegionInfo.RegionName); + + // Detaching attachments doesn't work for HG visitors, + // so I'm giving that up. + //Util.FireAndForget(delegate { DetachAttachments(agentID); }); + } + else + m_log.DebugFormat("[CAMERA-ONLY MODE]: NOT Sending camera-only-mode in {0}", m_scene.RegionInfo.RegionName); + } + + private void DetachAttachments(UUID agentID) + { + ScenePresence sp = m_scene.GetScenePresence(agentID); + if ((sp.TeleportFlags & TeleportFlags.ViaLogin) != 0) + // Wait a little, cos there's weird stuff going on at login related to + // the Current Outfit Folder + Thread.Sleep(8000); + + if (sp != null && m_scene.AttachmentsModule != null) + { + List attachs = sp.GetAttachments(); + if (attachs != null && attachs.Count > 0) + { + foreach (SceneObjectGroup sog in attachs) + { + m_log.DebugFormat("[CAMERA-ONLY MODE]: Forcibly detaching attach {0} from {1} in {2}", + sog.Name, sp.Name, m_scene.RegionInfo.RegionName); + + m_scene.AttachmentsModule.DetachSingleAttachmentToInv(sp, sog); + } + } + } + } + + } + +} diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/DynamicFloaterModule.cs b/OpenSim/Region/OptionalModules/ViewerSupport/DynamicFloaterModule.cs new file mode 100644 index 0000000..e76e8f2 --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/DynamicFloaterModule.cs @@ -0,0 +1,238 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim; +using OpenSim.Region; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using Mono.Addins; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DynamicFloater")] + public class DynamicFloaterModule : INonSharedRegionModule, IDynamicFloaterModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + + private Dictionary> m_floaters = new Dictionary>(); + + public string Name + { + get { return "DynamicFloaterModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource config) + { + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + m_scene = scene; + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnClientClosed += OnClientClosed; + m_scene.RegisterModuleInterface(this); + } + + public void RegionLoaded(Scene scene) + { + } + + public void RemoveRegion(Scene scene) + { + } + + private void OnNewClient(IClientAPI client) + { + client.OnChatFromClient += OnChatFromClient; + } + + private void OnClientClosed(UUID agentID, Scene scene) + { + m_floaters.Remove(agentID); + } + + private void SendToClient(ScenePresence sp, string msg) + { + sp.ControllingClient.SendChatMessage(msg, + (byte)ChatTypeEnum.Owner, + sp.AbsolutePosition, + "Server", + UUID.Zero, + UUID.Zero, + (byte)ChatSourceType.Object, + (byte)ChatAudibleLevel.Fully); + } + + public void DoUserFloater(UUID agentID, FloaterData dialogData, string configuration) + { + ScenePresence sp = m_scene.GetScenePresence(agentID); + if (sp == null || sp.IsChildAgent) + return; + + if (!m_floaters.ContainsKey(agentID)) + m_floaters[agentID] = new Dictionary(); + + if (m_floaters[agentID].ContainsKey(dialogData.Channel)) + return; + + m_floaters[agentID].Add(dialogData.Channel, dialogData); + + string xml; + if (dialogData.XmlText != null && dialogData.XmlText != String.Empty) + { + xml = dialogData.XmlText; + } + else + { + using (FileStream fs = File.Open(dialogData.XmlName + ".xml", FileMode.Open)) + { + using (StreamReader sr = new StreamReader(fs)) + xml = sr.ReadToEnd().Replace("\n", ""); + } + } + + List xparts = new List(); + + while (xml.Length > 0) + { + string x = xml; + if (x.Length > 600) + { + x = x.Substring(0, 600); + xml = xml.Substring(600); + } + else + { + xml = String.Empty; + } + + xparts.Add(x); + } + + for (int i = 0 ; i < xparts.Count ; i++) + SendToClient(sp, String.Format("># floater {2} create {0}/{1} " + xparts[i], i + 1, xparts.Count, dialogData.FloaterName)); + + SendToClient(sp, String.Format("># floater {0} {{notify:1}} {{channel: {1}}} {{node:cancel {{notify:1}}}} {{node:ok {{notify:1}}}} {2}", dialogData.FloaterName, (uint)dialogData.Channel, configuration)); + } + + private void OnChatFromClient(object sender, OSChatMessage msg) + { + if (msg.Sender == null) + return; + + //m_log.DebugFormat("chan {0} msg {1}", msg.Channel, msg.Message); + + IClientAPI client = msg.Sender; + + if (!m_floaters.ContainsKey(client.AgentId)) + return; + + string[] parts = msg.Message.Split(new char[] {':'}); + if (parts.Length == 0) + return; + + ScenePresence sp = m_scene.GetScenePresence(client.AgentId); + if (sp == null || sp.IsChildAgent) + return; + + Dictionary d = m_floaters[client.AgentId]; + + // Work around a viewer bug - VALUE from any + // dialog can appear on this channel and needs to + // be dispatched to ALL open dialogs for the user + if (msg.Channel == 427169570) + { + if (parts[0] == "VALUE") + { + foreach (FloaterData dd in d.Values) + { + if(dd.Handler(client, dd, parts)) + { + m_floaters[client.AgentId].Remove(dd.Channel); + SendToClient(sp, String.Format("># floater {0} destroy", dd.FloaterName)); + break; + } + } + } + return; + } + + if (!d.ContainsKey(msg.Channel)) + return; + + FloaterData data = d[msg.Channel]; + + if (parts[0] == "NOTIFY") + { + if (parts[1] == "cancel" || parts[1] == data.FloaterName) + { + m_floaters[client.AgentId].Remove(data.Channel); + SendToClient(sp, String.Format("># floater {0} destroy", data.FloaterName)); + } + } + + if (data.Handler != null && data.Handler(client, data, parts)) + { + m_floaters[client.AgentId].Remove(data.Channel); + SendToClient(sp, String.Format("># floater {0} destroy", data.FloaterName)); + } + } + + public void FloaterControl(ScenePresence sp, FloaterData d, string msg) + { + string sendData = String.Format("># floater {0} {1}", d.FloaterName, msg); + SendToClient(sp, sendData); + + } + } +} diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/DynamicMenuModule.cs b/OpenSim/Region/OptionalModules/ViewerSupport/DynamicMenuModule.cs new file mode 100644 index 0000000..d37369c --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/DynamicMenuModule.cs @@ -0,0 +1,304 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim; +using OpenSim.Region; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework; +//using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using Mono.Addins; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DynamicMenu")] + public class DynamicMenuModule : INonSharedRegionModule, IDynamicMenuModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private class MenuItemData + { + public string Title; + public UUID AgentID; + public InsertLocation Location; + public UserMode Mode; + public CustomMenuHandler Handler; + } + + private Dictionary> m_menuItems = + new Dictionary>(); + + private Scene m_scene; + + public string Name + { + get { return "DynamicMenuModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource config) + { + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + m_scene = scene; + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + m_scene.RegisterModuleInterface(this); + } + + public void RegionLoaded(Scene scene) + { + ISimulatorFeaturesModule featuresModule = m_scene.RequestModuleInterface(); + + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + } + + public void RemoveRegion(Scene scene) + { + } + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + OSD menus = new OSDMap(); + if (features.ContainsKey("menus")) + menus = features["menus"]; + + OSDMap agent = new OSDMap(); + OSDMap world = new OSDMap(); + OSDMap tools = new OSDMap(); + OSDMap advanced = new OSDMap(); + OSDMap admin = new OSDMap(); + if (((OSDMap)menus).ContainsKey("agent")) + agent = (OSDMap)((OSDMap)menus)["agent"]; + if (((OSDMap)menus).ContainsKey("world")) + world = (OSDMap)((OSDMap)menus)["world"]; + if (((OSDMap)menus).ContainsKey("tools")) + tools = (OSDMap)((OSDMap)menus)["tools"]; + if (((OSDMap)menus).ContainsKey("advanced")) + advanced = (OSDMap)((OSDMap)menus)["advanced"]; + if (((OSDMap)menus).ContainsKey("admin")) + admin = (OSDMap)((OSDMap)menus)["admin"]; + + if (m_menuItems.ContainsKey(UUID.Zero)) + { + foreach (MenuItemData d in m_menuItems[UUID.Zero]) + { + if (d.Mode == UserMode.God && (!m_scene.Permissions.IsGod(agentID))) + continue; + + OSDMap loc = null; + switch (d.Location) + { + case InsertLocation.Agent: + loc = agent; + break; + case InsertLocation.World: + loc = world; + break; + case InsertLocation.Tools: + loc = tools; + break; + case InsertLocation.Advanced: + loc = advanced; + break; + case InsertLocation.Admin: + loc = admin; + break; + } + + if (loc == null) + continue; + + loc[d.Title] = OSD.FromString(d.Title); + } + } + + if (m_menuItems.ContainsKey(agentID)) + { + foreach (MenuItemData d in m_menuItems[agentID]) + { + if (d.Mode == UserMode.God && (!m_scene.Permissions.IsGod(agentID))) + continue; + + OSDMap loc = null; + switch (d.Location) + { + case InsertLocation.Agent: + loc = agent; + break; + case InsertLocation.World: + loc = world; + break; + case InsertLocation.Tools: + loc = tools; + break; + case InsertLocation.Advanced: + loc = advanced; + break; + case InsertLocation.Admin: + loc = admin; + break; + } + + if (loc == null) + continue; + + loc[d.Title] = OSD.FromString(d.Title); + } + } + + + ((OSDMap)menus)["agent"] = agent; + ((OSDMap)menus)["world"] = world; + ((OSDMap)menus)["tools"] = tools; + ((OSDMap)menus)["advanced"] = advanced; + ((OSDMap)menus)["admin"] = admin; + + features["menus"] = menus; + } + + private void OnRegisterCaps(UUID agentID, Caps caps) + { + string capUrl = "/CAPS/" + UUID.Random() + "/"; + + capUrl = "/CAPS/" + UUID.Random() + "/"; + caps.RegisterHandler("CustomMenuAction", new MenuActionHandler(capUrl, "CustomMenuAction", agentID, this, m_scene)); + } + + internal void HandleMenuSelection(string action, UUID agentID, List selection) + { + if (m_menuItems.ContainsKey(agentID)) + { + foreach (MenuItemData d in m_menuItems[agentID]) + { + if (d.Title == action) + d.Handler(action, agentID, selection); + } + } + + if (m_menuItems.ContainsKey(UUID.Zero)) + { + foreach (MenuItemData d in m_menuItems[UUID.Zero]) + { + if (d.Title == action) + d.Handler(action, agentID, selection); + } + } + } + + public void AddMenuItem(string title, InsertLocation location, UserMode mode, CustomMenuHandler handler) + { + AddMenuItem(UUID.Zero, title, location, mode, handler); + } + + public void AddMenuItem(UUID agentID, string title, InsertLocation location, UserMode mode, CustomMenuHandler handler) + { + if (!m_menuItems.ContainsKey(agentID)) + m_menuItems[agentID] = new List(); + + m_menuItems[agentID].Add(new MenuItemData() { Title = title, AgentID = agentID, Location = location, Mode = mode, Handler = handler }); + } + + public void RemoveMenuItem(string action) + { + foreach (KeyValuePair> kvp in m_menuItems) + { + List pendingDeletes = new List(); + foreach (MenuItemData d in kvp.Value) + { + if (d.Title == action) + pendingDeletes.Add(d); + } + + foreach (MenuItemData d in pendingDeletes) + kvp.Value.Remove(d); + } + } + } + + public class MenuActionHandler : BaseStreamHandler + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private UUID m_agentID; + private Scene m_scene; + private DynamicMenuModule m_module; + + public MenuActionHandler(string path, string name, UUID agentID, DynamicMenuModule module, Scene scene) + :base("POST", path, name, agentID.ToString()) + { + m_agentID = agentID; + m_scene = scene; + m_module = module; + } + + protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + StreamReader reader = new StreamReader(request); + string requestBody = reader.ReadToEnd(); + + OSD osd = OSDParser.DeserializeLLSDXml(requestBody); + + string action = ((OSDMap)osd)["action"].AsString(); + OSDArray selection = (OSDArray)((OSDMap)osd)["selection"]; + List sel = new List(); + for (int i = 0 ; i < selection.Count ; i++) + sel.Add(selection[i].AsUInteger()); + + Util.FireAndForget( + x => { m_module.HandleMenuSelection(action, m_agentID, sel); }, null, "DynamicMenuModule.HandleMenuSelection"); + + Encoding encoding = Encoding.UTF8; + return encoding.GetBytes(OSDParser.SerializeLLSDXmlString(new OSD())); + } + } +} diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs b/OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs new file mode 100644 index 0000000..e0537a4 --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs @@ -0,0 +1,144 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GodNamesModule")] + public class GodNamesModule : ISharedRegionModule + { + // Infrastructure + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // Configuration + private static bool m_enabled = false; + private static List m_lastNames = new List(); + private static List m_fullNames = new List(); + + public void Initialise(IConfigSource config) + { + IConfig moduleConfig = config.Configs["GodNames"]; + + if (moduleConfig == null) { + return; + } + + if (!moduleConfig.GetBoolean("Enabled", false)) { + m_log.Info("[GODNAMES]: Addon is disabled"); + return; + } + + m_log.Info("[GODNAMES]: Enabled"); + m_enabled = true; + string conf_str = moduleConfig.GetString("FullNames", String.Empty); + foreach (string strl in conf_str.Split(',')) { + string strlan = strl.Trim(" \t".ToCharArray()); + m_log.DebugFormat("[GODNAMES]: Adding {0} as a God name", strlan); + m_fullNames.Add(strlan); + } + + conf_str = moduleConfig.GetString("Surnames", String.Empty); + foreach (string strl in conf_str.Split(',')) { + string strlan = strl.Trim(" \t".ToCharArray()); + m_log.DebugFormat("[GODNAMES]: Adding {0} as a God last name", strlan); + m_lastNames.Add(strlan); + } + } + + public void AddRegion(Scene scene) { + /*no op*/ + } + + public void RemoveRegion(Scene scene) { + /*no op*/ + } + + public void PostInitialise() { + /*no op*/ + } + + public void Close() { + /*no op*/ + } + + public Type ReplaceableInterface { + get { return null; } + } + + public string Name { + get { return "Godnames"; } + } + + public bool IsSharedModule { + get { return true; } + } + + public virtual void RegionLoaded(Scene scene) + { + if (!m_enabled) + return; + + ISimulatorFeaturesModule featuresModule = scene.RequestModuleInterface(); + + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + + } + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + OSD namesmap = new OSDMap(); + if (features.ContainsKey("god_names")) + namesmap = features["god_names"]; + else + features["god_names"] = namesmap; + + OSDArray fnames = new OSDArray(); + foreach (string name in m_fullNames) { + fnames.Add(name); + } + ((OSDMap)namesmap)["full_names"] = fnames; + + OSDArray lnames = new OSDArray(); + foreach (string name in m_lastNames) { + lnames.Add(name); + } + ((OSDMap)namesmap)["last_names"] = lnames; + } + } +} diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/SimulatorFeaturesHelper.cs b/OpenSim/Region/OptionalModules/ViewerSupport/SimulatorFeaturesHelper.cs new file mode 100644 index 0000000..2661522 --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/SimulatorFeaturesHelper.cs @@ -0,0 +1,171 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using System.Threading; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim; +using OpenSim.Region; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +//using OpenSim.Framework.Capabilities; +using Nini.Config; +using log4net; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using TeleportFlags = OpenSim.Framework.Constants.TeleportFlags; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + public class SimulatorFeaturesHelper + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IEntityTransferModule m_TransferModule; + private Scene m_scene; + + private struct RegionSend { + public UUID region; + public bool send; + }; + // Using a static cache so that we don't have to perform the time-consuming tests + // in ShouldSend on Extra SimFeatures that go on the same response but come from + // different modules. + // This cached is indexed on the agentID and maps to a list of regions + private static ExpiringCache> m_Cache = new ExpiringCache>(); + private const double TIMEOUT = 1.0; // time in cache + + public SimulatorFeaturesHelper(Scene scene, IEntityTransferModule et) + { + m_scene = scene; + m_TransferModule = et; + } + + public bool ShouldSend(UUID agentID) + { + List rsendlist; + RegionSend rsend; + if (m_Cache.TryGetValue(agentID, out rsendlist)) + { + rsend = rsendlist.Find(r => r.region == m_scene.RegionInfo.RegionID); + if (rsend.region != UUID.Zero) // Found it + { + return rsend.send; + } + } + + // Relatively complex logic for deciding whether to send the extra SimFeature or not. + // This is because the viewer calls this cap to all sims that it knows about, + // including the departing sims and non-neighbors (those that are cached). + rsend.region = m_scene.RegionInfo.RegionID; + rsend.send = false; + IClientAPI client = null; + int counter = 200; + + // Let's wait a little to see if we get a client here + while (!m_scene.TryGetClient(agentID, out client) && counter-- > 0) + Thread.Sleep(50); + + if (client != null) + { + ScenePresence sp = WaitGetScenePresence(agentID); + + if (sp != null) + { + // On the receiving region, the call to this cap may arrive before + // the agent is root. Make sure we only proceed from here when the agent + // has been made root + counter = 200; + while ((sp.IsInTransit || sp.IsChildAgent) && counter-- > 0) + { + Thread.Sleep(50); + } + + // The viewer calls this cap on the departing sims too. Make sure + // that we only proceed after the agent is not in transit anymore. + // The agent must be root and not going anywhere + if (!sp.IsChildAgent && !m_TransferModule.IsInTransit(agentID)) + rsend.send = true; + + } + } + //else + // m_log.DebugFormat("[XXX]: client is null"); + + + if (rsendlist == null) + { + rsendlist = new List(); + m_Cache.AddOrUpdate(agentID, rsendlist, TIMEOUT); + } + rsendlist.Add(rsend); + + return rsend.send; + } + + public int UserLevel(UUID agentID) + { + int level = 0; + UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, agentID); + if (account != null) + level = account.UserLevel; + + return level; + } + + protected virtual ScenePresence WaitGetScenePresence(UUID agentID) + { + int ntimes = 20; + ScenePresence sp = null; + while ((sp = m_scene.GetScenePresence(agentID)) == null && (ntimes-- > 0)) + Thread.Sleep(1000); + + if (sp == null) + m_log.WarnFormat( + "[XXX]: Did not find presence with id {0} in {1} before timeout", + agentID, m_scene.RegionInfo.RegionName); + else + { + ntimes = 10; + while (sp.IsInTransit && (ntimes-- > 0)) + Thread.Sleep(1000); + } + + return sp; + } + + } + +} diff --git a/OpenSim/Region/OptionalModules/ViewerSupport/SpecialUIModule.cs b/OpenSim/Region/OptionalModules/ViewerSupport/SpecialUIModule.cs new file mode 100644 index 0000000..3fe922d --- /dev/null +++ b/OpenSim/Region/OptionalModules/ViewerSupport/SpecialUIModule.cs @@ -0,0 +1,165 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim; +using OpenSim.Region; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework; +//using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using Mono.Addins; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using TeleportFlags = OpenSim.Framework.Constants.TeleportFlags; + +namespace OpenSim.Region.OptionalModules.ViewerSupport +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SpecialUI")] + public class SpecialUIModule : INonSharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private const string VIEWER_SUPPORT_DIR = "ViewerSupport"; + + private Scene m_scene; + private SimulatorFeaturesHelper m_Helper; + private bool m_Enabled; + private int m_UserLevel; + + public string Name + { + get { return "SpecialUIModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource config) + { + IConfig moduleConfig = config.Configs["SpecialUIModule"]; + if (moduleConfig != null) + { + m_Enabled = moduleConfig.GetBoolean("enabled", false); + if (m_Enabled) + { + m_UserLevel = moduleConfig.GetInt("UserLevel", 0); + m_log.Info("[SPECIAL UI]: SpecialUIModule enabled"); + } + + } + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + if (m_Enabled) + { + m_scene = scene; + } + } + + public void RegionLoaded(Scene scene) + { + if (m_Enabled) + { + IEntityTransferModule et = m_scene.RequestModuleInterface(); + m_Helper = new SimulatorFeaturesHelper(scene, et); + + ISimulatorFeaturesModule featuresModule = m_scene.RequestModuleInterface(); + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + } + } + + public void RemoveRegion(Scene scene) + { + } + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + m_log.DebugFormat("[SPECIAL UI]: OnSimulatorFeaturesRequest in {0}", m_scene.RegionInfo.RegionName); + if (m_Helper.ShouldSend(agentID) && m_Helper.UserLevel(agentID) <= m_UserLevel) + { + OSDMap extrasMap; + OSDMap specialUI = new OSDMap(); + using (StreamReader s = new StreamReader(Path.Combine(VIEWER_SUPPORT_DIR, "panel_toolbar.xml"))) + { + if (features.ContainsKey("OpenSimExtras")) + extrasMap = (OSDMap)features["OpenSimExtras"]; + else + { + extrasMap = new OSDMap(); + features["OpenSimExtras"] = extrasMap; + } + + specialUI["toolbar"] = OSDMap.FromString(s.ReadToEnd()); + extrasMap["special-ui"] = specialUI; + } + m_log.DebugFormat("[SPECIAL UI]: Sending panel_toolbar.xml in {0}", m_scene.RegionInfo.RegionName); + + if (Directory.Exists(Path.Combine(VIEWER_SUPPORT_DIR, "Floaters"))) + { + OSDMap floaters = new OSDMap(); + uint n = 0; + foreach (String name in Directory.GetFiles(Path.Combine(VIEWER_SUPPORT_DIR, "Floaters"), "*.xml")) + { + using (StreamReader s = new StreamReader(name)) + { + string simple_name = Path.GetFileNameWithoutExtension(name); + OSDMap floater = new OSDMap(); + floaters[simple_name] = OSDMap.FromString(s.ReadToEnd()); + n++; + } + } + specialUI["floaters"] = floaters; + m_log.DebugFormat("[SPECIAL UI]: Sending {0} floaters", n); + } + } + else + m_log.DebugFormat("[SPECIAL UI]: NOT Sending panel_toolbar.xml in {0}", m_scene.RegionInfo.RegionName); + + } + + } + +} diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 1d35c54..ceb3332 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -76,6 +76,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// AutoBackupBusyCheck: True/False. Default: True. /// If True, we will only take an auto-backup if a set of conditions are met. /// These conditions are heuristics to try and avoid taking a backup when the sim is busy. + /// AutoBackupSkipAssets + /// If true, assets are not saved to the oar file. Considerably reduces impact on simulator when backing up. Intended for when assets db is backed up separately + /// AutoBackupKeepFilesForDays + /// Backup files older than this value (in days) are deleted during the current backup process, 0 will disable this and keep all backup files indefinitely /// AutoBackupScript: String. Default: not specified (disabled). /// File path to an executable script or binary to run when an automatic backup is taken. /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. @@ -111,6 +115,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private delegate T DefaultGetter(string settingName, T defaultValue); private bool m_enabled; + private ICommandConsole m_console; + private List m_Scenes = new List (); + /// /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! @@ -202,8 +209,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. /// /// - void IRegionModuleBase.AddRegion(Scene scene) + void IRegionModuleBase.AddRegion (Scene scene) { + if (!this.m_enabled) { + return; + } + lock (m_Scenes) { + m_Scenes.Add (scene); + } + m_console = MainConsole.Instance; + + m_console.Commands.AddCommand ( + "AutoBackup", false, "dobackup", + "dobackup", + "do backup.", DoBackup); } /// @@ -216,7 +235,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { return; } - + m_Scenes.Remove (scene); if (this.m_states.ContainsKey(scene)) { AutoBackupModuleState abms = this.m_states[scene]; @@ -258,6 +277,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup AutoBackupModuleState abms = this.ParseConfig(scene, false); m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + + m_states.Add(scene, abms); } /// @@ -269,6 +290,28 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #endregion + private void DoBackup (string module, string[] args) + { + if (args.Length != 2) { + MainConsole.Instance.OutputFormat ("Usage: dobackup "); + return; + } + bool found = false; + string name = args [1]; + lock (m_Scenes) { + foreach (Scene s in m_Scenes) { + string test = s.Name.ToString (); + if (test == name) { + found = true; + DoRegionBackup (s); + } + } + if (!found) { + MainConsole.Instance.OutputFormat ("No such region {0}. Nothing to backup", name); + } + } + } + /// /// Set up internal state for a given scene. Fairly complex code. /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. @@ -334,7 +377,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup double interval = this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes, config, regionConfig) * 60000.0; - if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + if (state == null && interval != this.m_defaultState.IntervalMinutes * 60000.0) { state = new AutoBackupModuleState(); } @@ -412,6 +455,32 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.BusyCheck = tmpBusyCheck; } + // Included Option To Skip Assets + bool tmpSkipAssets = ResolveBoolean("AutoBackupSkipAssets", + this.m_defaultState.SkipAssets, config, regionConfig); + if (state == null && tmpSkipAssets != this.m_defaultState.SkipAssets) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.SkipAssets = tmpSkipAssets; + } + + // How long to keep backup files in days, 0 Disables this feature + int tmpKeepFilesForDays = ResolveInt("AutoBackupKeepFilesForDays", + this.m_defaultState.KeepFilesForDays, config, regionConfig); + if (state == null && tmpKeepFilesForDays != this.m_defaultState.KeepFilesForDays) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.KeepFilesForDays = tmpKeepFilesForDays; + } + // Set file naming algorithm string stmpNamingType = ResolveString("AutoBackupNaming", this.m_defaultState.NamingType.ToString(), config, regionConfig); @@ -480,7 +549,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup catch (Exception e) { m_log.Warn( - "BAD NEWS. You won't be able to save backups to directory " + + "[AUTO BACKUP]: BAD NEWS. You won't be able to save backups to directory " + state.BackupDir + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); @@ -488,6 +557,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } + if(state == null) + return m_defaultState; + return state; } @@ -594,7 +666,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup bool heuristicsPassed = false; if (!this.m_timerMap.ContainsKey((Timer) sender)) { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + m_log.Debug("[AUTO BACKUP]: Code-up error: timerMap doesn't contain timer " + sender); } List tmap = this.m_timerMap[(Timer) sender]; @@ -630,6 +702,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } this.DoRegionBackup(scene); } + + // Remove Old Backups + this.RemoveOldFiles(state); } } } @@ -640,7 +715,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// private void DoRegionBackup(IScene scene) { - if (scene.RegionStatus != RegionStatus.Up) + if (!scene.Ready) { // We won't backup a region that isn't operating normally. m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + @@ -662,7 +737,41 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_pendingSaves.Add(guid, scene); state.LiveRequests.Add(guid, savePath); ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); - iram.ArchiveRegion(savePath, guid, null); + + m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName); + + // Must pass options, even if dictionary is empty! + Dictionary options = new Dictionary(); + + if (state.SkipAssets) + options["noassets"] = true; + + iram.ArchiveRegion(savePath, guid, options); + } + + // For the given state, remove backup files older than the states KeepFilesForDays property + private void RemoveOldFiles(AutoBackupModuleState state) + { + // 0 Means Disabled, Keep Files Indefinitely + if (state.KeepFilesForDays > 0) + { + string[] files = Directory.GetFiles(state.BackupDir, "*.oar"); + DateTime CuttOffDate = DateTime.Now.AddDays(0 - state.KeepFilesForDays); + + foreach (string file in files) + { + try + { + FileInfo fi = new FileInfo(file); + if (fi.CreationTime < CuttOffDate) + fi.Delete(); + } + catch (Exception Ex) + { + m_log.Error("[AUTO BACKUP]: Error deleting old backup file '" + file + "': " + Ex.Message); + } + } + } } /// diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index f9e118b..ce7c368 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -45,9 +45,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.Enabled = false; this.BackupDir = "."; this.BusyCheck = true; + this.SkipAssets = false; this.Timer = null; this.NamingType = NamingType.Time; this.Script = null; + this.KeepFilesForDays = 0; } public Dictionary LiveRequests @@ -91,6 +93,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup set; } + public bool SkipAssets + { + get; + set; + } + public string Script { get; @@ -109,6 +117,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup set; } + public int KeepFilesForDays + { + get; + set; + } + public new string ToString() { string retval = ""; diff --git a/OpenSim/Region/OptionalModules/World/MoneyModule/SampleMoneyModule.cs b/OpenSim/Region/OptionalModules/World/MoneyModule/SampleMoneyModule.cs index a999b7f..4cd5676 100644 --- a/OpenSim/Region/OptionalModules/World/MoneyModule/SampleMoneyModule.cs +++ b/OpenSim/Region/OptionalModules/World/MoneyModule/SampleMoneyModule.cs @@ -103,7 +103,9 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule #region IMoneyModule Members +#pragma warning disable 0067 public event ObjectPaid OnObjectPaid; +#pragma warning restore 0067 public int UploadCharge { @@ -191,9 +193,14 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule // Please do not refactor these to be just one method // Existing implementations need the distinction // - public void ApplyCharge(UUID agentID, int amount, string text) + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type, string extraData) { } + + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + } + public void ApplyUploadCharge(UUID agentID, int amount, string text) { } @@ -322,7 +329,7 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule client.SendAlertMessage(e.Message + " "); } - client.SendMoneyBalance(TransactionID, true, new byte[0], returnfunds); + client.SendMoneyBalance(TransactionID, true, new byte[0], returnfunds, 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); } else { @@ -385,12 +392,12 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule { if (sender != null) { - sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(senderID)); + sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(senderID), 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); } if (receiver != null) { - receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(receiverID)); + receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(receiverID), 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); } } } @@ -555,7 +562,7 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule /// private int GetFundsForAgentID(UUID AgentID) { - int returnfunds = 75004; // Set it to the OpenSim version, plus the IG build number. Muahahaha; + int returnfunds = 0; return returnfunds; } @@ -688,19 +695,14 @@ namespace OpenSim.Region.OptionalModules.World.MoneyModule /// Event called Economy Data Request handler. /// /// - public void EconomyDataRequestHandler(UUID agentId) + public void EconomyDataRequestHandler(IClientAPI user) { - IClientAPI user = LocateClientObject(agentId); + Scene s = (Scene)user.Scene; - if (user != null) - { - Scene s = LocateSceneClientIn(user.AgentId); - - user.SendEconomyData(EnergyEfficiency, s.RegionInfo.ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, - PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, - PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, - TeleportMinPrice, TeleportPriceExponent); - } + user.SendEconomyData(EnergyEfficiency, s.RegionInfo.ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, + PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, + PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, + TeleportMinPrice, TeleportPriceExponent); } private void ValidateLandBuy(Object osender, EventManager.LandBuyArgs e) diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs index 5ea2bcd..fb644b7 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs @@ -44,10 +44,24 @@ namespace OpenSim.Region.OptionalModules.World.NPC { public bool SenseAsAgent { get; set; } + public delegate void ChatToNPC( + string message, byte type, Vector3 fromPos, string fromName, + UUID fromAgentID, UUID ownerID, byte source, byte audible); + + /// + /// Fired when the NPC receives a chat message. + /// + public event ChatToNPC OnChatToNPC; + + /// + /// Fired when the NPC receives an instant message. + /// + public event Action OnInstantMessageToNPC; + private readonly string m_firstname; private readonly string m_lastname; private readonly Vector3 m_startPos; - private readonly UUID m_uuid = UUID.Random(); + private readonly UUID m_uuid; private readonly Scene m_scene; private readonly UUID m_ownerID; @@ -57,6 +71,19 @@ namespace OpenSim.Region.OptionalModules.World.NPC m_firstname = firstname; m_lastname = lastname; m_startPos = position; + m_uuid = UUID.Random(); + m_scene = scene; + m_ownerID = ownerID; + SenseAsAgent = senseAsAgent; + } + + public NPCAvatar( + string firstname, string lastname, UUID agentID, Vector3 position, UUID ownerID, bool senseAsAgent, Scene scene) + { + m_firstname = firstname; + m_lastname = lastname; + m_startPos = position; + m_uuid = agentID; m_scene = scene; m_ownerID = ownerID; SenseAsAgent = senseAsAgent; @@ -258,6 +285,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC public event Action OnCompleteMovementToRegion; public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; + public event UpdateAgent OnAgentCameraUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; public event AvatarPickerRequest OnAvatarPickerRequest; @@ -391,6 +419,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC public event EstateTeleportAllUsersHomeRequest OnEstateTeleportAllUsersHomeRequest; public event EstateChangeInfo OnEstateChangeInfo; public event EstateManageTelehub OnEstateManageTelehub; + public event CachedTextureRequest OnCachedTextureRequest; public event ScriptReset OnScriptReset; public event GetScriptRunning OnGetScriptRunning; public event SetScriptRunning OnSetScriptRunning; @@ -569,6 +598,11 @@ namespace OpenSim.Region.OptionalModules.World.NPC { } + public void SendCachedTextureResponse(ISceneEntity avatar, int serial, List cachedTextures) + { + + } + public virtual void Kick(string message) { } @@ -586,7 +620,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC } - public virtual void SendKillObject(ulong regionHandle, List localID) + public virtual void SendKillObject(List localID) { } @@ -607,25 +641,26 @@ namespace OpenSim.Region.OptionalModules.World.NPC string message, byte type, Vector3 fromPos, string fromName, UUID fromAgentID, UUID ownerID, byte source, byte audible) { - } + ChatToNPC ctn = OnChatToNPC; - public virtual void SendChatMessage( - byte[] message, byte type, Vector3 fromPos, string fromName, - UUID fromAgentID, UUID ownerID, byte source, byte audible) - { + if (ctn != null) + ctn(message, type, fromPos, fromName, fromAgentID, ownerID, source, audible); } public void SendInstantMessage(GridInstantMessage im) { - + Action oimtn = OnInstantMessageToNPC; + + if (oimtn != null) + oimtn(im); } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { } - public void SendGenericMessage(string method, List message) + public void SendGenericMessage(string method, UUID invoice, List message) { } @@ -688,7 +723,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC { } - public virtual void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance) + public virtual void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance, int transactionType, UUID sourceID, bool sourceIsGroup, UUID destID, bool destIsGroup, int amount, string item) { } @@ -860,11 +895,6 @@ namespace OpenSim.Region.OptionalModules.World.NPC { } - public bool AddMoney(int debit) - { - return false; - } - public void SendSunPos(Vector3 sunPos, Vector3 sunVel, ulong time, uint dlen, uint ylen, float phase) { } @@ -1227,12 +1257,17 @@ namespace OpenSim.Region.OptionalModules.World.NPC { } - public void StopFlying(ISceneEntity presence) + public void SendAgentTerseUpdate(ISceneEntity presence) { } public void SendPlacesReply(UUID queryID, UUID transactionID, PlacesReplyData[] data) { } + + public void SendPartPhysicsProprieties(ISceneEntity entity) + { + } + } } diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs index d6cf1ab..9232db9 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs @@ -116,7 +116,8 @@ namespace OpenSim.Region.OptionalModules.World.NPC return false; // Delete existing npc attachments - scene.AttachmentsModule.DeleteAttachmentsFromScene(npc, false); + if(scene.AttachmentsModule != null) + scene.AttachmentsModule.DeleteAttachmentsFromScene(npc, false); // XXX: We can't just use IAvatarFactoryModule.SetAppearance() yet // since it doesn't transfer attachments @@ -125,7 +126,8 @@ namespace OpenSim.Region.OptionalModules.World.NPC npc.Appearance = npcAppearance; // Rez needed npc attachments - scene.AttachmentsModule.RezAttachments(npc); + if (scene.AttachmentsModule != null) + scene.AttachmentsModule.RezAttachments(npc); IAvatarFactoryModule module = scene.RequestModuleInterface(); @@ -138,15 +140,37 @@ namespace OpenSim.Region.OptionalModules.World.NPC Vector3 position, UUID owner, bool senseAsAgent, Scene scene, AvatarAppearance appearance) { - NPCAvatar npcAvatar = new NPCAvatar(firstname, lastname, position, - owner, senseAsAgent, scene); + return CreateNPC(firstname, lastname, position, UUID.Zero, owner, senseAsAgent, scene, appearance); + } + + public UUID CreateNPC(string firstname, string lastname, + Vector3 position, UUID agentID, UUID owner, bool senseAsAgent, Scene scene, + AvatarAppearance appearance) + { + NPCAvatar npcAvatar = null; + + try + { + if (agentID == UUID.Zero) + npcAvatar = new NPCAvatar(firstname, lastname, position, + owner, senseAsAgent, scene); + else + npcAvatar = new NPCAvatar(firstname, lastname, agentID, position, + owner, senseAsAgent, scene); + } + catch (Exception e) + { + m_log.Info("[NPC MODULE]: exception creating NPC avatar: " + e.ToString()); + return UUID.Zero; + } + npcAvatar.CircuitCode = (uint)Util.RandomClass.Next(0, int.MaxValue); m_log.DebugFormat( - "[NPC MODULE]: Creating NPC {0} {1} {2}, owner={3}, senseAsAgent={4} at {5} in {6}", - firstname, lastname, npcAvatar.AgentId, owner, - senseAsAgent, position, scene.RegionInfo.RegionName); + "[NPC MODULE]: Creating NPC {0} {1} {2}, owner={3}, senseAsAgent={4} at {5} in {6}", + firstname, lastname, npcAvatar.AgentId, owner, + senseAsAgent, position, scene.RegionInfo.RegionName); AgentCircuitData acd = new AgentCircuitData(); acd.AgentID = npcAvatar.AgentId; @@ -154,8 +178,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC acd.lastname = lastname; acd.ServiceURLs = new Dictionary(); - AvatarAppearance npcAppearance = new AvatarAppearance(appearance, - true); + AvatarAppearance npcAppearance = new AvatarAppearance(appearance, true); acd.Appearance = npcAppearance; /* @@ -173,7 +196,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC { scene.AuthenticateHandler.AddNewCircuit(npcAvatar.CircuitCode, acd); - scene.AddNewClient(npcAvatar, PresenceType.Npc); + scene.AddNewAgent(npcAvatar, PresenceType.Npc); ScenePresence sp; if (scene.TryGetScenePresence(npcAvatar.AgentId, out sp)) @@ -186,16 +209,16 @@ namespace OpenSim.Region.OptionalModules.World.NPC sp.CompleteMovement(npcAvatar, false); m_avatars.Add(npcAvatar.AgentId, npcAvatar); - m_log.DebugFormat("[NPC MODULE]: Created NPC {0} {1}", - npcAvatar.AgentId, sp.Name); + m_log.DebugFormat("[NPC MODULE]: Created NPC {0} {1}", npcAvatar.AgentId, sp.Name); return npcAvatar.AgentId; } else { m_log.WarnFormat( - "[NPC MODULE]: Could not find scene presence for NPC {0} {1}", - sp.Name, sp.UUID); + "[NPC MODULE]: Could not find scene presence for NPC {0} {1}", + sp.Name, sp.UUID); + return UUID.Zero; } } @@ -211,12 +234,13 @@ namespace OpenSim.Region.OptionalModules.World.NPC ScenePresence sp; if (scene.TryGetScenePresence(agentID, out sp)) { - /* - m_log.DebugFormat( - "[NPC MODULE]: Moving {0} to {1} in {2}, noFly {3}, landAtTarget {4}", - sp.Name, pos, scene.RegionInfo.RegionName, - noFly, landAtTarget); - */ + if (sp.IsSatOnObject || sp.SitGround) + return false; + +// m_log.DebugFormat( +// "[NPC MODULE]: Moving {0} to {1} in {2}, noFly {3}, landAtTarget {4}", +// sp.Name, pos, scene.RegionInfo.RegionName, +// noFly, landAtTarget); sp.MoveToTarget(pos, noFly, landAtTarget); sp.SetAlwaysRun = running; @@ -293,9 +317,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC ScenePresence sp; if (scene.TryGetScenePresence(agentID, out sp)) { - sp.HandleAgentRequestSit(m_avatars[agentID], agentID, - partID, Vector3.Zero); - //sp.HandleAgentSit(m_avatars[agentID], agentID); + sp.HandleAgentRequestSit(m_avatars[agentID], agentID, partID, Vector3.Zero); return true; } @@ -376,23 +398,30 @@ namespace OpenSim.Region.OptionalModules.World.NPC public bool DeleteNPC(UUID agentID, Scene scene) { + bool doRemove = false; + NPCAvatar av; lock (m_avatars) { - NPCAvatar av; if (m_avatars.TryGetValue(agentID, out av)) { /* m_log.DebugFormat("[NPC MODULE]: Found {0} {1} to remove", agentID, av.Name); */ - scene.RemoveClient(agentID, false); + doRemove = true; + } + } + + if (doRemove) + { + scene.CloseAgent(agentID, false); + lock (m_avatars) + { m_avatars.Remove(agentID); - /* - m_log.DebugFormat("[NPC MODULE]: Removed NPC {0} {1}", - agentID, av.Name); - */ - return true; } + m_log.DebugFormat("[NPC MODULE]: Removed NPC {0} {1}", + agentID, av.Name); + return true; } /* m_log.DebugFormat("[NPC MODULE]: Could not find {0} to remove", @@ -416,13 +445,20 @@ namespace OpenSim.Region.OptionalModules.World.NPC /// /// Check if the caller has permission to manipulate the given NPC. /// + /// + /// A caller has permission if + /// * The caller UUID given is UUID.Zero. + /// * The avatar is unowned (owner is UUID.Zero). + /// * The avatar is owned and the owner and callerID match. + /// * The avatar is owned and the callerID matches its agentID. + /// /// /// /// true if they do, false if they don't. private bool CheckPermissions(NPCAvatar av, UUID callerID) { return callerID == UUID.Zero || av.OwnerID == UUID.Zero || - av.OwnerID == callerID; + av.OwnerID == callerID || av.AgentId == callerID; } } } diff --git a/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs b/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs index bf23040..77dfd40 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs @@ -33,7 +33,6 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.CoreModules.Avatar.Attachments; using OpenSim.Region.CoreModules.Avatar.AvatarFactory; using OpenSim.Region.CoreModules.Framework.InventoryAccess; @@ -43,7 +42,6 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.AvatarService; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.OptionalModules.World.NPC.Tests { @@ -71,11 +69,13 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; } - [SetUp] - public void Init() + public void SetUpScene() { - base.SetUp(); + SetUpScene(256, 256); + } + public void SetUpScene(uint sizeX, uint sizeY) + { IConfigSource config = new IniConfigSource(); config.AddConfig("NPC"); config.Configs["NPC"].Set("Enabled", "true"); @@ -87,7 +87,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests m_attMod = new AttachmentsModule(); m_npcMod = new NPCModule(); - m_scene = new SceneHelpers().SetupScene(); + m_scene = new SceneHelpers().SetupScene("test scene", UUID.Random(), 1000, 1000, sizeX, sizeY, config); SceneHelpers.SetupSceneModules(m_scene, config, m_afMod, m_umMod, m_attMod, m_npcMod, new BasicInventoryAccessModule()); } @@ -97,6 +97,8 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); + SetUpScene(); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); // ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); @@ -110,7 +112,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests // ScenePresence.SendInitialData() to reset our entire appearance. m_scene.AssetService.Store(AssetHelpers.CreateNotecardAsset(originalFace8TextureId)); - m_afMod.SetAppearance(sp, originalTe, null); + m_afMod.SetAppearance(sp, originalTe, null, null); UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); @@ -133,6 +135,8 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); + SetUpScene(); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); // ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); @@ -155,7 +159,9 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests public void TestCreateWithAttachments() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); + + SetUpScene(); UUID userId = TestHelpers.ParseTail(0x1); UserAccountHelpers.CreateUserWithInventory(m_scene, userId); @@ -191,11 +197,66 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests } [Test] + public void TestCreateWithMultiAttachments() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(); +// m_attMod.DebugLevel = 1; + + UUID userId = TestHelpers.ParseTail(0x1); + UserAccountHelpers.CreateUserWithInventory(m_scene, userId); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + InventoryItemBase att1Item + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "att1", TestHelpers.ParseTail(0x2), TestHelpers.ParseTail(0x3), sp.UUID, InventoryType.Object); + InventoryItemBase att2Item + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "att2", TestHelpers.ParseTail(0x12), TestHelpers.ParseTail(0x13), sp.UUID, InventoryType.Object); + + m_attMod.RezSingleAttachmentFromInventory(sp, att1Item.ID, (uint)AttachmentPoint.Chest); + m_attMod.RezSingleAttachmentFromInventory(sp, att2Item.ID, (uint)AttachmentPoint.Chest | 0x80); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + // Check scene presence status + Assert.That(npc.HasAttachments(), Is.True); + List attachments = npc.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(2)); + + // Just for now, we won't test the name since this is (wrongly) the asset part name rather than the item + // name. TODO: Do need to fix ultimately since the item may be renamed before being passed on to an NPC. +// Assert.That(attSo.Name, Is.EqualTo(attName)); + + TestAttachedObject(attachments[0], AttachmentPoint.Chest, npc.UUID); + TestAttachedObject(attachments[1], AttachmentPoint.Chest, npc.UUID); + + // Attached objects on the same point must have different FromItemIDs to be shown to other avatars, at least + // on Singularity 1.8.5. Otherwise, only one (the first ObjectUpdate sent) appears. + Assert.AreNotEqual(attachments[0].FromItemID, attachments[1].FromItemID); + } + + private void TestAttachedObject(SceneObjectGroup attSo, AttachmentPoint attPoint, UUID ownerId) + { + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)attPoint)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.OwnerID, Is.EqualTo(ownerId)); + } + + [Test] public void TestLoadAppearance() { TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); + SetUpScene(); + UUID userId = TestHelpers.ParseTail(0x1); UserAccountHelpers.CreateUserWithInventory(m_scene, userId); ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); @@ -237,7 +298,9 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests public void TestMove() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); + + SetUpScene(); ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); // ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); @@ -303,11 +366,64 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests } [Test] + public void TestMoveInVarRegion() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(512, 512); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + Vector3 startPos = new Vector3(128, 246, 30); + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + // For now, we'll make the scene presence fly to simplify this test, but this needs to change. + npc.Flying = true; + + m_scene.Update(1); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + Vector3 targetPos = startPos + new Vector3(0, 20, 0); + m_npcMod.MoveToTarget(npc.UUID, m_scene, targetPos, false, false, false); + + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + //Assert.That(npc.Rotation, Is.EqualTo(new Quaternion(0, 0, 0.7071068f, 0.7071068f))); + Assert.That( + npc.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0.7071068f, 0.7071068f), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(npc.AbsolutePosition.X, Is.EqualTo(startPos.X)); + Assert.That(npc.AbsolutePosition.Y, Is.GreaterThan(startPos.Y)); + Assert.That(npc.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + Assert.That(npc.AbsolutePosition.Z, Is.LessThan(targetPos.X)); + + for (int i = 0; i < 20; i++) + { + m_scene.Update(1); +// Console.WriteLine("pos: {0}", npc.AbsolutePosition); + } + + double distanceToTarget = Util.GetDistanceTo(npc.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "NPC not within 1 unit of target position on first move"); + Assert.That(npc.AbsolutePosition, Is.EqualTo(targetPos)); + Assert.That(npc.AgentControlFlags, Is.EqualTo((uint)AgentManager.ControlFlags.NONE)); + } + + [Test] public void TestSitAndStandWithSitTarget() { TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); + SetUpScene(); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); Vector3 startPos = new Vector3(128, 128, 30); @@ -321,9 +437,9 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests Assert.That(part.SitTargetAvatar, Is.EqualTo(npcId)); Assert.That(npc.ParentID, Is.EqualTo(part.LocalId)); - Assert.That( - npc.AbsolutePosition, - Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition + ScenePresence.SIT_TARGET_ADJUSTMENT)); +// Assert.That( +// npc.AbsolutePosition, +// Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition + ScenePresence.SIT_TARGET_ADJUSTMENT)); m_npcMod.Stand(npc.UUID, m_scene); @@ -335,7 +451,9 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests public void TestSitAndStandWithNoSitTarget() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); + + SetUpScene(); ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); @@ -353,13 +471,11 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); Assert.That(npc.ParentID, Is.EqualTo(part.LocalId)); - // FIXME: This is different for live avatars - z position is adjusted. This is half the height of the - // default avatar. - // Curiously, Vector3.ToString() will not display the last two places of the float. For example, - // printing out npc.AbsolutePosition will give <0, 0, 0.8454993> not <0, 0, 0.845499337> + // We should really be using the NPC size but this would mean preserving the physics actor since it is + // removed on sit. Assert.That( npc.AbsolutePosition, - Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, 0.845499337f))); + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, sp.PhysicsActor.Size.Z / 2))); m_npcMod.Stand(npc.UUID, m_scene); @@ -367,4 +483,4 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests Assert.That(npc.ParentID, Is.EqualTo(0)); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/World/SceneCommands/SceneCommandsModule.cs b/OpenSim/Region/OptionalModules/World/SceneCommands/SceneCommandsModule.cs index 12169ab..0927c4f 100644 --- a/OpenSim/Region/OptionalModules/World/SceneCommands/SceneCommandsModule.cs +++ b/OpenSim/Region/OptionalModules/World/SceneCommands/SceneCommandsModule.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using System.Threading; using log4net; using Mono.Addins; using Nini.Config; @@ -48,7 +49,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SceneCommandsModule")] public class SceneCommandsModule : ISceneCommandsModule, INonSharedRegionModule { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Scene m_scene; @@ -93,28 +94,44 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments "Debug", this, "debug scene get", "debug scene get", "List current scene options.", - "If active is false then main scene update and maintenance loops are suspended.\n" - + "If animations is true then extra animations debug information is logged.\n" - + "If collisions is false then collisions with other objects are turned off.\n" - + "If pbackup is false then periodic scene backup is turned off.\n" - + "If physics is false then all physics objects are non-physical.\n" - + "If scripting is false then no scripting operations happen.\n" - + "If teleport is true then some extra teleport debug information is logged.\n" - + "If updates is true then any frame which exceeds double the maximum desired frame time is logged.", + "active - if false then main scene update and maintenance loops are suspended.\n" + + "animations - if true then extra animations debug information is logged.\n" + + "appear-refresh - if true then appearance is resent to other avatars every 60 seconds.\n" + + "child-repri - how far an avatar must move in meters before we update the position of its child agents in neighbouring regions.\n" + + "client-pos-upd - the tolerance before clients are updated with new rotation information for an avatar.\n" + + "client-rot-upd - the tolerance before clients are updated with new rotation information for an avatar.\n" + + "client-vel-upd - the tolerance before clients are updated with new velocity information for an avatar.\n" + + "root-upd-per - if greater than 1, terse updates are only sent to root agents other than the originator on every n updates.\n" + + "child-upd-per - if greater than 1, terse updates are only sent to child agents on every n updates.\n" + + "collisions - if false then collisions with other objects are turned off.\n" + + "pbackup - if false then periodic scene backup is turned off.\n" + + "physics - if false then all physics objects are non-physical.\n" + + "scripting - if false then no scripting operations happen.\n" + + "teleport - if true then some extra teleport debug information is logged.\n" + + "update-on-timer - If true then the scene is updated via a timer. If false then a thread with sleep is used.\n" + + "updates - if true then any frame which exceeds double the maximum desired frame time is logged.", HandleDebugSceneGetCommand); scene.AddCommand( "Debug", this, "debug scene set", - "debug scene set active|collisions|pbackup|physics|scripting|teleport|updates true|false", + "debug scene set ", "Turn on scene debugging options.", - "If active is false then main scene update and maintenance loops are suspended.\n" - + "If animations is true then extra animations debug information is logged.\n" - + "If collisions is false then collisions with other objects are turned off.\n" - + "If pbackup is false then periodic scene backup is turned off.\n" - + "If physics is false then all physics objects are non-physical.\n" - + "If scripting is false then no scripting operations happen.\n" - + "If teleport is true then some extra teleport debug information is logged.\n" - + "If updates is true then any frame which exceeds double the maximum desired frame time is logged.", + "active - if false then main scene update and maintenance loops are suspended.\n" + + "animations - if true then extra animations debug information is logged.\n" + + "appear-refresh - if true then appearance is resent to other avatars every 60 seconds.\n" + + "child-repri - how far an avatar must move in meters before we update the position of its child agents in neighbouring regions.\n" + + "client-pos-upd - the tolerance before clients are updated with new rotation information for an avatar.\n" + + "client-rot-upd - the tolerance before clients are updated with new rotation information for an avatar.\n" + + "client-vel-upd - the tolerance before clients are updated with new velocity information for an avatar.\n" + + "root-upd-per - if greater than 1, terse updates are only sent to root agents other than the originator on every n updates.\n" + + "child-upd-per - if greater than 1, terse updates are only sent to child agents on every n updates.\n" + + "collisions - if false then collisions with other objects are turned off.\n" + + "pbackup - if false then periodic scene backup is turned off.\n" + + "physics - if false then all physics objects are non-physical.\n" + + "scripting - if false then no scripting operations happen.\n" + + "teleport - if true then some extra teleport debug information is logged.\n" + + "update-on-timer - If true then the scene is updated via a timer. If false then a thread with sleep is used.\n" + + "updates - if true then any frame which exceeds double the maximum desired frame time is logged.", HandleDebugSceneSetCommand); } @@ -138,10 +155,18 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments ConsoleDisplayList cdl = new ConsoleDisplayList(); cdl.AddRow("active", m_scene.Active); cdl.AddRow("animations", m_scene.DebugAnimations); + cdl.AddRow("appear-refresh", m_scene.SendPeriodicAppearanceUpdates); + cdl.AddRow("child-repri", m_scene.ChildReprioritizationDistance); + cdl.AddRow("client-pos-upd", m_scene.RootPositionUpdateTolerance); + cdl.AddRow("client-rot-upd", m_scene.RootRotationUpdateTolerance); + cdl.AddRow("client-vel-upd", m_scene.RootVelocityUpdateTolerance); + cdl.AddRow("root-upd-per", m_scene.RootTerseUpdatePeriod); + cdl.AddRow("child-upd-per", m_scene.ChildTerseUpdatePeriod); cdl.AddRow("pbackup", m_scene.PeriodicBackup); cdl.AddRow("physics", m_scene.PhysicsEnabled); cdl.AddRow("scripting", m_scene.ScriptsEnabled); cdl.AddRow("teleport", m_scene.DebugTeleporting); + cdl.AddRow("update-on-timer", m_scene.UpdateOnTimer); cdl.AddRow("updates", m_scene.DebugUpdates); MainConsole.Instance.OutputFormat("Scene {0} options:", m_scene.Name); @@ -163,8 +188,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments } else { - MainConsole.Instance.Output( - "Usage: debug scene set active|collisions|pbackup|physics|scripting|teleport|updates true|false"); + MainConsole.Instance.Output("Usage: debug scene set "); } } @@ -186,6 +210,69 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments m_scene.DebugAnimations = active; } + if (options.ContainsKey("appear-refresh")) + { + bool newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleBool(MainConsole.Instance, options["appear-refresh"], out newValue)) + m_scene.SendPeriodicAppearanceUpdates = newValue; + } + + if (options.ContainsKey("child-repri")) + { + double newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleDouble(MainConsole.Instance, options["child-repri"], out newValue)) + m_scene.ChildReprioritizationDistance = newValue; + } + + if (options.ContainsKey("client-pos-upd")) + { + float newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleFloat(MainConsole.Instance, options["client-pos-upd"], out newValue)) + m_scene.RootPositionUpdateTolerance = newValue; + } + + if (options.ContainsKey("client-rot-upd")) + { + float newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleFloat(MainConsole.Instance, options["client-rot-upd"], out newValue)) + m_scene.RootRotationUpdateTolerance = newValue; + } + + if (options.ContainsKey("client-vel-upd")) + { + float newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleFloat(MainConsole.Instance, options["client-vel-upd"], out newValue)) + m_scene.RootVelocityUpdateTolerance = newValue; + } + + if (options.ContainsKey("root-upd-per")) + { + int newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, options["root-upd-per"], out newValue)) + m_scene.RootTerseUpdatePeriod = newValue; + } + + if (options.ContainsKey("child-upd-per")) + { + int newValue; + + // FIXME: This can only come from the console at the moment but might not always be true. + if (ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, options["child-upd-per"], out newValue)) + m_scene.ChildTerseUpdatePeriod = newValue; + } + if (options.ContainsKey("pbackup")) { bool active; @@ -221,6 +308,21 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments m_scene.DebugTeleporting = enableTeleportDebugging; } + if (options.ContainsKey("update-on-timer")) + { + bool enableUpdateOnTimer; + if (bool.TryParse(options["update-on-timer"], out enableUpdateOnTimer)) + { + m_scene.UpdateOnTimer = enableUpdateOnTimer; + m_scene.Active = false; + + while (m_scene.IsRunning) + Thread.Sleep(20); + + m_scene.Active = true; + } + } + if (options.ContainsKey("updates")) { bool enableUpdateDebugging; diff --git a/OpenSim/Region/OptionalModules/World/TreePopulator/TreePopulatorModule.cs b/OpenSim/Region/OptionalModules/World/TreePopulator/TreePopulatorModule.cs index 8144870..e4a3382 100644 --- a/OpenSim/Region/OptionalModules/World/TreePopulator/TreePopulatorModule.cs +++ b/OpenSim/Region/OptionalModules/World/TreePopulator/TreePopulatorModule.cs @@ -748,8 +748,8 @@ namespace OpenSim.Region.OptionalModules.World.TreePopulator position.X = s_tree.AbsolutePosition.X + (float)randX; position.Y = s_tree.AbsolutePosition.Y + (float)randY; - if (position.X <= ((int)Constants.RegionSize - 1) && position.X >= 0 && - position.Y <= ((int)Constants.RegionSize - 1) && position.Y >= 0 && + if (position.X <= (m_scene.RegionInfo.RegionSizeX - 1) && position.X >= 0 && + position.Y <= (m_scene.RegionInfo.RegionSizeY - 1) && position.Y >= 0 && Util.GetDistanceTo(position, copse.m_seed_point) <= copse.m_range) { UUID uuid = m_scene.RegionInfo.EstateSettings.EstateOwner; diff --git a/OpenSim/Region/OptionalModules/World/WorldView/WorldViewRequestHandler.cs b/OpenSim/Region/OptionalModules/World/WorldView/WorldViewRequestHandler.cs index 550b5d4..8720cc7 100644 --- a/OpenSim/Region/OptionalModules/World/WorldView/WorldViewRequestHandler.cs +++ b/OpenSim/Region/OptionalModules/World/WorldView/WorldViewRequestHandler.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.OptionalModules.World.WorldView m_WorldViewModule = fmodule; } - public override byte[] Handle(string path, Stream requestData, + protected override byte[] ProcessRequest(string path, Stream requestData, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { httpResponse.ContentType = "image/jpeg"; -- cgit v1.1