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. --- .../Framework/Caps/CapabilitiesModule.cs | 419 +++- .../Framework/DynamicAttributes/DAExampleModule.cs | 124 ++ .../Framework/DynamicAttributes/DOExampleModule.cs | 139 ++ .../EntityTransfer/EntityTransferModule.cs | 2058 +++++++++++++------- .../EntityTransfer/EntityTransferStateMachine.cs | 148 +- .../EntityTransfer/HGEntityTransferModule.cs | 290 ++- .../Framework/InventoryAccess/HGAssetMapper.cs | 224 ++- .../InventoryAccess/HGInventoryAccessModule.cs | 198 +- .../InventoryAccess/InventoryAccessModule.cs | 359 ++-- .../InventoryAccess/Tests/HGAssetMapperTests.cs | 146 ++ .../Tests/InventoryAccessModuleTests.cs | 7 +- .../CoreModules/Framework/Library/LibraryModule.cs | 6 +- .../Framework/Library/LocalInventoryService.cs | 48 +- .../Framework/Monitoring/MonitorModule.cs | 46 +- .../Framework/Search/BasicSearchModule.cs | 199 ++ .../ServiceThrottle/ServiceThrottleModule.cs | 256 +++ .../Statistics/Logging/BinaryLoggingModule.cs | 2 +- .../Framework/Statistics/Logging/LogWriter.cs | 170 -- .../UserManagement/HGUserManagementModule.cs | 19 +- .../Tests/HGUserManagementModuleTests.cs | 75 + .../UserManagement/UserManagementModule.cs | 619 ++++-- 21 files changed, 4043 insertions(+), 1509 deletions(-) create mode 100644 OpenSim/Region/CoreModules/Framework/DynamicAttributes/DAExampleModule.cs create mode 100644 OpenSim/Region/CoreModules/Framework/DynamicAttributes/DOExampleModule.cs create mode 100644 OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs create mode 100644 OpenSim/Region/CoreModules/Framework/Search/BasicSearchModule.cs create mode 100644 OpenSim/Region/CoreModules/Framework/ServiceThrottle/ServiceThrottleModule.cs delete mode 100755 OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs create mode 100644 OpenSim/Region/CoreModules/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs (limited to 'OpenSim/Region/CoreModules/Framework') diff --git a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs index 8329af0..817ef85 100644 --- a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text; using log4net; @@ -37,6 +38,7 @@ using OpenMetaverse; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using Caps=OpenSim.Framework.Capabilities.Caps; @@ -57,8 +59,9 @@ namespace OpenSim.Region.CoreModules.Framework /// protected Dictionary m_capsObjects = new Dictionary(); - protected Dictionary capsPaths = new Dictionary(); - protected Dictionary> childrenSeeds + protected Dictionary m_capsPaths = new Dictionary(); + + protected Dictionary> m_childrenSeeds = new Dictionary>(); public void Initialise(IConfigSource source) @@ -70,9 +73,24 @@ namespace OpenSim.Region.CoreModules.Framework m_scene = scene; m_scene.RegisterModuleInterface(this); - MainConsole.Instance.Commands.AddCommand("Comms", false, "show caps", - "show caps", - "Shows all registered capabilities for users", HandleShowCapsCommand); + MainConsole.Instance.Commands.AddCommand( + "Comms", false, "show caps list", + "show caps list", + "Shows list of registered capabilities for users.", HandleShowCapsListCommand); + + MainConsole.Instance.Commands.AddCommand( + "Comms", false, "show caps stats by user", + "show caps stats by user [ ]", + "Shows statistics on capabilities use by user.", + "If a user name is given, then prints a detailed breakdown of caps use ordered by number of requests received.", + HandleShowCapsStatsByUserCommand); + + MainConsole.Instance.Commands.AddCommand( + "Comms", false, "show caps stats by cap", + "show caps stats by cap []", + "Shows statistics on capabilities use by capability.", + "If a capability name is given, then prints a detailed breakdown of use by each user.", + HandleShowCapsStatsByCapCommand); } public void RegionLoaded(Scene scene) @@ -105,35 +123,43 @@ namespace OpenSim.Region.CoreModules.Framework if (m_scene.RegionInfo.EstateSettings.IsBanned(agentId)) return; + Caps caps; String capsObjectPath = GetCapsPath(agentId); - if (m_capsObjects.ContainsKey(agentId)) + lock (m_capsObjects) { - Caps oldCaps = m_capsObjects[agentId]; - - m_log.DebugFormat( - "[CAPS]: Recreating caps for agent {0}. Old caps path {1}, new caps path {2}. ", - agentId, oldCaps.CapsObjectPath, capsObjectPath); - // This should not happen. The caller code is confused. We need to fix that. - // CAPs can never be reregistered, or the client will be confused. - // Hence this return here. - //return; - } + if (m_capsObjects.ContainsKey(agentId)) + { + Caps oldCaps = m_capsObjects[agentId]; + + //m_log.WarnFormat( + // "[CAPS]: Recreating caps for agent {0} in region {1}. Old caps path {2}, new caps path {3}. ", + // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath); + } - Caps caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, - (MainServer.Instance == null) ? 0: MainServer.Instance.Port, - capsObjectPath, agentId, m_scene.RegionInfo.RegionName); +// m_log.DebugFormat( +// "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}", +// agentId, m_scene.RegionInfo.RegionName, capsObjectPath); - m_capsObjects[agentId] = caps; + caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, + (MainServer.Instance == null) ? 0: MainServer.Instance.Port, + capsObjectPath, agentId, m_scene.RegionInfo.RegionName); + + m_capsObjects[agentId] = caps; + } m_scene.EventManager.TriggerOnRegisterCaps(agentId, caps); } public void RemoveCaps(UUID agentId) { - if (childrenSeeds.ContainsKey(agentId)) + m_log.DebugFormat("[CAPS]: Remove caps for agent {0} in region {1}", agentId, m_scene.RegionInfo.RegionName); + lock (m_childrenSeeds) { - childrenSeeds.Remove(agentId); + if (m_childrenSeeds.ContainsKey(agentId)) + { + m_childrenSeeds.Remove(agentId); + } } lock (m_capsObjects) @@ -168,16 +194,22 @@ namespace OpenSim.Region.CoreModules.Framework public void SetAgentCapsSeeds(AgentCircuitData agent) { - capsPaths[agent.AgentID] = agent.CapsPath; - childrenSeeds[agent.AgentID] - = ((agent.ChildrenCapSeeds == null) ? new Dictionary() : agent.ChildrenCapSeeds); + lock (m_capsPaths) + m_capsPaths[agent.AgentID] = agent.CapsPath; + + lock (m_childrenSeeds) + m_childrenSeeds[agent.AgentID] + = ((agent.ChildrenCapSeeds == null) ? new Dictionary() : agent.ChildrenCapSeeds); } public string GetCapsPath(UUID agentId) { - if (capsPaths.ContainsKey(agentId)) + lock (m_capsPaths) { - return capsPaths[agentId]; + if (m_capsPaths.ContainsKey(agentId)) + { + return m_capsPaths[agentId]; + } } return null; @@ -186,17 +218,24 @@ namespace OpenSim.Region.CoreModules.Framework public Dictionary GetChildrenSeeds(UUID agentID) { Dictionary seeds = null; - if (childrenSeeds.TryGetValue(agentID, out seeds)) - return seeds; + + lock (m_childrenSeeds) + if (m_childrenSeeds.TryGetValue(agentID, out seeds)) + return seeds; + return new Dictionary(); } public void DropChildSeed(UUID agentID, ulong handle) { Dictionary seeds; - if (childrenSeeds.TryGetValue(agentID, out seeds)) + + lock (m_childrenSeeds) { - seeds.Remove(handle); + if (m_childrenSeeds.TryGetValue(agentID, out seeds)) + { + seeds.Remove(handle); + } } } @@ -204,53 +243,327 @@ namespace OpenSim.Region.CoreModules.Framework { Dictionary seeds; string returnval; - if (childrenSeeds.TryGetValue(agentID, out seeds)) + + lock (m_childrenSeeds) { - if (seeds.TryGetValue(handle, out returnval)) - return returnval; + if (m_childrenSeeds.TryGetValue(agentID, out seeds)) + { + if (seeds.TryGetValue(handle, out returnval)) + return returnval; + } } + return null; } public void SetChildrenSeed(UUID agentID, Dictionary seeds) { //m_log.DebugFormat(" !!! Setting child seeds in {0} to {1}", m_scene.RegionInfo.RegionName, seeds.Count); - childrenSeeds[agentID] = seeds; + + lock (m_childrenSeeds) + m_childrenSeeds[agentID] = seeds; } public void DumpChildrenSeeds(UUID agentID) { m_log.Info("================ ChildrenSeed "+m_scene.RegionInfo.RegionName+" ================"); - foreach (KeyValuePair kvp in childrenSeeds[agentID]) + + lock (m_childrenSeeds) + { + foreach (KeyValuePair kvp in m_childrenSeeds[agentID]) + { + uint x, y; + Util.RegionHandleToRegionLoc(kvp.Key, out x, out y); + m_log.Info(" >> "+x+", "+y+": "+kvp.Value); + } + } + } + + private void HandleShowCapsListCommand(string module, string[] cmdParams) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene) + return; + + StringBuilder capsReport = new StringBuilder(); + capsReport.AppendFormat("Region {0}:\n", m_scene.RegionInfo.RegionName); + + lock (m_capsObjects) { - uint x, y; - Utils.LongToUInts(kvp.Key, out x, out y); - x = x / Constants.RegionSize; - y = y / Constants.RegionSize; - m_log.Info(" >> "+x+", "+y+": "+kvp.Value); + foreach (KeyValuePair kvp in m_capsObjects) + { + capsReport.AppendFormat("** User {0}:\n", kvp.Key); + Caps caps = kvp.Value; + + for (IDictionaryEnumerator kvp2 = caps.CapsHandlers.GetCapsDetails(false, null).GetEnumerator(); kvp2.MoveNext(); ) + { + Uri uri = new Uri(kvp2.Value.ToString()); + capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, uri.PathAndQuery); + } + + foreach (KeyValuePair kvp2 in caps.GetPollHandlers()) + capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, kvp2.Value.Url); + + foreach (KeyValuePair kvp3 in caps.ExternalCapsHandlers) + capsReport.AppendFormat(m_showCapsCommandFormat, kvp3.Key, kvp3.Value); + } } + + MainConsole.Instance.Output(capsReport.ToString()); } - private void HandleShowCapsCommand(string module, string[] cmdparams) + private void HandleShowCapsStatsByCapCommand(string module, string[] cmdParams) { - StringBuilder caps = new StringBuilder(); - caps.AppendFormat("Region {0}:\n", m_scene.RegionInfo.RegionName); + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene) + return; + + if (cmdParams.Length != 5 && cmdParams.Length != 6) + { + MainConsole.Instance.Output("Usage: show caps stats by cap []"); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Region {0}:\n", m_scene.Name); + + if (cmdParams.Length == 5) + { + BuildSummaryStatsByCapReport(sb); + } + else if (cmdParams.Length == 6) + { + BuildDetailedStatsByCapReport(sb, cmdParams[5]); + } + + MainConsole.Instance.Output(sb.ToString()); + } + + private void BuildDetailedStatsByCapReport(StringBuilder sb, string capName) + { + sb.AppendFormat("Capability name {0}\n", capName); + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("User Name", 34); + cdt.AddColumn("Req Received", 12); + cdt.AddColumn("Req Handled", 12); + cdt.Indent = 2; + + Dictionary receivedStats = new Dictionary(); + Dictionary handledStats = new Dictionary(); + + m_scene.ForEachScenePresence( + sp => + { + Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID); - foreach (KeyValuePair kvp in m_capsObjects) + if (caps == null) + return; + + Dictionary capsHandlers = caps.CapsHandlers.GetCapsHandlers(); + + IRequestHandler reqHandler; + if (capsHandlers.TryGetValue(capName, out reqHandler)) + { + receivedStats[sp.Name] = reqHandler.RequestsReceived; + handledStats[sp.Name] = reqHandler.RequestsHandled; + } + else + { + PollServiceEventArgs pollHandler = null; + if (caps.TryGetPollHandler(capName, out pollHandler)) + { + receivedStats[sp.Name] = pollHandler.RequestsReceived; + handledStats[sp.Name] = pollHandler.RequestsHandled; + } + } + } + ); + + foreach (KeyValuePair kvp in receivedStats.OrderByDescending(kp => kp.Value)) { - caps.AppendFormat("** User {0}:\n", kvp.Key); + cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]); + } + + sb.Append(cdt.ToString()); + } + + private void BuildSummaryStatsByCapReport(StringBuilder sb) + { + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Name", 34); + cdt.AddColumn("Req Received", 12); + cdt.AddColumn("Req Handled", 12); + cdt.Indent = 2; + + Dictionary receivedStats = new Dictionary(); + Dictionary handledStats = new Dictionary(); - for (IDictionaryEnumerator kvp2 = kvp.Value.CapsHandlers.GetCapsDetails(false).GetEnumerator(); kvp2.MoveNext(); ) + m_scene.ForEachScenePresence( + sp => { - Uri uri = new Uri(kvp2.Value.ToString()); - caps.AppendFormat(m_showCapsCommandFormat, kvp2.Key, uri.PathAndQuery); + Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID); + + if (caps == null) + return; + + foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values) + { + string reqName = reqHandler.Name ?? ""; + + if (!receivedStats.ContainsKey(reqName)) + { + receivedStats[reqName] = reqHandler.RequestsReceived; + handledStats[reqName] = reqHandler.RequestsHandled; + } + else + { + receivedStats[reqName] += reqHandler.RequestsReceived; + handledStats[reqName] += reqHandler.RequestsHandled; + } + } + + foreach (KeyValuePair kvp in caps.GetPollHandlers()) + { + string name = kvp.Key; + PollServiceEventArgs pollHandler = kvp.Value; + + if (!receivedStats.ContainsKey(name)) + { + receivedStats[name] = pollHandler.RequestsReceived; + handledStats[name] = pollHandler.RequestsHandled; + } + else + { + receivedStats[name] += pollHandler.RequestsReceived; + handledStats[name] += pollHandler.RequestsHandled; + } + } } + ); + + foreach (KeyValuePair kvp in receivedStats.OrderByDescending(kp => kp.Value)) + cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]); + + sb.Append(cdt.ToString()); + } + + private void HandleShowCapsStatsByUserCommand(string module, string[] cmdParams) + { + if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene) + return; + + if (cmdParams.Length != 5 && cmdParams.Length != 7) + { + MainConsole.Instance.Output("Usage: show caps stats by user [ ]"); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Region {0}:\n", m_scene.Name); + + if (cmdParams.Length == 5) + { + BuildSummaryStatsByUserReport(sb); + } + else if (cmdParams.Length == 7) + { + string firstName = cmdParams[5]; + string lastName = cmdParams[6]; + + ScenePresence sp = m_scene.GetScenePresence(firstName, lastName); + + if (sp == null) + return; - foreach (KeyValuePair kvp3 in kvp.Value.ExternalCapsHandlers) - caps.AppendFormat(m_showCapsCommandFormat, kvp3.Key, kvp3.Value); + BuildDetailedStatsByUserReport(sb, sp); } - MainConsole.Instance.Output(caps.ToString()); + MainConsole.Instance.Output(sb.ToString()); + } + + private void BuildDetailedStatsByUserReport(StringBuilder sb, ScenePresence sp) + { + sb.AppendFormat("Avatar name {0}, type {1}\n", sp.Name, sp.IsChildAgent ? "child" : "root"); + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Cap Name", 34); + cdt.AddColumn("Req Received", 12); + cdt.AddColumn("Req Handled", 12); + cdt.Indent = 2; + + Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID); + + if (caps == null) + return; + + List capRows = new List(); + + foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values) + capRows.Add(new CapTableRow(reqHandler.Name, reqHandler.RequestsReceived, reqHandler.RequestsHandled)); + + foreach (KeyValuePair kvp in caps.GetPollHandlers()) + capRows.Add(new CapTableRow(kvp.Key, kvp.Value.RequestsReceived, kvp.Value.RequestsHandled)); + + foreach (CapTableRow ctr in capRows.OrderByDescending(ctr => ctr.RequestsReceived)) + cdt.AddRow(ctr.Name, ctr.RequestsReceived, ctr.RequestsHandled); + + sb.Append(cdt.ToString()); + } + + private void BuildSummaryStatsByUserReport(StringBuilder sb) + { + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Name", 32); + cdt.AddColumn("Type", 5); + cdt.AddColumn("Req Received", 12); + cdt.AddColumn("Req Handled", 12); + cdt.Indent = 2; + + m_scene.ForEachScenePresence( + sp => + { + Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID); + + if (caps == null) + return; + + Dictionary capsHandlers = caps.CapsHandlers.GetCapsHandlers(); + + int totalRequestsReceived = 0; + int totalRequestsHandled = 0; + + foreach (IRequestHandler reqHandler in capsHandlers.Values) + { + totalRequestsReceived += reqHandler.RequestsReceived; + totalRequestsHandled += reqHandler.RequestsHandled; + } + + Dictionary capsPollHandlers = caps.GetPollHandlers(); + + foreach (PollServiceEventArgs handler in capsPollHandlers.Values) + { + totalRequestsReceived += handler.RequestsReceived; + totalRequestsHandled += handler.RequestsHandled; + } + + cdt.AddRow(sp.Name, sp.IsChildAgent ? "child" : "root", totalRequestsReceived, totalRequestsHandled); + } + ); + + sb.Append(cdt.ToString()); + } + + private class CapTableRow + { + public string Name { get; set; } + public int RequestsReceived { get; set; } + public int RequestsHandled { get; set; } + + public CapTableRow(string name, int requestsReceived, int requestsHandled) + { + Name = name; + RequestsReceived = requestsReceived; + RequestsHandled = requestsHandled; + } } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DAExampleModule.cs b/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DAExampleModule.cs new file mode 100644 index 0000000..0c632b1 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DAExampleModule.cs @@ -0,0 +1,124 @@ +/* + * 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.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Framework.DynamicAttributes.DAExampleModule +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DAExampleModule")] + public class DAExampleModule : INonSharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private readonly bool ENABLED = false; // enable for testing + + public const string Namespace = "Example"; + public const string StoreName = "DA"; + + protected Scene m_scene; + protected IDialogModule m_dialogMod; + + public string Name { get { return "DAExample Module"; } } + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) {} + + public void AddRegion(Scene scene) + { + if (ENABLED) + { + m_scene = scene; + m_scene.EventManager.OnSceneGroupMove += OnSceneGroupMove; + m_dialogMod = m_scene.RequestModuleInterface(); + + m_log.DebugFormat("[DA EXAMPLE MODULE]: Added region {0}", m_scene.Name); + } + } + + public void RemoveRegion(Scene scene) + { + if (ENABLED) + { + m_scene.EventManager.OnSceneGroupMove -= OnSceneGroupMove; + } + } + + public void RegionLoaded(Scene scene) {} + + public void Close() + { + RemoveRegion(m_scene); + } + + protected bool OnSceneGroupMove(UUID groupId, Vector3 delta) + { + OSDMap attrs = null; + SceneObjectPart sop = m_scene.GetSceneObjectPart(groupId); + + if (sop == null) + return true; + + if (!sop.DynAttrs.TryGetStore(Namespace, StoreName, out attrs)) + attrs = new OSDMap(); + + OSDInteger newValue; + + // We have to lock on the entire dynamic attributes map to avoid race conditions with serialization code. + lock (sop.DynAttrs) + { + if (!attrs.ContainsKey("moves")) + newValue = new OSDInteger(1); + else + newValue = new OSDInteger(attrs["moves"].AsInteger() + 1); + + attrs["moves"] = newValue; + + sop.DynAttrs.SetStore(Namespace, StoreName, attrs); + } + + sop.ParentGroup.HasGroupChanged = true; + + string msg = string.Format("{0} {1} moved {2} times", sop.Name, sop.UUID, newValue); + m_log.DebugFormat("[DA EXAMPLE MODULE]: {0}", msg); + m_dialogMod.SendGeneralAlert(msg); + + return true; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DOExampleModule.cs b/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DOExampleModule.cs new file mode 100644 index 0000000..166a994 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/DynamicAttributes/DOExampleModule.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 System.Collections.Generic; +using System.Reflection; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.CoreModules.Framework.DynamicAttributes.DAExampleModule; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.Framework.DynamicAttributes.DOExampleModule +{ + /// + /// Example module for experimenting with and demonstrating dynamic object ideas. + /// + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DOExampleModule")] + public class DOExampleModule : INonSharedRegionModule + { + public class MyObject + { + public int Moves { get; set; } + + public MyObject(int moves) + { + Moves = moves; + } + } + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static readonly bool ENABLED = false; // enable for testing + + private Scene m_scene; + private IDialogModule m_dialogMod; + + public string Name { get { return "DO"; } } + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) {} + + public void AddRegion(Scene scene) + { + if (ENABLED) + { + m_scene = scene; + m_scene.EventManager.OnObjectAddedToScene += OnObjectAddedToScene; + m_scene.EventManager.OnSceneGroupMove += OnSceneGroupMove; + m_dialogMod = m_scene.RequestModuleInterface(); + } + } + + public void RemoveRegion(Scene scene) + { + if (ENABLED) + { + m_scene.EventManager.OnSceneGroupMove -= OnSceneGroupMove; + } + } + + public void RegionLoaded(Scene scene) {} + + public void Close() + { + RemoveRegion(m_scene); + } + + private void OnObjectAddedToScene(SceneObjectGroup so) + { + SceneObjectPart rootPart = so.RootPart; + + OSDMap attrs; + + int movesSoFar = 0; + +// Console.WriteLine("Here for {0}", so.Name); + + if (rootPart.DynAttrs.TryGetStore(DAExampleModule.Namespace, DAExampleModule.StoreName, out attrs)) + { + movesSoFar = attrs["moves"].AsInteger(); + + m_log.DebugFormat( + "[DO EXAMPLE MODULE]: Found saved moves {0} for {1} in {2}", movesSoFar, so.Name, m_scene.Name); + } + + rootPart.DynObjs.Add(DAExampleModule.Namespace, Name, new MyObject(movesSoFar)); + } + + private bool OnSceneGroupMove(UUID groupId, Vector3 delta) + { + SceneObjectGroup so = m_scene.GetSceneObjectGroup(groupId); + + if (so == null) + return true; + + object rawObj = so.RootPart.DynObjs.Get(Name); + + if (rawObj != null) + { + MyObject myObj = (MyObject)rawObj; + + m_dialogMod.SendGeneralAlert(string.Format("{0} {1} moved {2} times", so.Name, so.UUID, ++myObj.Moves)); + } + + return true; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index c43edd2..a2417c4 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -33,9 +33,10 @@ using System.Threading; using OpenSim.Framework; using OpenSim.Framework.Capabilities; using OpenSim.Framework.Client; +using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Services.Interfaces; using GridRegion = OpenSim.Services.Interfaces.GridRegion; @@ -51,15 +52,61 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer public class EntityTransferModule : INonSharedRegionModule, IEntityTransferModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[ENTITY TRANSFER MODULE]"; + public const int DefaultMaxTransferDistance = 4095; public const bool WaitForAgentArrivedAtDestinationDefault = true; /// + /// The maximum distance, in standard region units (256m) that an agent is allowed to transfer. + /// + public int MaxTransferDistance { get; set; } + + /// /// If true then on a teleport, the source region waits for a callback from the destination region. If /// a callback fails to arrive within a set time then the user is pulled back into the source region. /// public bool WaitForAgentArrivedAtDestination { get; set; } + /// + /// If true then we ask the viewer to disable teleport cancellation and ignore teleport requests. + /// + /// + /// This is useful in situations where teleport is very likely to always succeed and we want to avoid a + /// situation where avatars can be come 'stuck' due to a failed teleport cancellation. Unfortunately, the + /// nature of the teleport protocol makes it extremely difficult (maybe impossible) to make teleport + /// cancellation consistently suceed. + /// + public bool DisableInterRegionTeleportCancellation { get; set; } + + /// + /// Number of times inter-region teleport was attempted. + /// + private Stat m_interRegionTeleportAttempts; + + /// + /// Number of times inter-region teleport was aborted (due to simultaneous client logout). + /// + private Stat m_interRegionTeleportAborts; + + /// + /// Number of times inter-region teleport was successfully cancelled by the client. + /// + private Stat m_interRegionTeleportCancels; + + /// + /// Number of times inter-region teleport failed due to server/client/network problems (e.g. viewer failed to + /// connect with destination region). + /// + /// + /// This is not necessarily a problem for this simulator - in open-grid/hg conditions, viewer connectivity to + /// destination simulator is unknown. + /// + private Stat m_interRegionTeleportFailures; + + protected string m_ThisHomeURI; + protected string m_GatekeeperURI; + protected bool m_Enabled = false; public Scene Scene { get; private set; } @@ -70,10 +117,56 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// private EntityTransferStateMachine m_entityTransferStateMachine; - private ExpiringCache> m_bannedRegions = - new ExpiringCache>(); + // For performance, we keed a cached of banned regions so we don't keep going + // to the grid service. + private class BannedRegionCache + { + private ExpiringCache> m_bannedRegions = + new ExpiringCache>(); + ExpiringCache m_idCache; + DateTime m_banUntil; + public BannedRegionCache() + { + } + // Return 'true' if there is a valid ban entry for this agent in this region + public bool IfBanned(ulong pRegionHandle, UUID pAgentID) + { + bool ret = false; + if (m_bannedRegions.TryGetValue(pAgentID, out m_idCache)) + { + if (m_idCache.TryGetValue(pRegionHandle, out m_banUntil)) + { + if (DateTime.Now < m_banUntil) + { + ret = true; + } + } + } + return ret; + } + // Add this agent in this region as a banned person + public void Add(ulong pRegionHandle, UUID pAgentID) + { + if (!m_bannedRegions.TryGetValue(pAgentID, out m_idCache)) + { + m_idCache = new ExpiringCache(); + m_bannedRegions.Add(pAgentID, m_idCache, TimeSpan.FromSeconds(45)); + } + m_idCache.Add(pRegionHandle, DateTime.Now + TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15)); + } + // Remove the agent from the region's banned list + public void Remove(ulong pRegionHandle, UUID pAgentID) + { + if (m_bannedRegions.TryGetValue(pAgentID, out m_idCache)) + { + m_idCache.Remove(pRegionHandle); + } + } + } + private BannedRegionCache m_bannedRegionCache = new BannedRegionCache(); private IEventQueue m_eqModule; + private IRegionCombinerModule m_regionCombinerModule; #region ISharedRegionModule @@ -107,11 +200,32 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// protected virtual void InitialiseCommon(IConfigSource source) { + IConfig hypergridConfig = source.Configs["Hypergrid"]; + if (hypergridConfig != null) + { + m_ThisHomeURI = hypergridConfig.GetString("HomeURI", string.Empty); + if (m_ThisHomeURI != string.Empty && !m_ThisHomeURI.EndsWith("/")) + m_ThisHomeURI += '/'; + + m_GatekeeperURI = hypergridConfig.GetString("GatekeeperURI", string.Empty); + if (m_GatekeeperURI != string.Empty && !m_GatekeeperURI.EndsWith("/")) + m_GatekeeperURI += '/'; + } + IConfig transferConfig = source.Configs["EntityTransfer"]; if (transferConfig != null) { + DisableInterRegionTeleportCancellation + = transferConfig.GetBoolean("DisableInterRegionTeleportCancellation", false); + WaitForAgentArrivedAtDestination = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault); + + MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance); + } + else + { + MaxTransferDistance = DefaultMaxTransferDistance; } m_entityTransferStateMachine = new EntityTransferStateMachine(this); @@ -130,19 +244,87 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer Scene = scene; + m_interRegionTeleportAttempts = + new Stat( + "InterRegionTeleportAttempts", + "Number of inter-region teleports attempted.", + "This does not count attempts which failed due to pre-conditions (e.g. target simulator refused access).\n" + + "You can get successfully teleports by subtracting aborts, cancels and teleport failures from this figure.", + "", + "entitytransfer", + Scene.Name, + StatType.Push, + null, + StatVerbosity.Debug); + + m_interRegionTeleportAborts = + new Stat( + "InterRegionTeleportAborts", + "Number of inter-region teleports aborted due to client actions.", + "The chief action is simultaneous logout whilst teleporting.", + "", + "entitytransfer", + Scene.Name, + StatType.Push, + null, + StatVerbosity.Debug); + + m_interRegionTeleportCancels = + new Stat( + "InterRegionTeleportCancels", + "Number of inter-region teleports cancelled by the client.", + null, + "", + "entitytransfer", + Scene.Name, + StatType.Push, + null, + StatVerbosity.Debug); + + m_interRegionTeleportFailures = + new Stat( + "InterRegionTeleportFailures", + "Number of inter-region teleports that failed due to server/client/network issues.", + "This number may not be very helpful in open-grid/hg situations as the network connectivity/quality of destinations is uncontrollable.", + "", + "entitytransfer", + Scene.Name, + StatType.Push, + null, + StatVerbosity.Debug); + + StatsManager.RegisterStat(m_interRegionTeleportAttempts); + StatsManager.RegisterStat(m_interRegionTeleportAborts); + StatsManager.RegisterStat(m_interRegionTeleportCancels); + StatsManager.RegisterStat(m_interRegionTeleportFailures); + scene.RegisterModuleInterface(this); scene.EventManager.OnNewClient += OnNewClient; } protected virtual void OnNewClient(IClientAPI client) { - client.OnTeleportHomeRequest += TeleportHome; + client.OnTeleportHomeRequest += TriggerTeleportHome; client.OnTeleportLandmarkRequest += RequestTeleportLandmark; + + if (!DisableInterRegionTeleportCancellation) + client.OnTeleportCancel += OnClientCancelTeleport; + + client.OnConnectionClosed += OnConnectionClosed; } public virtual void Close() {} - public virtual void RemoveRegion(Scene scene) {} + public virtual void RemoveRegion(Scene scene) + { + if (m_Enabled) + { + StatsManager.DeregisterStat(m_interRegionTeleportAttempts); + StatsManager.DeregisterStat(m_interRegionTeleportAborts); + StatsManager.DeregisterStat(m_interRegionTeleportCancels); + StatsManager.DeregisterStat(m_interRegionTeleportFailures); + } + } public virtual void RegionLoaded(Scene scene) { @@ -150,12 +332,32 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; m_eqModule = Scene.RequestModuleInterface(); + m_regionCombinerModule = Scene.RequestModuleInterface(); } #endregion #region Agent Teleports + private void OnConnectionClosed(IClientAPI client) + { + if (client.IsLoggingOut && m_entityTransferStateMachine.UpdateInTransit(client.AgentId, AgentTransferState.Aborting)) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport request from {0} in {1} due to simultaneous logout", + client.Name, Scene.Name); + } + } + + private void OnClientCancelTeleport(IClientAPI client) + { + m_entityTransferStateMachine.UpdateInTransit(client.AgentId, AgentTransferState.Cancelling); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Received teleport cancel request from {0} in {1}", client.Name, Scene.Name); + } + + // Attempt to teleport the ScenePresence to the specified position in the specified region (spec'ed by its handle). public void Teleport(ScenePresence sp, ulong regionHandle, Vector3 position, Vector3 lookAt, uint teleportFlags) { if (sp.Scene.Permissions.IsGridGod(sp.UUID)) @@ -167,13 +369,26 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!sp.Scene.Permissions.CanTeleport(sp.UUID)) return; - // Reset animations; the viewer does that in teleports. - sp.Animator.ResetAnimations(); - string destinationRegionName = "(not found)"; + // Record that this agent is in transit so that we can prevent simultaneous requests and do later detection + // of whether the destination region completes the teleport. + if (!m_entityTransferStateMachine.SetInTransit(sp.UUID)) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2}@{3} - agent is already in transit.", + sp.Name, sp.UUID, position, regionHandle); + + sp.ControllingClient.SendTeleportFailed("Previous teleport process incomplete. Please retry shortly."); + + return; + } + try { + // Reset animations; the viewer does that in teleports. + sp.Animator.ResetAnimations(); + if (regionHandle == sp.Scene.RegionInfo.RegionHandle) { destinationRegionName = sp.Scene.RegionInfo.RegionName; @@ -182,12 +397,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } else // Another region possibly in another simulator { - GridRegion finalDestination; - TeleportAgentToDifferentRegion( - sp, regionHandle, position, lookAt, teleportFlags, out finalDestination); - - if (finalDestination != null) - destinationRegionName = finalDestination.RegionName; + GridRegion finalDestination = null; + try + { + TeleportAgentToDifferentRegion( + sp, regionHandle, position, lookAt, teleportFlags, out finalDestination); + } + finally + { + if (finalDestination != null) + destinationRegionName = finalDestination.RegionName; + } } } catch (Exception e) @@ -197,11 +417,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sp.Name, sp.AbsolutePosition, sp.Scene.RegionInfo.RegionName, position, destinationRegionName, e.Message, e.StackTrace); - // Make sure that we clear the in-transit flag so that future teleport attempts don't always fail. - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); - sp.ControllingClient.SendTeleportFailed("Internal error"); } + finally + { + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); + } } /// @@ -210,30 +431,21 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// /// /// - /// private void TeleportAgentWithinRegion(ScenePresence sp, Vector3 position, Vector3 lookAt, uint teleportFlags) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Teleport for {0} to {1} within {2}", sp.Name, position, sp.Scene.RegionInfo.RegionName); - if (!m_entityTransferStateMachine.SetInTransit(sp.UUID)) - { - m_log.DebugFormat( - "[ENTITY TRANSFER MODULE]: Ignoring within region teleport request of {0} {1} to {2} - agent is already in transit.", - sp.Name, sp.UUID, position); - - return; - } - // Teleport within the same region - if (IsOutsideRegion(sp.Scene, position) || position.Z < 0) + if (!sp.Scene.PositionIsInCurrentRegion(position) || position.Z < 0) { Vector3 emergencyPos = new Vector3(128, 128, 128); m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: RequestTeleportToLocation() was given an illegal position of {0} for avatar {1}, {2}. Substituting {3}", - position, sp.Name, sp.UUID, emergencyPos); + "[ENTITY TRANSFER MODULE]: RequestTeleportToLocation() was given an illegal position of {0} for avatar {1}, {2} in {3}. Substituting {4}", + position, sp.Name, sp.UUID, Scene.Name, emergencyPos); position = emergencyPos; } @@ -243,10 +455,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer float posZLimit = 22; // TODO: Check other Scene HeightField - if (position.X > 0 && position.X <= (int)Constants.RegionSize && position.Y > 0 && position.Y <= (int)Constants.RegionSize) - { - posZLimit = (float)sp.Scene.Heightmap[(int)position.X, (int)position.Y]; - } + posZLimit = (float)sp.Scene.Heightmap[(int)position.X, (int)position.Y]; float newPosZ = posZLimit + localAVHeight; if (posZLimit >= (position.Z - (localAVHeight / 2)) && !(Single.IsInfinity(newPosZ) || Single.IsNaN(newPosZ))) @@ -254,11 +463,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer position.Z = newPosZ; } + if (sp.Flying) + teleportFlags |= (uint)TeleportFlags.IsFlying; + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); sp.ControllingClient.SendTeleportStart(teleportFlags); sp.ControllingClient.SendLocalTeleport(position, lookAt, teleportFlags); + sp.TeleportFlags = (Constants.TeleportFlags)teleportFlags; sp.Velocity = Vector3.Zero; sp.Teleport(position); @@ -270,7 +483,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); } /// @@ -286,21 +498,23 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer ScenePresence sp, ulong regionHandle, Vector3 position, Vector3 lookAt, uint teleportFlags, out GridRegion finalDestination) { - uint x = 0, y = 0; - Utils.LongToUInts(regionHandle, out x, out y); - GridRegion reg = Scene.GridService.GetRegionByPosition(sp.Scene.RegionInfo.ScopeID, (int)x, (int)y); + // Get destination region taking into account that the address could be an offset + // region inside a varregion. + GridRegion reg = GetTeleportDestinationRegion(sp.Scene.GridService, sp.Scene.RegionInfo.ScopeID, regionHandle, ref position); if (reg != null) { - finalDestination = GetFinalDestination(reg); + string homeURI = Scene.GetAgentHomeURI(sp.ControllingClient.AgentId); + + string message; + finalDestination = GetFinalDestination(reg, sp.ControllingClient.AgentId, homeURI, out message); if (finalDestination == null) { - m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: Final destination is having problems. Unable to teleport {0} {1}", - sp.Name, sp.UUID); + m_log.WarnFormat( "{0} Final destination is having problems. Unable to teleport {1} {2}: {3}", + LogHeader, sp.Name, sp.UUID, message); - sp.ControllingClient.SendTeleportFailed("Problem at destination"); + sp.ControllingClient.SendTeleportFailed(message); return; } @@ -321,10 +535,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } + if (message != null) + sp.ControllingClient.SendAgentAlertMessage(message, true); + // // This is it // - DoTeleport(sp, reg, finalDestination, position, lookAt, teleportFlags); + DoTeleportInternal(sp, reg, finalDestination, position, lookAt, teleportFlags); // // // @@ -339,12 +556,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // and set the map-tile to '(Offline)' uint regX, regY; - Utils.LongToUInts(regionHandle, out regX, out regY); + Util.RegionHandleToRegionLoc(regionHandle, out regX, out regY); MapBlockData block = new MapBlockData(); - block.X = (ushort)(regX / Constants.RegionSize); - block.Y = (ushort)(regY / Constants.RegionSize); - block.Access = 254; // == not there + block.X = (ushort)regX; + block.Y = (ushort)regY; + block.Access = (byte)SimAccess.Down; List blocks = new List(); blocks.Add(block); @@ -352,6 +569,31 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } } + // The teleport address could be an address in a subregion of a larger varregion. + // Find the real base region and adjust the teleport location to account for the + // larger region. + private GridRegion GetTeleportDestinationRegion(IGridService gridService, UUID scope, ulong regionHandle, ref Vector3 position) + { + uint x = 0, y = 0; + Util.RegionHandleToWorldLoc(regionHandle, out x, out y); + + // Compute the world location we're teleporting to + double worldX = (double)x + position.X; + double worldY = (double)y + position.Y; + + // Find the region that contains the position + GridRegion reg = GetRegionContainingWorldLocation(gridService, scope, worldX, worldY); + + if (reg != null) + { + // modify the position for the offset into the actual region returned + position.X += x - reg.RegionLocX; + position.Y += y - reg.RegionLocY; + } + + return reg; + } + // Nothing to validate here protected virtual bool ValidateGenericConditions(ScenePresence sp, GridRegion reg, GridRegion finalDestination, uint teleportFlags, out string reason) { @@ -359,6 +601,32 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return true; } + /// + /// Determines whether this instance is within the max transfer distance. + /// + /// + /// + /// + /// true if this instance is within max transfer distance; otherwise, false. + /// + private bool IsWithinMaxTeleportDistance(RegionInfo sourceRegion, GridRegion destRegion) + { + if(MaxTransferDistance == 0) + return true; + +// m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Source co-ords are x={0} y={1}", curRegionX, curRegionY); +// +// m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Final dest is x={0} y={1} {2}@{3}", +// destRegionX, destRegionY, finalDestination.RegionID, finalDestination.ServerURI); + + // Insanely, RegionLoc on RegionInfo is the 256m map co-ord whilst GridRegion.RegionLoc is the raw meters position. + return Math.Abs(sourceRegion.RegionLocX - destRegion.RegionCoordX) <= MaxTransferDistance + && Math.Abs(sourceRegion.RegionLocY - destRegion.RegionCoordY) <= MaxTransferDistance; + } + + /// + /// Wraps DoTeleportInternal() and manages the transfer state. + /// public void DoTeleport( ScenePresence sp, GridRegion reg, GridRegion finalDestination, Vector3 position, Vector3 lookAt, uint teleportFlags) @@ -370,18 +638,45 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2} ({3}) {4}/{5} - agent is already in transit.", sp.Name, sp.UUID, reg.ServerURI, finalDestination.ServerURI, finalDestination.RegionName, position); - + sp.ControllingClient.SendTeleportFailed("Agent is already in transit."); return; } + + try + { + DoTeleportInternal(sp, reg, finalDestination, position, lookAt, teleportFlags); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ENTITY TRANSFER MODULE]: Exception on teleport of {0} from {1}@{2} to {3}@{4}: {5}{6}", + sp.Name, sp.AbsolutePosition, sp.Scene.RegionInfo.RegionName, position, finalDestination.RegionName, + e.Message, e.StackTrace); - if (reg == null || finalDestination == null) + sp.ControllingClient.SendTeleportFailed("Internal error"); + } + finally { - sp.ControllingClient.SendTeleportFailed("Unable to locate destination"); m_entityTransferStateMachine.ResetFromTransit(sp.UUID); + } + } + /// + /// Teleports the agent to another region. + /// This method doesn't manage the transfer state; the caller must do that. + /// + private void DoTeleportInternal( + ScenePresence sp, GridRegion reg, GridRegion finalDestination, + Vector3 position, Vector3 lookAt, uint teleportFlags) + { + if (reg == null || finalDestination == null) + { + sp.ControllingClient.SendTeleportFailed("Unable to locate destination"); return; } + string homeURI = Scene.GetAgentHomeURI(sp.ControllingClient.AgentId); + m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Teleporting {0} {1} from {2} to {3} ({4}) {5}/{6}", sp.Name, sp.UUID, sp.Scene.RegionInfo.RegionName, @@ -389,10 +684,21 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer RegionInfo sourceRegion = sp.Scene.RegionInfo; - uint newRegionX = (uint)(reg.RegionHandle >> 40); - uint newRegionY = (((uint)(reg.RegionHandle)) >> 8); - uint oldRegionX = (uint)(sp.Scene.RegionInfo.RegionHandle >> 40); - uint oldRegionY = (((uint)(sp.Scene.RegionInfo.RegionHandle)) >> 8); + if (!IsWithinMaxTeleportDistance(sourceRegion, finalDestination)) + { + sp.ControllingClient.SendTeleportFailed( + string.Format( + "Can't teleport to {0} ({1},{2}) from {3} ({4},{5}), destination is more than {6} regions way", + finalDestination.RegionName, finalDestination.RegionCoordX, finalDestination.RegionCoordY, + sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY, + MaxTransferDistance)); + + return; + } + + uint newRegionX, newRegionY, oldRegionX, oldRegionY; + Util.RegionHandleToRegionLoc(reg.RegionHandle, out newRegionX, out newRegionY); + Util.RegionHandleToRegionLoc(sp.Scene.RegionInfo.RegionHandle, out oldRegionX, out oldRegionY); ulong destinationHandle = finalDestination.RegionHandle; @@ -400,11 +706,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // This may be a costly operation. The reg.ExternalEndPoint field is not a passive field, // it's actually doing a lot of work. IPEndPoint endPoint = finalDestination.ExternalEndPoint; - - if (endPoint.Address == null) + if (endPoint == null || endPoint.Address == null) { sp.ControllingClient.SendTeleportFailed("Remote Region appears to be down"); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); return; } @@ -412,35 +716,41 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!sp.ValidateAttachments()) m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Failed validation of all attachments for teleport of {0} from {1} to {2}. Continuing.", - sp.Name, sp.Scene.RegionInfo.RegionName, finalDestination.RegionName); - -// if (!sp.ValidateAttachments()) -// { -// sp.ControllingClient.SendTeleportFailed("Inconsistent attachment state"); -// return; -// } + sp.Name, sp.Scene.Name, finalDestination.RegionName); string reason; - string version; + EntityTransferContext ctx = new EntityTransferContext(); + if (!Scene.SimulationService.QueryAccess( - finalDestination, sp.ControllingClient.AgentId, Vector3.Zero, out version, out reason)) + finalDestination, sp.ControllingClient.AgentId, homeURI, true, position, sp.Scene.GetFormatsOffered(), ctx, out reason)) { sp.ControllingClient.SendTeleportFailed(reason); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); m_log.DebugFormat( - "[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because {3}", - sp.Name, sp.Scene.RegionInfo.RegionName, finalDestination.RegionName, reason); + "[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because: {3}", + sp.Name, sp.Scene.Name, finalDestination.RegionName, reason); return; } - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Destination is running version {0}", version); + // Before this point, teleport 'failure' is due to checkable pre-conditions such as whether the target + // simulator can be found and is explicitly prepared to allow access. Therefore, we will not count these + // as server attempts. + m_interRegionTeleportAttempts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: {0} transfer protocol version to {1} is {2} / {3}", + sp.Scene.Name, finalDestination.RegionName, ctx.OutboundVersion, ctx.InboundVersion); // Fixing a bug where teleporting while sitting results in the avatar ending up removed from // both regions if (sp.ParentID != (uint)0) sp.StandUp(); + else if (sp.Flying) + teleportFlags |= (uint)TeleportFlags.IsFlying; + + if (DisableInterRegionTeleportCancellation) + teleportFlags |= (uint)TeleportFlags.DisableCancel; // At least on LL 3.3.4, this is not strictly necessary - a teleport will succeed without sending this to // the viewer. However, it might mean that the viewer does not see the black teleport screen (untested). @@ -454,13 +764,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // once we reach here... //avatar.Scene.RemoveCapsHandler(avatar.UUID); - string capsPath = String.Empty; - AgentCircuitData currentAgentCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); AgentCircuitData agentCircuit = sp.ControllingClient.RequestClientInfo(); agentCircuit.startpos = position; agentCircuit.child = true; - agentCircuit.Appearance = sp.Appearance; + agentCircuit.Appearance = new AvatarAppearance(); + agentCircuit.Appearance.PackLegacyWearables = true; if (currentAgentCircuit != null) { agentCircuit.ServiceURLs = currentAgentCircuit.ServiceURLs; @@ -471,23 +780,66 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer agentCircuit.Id0 = currentAgentCircuit.Id0; } - if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + // if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + float dist = (float)Math.Max(sp.Scene.DefaultDrawDistance, + (float)Math.Max(sp.Scene.RegionInfo.RegionSizeX, sp.Scene.RegionInfo.RegionSizeY)); + if (NeedsNewAgent(dist, oldRegionX, newRegionX, oldRegionY, newRegionY)) { // brand new agent, let's create a new caps seed agentCircuit.CapsPath = CapsUtil.GetRandomCapsObjectPath(); } - // Let's create an agent there if one doesn't exist yet. + // We're going to fallback to V1 if the destination gives us anything smaller than 0.2 + if (ctx.OutboundVersion >= 0.2f) + TransferAgent_V2(sp, agentCircuit, reg, finalDestination, endPoint, teleportFlags, oldRegionX, newRegionX, oldRegionY, newRegionY, ctx, out reason); + else + TransferAgent_V1(sp, agentCircuit, reg, finalDestination, endPoint, teleportFlags, oldRegionX, newRegionX, oldRegionY, newRegionY, ctx, out reason); + } + + private void TransferAgent_V1(ScenePresence sp, AgentCircuitData agentCircuit, GridRegion reg, GridRegion finalDestination, + IPEndPoint endPoint, uint teleportFlags, uint oldRegionX, uint newRegionX, uint oldRegionY, uint newRegionY, EntityTransferContext ctx, out string reason) + { + ulong destinationHandle = finalDestination.RegionHandle; + AgentCircuitData currentAgentCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Using TP V1 for {0} going from {1} to {2}", + sp.Name, Scene.Name, finalDestination.RegionName); + + // Let's create an agent there if one doesn't exist yet. + // NOTE: logout will always be false for a non-HG teleport. bool logout = false; if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { - sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason)); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); + m_interRegionTeleportFailures.Value++; m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}", sp.Name, sp.Scene.RegionInfo.RegionName, finalDestination.RegionName, reason); + sp.ControllingClient.SendTeleportFailed(reason); + + return; + } + + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) + { + m_interRegionTeleportCancels.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after CreateAgent on client request", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + else if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after CreateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + return; } @@ -497,9 +849,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // OK, it got this agent. Let's close some child agents sp.CloseChildAgents(newRegionX, newRegionY); - IClientIPEndpoint ipepClient; - if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + IClientIPEndpoint ipepClient; + string capsPath = String.Empty; + float dist = (float)Math.Max(sp.Scene.DefaultDrawDistance, + (float)Math.Max(sp.Scene.RegionInfo.RegionSizeX, sp.Scene.RegionInfo.RegionSizeY)); + if (NeedsNewAgent(dist, oldRegionX, newRegionX, oldRegionY, newRegionY)) { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Determined that region {0} at {1},{2} needs new child agent for incoming agent {3} from {4}", + finalDestination.RegionName, newRegionX, newRegionY, sp.Name, Scene.Name); + //sp.ControllingClient.SendTeleportProgress(teleportFlags, "Creating agent..."); #region IP Translation for NAT // Uses ipepClient above @@ -512,21 +871,30 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (m_eqModule != null) { - m_eqModule.EnableSimulator(destinationHandle, endPoint, sp.UUID); - - // ES makes the client send a UseCircuitCode message to the destination, - // which triggers a bunch of things there. - // So let's wait + // The EnableSimulator message makes the client establish a connection with the destination + // simulator by sending the initial UseCircuitCode UDP packet to the destination containing the + // correct circuit code. + m_eqModule.EnableSimulator(destinationHandle, endPoint, sp.UUID, + finalDestination.RegionSizeX, finalDestination.RegionSizeY); + m_log.DebugFormat("{0} Sent EnableSimulator. regName={1}, size=<{2},{3}>", LogHeader, + finalDestination.RegionName, finalDestination.RegionSizeX, finalDestination.RegionSizeY); + + // XXX: Is this wait necessary? We will always end up waiting on UpdateAgent for the destination + // simulator to confirm that it has established communication with the viewer. Thread.Sleep(200); // At least on LL 3.3.4 for teleports between different regions on the same simulator this appears // unnecessary - teleport will succeed and SEED caps will be requested without it (though possibly // only on TeleportFinish). This is untested for region teleport between different simulators // though this probably also works. - m_eqModule.EstablishAgentCommunication(sp.UUID, endPoint, capsPath); + m_eqModule.EstablishAgentCommunication(sp.UUID, endPoint, capsPath, finalDestination.RegionHandle, + finalDestination.RegionSizeX, finalDestination.RegionSizeY); } else { + // XXX: This is a little misleading since we're information the client of its avatar destination, + // which may or may not be a neighbour region of the source region. This path is probably little + // used anyway (with EQ being the one used). But it is currently being used for test code. sp.ControllingClient.InformClientOfNeighbour(destinationHandle, endPoint); } } @@ -539,31 +907,77 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Let's send a full update of the agent. This is a synchronous call. AgentData agent = new AgentData(); sp.CopyTo(agent); - agent.Position = position; + if (ctx.OutboundVersion < 0.5f) + agent.Appearance.PackLegacyWearables = true; + agent.Position = agentCircuit.startpos; SetCallbackURL(agent, sp.Scene.RegionInfo); - //sp.ControllingClient.SendTeleportProgress(teleportFlags, "Updating agent..."); + // We will check for an abort before UpdateAgent since UpdateAgent will require an active viewer to + // establish th econnection to the destination which makes it return true. + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} before UpdateAgent", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + + // A common teleport failure occurs when we can send CreateAgent to the + // destination region but the viewer cannot establish the connection (e.g. due to network issues between + // the viewer and the destination). In this case, UpdateAgent timesout after 10 seconds, although then + // there's a further 10 second wait whilst we attempt to tell the destination to delete the agent in Fail(). if (!UpdateAgent(reg, finalDestination, agent, sp)) { - // Region doesn't take it + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after UpdateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: UpdateAgent failed on teleport of {0} to {1} from {2}. Returning avatar to source region.", - sp.Name, finalDestination.RegionName, sp.Scene.RegionInfo.RegionName); - - Fail(sp, finalDestination, logout); + "[ENTITY TRANSFER MODULE]: UpdateAgent failed on teleport of {0} to {1}. Keeping avatar in {2}", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + Fail(sp, finalDestination, logout, currentAgentCircuit.SessionID.ToString(), "Connection between viewer and destination region could not be established."); return; } - sp.ControllingClient.SendTeleportProgress(teleportFlags | (uint)TeleportFlags.DisableCancel, "sending_dest"); + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) + { + m_interRegionTeleportCancels.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after UpdateAgent on client request", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + CleanupFailedInterRegionTeleport(sp, currentAgentCircuit.SessionID.ToString(), finalDestination); + + return; + } m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}", capsPath, sp.Scene.RegionInfo.RegionName, sp.Name); + // We need to set this here to avoid an unlikely race condition when teleporting to a neighbour simulator, + // where that neighbour simulator could otherwise request a child agent create on the source which then + // closes our existing agent which is still signalled as root. + sp.IsChildAgent = true; + + // OK, send TPFinish to the client, so that it starts the process of contacting the destination region if (m_eqModule != null) { - m_eqModule.TeleportFinishEvent(destinationHandle, 13, endPoint, 0, teleportFlags, capsPath, sp.UUID); + m_eqModule.TeleportFinishEvent(destinationHandle, 13, endPoint, 0, teleportFlags, capsPath, sp.UUID, + finalDestination.RegionSizeX, finalDestination.RegionSizeY); } else { @@ -571,31 +985,43 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer teleportFlags, capsPath); } - // Let's set this to true tentatively. This does not trigger OnChildAgent - sp.IsChildAgent = true; - // TeleportFinish makes the client send CompleteMovementIntoRegion (at the destination), which // trigers a whole shebang of things there, including MakeRoot. So let's wait for confirmation // that the client contacted the destination before we close things here. if (!m_entityTransferStateMachine.WaitForAgentArrivedAtDestination(sp.UUID)) { + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after WaitForAgentArrivedAtDestination due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + m_log.WarnFormat( "[ENTITY TRANSFER MODULE]: Teleport of {0} to {1} from {2} failed due to no callback from destination region. Returning avatar to source region.", sp.Name, finalDestination.RegionName, sp.Scene.RegionInfo.RegionName); - - Fail(sp, finalDestination, logout); + + Fail(sp, finalDestination, logout, currentAgentCircuit.SessionID.ToString(), "Destination region did not signal teleport completion."); + return; } m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); +/* + // TODO: This may be 0.6. Check if still needed // For backwards compatibility - if (version == "Unknown" || version == string.Empty) + if (version == 0f) { // CrossAttachmentsIntoNewRegion is a synchronous call. We shouldn't need to wait after it m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Old simulator, sending attachments one by one..."); CrossAttachmentsIntoNewRegion(finalDestination, sp, true); } +*/ // May need to logout or other cleanup AgentHasMovedAway(sp, logout); @@ -606,12 +1032,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Now let's make it officially a child agent sp.MakeChildAgent(); -// sp.Scene.CleanDroppedAttachments(); - // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone - if (NeedsClosing(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) + if (NeedsClosing(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) { + if (!sp.Scene.IncomingPreCloseClient(sp)) + return; + // We need to delay here because Imprudence viewers, unlike v1 or v3, have a short (<200ms, <500ms) delay before // they regard the new region as the current region after receiving the AgentMovementComplete // response. If close is sent before then, it will cause the viewer to quit instead. @@ -620,51 +1047,243 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // an agent cannot teleport back to this region if it has teleported away. Thread.Sleep(2000); - sp.Scene.IncomingCloseAgent(sp.UUID, false); + sp.Scene.CloseAgent(sp.UUID, false); } else { // now we have a child agent in this region. sp.Reset(); } + } - // Commented pending deletion since this method no longer appears to do anything at all -// // REFACTORING PROBLEM. Well, not a problem, but this method is HORRIBLE! -// if (sp.Scene.NeedSceneCacheClear(sp.UUID)) -// { -// m_log.DebugFormat( -// "[ENTITY TRANSFER MODULE]: User {0} is going to another region, profile cache removed", -// sp.UUID); -// } + private void TransferAgent_V2(ScenePresence sp, AgentCircuitData agentCircuit, GridRegion reg, GridRegion finalDestination, + IPEndPoint endPoint, uint teleportFlags, uint oldRegionX, uint newRegionX, uint oldRegionY, uint newRegionY, EntityTransferContext ctx, out string reason) + { + ulong destinationHandle = finalDestination.RegionHandle; + AgentCircuitData currentAgentCircuit = sp.Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); + + // Let's create an agent there if one doesn't exist yet. + // NOTE: logout will always be false for a non-HG teleport. + bool logout = false; + if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) + { + m_interRegionTeleportFailures.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}", + sp.Name, sp.Scene.RegionInfo.RegionName, finalDestination.RegionName, reason); + + sp.ControllingClient.SendTeleportFailed(reason); + + return; + } + + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) + { + m_interRegionTeleportCancels.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after CreateAgent on client request", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + else if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after CreateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); + return; + } + + // Past this point we have to attempt clean up if the teleport fails, so update transfer state. + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); + + IClientIPEndpoint ipepClient; + string capsPath = String.Empty; + float dist = (float)Math.Max(sp.Scene.DefaultDrawDistance, + (float)Math.Max(sp.Scene.RegionInfo.RegionSizeX, sp.Scene.RegionInfo.RegionSizeY)); + if (NeedsNewAgent(dist, oldRegionX, newRegionX, oldRegionY, newRegionY)) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Determined that region {0} at {1},{2} needs new child agent for agent {3} from {4}", + finalDestination.RegionName, newRegionX, newRegionY, sp.Name, Scene.Name); + + //sp.ControllingClient.SendTeleportProgress(teleportFlags, "Creating agent..."); + #region IP Translation for NAT + // Uses ipepClient above + if (sp.ClientView.TryGet(out ipepClient)) + { + endPoint.Address = NetworkUtil.GetIPFor(ipepClient.EndPoint, endPoint.Address); + } + #endregion + capsPath = finalDestination.ServerURI + CapsUtil.GetCapsSeedPath(agentCircuit.CapsPath); + } + else + { + agentCircuit.CapsPath = sp.Scene.CapsModule.GetChildSeed(sp.UUID, reg.RegionHandle); + capsPath = finalDestination.ServerURI + CapsUtil.GetCapsSeedPath(agentCircuit.CapsPath); + } + + // We need to set this here to avoid an unlikely race condition when teleporting to a neighbour simulator, + // where that neighbour simulator could otherwise request a child agent create on the source which then + // closes our existing agent which is still signalled as root. + //sp.IsChildAgent = true; + + // New protocol: send TP Finish directly, without prior ES or EAC. That's what happens in the Linden grid + if (m_eqModule != null) + m_eqModule.TeleportFinishEvent(destinationHandle, 13, endPoint, 0, teleportFlags, capsPath, sp.UUID, + finalDestination.RegionSizeX, finalDestination.RegionSizeY); + else + sp.ControllingClient.SendRegionTeleport(destinationHandle, 13, endPoint, 4, + teleportFlags, capsPath); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}", + capsPath, sp.Scene.RegionInfo.RegionName, sp.Name); + + // Let's send a full update of the agent. + AgentData agent = new AgentData(); + sp.CopyTo(agent); + if (ctx.OutboundVersion < 0.5f) + agent.Appearance.PackLegacyWearables = true; + agent.Position = agentCircuit.startpos; + agent.SenderWantsToWaitForRoot = true; + //SetCallbackURL(agent, sp.Scene.RegionInfo); + + // Reset the do not close flag. This must be done before the destination opens child connections (here + // triggered by UpdateAgent) to avoid race conditions. However, we also want to reset it as late as possible + // to avoid a situation where an unexpectedly early call to Scene.NewUserConnection() wrongly results + // in no close. + sp.DoNotCloseAfterTeleport = false; + + // Send the Update. If this returns true, we know the client has contacted the destination + // via CompleteMovementIntoRegion, so we can let go. + // If it returns false, something went wrong, and we need to abort. + if (!UpdateAgent(reg, finalDestination, agent, sp)) + { + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_interRegionTeleportAborts.Value++; + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after UpdateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + + m_log.WarnFormat( + "[ENTITY TRANSFER MODULE]: UpdateAgent failed on teleport of {0} to {1}. Keeping avatar in {2}", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + Fail(sp, finalDestination, logout, currentAgentCircuit.SessionID.ToString(), "Connection between viewer and destination region could not be established."); + return; + } + + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); + + // Need to signal neighbours whether child agents may need closing irrespective of whether this + // one needed closing. We also need to close child agents as quickly as possible to avoid complicated + // race conditions with rapid agent releporting (e.g. from A1 to a non-neighbour B, back + // to a neighbour A2 then off to a non-neighbour C). Closing child agents any later requires complex + // distributed checks to avoid problems in rapid reteleporting scenarios and where child agents are + // abandoned without proper close by viewer but then re-used by an incoming connection. + sp.CloseChildAgents(newRegionX, newRegionY); + + // May need to logout or other cleanup + AgentHasMovedAway(sp, logout); + + // Well, this is it. The agent is over there. + KillEntity(sp.Scene, sp.LocalId); + + // Now let's make it officially a child agent + sp.MakeChildAgent(); + + // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone + if (NeedsClosing(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) + { + if (!sp.Scene.IncomingPreCloseClient(sp)) + return; + + // RED ALERT!!!! + // PLEASE DO NOT DECREASE THIS WAIT TIME UNDER ANY CIRCUMSTANCES. + // THE VIEWERS SEEM TO NEED SOME TIME AFTER RECEIVING MoveAgentIntoRegion + // BEFORE THEY SETTLE IN THE NEW REGION. + // DECREASING THE WAIT TIME HERE WILL EITHER RESULT IN A VIEWER CRASH OR + // IN THE AVIE BEING PLACED IN INFINITY FOR A COUPLE OF SECONDS. + Thread.Sleep(15000); + + // OK, it got this agent. Let's close everything + // If we shouldn't close the agent due to some other region renewing the connection + // then this will be handled in IncomingCloseAgent under lock conditions + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Closing agent {0} in {1} after teleport", sp.Name, Scene.Name); + + sp.Scene.CloseAgent(sp.UUID, false); + } + else + { + // now we have a child agent in this region. + sp.Reset(); + } } - protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) + /// + /// Clean up an inter-region teleport that did not complete, either because of simulator failure or cancellation. + /// + /// + /// All operations here must be idempotent so that we can call this method at any point in the teleport process + /// up until we send the TeleportFinish event quene event to the viewer. + /// + /// + /// + protected virtual void CleanupFailedInterRegionTeleport(ScenePresence sp, string auth_token, GridRegion finalDestination) { m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); - // Client never contacted destination. Let's restore everything back - sp.ControllingClient.SendTeleportFailed("Problems connecting to destination."); + if (sp.IsChildAgent) // We had set it to child before attempted TP (V1) + { + sp.IsChildAgent = false; + ReInstantiateScripts(sp); - // Fail. Reset it back - sp.IsChildAgent = false; - ReInstantiateScripts(sp); + EnableChildAgents(sp); + } + // Finally, kill the agent we just created at the destination. + // XXX: Possibly this should be done asynchronously. + Scene.SimulationService.CloseAgent(finalDestination, sp.UUID, auth_token); + } - EnableChildAgents(sp); + /// + /// Signal that the inter-region teleport failed and perform cleanup. + /// + /// + /// + /// + /// Human readable reason for teleport failure. Will be sent to client. + protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout, string auth_code, string reason) + { + CleanupFailedInterRegionTeleport(sp, auth_code, finalDestination); - // Finally, kill the agent we just created at the destination. - Scene.SimulationService.CloseAgent(finalDestination, sp.UUID); + m_interRegionTeleportFailures.Value++; - sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); + sp.ControllingClient.SendTeleportFailed( + string.Format( + "Problems connecting to destination {0}, reason: {1}", finalDestination.RegionName, reason)); - m_entityTransferStateMachine.ResetFromTransit(sp.UUID); + sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); } protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, out string reason, out bool logout) { + GridRegion source = new GridRegion(Scene.RegionInfo); + source.RawServerURI = m_GatekeeperURI; + logout = false; - bool success = Scene.SimulationService.CreateAgent(finalDestination, agentCircuit, teleportFlags, out reason); + bool success = Scene.SimulationService.CreateAgent(source, finalDestination, agentCircuit, teleportFlags, out reason); if (success) sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout); @@ -702,14 +1321,32 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer scene.SendKillObject(new List { localID }); } - protected virtual GridRegion GetFinalDestination(GridRegion region) + protected virtual GridRegion GetFinalDestination(GridRegion region, UUID agentID, string agentHomeURI, out string message) { + message = null; return region; } + // This returns 'true' if the new region already has a child agent for our + // incoming agent. The implication is that, if 'false', we have to create the + // child and then teleport into the region. protected virtual bool NeedsNewAgent(float drawdist, uint oldRegionX, uint newRegionX, uint oldRegionY, uint newRegionY) { - return Util.IsOutsideView(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY); + if (m_regionCombinerModule != null && m_regionCombinerModule.IsRootForMegaregion(Scene.RegionInfo.RegionID)) + { + Vector2 swCorner, neCorner; + GetMegaregionViewRange(out swCorner, out neCorner); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Megaregion view of {0} is from {1} to {2} with new agent check for {3},{4}", + Scene.Name, swCorner, neCorner, newRegionX, newRegionY); + + return !(newRegionX >= swCorner.X && newRegionX <= neCorner.X && newRegionY >= swCorner.Y && newRegionY <= neCorner.Y); + } + else + { + return Util.IsOutsideView(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY); + } } protected virtual bool NeedsClosing(float drawdist, uint oldRegionX, uint newRegionX, uint oldRegionY, uint newRegionY, GridRegion reg) @@ -717,20 +1354,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return Util.IsOutsideView(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY); } - protected virtual bool IsOutsideRegion(Scene s, Vector3 pos) - { - if (s.TestBorderCross(pos, Cardinals.N)) - return true; - if (s.TestBorderCross(pos, Cardinals.S)) - return true; - if (s.TestBorderCross(pos, Cardinals.E)) - return true; - if (s.TestBorderCross(pos, Cardinals.W)) - return true; - - return false; - } - #endregion #region Landmark Teleport @@ -758,7 +1381,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #region Teleport Home - public virtual void TeleportHome(UUID id, IClientAPI client) + public virtual void TriggerTeleportHome(UUID id, IClientAPI client) + { + TeleportHome(id, client); + } + + public virtual bool TeleportHome(UUID id, IClientAPI client) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId); @@ -768,12 +1396,20 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (uinfo != null) { + if (uinfo.HomeRegionID == UUID.Zero) + { + // can't find the Home region: Tell viewer and abort + m_log.ErrorFormat("{0} No grid user info found for {1} {2}. Cannot send home.", + LogHeader, client.Name, client.AgentId); + client.SendTeleportFailed("You don't have a home position set."); + return false; + } GridRegion regionInfo = Scene.GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID); if (regionInfo == null) { // can't find the Home region: Tell viewer and abort client.SendTeleportFailed("Your home region could not be found."); - return; + return false; } m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Home region of {0} is {1} ({2}-{3})", @@ -783,13 +1419,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer ((Scene)(client.Scene)).RequestTeleportLocation( client, regionInfo.RegionHandle, uinfo.HomePosition, uinfo.HomeLookAt, (uint)(Constants.TeleportFlags.SetLastToTarget | Constants.TeleportFlags.ViaHome)); + return true; } else { - m_log.ErrorFormat( - "[ENTITY TRANSFER MODULE]: No grid user information found for {0} {1}. Cannot send home.", - client.Name, client.AgentId); + // can't find the Home region: Tell viewer and abort + client.SendTeleportFailed("Your home region could not be found."); } + return false; } #endregion @@ -797,230 +1434,112 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #region Agent Crossings - public bool Cross(ScenePresence agent, bool isFlying) + // Given a position relative to the current region (which has previously been tested to + // see that it is actually outside the current region), find the new region that the + // point is actually in. + // Returns the coordinates and information of the new region or 'null' of it doesn't exist. + public GridRegion GetDestination(Scene scene, UUID agentID, Vector3 pos, + EntityTransferContext ctx, out Vector3 newpos, out string failureReason) { - Scene scene = agent.Scene; - Vector3 pos = agent.AbsolutePosition; - Vector3 newpos = new Vector3(pos.X, pos.Y, pos.Z); - uint neighbourx = scene.RegionInfo.RegionLocX; - uint neighboury = scene.RegionInfo.RegionLocY; - const float boundaryDistance = 1.7f; - Vector3 northCross = new Vector3(0, boundaryDistance, 0); - Vector3 southCross = new Vector3(0, -1 * boundaryDistance, 0); - Vector3 eastCross = new Vector3(boundaryDistance, 0, 0); - Vector3 westCross = new Vector3(-1 * boundaryDistance, 0, 0); - - // distance into new region to place avatar - const float enterDistance = 0.5f; - - if (scene.TestBorderCross(pos + westCross, Cardinals.W)) - { - if (scene.TestBorderCross(pos + northCross, Cardinals.N)) - { - Border b = scene.GetCrossedBorder(pos + northCross, Cardinals.N); - neighboury += (uint)(int)(b.BorderLine.Z / (int)Constants.RegionSize); - } - else if (scene.TestBorderCross(pos + southCross, Cardinals.S)) - { - Border b = scene.GetCrossedBorder(pos + southCross, Cardinals.S); - if (b.TriggerRegionX == 0 && b.TriggerRegionY == 0) - { - neighboury--; - newpos.Y = Constants.RegionSize - enterDistance; - } - else - { - agent.IsInTransit = true; - - neighboury = b.TriggerRegionY; - neighbourx = b.TriggerRegionX; - - Vector3 newposition = pos; - newposition.X += (scene.RegionInfo.RegionLocX - neighbourx) * Constants.RegionSize; - newposition.Y += (scene.RegionInfo.RegionLocY - neighboury) * Constants.RegionSize; - agent.ControllingClient.SendAgentAlertMessage( - String.Format("Moving you to region {0},{1}", neighbourx, neighboury), false); - InformClientToInitateTeleportToLocation(agent, neighbourx, neighboury, newposition, scene); - return true; - } - } - - Border ba = scene.GetCrossedBorder(pos + westCross, Cardinals.W); - if (ba.TriggerRegionX == 0 && ba.TriggerRegionY == 0) - { - neighbourx--; - newpos.X = Constants.RegionSize - enterDistance; - } - else - { - agent.IsInTransit = true; + newpos = pos; + failureReason = string.Empty; + string homeURI = scene.GetAgentHomeURI(agentID); - neighboury = ba.TriggerRegionY; - neighbourx = ba.TriggerRegionX; +// m_log.DebugFormat( +// "[ENTITY TRANSFER MODULE]: Crossing agent {0} at pos {1} in {2}", agent.Name, pos, scene.Name); - Vector3 newposition = pos; - newposition.X += (scene.RegionInfo.RegionLocX - neighbourx) * Constants.RegionSize; - newposition.Y += (scene.RegionInfo.RegionLocY - neighboury) * Constants.RegionSize; - agent.ControllingClient.SendAgentAlertMessage( - String.Format("Moving you to region {0},{1}", neighbourx, neighboury), false); - InformClientToInitateTeleportToLocation(agent, neighbourx, neighboury, newposition, scene); + // Compute world location of the object's position + double presenceWorldX = (double)scene.RegionInfo.WorldLocX + pos.X; + double presenceWorldY = (double)scene.RegionInfo.WorldLocY + pos.Y; - return true; - } + // Call the grid service to lookup the region containing the new position. + GridRegion neighbourRegion = GetRegionContainingWorldLocation(scene.GridService, scene.RegionInfo.ScopeID, + presenceWorldX, presenceWorldY, + Math.Max(scene.RegionInfo.RegionSizeX, scene.RegionInfo.RegionSizeY)); - } - else if (scene.TestBorderCross(pos + eastCross, Cardinals.E)) + if (neighbourRegion != null) { - Border b = scene.GetCrossedBorder(pos + eastCross, Cardinals.E); - neighbourx += (uint)(int)(b.BorderLine.Z / (int)Constants.RegionSize); - newpos.X = enterDistance; + // Compute the entity's position relative to the new region + newpos = new Vector3((float)(presenceWorldX - (double)neighbourRegion.RegionLocX), + (float)(presenceWorldY - (double)neighbourRegion.RegionLocY), + pos.Z); - if (scene.TestBorderCross(pos + southCross, Cardinals.S)) - { - Border ba = scene.GetCrossedBorder(pos + southCross, Cardinals.S); - if (ba.TriggerRegionX == 0 && ba.TriggerRegionY == 0) - { - neighboury--; - newpos.Y = Constants.RegionSize - enterDistance; - } - else - { - agent.IsInTransit = true; - - neighboury = ba.TriggerRegionY; - neighbourx = ba.TriggerRegionX; - Vector3 newposition = pos; - newposition.X += (scene.RegionInfo.RegionLocX - neighbourx) * Constants.RegionSize; - newposition.Y += (scene.RegionInfo.RegionLocY - neighboury) * Constants.RegionSize; - agent.ControllingClient.SendAgentAlertMessage( - String.Format("Moving you to region {0},{1}", neighbourx, neighboury), false); - InformClientToInitateTeleportToLocation(agent, neighbourx, neighboury, newposition, scene); - return true; - } - } - else if (scene.TestBorderCross(pos + northCross, Cardinals.N)) - { - Border c = scene.GetCrossedBorder(pos + northCross, Cardinals.N); - neighboury += (uint)(int)(c.BorderLine.Z / (int)Constants.RegionSize); - newpos.Y = enterDistance; - } - } - else if (scene.TestBorderCross(pos + southCross, Cardinals.S)) - { - Border b = scene.GetCrossedBorder(pos + southCross, Cardinals.S); - if (b.TriggerRegionX == 0 && b.TriggerRegionY == 0) + if (m_bannedRegionCache.IfBanned(neighbourRegion.RegionHandle, agentID)) { - neighboury--; - newpos.Y = Constants.RegionSize - enterDistance; + failureReason = "Cannot region cross into banned parcel"; + neighbourRegion = null; } else { - agent.IsInTransit = true; - - neighboury = b.TriggerRegionY; - neighbourx = b.TriggerRegionX; - Vector3 newposition = pos; - newposition.X += (scene.RegionInfo.RegionLocX - neighbourx) * Constants.RegionSize; - newposition.Y += (scene.RegionInfo.RegionLocY - neighboury) * Constants.RegionSize; - agent.ControllingClient.SendAgentAlertMessage( - String.Format("Moving you to region {0},{1}", neighbourx, neighboury), false); - InformClientToInitateTeleportToLocation(agent, neighbourx, neighboury, newposition, scene); - return true; + // If not banned, make sure this agent is not in the list. + m_bannedRegionCache.Remove(neighbourRegion.RegionHandle, agentID); } - } - else if (scene.TestBorderCross(pos + northCross, Cardinals.N)) - { - Border b = scene.GetCrossedBorder(pos + northCross, Cardinals.N); - neighboury += (uint)(int)(b.BorderLine.Z / (int)Constants.RegionSize); - newpos.Y = enterDistance; - } - - /* - - if (pos.X < boundaryDistance) //West - { - neighbourx--; - newpos.X = Constants.RegionSize - enterDistance; - } - else if (pos.X > Constants.RegionSize - boundaryDistance) // East - { - neighbourx++; - newpos.X = enterDistance; - } - - if (pos.Y < boundaryDistance) // South - { - neighboury--; - newpos.Y = Constants.RegionSize - enterDistance; - } - else if (pos.Y > Constants.RegionSize - boundaryDistance) // North - { - neighboury++; - newpos.Y = enterDistance; - } - */ - ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize)); - - int x = (int)(neighbourx * Constants.RegionSize), y = (int)(neighboury * Constants.RegionSize); - - ExpiringCache r; - DateTime banUntil; - - if (m_bannedRegions.TryGetValue(agent.ControllingClient.AgentId, out r)) - { - if (r.TryGetValue(neighbourHandle, out banUntil)) + // Check to see if we have access to the target region. + if (neighbourRegion != null + && !scene.SimulationService.QueryAccess(neighbourRegion, agentID, homeURI, false, newpos, scene.GetFormatsOffered(), ctx, out failureReason)) { - if (DateTime.Now < banUntil) - return false; - r.Remove(neighbourHandle); + // remember banned + m_bannedRegionCache.Add(neighbourRegion.RegionHandle, agentID); + neighbourRegion = null; } } else { - r = null; + // The destination region just doesn't exist + failureReason = "Cannot cross into non-existent region"; } - GridRegion neighbourRegion = scene.GridService.GetRegionByPosition(scene.RegionInfo.ScopeID, (int)x, (int)y); + if (neighbourRegion == null) + m_log.DebugFormat("{0} GetDestination: region not found. Old region name={1} at <{2},{3}> of size <{4},{5}>. Old pos={6}", + LogHeader, scene.RegionInfo.RegionName, + scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY, + scene.RegionInfo.RegionSizeX, scene.RegionInfo.RegionSizeY, + pos); + else + m_log.DebugFormat("{0} GetDestination: new region={1} at <{2},{3}> of size <{4},{5}>, newpos=<{6},{7}>", + LogHeader, neighbourRegion.RegionName, + neighbourRegion.RegionLocX, neighbourRegion.RegionLocY, neighbourRegion.RegionSizeX, neighbourRegion.RegionSizeY, + newpos.X, newpos.Y); - string reason; - string version; - if (!scene.SimulationService.QueryAccess(neighbourRegion, agent.ControllingClient.AgentId, newpos, out version, out reason)) - { - agent.ControllingClient.SendAlertMessage("Cannot region cross into banned parcel"); - if (r == null) - { - r = new ExpiringCache(); - r.Add(neighbourHandle, DateTime.Now + TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15)); + return neighbourRegion; + } - m_bannedRegions.Add(agent.ControllingClient.AgentId, r, TimeSpan.FromSeconds(45)); - } - else - { - r.Add(neighbourHandle, DateTime.Now + TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15)); - } + public bool Cross(ScenePresence agent, bool isFlying) + { + Vector3 newpos; + EntityTransferContext ctx = new EntityTransferContext(); + string failureReason; + + GridRegion neighbourRegion = GetDestination(agent.Scene, agent.UUID, agent.AbsolutePosition, + ctx, out newpos, out failureReason); + if (neighbourRegion == null) + { + agent.ControllingClient.SendAlertMessage(failureReason); return false; } agent.IsInTransit = true; CrossAgentToNewRegionDelegate d = CrossAgentToNewRegionAsync; - d.BeginInvoke(agent, newpos, neighbourx, neighboury, neighbourRegion, isFlying, version, CrossAgentToNewRegionCompleted, d); + d.BeginInvoke(agent, newpos, neighbourRegion, isFlying, ctx, CrossAgentToNewRegionCompleted, d); + + Scene.EventManager.TriggerCrossAgentToNewRegion(agent, isFlying, neighbourRegion); return true; } - public delegate void InformClientToInitateTeleportToLocationDelegate(ScenePresence agent, uint regionX, uint regionY, + public delegate void InformClientToInitiateTeleportToLocationDelegate(ScenePresence agent, uint regionX, uint regionY, Vector3 position, Scene initiatingScene); - private void InformClientToInitateTeleportToLocation(ScenePresence agent, uint regionX, uint regionY, Vector3 position, Scene initiatingScene) + private void InformClientToInitiateTeleportToLocation(ScenePresence agent, uint regionX, uint regionY, Vector3 position, Scene initiatingScene) { // This assumes that we know what our neighbours are. - InformClientToInitateTeleportToLocationDelegate d = InformClientToInitiateTeleportToLocationAsync; + InformClientToInitiateTeleportToLocationDelegate d = InformClientToInitiateTeleportToLocationAsync; d.BeginInvoke(agent, regionX, regionY, position, initiatingScene, InformClientToInitiateTeleportToLocationCompleted, d); @@ -1030,16 +1549,24 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer Scene initiatingScene) { Thread.Sleep(10000); - + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Auto-reteleporting {0} to correct megaregion location {1},{2},{3} from {4}", + agent.Name, regionX, regionY, position, initiatingScene.Name); + + agent.Scene.RequestTeleportLocation( + agent.ControllingClient, + Util.RegionLocToHandle(regionX, regionY), + position, + agent.Lookat, + (uint)Constants.TeleportFlags.ViaLocation); + + /* IMessageTransferModule im = initiatingScene.RequestModuleInterface(); if (im != null) { UUID gotoLocation = Util.BuildFakeParcelID( - Util.UIntsToLong( - (regionX * - (uint)Constants.RegionSize), - (regionY * - (uint)Constants.RegionSize)), + Util.RegionLocToHandle(regionX, regionY), (uint)(int)position.X, (uint)(int)position.Y, (uint)(int)position.Z); @@ -1065,53 +1592,73 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer }); } + */ } private void InformClientToInitiateTeleportToLocationCompleted(IAsyncResult iar) { - InformClientToInitateTeleportToLocationDelegate icon = - (InformClientToInitateTeleportToLocationDelegate)iar.AsyncState; + InformClientToInitiateTeleportToLocationDelegate icon = + (InformClientToInitiateTeleportToLocationDelegate)iar.AsyncState; icon.EndInvoke(iar); } - public delegate ScenePresence CrossAgentToNewRegionDelegate(ScenePresence agent, Vector3 pos, uint neighbourx, uint neighboury, GridRegion neighbourRegion, bool isFlying, string version); + public bool CrossAgentToNewRegionPrep(ScenePresence agent, GridRegion neighbourRegion) + { + if (neighbourRegion == null) + return false; + + m_entityTransferStateMachine.SetInTransit(agent.UUID); + + agent.RemoveFromPhysicalScene(); + + return true; + } /// /// This Closes child agents on neighbouring regions /// Calls an asynchronous method to do so.. so it doesn't lag the sim. /// - protected ScenePresence CrossAgentToNewRegionAsync( - ScenePresence agent, Vector3 pos, uint neighbourx, uint neighboury, GridRegion neighbourRegion, - bool isFlying, string version) + public ScenePresence CrossAgentToNewRegionAsync( + ScenePresence agent, Vector3 pos, GridRegion neighbourRegion, + bool isFlying, EntityTransferContext ctx) { - if (neighbourRegion == null) - return agent; - try { - m_entityTransferStateMachine.SetInTransit(agent.UUID); + m_log.DebugFormat("{0}: CrossAgentToNewRegionAsync: new region={1} at <{2},{3}>. newpos={4}", + LogHeader, neighbourRegion.RegionName, neighbourRegion.RegionLocX, neighbourRegion.RegionLocY, pos); - ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize)); - - m_log.DebugFormat( - "[ENTITY TRANSFER MODULE]: Crossing agent {0} {1} to {2}-{3} running version {4}", - agent.Firstname, agent.Lastname, neighbourx, neighboury, version); - - Scene m_scene = agent.Scene; + if (!CrossAgentToNewRegionPrep(agent, neighbourRegion)) + { + m_log.DebugFormat("{0}: CrossAgentToNewRegionAsync: prep failed. Resetting transfer state", LogHeader); + m_entityTransferStateMachine.ResetFromTransit(agent.UUID); + } - if (!agent.ValidateAttachments()) - m_log.DebugFormat( - "[ENTITY TRANSFER MODULE]: Failed validation of all attachments for region crossing of {0} from {1} to {2}. Continuing.", - agent.Name, agent.Scene.RegionInfo.RegionName, neighbourRegion.RegionName); + if (!CrossAgentIntoNewRegionMain(agent, pos, neighbourRegion, isFlying, ctx)) + { + m_log.DebugFormat("{0}: CrossAgentToNewRegionAsync: cross main failed. Resetting transfer state", LogHeader); + m_entityTransferStateMachine.ResetFromTransit(agent.UUID); + } - pos = pos + agent.Velocity; - Vector3 vel2 = new Vector3(agent.Velocity.X, agent.Velocity.Y, 0); + CrossAgentToNewRegionPost(agent, pos, neighbourRegion, isFlying, ctx); + } + catch (Exception e) + { + m_log.Error(string.Format("{0}: CrossAgentToNewRegionAsync: failed with exception ", LogHeader), e); + } - agent.RemoveFromPhysicalScene(); + return agent; + } + public bool CrossAgentIntoNewRegionMain(ScenePresence agent, Vector3 pos, GridRegion neighbourRegion, bool isFlying, EntityTransferContext ctx) + { + try + { AgentData cAgent = new AgentData(); agent.CopyTo(cAgent); + if (ctx.OutboundVersion < 0.5f) + cAgent.Appearance.PackLegacyWearables = true; cAgent.Position = pos; + if (isFlying) cAgent.ControlFlags |= (uint)AgentManager.ControlFlags.AGENT_CONTROL_FLY; @@ -1121,102 +1668,121 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Beyond this point, extra cleanup is needed beyond removing transit state m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.Transferring); - if (!m_scene.SimulationService.UpdateAgent(neighbourRegion, cAgent)) + if (!agent.Scene.SimulationService.UpdateAgent(neighbourRegion, cAgent)) { // region doesn't take it m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); + m_log.WarnFormat( + "[ENTITY TRANSFER MODULE]: Region {0} would not accept update for agent {1} on cross attempt. Returning to original region.", + neighbourRegion.RegionName, agent.Name); + ReInstantiateScripts(agent); agent.AddToPhysicalScene(isFlying); - m_entityTransferStateMachine.ResetFromTransit(agent.UUID); - return agent; + return false; } - //AgentCircuitData circuitdata = m_controllingClient.RequestClientInfo(); - agent.ControllingClient.RequestClientInfo(); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ENTITY TRANSFER MODULE]: Problem crossing user {0} to new region {1} from {2}. Exception {3}{4}", + agent.Name, neighbourRegion.RegionName, agent.Scene.RegionInfo.RegionName, e.Message, e.StackTrace); - //m_log.Debug("BEFORE CROSS"); - //Scene.DumpChildrenSeeds(UUID); - //DumpKnownRegions(); - string agentcaps; - if (!agent.KnownRegions.TryGetValue(neighbourRegion.RegionHandle, out agentcaps)) - { - m_log.ErrorFormat("[ENTITY TRANSFER MODULE]: No ENTITY TRANSFER MODULE information for region handle {0}, exiting CrossToNewRegion.", - neighbourRegion.RegionHandle); - return agent; - } - // No turning back - agent.IsChildAgent = true; + // TODO: Might be worth attempting other restoration here such as reinstantiation of scripts, etc. + return false; + } - string capsPath = neighbourRegion.ServerURI + CapsUtil.GetCapsSeedPath(agentcaps); + return true; + } - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} to client {1}", capsPath, agent.UUID); + public void CrossAgentToNewRegionPost(ScenePresence agent, Vector3 pos, GridRegion neighbourRegion, + bool isFlying, EntityTransferContext ctx) + { + agent.ControllingClient.RequestClientInfo(); - if (m_eqModule != null) - { - m_eqModule.CrossRegion( - neighbourHandle, pos, vel2 /* agent.Velocity */, neighbourRegion.ExternalEndPoint, - capsPath, agent.UUID, agent.ControllingClient.SessionId); - } - else - { - agent.ControllingClient.CrossRegion(neighbourHandle, pos, agent.Velocity, neighbourRegion.ExternalEndPoint, - capsPath); - } + string agentcaps; + if (!agent.KnownRegions.TryGetValue(neighbourRegion.RegionHandle, out agentcaps)) + { + m_log.ErrorFormat("[ENTITY TRANSFER MODULE]: No ENTITY TRANSFER MODULE information for region handle {0}, exiting CrossToNewRegion.", + neighbourRegion.RegionHandle); + return; + } - // SUCCESS! - m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination); + // No turning back + agent.IsChildAgent = true; - // Unlike a teleport, here we do not wait for the destination region to confirm the receipt. - m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); + string capsPath = neighbourRegion.ServerURI + CapsUtil.GetCapsSeedPath(agentcaps); - agent.MakeChildAgent(); + m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} to client {1}", capsPath, agent.UUID); + + Vector3 vel2 = new Vector3(agent.Velocity.X, agent.Velocity.Y, 0); + + if (m_eqModule != null) + { + m_eqModule.CrossRegion( + neighbourRegion.RegionHandle, pos + agent.Velocity, vel2 /* agent.Velocity */, + neighbourRegion.ExternalEndPoint, + capsPath, agent.UUID, agent.ControllingClient.SessionId, + neighbourRegion.RegionSizeX, neighbourRegion.RegionSizeY); + } + else + { + m_log.ErrorFormat("{0} Using old CrossRegion packet. Varregion will not work!!", LogHeader); + agent.ControllingClient.CrossRegion(neighbourRegion.RegionHandle, pos + agent.Velocity, agent.Velocity, neighbourRegion.ExternalEndPoint, + capsPath); + } - // FIXME: Possibly this should occur lower down after other commands to close other agents, - // but not sure yet what the side effects would be. - m_entityTransferStateMachine.ResetFromTransit(agent.UUID); + // SUCCESS! + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination); - // now we have a child agent in this region. Request all interesting data about other (root) agents - agent.SendOtherAgentsAvatarDataToMe(); - agent.SendOtherAgentsAppearanceToMe(); + // Unlike a teleport, here we do not wait for the destination region to confirm the receipt. + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); - // Backwards compatibility. Best effort - if (version == "Unknown" || version == string.Empty) - { - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: neighbor with old version, passing attachments one by one..."); - Thread.Sleep(3000); // wait a little now that we're not waiting for the callback - CrossAttachmentsIntoNewRegion(neighbourRegion, agent, true); - } + agent.MakeChildAgent(); - // Next, let's close the child agent connections that are too far away. - agent.CloseChildAgents(neighbourx, neighboury); + // FIXME: Possibly this should occur lower down after other commands to close other agents, + // but not sure yet what the side effects would be. + m_entityTransferStateMachine.ResetFromTransit(agent.UUID); - AgentHasMovedAway(agent, false); + // now we have a child agent in this region. Request all interesting data about other (root) agents + agent.SendOtherAgentsAvatarDataToClient(); + agent.SendOtherAgentsAppearanceToClient(); -// // the user may change their profile information in other region, -// // so the userinfo in UserProfileCache is not reliable any more, delete it -// // REFACTORING PROBLEM. Well, not a problem, but this method is HORRIBLE! -// if (agent.Scene.NeedSceneCacheClear(agent.UUID)) -// { -// m_log.DebugFormat( -// "[ENTITY TRANSFER MODULE]: User {0} is going to another region", agent.UUID); -// } - - //m_log.Debug("AFTER CROSS"); - //Scene.DumpChildrenSeeds(UUID); - //DumpKnownRegions(); - } - catch (Exception e) + // TODO: Check since what version this wasn't needed anymore. May be as old as 0.6 +/* + // Backwards compatibility. Best effort + if (version == 0f) { - m_log.ErrorFormat( - "[ENTITY TRANSFER MODULE]: Problem crossing user {0} to new region {1} from {2}. Exception {3}{4}", - agent.Name, neighbourRegion.RegionName, agent.Scene.RegionInfo.RegionName, e.Message, e.StackTrace); - - // TODO: Might be worth attempting other restoration here such as reinstantiation of scripts, etc. + m_log.DebugFormat("[ENTITY TRANSFER MODULE]: neighbor with old version, passing attachments one by one..."); + Thread.Sleep(3000); // wait a little now that we're not waiting for the callback + CrossAttachmentsIntoNewRegion(neighbourRegion, agent, true); } +*/ + // Next, let's close the child agent connections that are too far away. + uint neighbourx; + uint neighboury; + Util.RegionHandleToRegionLoc(neighbourRegion.RegionHandle, out neighbourx, out neighboury); - return agent; + agent.CloseChildAgents(neighbourx, neighboury); + + AgentHasMovedAway(agent, false); + + // the user may change their profile information in other region, + // so the userinfo in UserProfileCache is not reliable any more, delete it + // REFACTORING PROBLEM. Well, not a problem, but this method is HORRIBLE! +// if (agent.Scene.NeedSceneCacheClear(agent.UUID)) +// { +// m_log.DebugFormat( +// "[ENTITY TRANSFER MODULE]: User {0} is going to another region", agent.UUID); +// } + + //m_log.Debug("AFTER CROSS"); + //Scene.DumpChildrenSeeds(UUID); + //DumpKnownRegions(); + + return; } private void CrossAgentToNewRegionCompleted(IAsyncResult iar) @@ -1256,7 +1822,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer agent.InventoryFolder = UUID.Zero; agent.startpos = new Vector3(128, 128, 70); agent.child = true; - agent.Appearance = sp.Appearance; + agent.Appearance = new AvatarAppearance(); + agent.Appearance.PackLegacyWearables = true; agent.CapsPath = CapsUtil.GetRandomCapsObjectPath(); agent.ChildrenCapSeeds = new Dictionary(sp.Scene.CapsModule.GetChildrenSeeds(sp.UUID)); @@ -1270,7 +1837,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer //foreach (ulong h in agent.ChildrenCapSeeds.Keys) // m_log.DebugFormat("[XXX] --> {0}", h); //m_log.DebugFormat("[XXX] Adding {0}", region.RegionHandle); - agent.ChildrenCapSeeds.Add(region.RegionHandle, agent.CapsPath); + if (agent.ChildrenCapSeeds.ContainsKey(region.RegionHandle)) + { + m_log.WarnFormat( + "[ENTITY TRANSFER]: Overwriting caps seed {0} with {1} for region {2} (handle {3}) for {4} in {5}", + agent.ChildrenCapSeeds[region.RegionHandle], agent.CapsPath, + region.RegionName, region.RegionHandle, sp.Name, Scene.Name); + } + + agent.ChildrenCapSeeds[region.RegionHandle] = agent.CapsPath; if (sp.Scene.CapsModule != null) { @@ -1287,10 +1862,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer agent.Id0 = currentAgentCircuit.Id0; } - InformClientOfNeighbourDelegate d = InformClientOfNeighbourAsync; - d.BeginInvoke(sp, agent, region, region.ExternalEndPoint, true, + IPEndPoint external = region.ExternalEndPoint; + if (external != null) + { + InformClientOfNeighbourDelegate d = InformClientOfNeighbourAsync; + d.BeginInvoke(sp, agent, region, external, true, InformClientOfNeighbourCompleted, d); + } } #endregion @@ -1310,7 +1889,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (m_regionInfo != null) { - neighbours = RequestNeighbours(sp, m_regionInfo.RegionLocX, m_regionInfo.RegionLocY); + neighbours = GetNeighbours(sp, m_regionInfo.RegionLocX, m_regionInfo.RegionLocY); } else { @@ -1336,10 +1915,10 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer List newRegions = NewNeighbours(neighbourHandles, previousRegionNeighbourHandles); List oldRegions = OldNeighbours(neighbourHandles, previousRegionNeighbourHandles); - //Dump("Current Neighbors", neighbourHandles); - //Dump("Previous Neighbours", previousRegionNeighbourHandles); - //Dump("New Neighbours", newRegions); - //Dump("Old Neighbours", oldRegions); +// Dump("Current Neighbors", neighbourHandles); +// Dump("Previous Neighbours", previousRegionNeighbourHandles); +// Dump("New Neighbours", newRegions); +// Dump("Old Neighbours", oldRegions); /// Update the scene presence's known regions here on this region sp.DropOldNeighbours(oldRegions); @@ -1347,8 +1926,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// Collect as many seeds as possible Dictionary seeds; if (sp.Scene.CapsModule != null) - seeds - = new Dictionary(sp.Scene.CapsModule.GetChildrenSeeds(sp.UUID)); + seeds = new Dictionary(sp.Scene.CapsModule.GetChildrenSeeds(sp.UUID)); else seeds = new Dictionary(); @@ -1368,7 +1946,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer agent.InventoryFolder = UUID.Zero; agent.startpos = sp.AbsolutePosition + CalculateOffset(sp, neighbour); agent.child = true; - agent.Appearance = sp.Appearance; + agent.Appearance = new AvatarAppearance(); + agent.Appearance.PackLegacyWearables = true; if (currentAgentCircuit != null) { agent.ServiceURLs = currentAgentCircuit.ServiceURLs; @@ -1418,6 +1997,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer newAgent = true; else newAgent = false; +// continue; if (neighbour.RegionHandle != sp.Scene.RegionInfo.RegionHandle) { @@ -1464,15 +2044,195 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } } + // Computes the difference between two region bases. + // Returns a vector of world coordinates (meters) from base of first region to the second. + // The first region is the home region of the passed scene presence. Vector3 CalculateOffset(ScenePresence sp, GridRegion neighbour) { - int rRegionX = (int)sp.Scene.RegionInfo.RegionLocX; - int rRegionY = (int)sp.Scene.RegionInfo.RegionLocY; + /* + int rRegionX = (int)sp.Scene.RegionInfo.LegacyRegionLocX; + int rRegionY = (int)sp.Scene.RegionInfo.LegacyRegionLocY; int tRegionX = neighbour.RegionLocX / (int)Constants.RegionSize; int tRegionY = neighbour.RegionLocY / (int)Constants.RegionSize; int shiftx = (rRegionX - tRegionX) * (int)Constants.RegionSize; int shifty = (rRegionY - tRegionY) * (int)Constants.RegionSize; return new Vector3(shiftx, shifty, 0f); + */ + return new Vector3( sp.Scene.RegionInfo.WorldLocX - neighbour.RegionLocX, + sp.Scene.RegionInfo.WorldLocY - neighbour.RegionLocY, + 0f); + } + + public GridRegion GetRegionContainingWorldLocation(IGridService pGridService, UUID pScopeID, double px, double py) + { + // Since we don't know how big the regions could be, we have to search a very large area + // to find possible regions. + return GetRegionContainingWorldLocation(pGridService, pScopeID, px, py, Constants.MaximumRegionSize); + } + + #region NotFoundLocationCache class + // A collection of not found locations to make future lookups 'not found' lookups quick. + // A simple expiring cache that keeps not found locations for some number of seconds. + // A 'not found' location is presumed to be anywhere in the minimum sized region that + // contains that point. A conservitive estimate. + private class NotFoundLocationCache + { + private struct NotFoundLocation + { + public double minX, maxX, minY, maxY; + public DateTime expireTime; + } + private List m_notFoundLocations = new List(); + public NotFoundLocationCache() + { + } + // Add an area to the list of 'not found' places. The area is the snapped region + // area around the added point. + public void Add(double pX, double pY) + { + lock (m_notFoundLocations) + { + if (!LockedContains(pX, pY)) + { + NotFoundLocation nfl = new NotFoundLocation(); + // A not found location is not found for at least a whole region sized area + nfl.minX = pX - (pX % (double)Constants.RegionSize); + nfl.minY = pY - (pY % (double)Constants.RegionSize); + nfl.maxX = nfl.minX + (double)Constants.RegionSize; + nfl.maxY = nfl.minY + (double)Constants.RegionSize; + nfl.expireTime = DateTime.Now + TimeSpan.FromSeconds(30); + m_notFoundLocations.Add(nfl); + } + } + + } + // Test to see of this point is in any of the 'not found' areas. + // Return 'true' if the point is found inside the 'not found' areas. + public bool Contains(double pX, double pY) + { + bool ret = false; + lock (m_notFoundLocations) + ret = LockedContains(pX, pY); + return ret; + } + private bool LockedContains(double pX, double pY) + { + bool ret = false; + this.DoExpiration(); + foreach (NotFoundLocation nfl in m_notFoundLocations) + { + if (pX >= nfl.minX && pX < nfl.maxX && pY >= nfl.minY && pY < nfl.maxY) + { + ret = true; + break; + } + } + return ret; + } + private void DoExpiration() + { + List m_toRemove = null; + DateTime now = DateTime.Now; + foreach (NotFoundLocation nfl in m_notFoundLocations) + { + if (nfl.expireTime < now) + { + if (m_toRemove == null) + m_toRemove = new List(); + m_toRemove.Add(nfl); + } + } + if (m_toRemove != null) + { + foreach (NotFoundLocation nfl in m_toRemove) + m_notFoundLocations.Remove(nfl); + m_toRemove.Clear(); + } + } + } + #endregion // NotFoundLocationCache class + private NotFoundLocationCache m_notFoundLocationCache = new NotFoundLocationCache(); + + // Given a world position (fractional meter coordinate), get the GridRegion info for + // the region containing that point. + // Someday this should be a method on GridService. + // 'pSizeHint' is the size of the source region but since the destination point can be anywhere + // the size of the target region is unknown thus the search area might have to be very large. + // Return 'null' if no such region exists. + public GridRegion GetRegionContainingWorldLocation(IGridService pGridService, UUID pScopeID, + double px, double py, uint pSizeHint) + { + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: query, loc=<{1},{2}>", LogHeader, px, py); + GridRegion ret = null; + const double fudge = 2.0; + + // One problem with this routine is negative results. That is, this can be called lots of times + // for regions that don't exist. m_notFoundLocationCache remembers 'not found' results so they + // will be quick 'not found's next time. + // NotFoundLocationCache is an expiring cache so it will eventually forget about 'not found' and + // thus re-ask the GridService about the location. + if (m_notFoundLocationCache.Contains(px, py)) + { + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: Not found via cache. loc=<{1},{2}>", LogHeader, px, py); + return null; + } + + // As an optimization, since most regions will be legacy sized regions (256x256), first try to get + // the region at the appropriate legacy region location. + uint possibleX = (uint)Math.Floor(px); + possibleX -= possibleX % Constants.RegionSize; + uint possibleY = (uint)Math.Floor(py); + possibleY -= possibleY % Constants.RegionSize; + ret = pGridService.GetRegionByPosition(pScopeID, (int)possibleX, (int)possibleY); + if (ret != null) + { + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: Found region using legacy size. rloc=<{1},{2}>. Rname={3}", + LogHeader, possibleX, possibleY, ret.RegionName); + } + + if (ret == null) + { + // If the simple lookup failed, search the larger area for a region that contains this point + double range = (double)pSizeHint + fudge; + while (ret == null && range <= (Constants.MaximumRegionSize + Constants.RegionSize)) + { + // Get from the grid service a list of regions that might contain this point. + // The region origin will be in the zero direction so only subtract the range. + List possibleRegions = pGridService.GetRegionRange(pScopeID, + (int)(px - range), (int)(px), + (int)(py - range), (int)(py)); + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: possibleRegions cnt={1}, range={2}", + LogHeader, possibleRegions.Count, range); + if (possibleRegions != null && possibleRegions.Count > 0) + { + // If we found some regions, check to see if the point is within + foreach (GridRegion gr in possibleRegions) + { + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: possibleRegion nm={1}, regionLoc=<{2},{3}>, regionSize=<{4},{5}>", + LogHeader, gr.RegionName, gr.RegionLocX, gr.RegionLocY, gr.RegionSizeX, gr.RegionSizeY); + if (px >= (double)gr.RegionLocX && px < (double)(gr.RegionLocX + gr.RegionSizeX) + && py >= (double)gr.RegionLocY && py < (double)(gr.RegionLocY + gr.RegionSizeY)) + { + // Found a region that contains the point + ret = gr; + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: found. RegionName={1}", LogHeader, ret.RegionName); + break; + } + } + } + // Larger search area for next time around if not found + range *= 2; + } + } + + if (ret == null) + { + // remember this location was not found so we can quickly not find it next time + m_notFoundLocationCache.Add(px, py); + m_log.DebugFormat("{0} GetRegionContainingWorldLocation: Not found. Remembering loc=<{1},{2}>", LogHeader, px, py); + } + + return ret; } private void InformClientOfNeighbourCompleted(IAsyncResult iar) @@ -1500,7 +2260,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer Thread.Sleep(500); Scene scene = sp.Scene; - + m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Informing {0} {1} about neighbour {2} {3} at ({4},{5})", sp.Name, sp.UUID, reg.RegionName, endPoint, reg.RegionCoordX, reg.RegionCoordY); @@ -1509,7 +2269,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer string reason = String.Empty; - bool regionAccepted = scene.SimulationService.CreateAgent(reg, a, (uint)TeleportFlags.Default, out reason); + bool regionAccepted = scene.SimulationService.CreateAgent(null, reg, a, (uint)TeleportFlags.Default, out reason); if (regionAccepted && newAgent) { @@ -1523,12 +2283,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } #endregion - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: {0} is sending {1} EnableSimulator for neighbour region {2} @ {3} " + - "and EstablishAgentCommunication with seed cap {4}", - scene.RegionInfo.RegionName, sp.Name, reg.RegionName, reg.RegionHandle, capsPath); + m_log.DebugFormat("{0} {1} is sending {2} EnableSimulator for neighbour region {3}(loc=<{4},{5}>,siz=<{6},{7}>) " + + "and EstablishAgentCommunication with seed cap {8}", LogHeader, + scene.RegionInfo.RegionName, sp.Name, + reg.RegionName, reg.RegionLocX, reg.RegionLocY, reg.RegionSizeX, reg.RegionSizeY , capsPath); - m_eqModule.EnableSimulator(reg.RegionHandle, endPoint, sp.UUID); - m_eqModule.EstablishAgentCommunication(sp.UUID, endPoint, capsPath); + m_eqModule.EnableSimulator(reg.RegionHandle, endPoint, sp.UUID, reg.RegionSizeX, reg.RegionSizeY); + m_eqModule.EstablishAgentCommunication(sp.UUID, endPoint, capsPath, reg.RegionHandle, reg.RegionSizeX, reg.RegionSizeY); } else { @@ -1546,68 +2307,86 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } /// - /// Return the list of regions that are considered to be neighbours to the given scene. + /// Gets the range considered in view of this megaregion (assuming this is a megaregion). + /// + /// Expressed in 256m units + /// + /// + private void GetMegaregionViewRange(out Vector2 swCorner, out Vector2 neCorner) + { + Vector2 extent = Vector2.Zero; + + if (m_regionCombinerModule != null) + { + Vector2 megaRegionSize = m_regionCombinerModule.GetSizeOfMegaregion(Scene.RegionInfo.RegionID); + extent.X = (float)Util.WorldToRegionLoc((uint)megaRegionSize.X); + extent.Y = (float)Util.WorldToRegionLoc((uint)megaRegionSize.Y); + } + + swCorner.X = Scene.RegionInfo.RegionLocX - 1; + swCorner.Y = Scene.RegionInfo.RegionLocY - 1; + neCorner.X = Scene.RegionInfo.RegionLocX + extent.X; + neCorner.Y = Scene.RegionInfo.RegionLocY + extent.Y; + } + + /// + /// Return the list of online regions that are considered to be neighbours to the given scene. /// - /// + /// /// /// /// - protected List RequestNeighbours(ScenePresence avatar, uint pRegionLocX, uint pRegionLocY) + protected List GetNeighbours(ScenePresence avatar, uint pRegionLocX, uint pRegionLocY) { Scene pScene = avatar.Scene; RegionInfo m_regionInfo = pScene.RegionInfo; - - Border[] northBorders = pScene.NorthBorders.ToArray(); - Border[] southBorders = pScene.SouthBorders.ToArray(); - Border[] eastBorders = pScene.EastBorders.ToArray(); - Border[] westBorders = pScene.WestBorders.ToArray(); + List neighbours; // Leaving this as a "megaregions" computation vs "non-megaregions" computation; it isn't // clear what should be done with a "far view" given that megaregions already extended the // view to include everything in the megaregion - if (northBorders.Length <= 1 && southBorders.Length <= 1 && eastBorders.Length <= 1 && westBorders.Length <= 1) + if (m_regionCombinerModule == null || !m_regionCombinerModule.IsRootForMegaregion(Scene.RegionInfo.RegionID)) { - int dd = avatar.DrawDistance < Constants.RegionSize ? (int)Constants.RegionSize : (int)avatar.DrawDistance; + // The area to check is as big as the current region. + // We presume all adjacent regions are the same size as this region. + uint dd = Math.Max((uint)avatar.Scene.DefaultDrawDistance, + Math.Max(Scene.RegionInfo.RegionSizeX, Scene.RegionInfo.RegionSizeY)); - int startX = (int)pRegionLocX * (int)Constants.RegionSize - dd + (int)(Constants.RegionSize/2); - int startY = (int)pRegionLocY * (int)Constants.RegionSize - dd + (int)(Constants.RegionSize/2); + uint startX = Util.RegionToWorldLoc(pRegionLocX) - dd + Constants.RegionSize/2; + uint startY = Util.RegionToWorldLoc(pRegionLocY) - dd + Constants.RegionSize/2; - int endX = (int)pRegionLocX * (int)Constants.RegionSize + dd + (int)(Constants.RegionSize/2); - int endY = (int)pRegionLocY * (int)Constants.RegionSize + dd + (int)(Constants.RegionSize/2); + uint endX = Util.RegionToWorldLoc(pRegionLocX) + dd + Constants.RegionSize/2; + uint endY = Util.RegionToWorldLoc(pRegionLocY) + dd + Constants.RegionSize/2; - List neighbours = - avatar.Scene.GridService.GetRegionRange(m_regionInfo.ScopeID, startX, endX, startY, endY); - - neighbours.RemoveAll(delegate(GridRegion r) { return r.RegionID == m_regionInfo.RegionID; }); - return neighbours; + neighbours + = avatar.Scene.GridService.GetRegionRange( + m_regionInfo.ScopeID, (int)startX, (int)endX, (int)startY, (int)endY); } else { - Vector2 extent = Vector2.Zero; - for (int i = 0; i < eastBorders.Length; i++) - { - extent.X = (eastBorders[i].BorderLine.Z > extent.X) ? eastBorders[i].BorderLine.Z : extent.X; - } - for (int i = 0; i < northBorders.Length; i++) - { - extent.Y = (northBorders[i].BorderLine.Z > extent.Y) ? northBorders[i].BorderLine.Z : extent.Y; - } - - // Loss of fraction on purpose - extent.X = ((int)extent.X / (int)Constants.RegionSize) + 1; - extent.Y = ((int)extent.Y / (int)Constants.RegionSize) + 1; + Vector2 swCorner, neCorner; + GetMegaregionViewRange(out swCorner, out neCorner); - int startX = (int)(pRegionLocX - 1) * (int)Constants.RegionSize; - int startY = (int)(pRegionLocY - 1) * (int)Constants.RegionSize; + neighbours + = pScene.GridService.GetRegionRange( + m_regionInfo.ScopeID, + (int)Util.RegionToWorldLoc((uint)swCorner.X), (int)Util.RegionToWorldLoc((uint)neCorner.X), + (int)Util.RegionToWorldLoc((uint)swCorner.Y), (int)Util.RegionToWorldLoc((uint)neCorner.Y)); + } - int endX = ((int)pRegionLocX + (int)extent.X) * (int)Constants.RegionSize; - int endY = ((int)pRegionLocY + (int)extent.Y) * (int)Constants.RegionSize; +// neighbours.ForEach( +// n => +// m_log.DebugFormat( +// "[ENTITY TRANSFER MODULE]: Region flags for {0} as seen by {1} are {2}", +// n.RegionName, Scene.Name, n.RegionFlags != null ? n.RegionFlags.ToString() : "not present")); - List neighbours = pScene.GridService.GetRegionRange(m_regionInfo.ScopeID, startX, endX, startY, endY); - neighbours.RemoveAll(delegate(GridRegion r) { return r.RegionID == m_regionInfo.RegionID; }); + // The r.RegionFlags == null check only needs to be made for simulators before 2015-01-14 (pre 0.8.1). + neighbours.RemoveAll( + r => + r.RegionID == m_regionInfo.RegionID + || (r.RegionFlags != null && (r.RegionFlags & OpenSim.Framework.RegionFlags.RegionOnline) == 0)); - return neighbours; - } + return neighbours; } private List NewNeighbours(List currentNeighbours, List previousNeighbours) @@ -1665,10 +2444,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// Move the given scene object into a new region depending on which region its absolute position has moved /// into. /// - /// This method locates the new region handle and offsets the prim position for the new region + /// Using the objects new world location, ask the grid service for a the new region and adjust the prim + /// position to be relative to the new region. /// - /// the attempted out of region position of the scene object /// the scene object that we're crossing + /// the attempted out of region position of the scene object. This position is + /// relative to the region the object currently is in. + /// if 'true', the deletion of the client from the region is not broadcast to the clients public void Cross(SceneObjectGroup grp, Vector3 attemptedPosition, bool silent) { if (grp == null) @@ -1694,204 +2476,49 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - int thisx = (int)scene.RegionInfo.RegionLocX; - int thisy = (int)scene.RegionInfo.RegionLocY; - Vector3 EastCross = new Vector3(0.1f, 0, 0); - Vector3 WestCross = new Vector3(-0.1f, 0, 0); - Vector3 NorthCross = new Vector3(0, 0.1f, 0); - Vector3 SouthCross = new Vector3(0, -0.1f, 0); - - - // use this if no borders were crossed! - ulong newRegionHandle - = Util.UIntsToLong((uint)((thisx) * Constants.RegionSize), - (uint)((thisy) * Constants.RegionSize)); - - Vector3 pos = attemptedPosition; - - int changeX = 1; - int changeY = 1; - - if (scene.TestBorderCross(attemptedPosition + WestCross, Cardinals.W)) - { - if (scene.TestBorderCross(attemptedPosition + SouthCross, Cardinals.S)) - { - - Border crossedBorderx = scene.GetCrossedBorder(attemptedPosition + WestCross, Cardinals.W); - - if (crossedBorderx.BorderLine.Z > 0) - { - pos.X = ((pos.X + crossedBorderx.BorderLine.Z)); - changeX = (int)(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.X = ((pos.X + Constants.RegionSize)); - - Border crossedBordery = scene.GetCrossedBorder(attemptedPosition + SouthCross, Cardinals.S); - //(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize) - - if (crossedBordery.BorderLine.Z > 0) - { - pos.Y = ((pos.Y + crossedBordery.BorderLine.Z)); - changeY = (int)(crossedBordery.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.Y = ((pos.Y + Constants.RegionSize)); - - - - newRegionHandle - = Util.UIntsToLong((uint)((thisx - changeX) * Constants.RegionSize), - (uint)((thisy - changeY) * Constants.RegionSize)); - // x - 1 - // y - 1 - } - else if (scene.TestBorderCross(attemptedPosition + NorthCross, Cardinals.N)) - { - Border crossedBorderx = scene.GetCrossedBorder(attemptedPosition + WestCross, Cardinals.W); - - if (crossedBorderx.BorderLine.Z > 0) - { - pos.X = ((pos.X + crossedBorderx.BorderLine.Z)); - changeX = (int)(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.X = ((pos.X + Constants.RegionSize)); - - - Border crossedBordery = scene.GetCrossedBorder(attemptedPosition + SouthCross, Cardinals.S); - //(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize) - - if (crossedBordery.BorderLine.Z > 0) - { - pos.Y = ((pos.Y + crossedBordery.BorderLine.Z)); - changeY = (int)(crossedBordery.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.Y = ((pos.Y + Constants.RegionSize)); - - newRegionHandle - = Util.UIntsToLong((uint)((thisx - changeX) * Constants.RegionSize), - (uint)((thisy + changeY) * Constants.RegionSize)); - // x - 1 - // y + 1 - } - else - { - Border crossedBorderx = scene.GetCrossedBorder(attemptedPosition + WestCross, Cardinals.W); - - if (crossedBorderx.BorderLine.Z > 0) - { - pos.X = ((pos.X + crossedBorderx.BorderLine.Z)); - changeX = (int)(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.X = ((pos.X + Constants.RegionSize)); - - newRegionHandle - = Util.UIntsToLong((uint)((thisx - changeX) * Constants.RegionSize), - (uint)(thisy * Constants.RegionSize)); - // x - 1 - } - } - else if (scene.TestBorderCross(attemptedPosition + EastCross, Cardinals.E)) - { - if (scene.TestBorderCross(attemptedPosition + SouthCross, Cardinals.S)) - { - - pos.X = ((pos.X - Constants.RegionSize)); - Border crossedBordery = scene.GetCrossedBorder(attemptedPosition + SouthCross, Cardinals.S); - //(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize) - - if (crossedBordery.BorderLine.Z > 0) - { - pos.Y = ((pos.Y + crossedBordery.BorderLine.Z)); - changeY = (int)(crossedBordery.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.Y = ((pos.Y + Constants.RegionSize)); - + // Remember the old group position in case the region lookup fails so position can be restored. + Vector3 oldGroupPosition = grp.RootPart.GroupPosition; - newRegionHandle - = Util.UIntsToLong((uint)((thisx + changeX) * Constants.RegionSize), - (uint)((thisy - changeY) * Constants.RegionSize)); - // x + 1 - // y - 1 - } - else if (scene.TestBorderCross(attemptedPosition + NorthCross, Cardinals.N)) - { - pos.X = ((pos.X - Constants.RegionSize)); - pos.Y = ((pos.Y - Constants.RegionSize)); - newRegionHandle - = Util.UIntsToLong((uint)((thisx + changeX) * Constants.RegionSize), - (uint)((thisy + changeY) * Constants.RegionSize)); - // x + 1 - // y + 1 - } - else - { - pos.X = ((pos.X - Constants.RegionSize)); - newRegionHandle - = Util.UIntsToLong((uint)((thisx + changeX) * Constants.RegionSize), - (uint)(thisy * Constants.RegionSize)); - // x + 1 - } - } - else if (scene.TestBorderCross(attemptedPosition + SouthCross, Cardinals.S)) - { - Border crossedBordery = scene.GetCrossedBorder(attemptedPosition + SouthCross, Cardinals.S); - //(crossedBorderx.BorderLine.Z / (int)Constants.RegionSize) + // Compute the absolute position of the object. + double objectWorldLocX = (double)scene.RegionInfo.WorldLocX + attemptedPosition.X; + double objectWorldLocY = (double)scene.RegionInfo.WorldLocY + attemptedPosition.Y; - if (crossedBordery.BorderLine.Z > 0) - { - pos.Y = ((pos.Y + crossedBordery.BorderLine.Z)); - changeY = (int)(crossedBordery.BorderLine.Z / (int)Constants.RegionSize); - } - else - pos.Y = ((pos.Y + Constants.RegionSize)); + // Ask the grid service for the region that contains the passed address + GridRegion destination = GetRegionContainingWorldLocation(scene.GridService, scene.RegionInfo.ScopeID, + objectWorldLocX, objectWorldLocY); - newRegionHandle - = Util.UIntsToLong((uint)(thisx * Constants.RegionSize), (uint)((thisy - changeY) * Constants.RegionSize)); - // y - 1 - } - else if (scene.TestBorderCross(attemptedPosition + NorthCross, Cardinals.N)) + Vector3 pos = Vector3.Zero; + if (destination != null) { - - pos.Y = ((pos.Y - Constants.RegionSize)); - newRegionHandle - = Util.UIntsToLong((uint)(thisx * Constants.RegionSize), (uint)((thisy + changeY) * Constants.RegionSize)); - // y + 1 + // Adjust the object's relative position from the old region (attemptedPosition) + // to be relative to the new region (pos). + pos = new Vector3( (float)(objectWorldLocX - (double)destination.RegionLocX), + (float)(objectWorldLocY - (double)destination.RegionLocY), + attemptedPosition.Z); } - // Offset the positions for the new region across the border - Vector3 oldGroupPosition = grp.RootPart.GroupPosition; - - // If we fail to cross the border, then reset the position of the scene object on that border. - uint x = 0, y = 0; - Utils.LongToUInts(newRegionHandle, out x, out y); - GridRegion destination = scene.GridService.GetRegionByPosition(scene.RegionInfo.ScopeID, (int)x, (int)y); - if (destination == null || !CrossPrimGroupIntoNewRegion(destination, pos, grp, silent)) { - m_log.InfoFormat("[ENTITY TRANSFER MODULE] cross region transfer failed for object {0}",grp.UUID); + m_log.InfoFormat("[ENTITY TRANSFER MODULE] cross region transfer failed for object {0}", grp.UUID); // We are going to move the object back to the old position so long as the old position // is in the region - oldGroupPosition.X = Util.Clamp(oldGroupPosition.X,1.0f,(float)Constants.RegionSize-1); - oldGroupPosition.Y = Util.Clamp(oldGroupPosition.Y,1.0f,(float)Constants.RegionSize-1); - oldGroupPosition.Z = Util.Clamp(oldGroupPosition.Z,1.0f,4096.0f); + oldGroupPosition.X = Util.Clamp(oldGroupPosition.X, 1.0f, (float)(scene.RegionInfo.RegionSizeX - 1)); + oldGroupPosition.Y = Util.Clamp(oldGroupPosition.Y, 1.0f, (float)(scene.RegionInfo.RegionSizeY - 1)); + oldGroupPosition.Z = Util.Clamp(oldGroupPosition.Z, 1.0f, Constants.RegionHeight); - grp.RootPart.GroupPosition = oldGroupPosition; + grp.AbsolutePosition = oldGroupPosition; + grp.Velocity = Vector3.Zero; + if (grp.RootPart.PhysActor != null) + grp.RootPart.PhysActor.CrossingFailure(); - // Need to turn off the physics flags, otherwise the object will continue to attempt to - // move out of the region creating an infinite loop of failed attempts to cross - grp.UpdatePrimFlags(grp.RootPart.LocalId,false,grp.IsTemporary,grp.IsPhantom,false); + if (grp.RootPart.KeyframeMotion != null) + grp.RootPart.KeyframeMotion.CrossingFailure(); grp.ScheduleGroupForFullUpdate(); } } - /// /// Move the given scene object into a new region /// @@ -1942,17 +2569,30 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer grp, e); } } +/* + * done on caller ( not in attachments crossing for now) else { + if (!grp.IsDeleted) { PhysicsActor pa = grp.RootPart.PhysActor; if (pa != null) + { pa.CrossingFailure(); + if (grp.RootPart.KeyframeMotion != null) + { + // moved to KeyframeMotion.CrossingFailure +// grp.RootPart.Velocity = Vector3.Zero; + grp.RootPart.KeyframeMotion.CrossingFailure(); +// grp.SendGroupRootTerseUpdate(); + } + } } m_log.ErrorFormat("[ENTITY TRANSFER MODULE]: Prim crossing failed for {0}", grp); } + */ } else { @@ -2007,7 +2647,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer public bool IsInTransit(UUID id) { - return m_entityTransferStateMachine.IsInTransit(id); + return m_entityTransferStateMachine.GetAgentTransferState(id) != null; } protected void ReInstantiateScripts(ScenePresence sp) @@ -2036,5 +2676,69 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } #endregion + public virtual bool HandleIncomingSceneObject(SceneObjectGroup so, Vector3 newPosition) + { + // If the user is banned, we won't let any of their objects + // enter. Period. + // + if (Scene.RegionInfo.EstateSettings.IsBanned(so.OwnerID)) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Denied prim crossing of {0} {1} into {2} for banned avatar {3}", + so.Name, so.UUID, Scene.Name, so.OwnerID); + + return false; + } + + if (newPosition != Vector3.Zero) + so.RootPart.GroupPosition = newPosition; + + if (!Scene.AddSceneObject(so)) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Problem adding scene object {0} {1} into {2} ", + so.Name, so.UUID, Scene.Name); + + return false; + } + + if (!so.IsAttachment) + { + // FIXME: It would be better to never add the scene object at all rather than add it and then delete + // it + if (!Scene.Permissions.CanObjectEntry(so.UUID, true, so.AbsolutePosition)) + { + // Deny non attachments based on parcel settings + // + m_log.Info("[ENTITY TRANSFER MODULE]: Denied prim crossing because of parcel settings"); + + Scene.DeleteSceneObject(so, false); + + return false; + } + + // For attachments, we need to wait until the agent is root + // before we restart the scripts, or else some functions won't work. + so.RootPart.ParentGroup.CreateScriptInstances( + 0, false, Scene.DefaultScriptEngine, GetStateSource(so)); + + so.ResumeScripts(); + + if (so.RootPart.KeyframeMotion != null) + so.RootPart.KeyframeMotion.UpdateSceneObject(so); + } + + return true; + } + + private int GetStateSource(SceneObjectGroup sog) + { + ScenePresence sp = Scene.GetScenePresence(sog.OwnerID); + + if (sp != null) + return sp.GetStateSource(); + + return 2; // StateSource.PrimCrossing + } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs index d0cab49..a3109e0 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs @@ -38,7 +38,7 @@ using OpenSim.Framework.Capabilities; using OpenSim.Framework.Client; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Services.Interfaces; using GridRegion = OpenSim.Services.Interfaces.GridRegion; @@ -51,10 +51,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// This is a state machine. /// /// [Entry] => Preparing - /// Preparing => { Transferring || CleaningUp || [Exit] } - /// Transferring => { ReceivedAtDestination || CleaningUp } - /// ReceivedAtDestination => CleaningUp + /// Preparing => { Transferring || Cancelling || CleaningUp || Aborting || [Exit] } + /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp || Aborting } + /// Cancelling => CleaningUp || Aborting + /// ReceivedAtDestination => CleaningUp || Aborting /// CleaningUp => [Exit] + /// Aborting => [Exit] /// /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp /// However, any state can transition to CleaningUp if the teleport has failed. @@ -64,7 +66,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer Preparing, // The agent is being prepared for transfer Transferring, // The agent is in the process of being transferred to a destination ReceivedAtDestination, // The destination has notified us that the agent has been successfully received - CleaningUp // The agent is being changed to child/removed after a transfer + CleaningUp, // The agent is being changed to child/removed after a transfer + Cancelling, // The user has cancelled the teleport but we have yet to act upon this. + Aborting // The transfer is aborting. Unlike Cancelling, no compensating actions should be performed } /// @@ -73,6 +77,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer public class EntityTransferStateMachine { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[ENTITY TRANSFER STATE MACHINE]"; /// /// If true then on a teleport, the source region waits for a callback from the destination region. If @@ -96,6 +101,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// true if the agent was not already in transit, false if it was internal bool SetInTransit(UUID id) { + m_log.DebugFormat("{0} SetInTransit. agent={1}, newState=Preparing", LogHeader, id); lock (m_agentsInTransit) { if (!m_agentsInTransit.ContainsKey(id)) @@ -115,42 +121,117 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// /// /// Illegal transitions will throw an Exception - internal void UpdateInTransit(UUID id, AgentTransferState newState) + internal bool UpdateInTransit(UUID id, AgentTransferState newState) { + m_log.DebugFormat("{0} UpdateInTransit. agent={1}, newState={2}", LogHeader, id, newState); + + bool transitionOkay = false; + + // We don't want to throw an exception on cancel since this can come it at any time. + bool failIfNotOkay = true; + + // Should be a failure message if failure is not okay. + string failureMessage = null; + + AgentTransferState? oldState = null; + lock (m_agentsInTransit) { // Illegal to try and update an agent that's not actually in transit. if (!m_agentsInTransit.ContainsKey(id)) - throw new Exception( - string.Format( - "Agent with ID {0} is not registered as in transit in {1}", - id, m_mod.Scene.RegionInfo.RegionName)); - - AgentTransferState oldState = m_agentsInTransit[id]; + { + if (newState != AgentTransferState.Cancelling && newState != AgentTransferState.Aborting) + failureMessage = string.Format( + "Agent with ID {0} is not registered as in transit in {1}", + id, m_mod.Scene.RegionInfo.RegionName); + else + failIfNotOkay = false; + } + else + { + oldState = m_agentsInTransit[id]; - bool transitionOkay = false; + if (newState == AgentTransferState.Aborting) + { + transitionOkay = true; + } + else if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) + { + transitionOkay = true; + } + else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing) + { + transitionOkay = true; + } + else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring) + { + transitionOkay = true; + } + else + { + if (newState == AgentTransferState.Cancelling + && (oldState == AgentTransferState.Preparing || oldState == AgentTransferState.Transferring)) + { + transitionOkay = true; + } + else + { + failIfNotOkay = false; + } + } - if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) - transitionOkay = true; - else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing) - transitionOkay = true; - else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring) - transitionOkay = true; + if (!transitionOkay) + failureMessage + = string.Format( + "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}", + id, oldState, newState, m_mod.Scene.RegionInfo.RegionName); + } if (transitionOkay) + { m_agentsInTransit[id] = newState; - else - throw new Exception( - string.Format( - "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}", - id, oldState, newState, m_mod.Scene.RegionInfo.RegionName)); + +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}", +// id, oldState, newState, m_mod.Scene.Name); + } + else if (failIfNotOkay) + { + m_log.DebugFormat("{0} UpdateInTransit. Throwing transition failure = {1}", LogHeader, failureMessage); + throw new Exception(failureMessage); + } +// else +// { +// if (oldState != null) +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} from state {1} to {2} in {3}", +// id, oldState, newState, m_mod.Scene.Name); +// else +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} to state {1} in {2} since agent not in transit", +// id, newState, m_mod.Scene.Name); +// } } + + return transitionOkay; } - internal bool IsInTransit(UUID id) + /// + /// Gets the current agent transfer state. + /// + /// Null if the agent is not in transit + /// + /// Identifier. + /// + internal AgentTransferState? GetAgentTransferState(UUID id) { lock (m_agentsInTransit) - return m_agentsInTransit.ContainsKey(id); + { + if (!m_agentsInTransit.ContainsKey(id)) + return null; + else + return m_agentsInTransit[id]; + } } /// @@ -203,14 +284,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer lock (m_agentsInTransit) { - if (!IsInTransit(id)) + AgentTransferState? currentState = GetAgentTransferState(id); + + if (currentState == null) throw new Exception( string.Format( "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit", id, m_mod.Scene.RegionInfo.RegionName)); - AgentTransferState currentState = m_agentsInTransit[id]; - if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination) throw new Exception( string.Format( @@ -222,8 +303,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // There should be no race condition here since no other code should be removing the agent transfer or // changing the state to another other than Transferring => ReceivedAtDestination. - while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0) + + while (count-- > 0) { + lock (m_agentsInTransit) + { + if (m_agentsInTransit[id] == AgentTransferState.ReceivedAtDestination) + break; + } + // m_log.Debug(" >>> Waiting... " + count); Thread.Sleep(100); } diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs index b188741..fa23590 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -31,6 +31,7 @@ using System.Reflection; using OpenSim.Framework; using OpenSim.Framework.Client; +using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Connectors.Hypergrid; @@ -55,6 +56,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer private int m_levelHGTeleport = 0; private GatekeeperServiceConnector m_GatekeeperConnector; + private IUserAgentService m_UAS; protected bool m_RestrictAppearanceAbroad; protected string m_AccountName; @@ -109,6 +111,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } } + /// + /// Used for processing analysis of incoming attachments in a controlled fashion. + /// + private JobEngine m_incomingSceneObjectEngine; + #region ISharedRegionModule public override string Name @@ -152,39 +159,33 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (m_Enabled) { scene.RegisterModuleInterface(this); - scene.EventManager.OnIncomingSceneObject += OnIncomingSceneObject; - } - } - - void OnIncomingSceneObject(SceneObjectGroup so) - { - if (!so.IsAttachment) - return; - - if (so.Scene.UserManagementModule.IsLocalGridUser(so.AttachedAvatar)) - return; - - // foreign user - AgentCircuitData aCircuit = so.Scene.AuthenticateHandler.GetAgentCircuitData(so.AttachedAvatar); - if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0) - { - if (aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("AssetServerURI")) - { - string url = aCircuit.ServiceURLs["AssetServerURI"].ToString(); - m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Incoming attachement {0} for HG user {1} with asset server {2}", so.Name, so.AttachedAvatar, url); - Dictionary ids = new Dictionary(); - HGUuidGatherer uuidGatherer = new HGUuidGatherer(so.Scene.AssetService, url); - uuidGatherer.GatherAssetUuids(so, ids); - - foreach (KeyValuePair kvp in ids) - uuidGatherer.FetchAsset(kvp.Key); - } + //scene.EventManager.OnIncomingSceneObject += OnIncomingSceneObject; + + m_incomingSceneObjectEngine + = new JobEngine( + string.Format("HG Incoming Scene Object Engine ({0})", scene.Name), + "HG INCOMING SCENE OBJECT ENGINE"); + + StatsManager.RegisterStat( + new Stat( + "HGIncomingAttachmentsWaiting", + "Number of incoming attachments waiting for processing.", + "", + "", + "entitytransfer", + Name, + StatType.Pull, + MeasuresOfInterest.None, + stat => stat.Value = m_incomingSceneObjectEngine.JobsWaiting, + StatVerbosity.Debug)); + + m_incomingSceneObjectEngine.Start(); } } protected override void OnNewClient(IClientAPI client) { - client.OnTeleportHomeRequest += TeleportHome; + client.OnTeleportHomeRequest += TriggerTeleportHome; client.OnTeleportLandmarkRequest += RequestTeleportLandmark; client.OnConnectionClosed += new Action(OnConnectionClosed); } @@ -194,34 +195,44 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer base.RegionLoaded(scene); if (m_Enabled) + { m_GatekeeperConnector = new GatekeeperServiceConnector(scene.AssetService); + m_UAS = scene.RequestModuleInterface(); + if (m_UAS == null) + m_UAS = new UserAgentServiceConnector(m_ThisHomeURI); + + } } public override void RemoveRegion(Scene scene) { - base.AddRegion(scene); + base.RemoveRegion(scene); if (m_Enabled) + { scene.UnregisterModuleInterface(this); + m_incomingSceneObjectEngine.Stop(); + } } #endregion - #region HG overrides of IEntiryTransferModule + #region HG overrides of IEntityTransferModule - protected override GridRegion GetFinalDestination(GridRegion region) + protected override GridRegion GetFinalDestination(GridRegion region, UUID agentID, string agentHomeURI, out string message) { int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, region.RegionID); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: region {0} flags: {1}", region.RegionName, flags); + message = null; if ((flags & (int)OpenSim.Framework.RegionFlags.Hyperlink) != 0) { m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Destination region is hyperlink"); - GridRegion real_destination = m_GatekeeperConnector.GetHyperlinkRegion(region, region.RegionID); + GridRegion real_destination = m_GatekeeperConnector.GetHyperlinkRegion(region, region.RegionID, agentID, agentHomeURI, out message); if (real_destination != null) - m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: GetFinalDestination serveruri -> {0}", real_destination.ServerURI); + m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: GetFinalDestination: ServerURI={0}", real_destination.ServerURI); else - m_log.WarnFormat("[HG ENTITY TRANSFER MODULE]: GetHyperlinkRegion to Gatekeeper {0} failed", region.ServerURI); + m_log.WarnFormat("[HG ENTITY TRANSFER MODULE]: GetHyperlinkRegion of region {0} from Gatekeeper {1} failed: {2}", region.RegionID, region.ServerURI, message); return real_destination; } @@ -272,12 +283,21 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (agentCircuit.ServiceURLs.ContainsKey("HomeURI")) { string userAgentDriver = agentCircuit.ServiceURLs["HomeURI"].ToString(); - IUserAgentService connector = new UserAgentServiceConnector(userAgentDriver); - bool success = connector.LoginAgentToGrid(agentCircuit, reg, finalDestination, out reason); + IUserAgentService connector; + + if (userAgentDriver.Equals(m_ThisHomeURI) && m_UAS != null) + connector = m_UAS; + else + connector = new UserAgentServiceConnector(userAgentDriver); + + GridRegion source = new GridRegion(Scene.RegionInfo); + source.RawServerURI = m_GatekeeperURI; + + bool success = connector.LoginAgentToGrid(source, agentCircuit, reg, finalDestination, false, out reason); logout = success; // flag for later logout from this grid; this is an HG TP if (success) - sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout); + Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout); return success; } @@ -409,7 +429,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // return base.UpdateAgent(reg, finalDestination, agentData, sp); //} - public override void TeleportHome(UUID id, IClientAPI client) + public override void TriggerTeleportHome(UUID id, IClientAPI client) + { + TeleportHome(id, client); + } + + public override bool TeleportHome(UUID id, IClientAPI client) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId); @@ -420,8 +445,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { // local grid user m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: User is local"); - base.TeleportHome(id, client); - return; + return base.TeleportHome(id, client); } // Foreign user wants to go home @@ -431,17 +455,27 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { client.SendTeleportFailed("Your information has been lost"); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Unable to locate agent's gateway information"); - return; + return false; } IUserAgentService userAgentService = new UserAgentServiceConnector(aCircuit.ServiceURLs["HomeURI"].ToString()); Vector3 position = Vector3.UnitY, lookAt = Vector3.UnitY; - GridRegion finalDestination = userAgentService.GetHomeRegion(aCircuit.AgentID, out position, out lookAt); + + GridRegion finalDestination = null; + try + { + finalDestination = userAgentService.GetHomeRegion(aCircuit.AgentID, out position, out lookAt); + } + catch (Exception e) + { + m_log.Debug("[HG ENTITY TRANSFER MODULE]: GetHomeRegion call failed ", e); + } + if (finalDestination == null) { client.SendTeleportFailed("Your home region could not be found"); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Agent's home region not found"); - return; + return false; } ScenePresence sp = ((Scene)(client.Scene)).GetScenePresence(client.AgentId); @@ -449,7 +483,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { client.SendTeleportFailed("Internal error"); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Agent not found in the scene where it is supposed to be"); - return; + return false; } GridRegion homeGatekeeper = MakeRegion(aCircuit); @@ -460,6 +494,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer DoTeleport( sp, homeGatekeeper, finalDestination, position, lookAt, (uint)(Constants.TeleportFlags.SetLastToTarget | Constants.TeleportFlags.ViaHome)); + return true; } /// @@ -484,35 +519,174 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Local region? if (info != null) { - ((Scene)(remoteClient.Scene)).RequestTeleportLocation(remoteClient, info.RegionHandle, lm.Position, + Scene.RequestTeleportLocation( + remoteClient, info.RegionHandle, lm.Position, Vector3.Zero, (uint)(Constants.TeleportFlags.SetLastToTarget | Constants.TeleportFlags.ViaLandmark)); - - return; } else { // Foreign region - Scene scene = (Scene)(remoteClient.Scene); GatekeeperServiceConnector gConn = new GatekeeperServiceConnector(); GridRegion gatekeeper = new GridRegion(); gatekeeper.ServerURI = lm.Gatekeeper; - GridRegion finalDestination = gConn.GetHyperlinkRegion(gatekeeper, new UUID(lm.RegionID)); + string homeURI = Scene.GetAgentHomeURI(remoteClient.AgentId); + + string message; + GridRegion finalDestination = gConn.GetHyperlinkRegion(gatekeeper, new UUID(lm.RegionID), remoteClient.AgentId, homeURI, out message); if (finalDestination != null) { - ScenePresence sp = scene.GetScenePresence(remoteClient.AgentId); - IEntityTransferModule transferMod = scene.RequestModuleInterface(); + ScenePresence sp = Scene.GetScenePresence(remoteClient.AgentId); - if (transferMod != null && sp != null) - transferMod.DoTeleport( + if (sp != null) + { + if (message != null) + sp.ControllingClient.SendAgentAlertMessage(message, true); + + // Validate assorted conditions + string reason = string.Empty; + if (!ValidateGenericConditions(sp, gatekeeper, finalDestination, 0, out reason)) + { + sp.ControllingClient.SendTeleportFailed(reason); + return; + } + + DoTeleport( sp, gatekeeper, finalDestination, lm.Position, Vector3.UnitX, (uint)(Constants.TeleportFlags.SetLastToTarget | Constants.TeleportFlags.ViaLandmark)); + } + } + else + { + remoteClient.SendTeleportFailed(message); } } + } + + private void RemoveIncomingSceneObjectJobs(string commonIdToRemove) + { + List jobsToReinsert = new List(); + int jobsRemoved = 0; - // can't find the region: Tell viewer and abort - remoteClient.SendTeleportFailed("The teleport destination could not be found."); + JobEngine.Job job; + while ((job = m_incomingSceneObjectEngine.RemoveNextJob()) != null) + { + if (job.CommonId != commonIdToRemove) + jobsToReinsert.Add(job); + else + jobsRemoved++; + } + + m_log.DebugFormat( + "[HG ENTITY TRANSFER]: Removing {0} jobs with common ID {1} and reinserting {2} other jobs", + jobsRemoved, commonIdToRemove, jobsToReinsert.Count); + + if (jobsToReinsert.Count > 0) + { + foreach (JobEngine.Job jobToReinsert in jobsToReinsert) + m_incomingSceneObjectEngine.QueueJob(jobToReinsert); + } + } + + public override bool HandleIncomingSceneObject(SceneObjectGroup so, Vector3 newPosition) + { + // FIXME: We must make it so that we can use SOG.IsAttachment here. At the moment it is always null! + if (!so.IsAttachmentCheckFull()) + return base.HandleIncomingSceneObject(so, newPosition); + + // Equally, we can't use so.AttachedAvatar here. + if (so.OwnerID == UUID.Zero || Scene.UserManagementModule.IsLocalGridUser(so.OwnerID)) + return base.HandleIncomingSceneObject(so, newPosition); + + // foreign user + AgentCircuitData aCircuit = Scene.AuthenticateHandler.GetAgentCircuitData(so.OwnerID); + if (aCircuit != null) + { + if ((aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) == 0) + { + // We have already pulled the necessary attachments from the source grid. + base.HandleIncomingSceneObject(so, newPosition); + } + else + { + if (aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("AssetServerURI")) + { + m_incomingSceneObjectEngine.QueueJob( + string.Format("HG UUID Gather for attachment {0} for {1}", so.Name, aCircuit.Name), + () => + { + string url = aCircuit.ServiceURLs["AssetServerURI"].ToString(); + // m_log.DebugFormat( + // "[HG ENTITY TRANSFER MODULE]: Incoming attachment {0} for HG user {1} with asset service {2}", + // so.Name, so.AttachedAvatar, url); + + IDictionary ids = new Dictionary(); + HGUuidGatherer uuidGatherer + = new HGUuidGatherer(Scene.AssetService, url, ids); + uuidGatherer.AddForInspection(so); + + while (!uuidGatherer.Complete) + { + int tickStart = Util.EnvironmentTickCount(); + + UUID? nextUuid = uuidGatherer.NextUuidToInspect; + uuidGatherer.GatherNext(); + + // m_log.DebugFormat( + // "[HG ENTITY TRANSFER]: Gathered attachment asset uuid {0} for object {1} for HG user {2} took {3} ms with asset service {4}", + // nextUuid, so.Name, so.OwnerID, Util.EnvironmentTickCountSubtract(tickStart), url); + + int ticksElapsed = Util.EnvironmentTickCountSubtract(tickStart); + + if (ticksElapsed > 30000) + { + m_log.WarnFormat( + "[HG ENTITY TRANSFER]: Removing incoming scene object jobs for HG user {0} as gather of {1} from {2} took {3} ms to respond (> {4} ms)", + so.OwnerID, so.Name, url, ticksElapsed, 30000); + + RemoveIncomingSceneObjectJobs(so.OwnerID.ToString()); + + return; + } + } + + // m_log.DebugFormat( + // "[HG ENTITY TRANSFER]: Fetching {0} assets for attachment {1} for HG user {2} with asset service {3}", + // ids.Count, so.Name, so.OwnerID, url); + + foreach (KeyValuePair kvp in ids) + { + int tickStart = Util.EnvironmentTickCount(); + + uuidGatherer.FetchAsset(kvp.Key); + + int ticksElapsed = Util.EnvironmentTickCountSubtract(tickStart); + + if (ticksElapsed > 30000) + { + m_log.WarnFormat( + "[HG ENTITY TRANSFER]: Removing incoming scene object jobs for HG user {0} as fetch of {1} from {2} took {3} ms to respond (> {4} ms)", + so.OwnerID, kvp.Key, url, ticksElapsed, 30000); + + RemoveIncomingSceneObjectJobs(so.OwnerID.ToString()); + + return; + } + } + + base.HandleIncomingSceneObject(so, newPosition); + + // m_log.DebugFormat( + // "[HG ENTITY TRANSFER MODULE]: Completed incoming attachment {0} for HG user {1} with asset server {2}", + // so.Name, so.OwnerID, url); + }, + so.OwnerID.ToString()); + } + } + } + + return true; } #endregion @@ -549,12 +723,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (uMan != null && uMan.IsLocalGridUser(obj.AgentId)) { // local grid user + m_UAS.LogoutAgent(obj.AgentId, obj.SessionId); return; } AgentCircuitData aCircuit = ((Scene)(obj.Scene)).AuthenticateHandler.GetAgentCircuitData(obj.CircuitCode); - - if (aCircuit.ServiceURLs.ContainsKey("HomeURI")) + if (aCircuit != null && aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("HomeURI")) { string url = aCircuit.ServiceURLs["HomeURI"].ToString(); IUserAgentService security = new UserAgentServiceConnector(url); diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs index 7871eda..f54298c 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs @@ -35,6 +35,7 @@ using System.Xml; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Serialization.External; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; @@ -73,6 +74,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess private AssetMetadata FetchMetadata(string url, UUID assetID) { + if (string.IsNullOrEmpty(url)) + return null; + if (!url.EndsWith("/") && !url.EndsWith("=")) url = url + "/"; @@ -92,6 +96,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess AssetBase asset = m_scene.AssetService.Get(assetID.ToString()); if (asset == null) { + if (string.IsNullOrEmpty(url)) + return null; + if (!url.EndsWith("/") && !url.EndsWith("=")) url = url + "/"; @@ -109,45 +116,47 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess public bool PostAsset(string url, AssetBase asset) { - if (asset != null) - { - if (!url.EndsWith("/") && !url.EndsWith("=")) - url = url + "/"; + if (string.IsNullOrEmpty(url)) + return false; - bool success = true; - // See long comment in AssetCache.AddAsset - if (!asset.Temporary || asset.Local) - { - // We need to copy the asset into a new asset, because - // we need to set its ID to be URL+UUID, so that the - // HGAssetService dispatches it to the remote grid. - // It's not pretty, but the best that can be done while - // not having a global naming infrastructure - AssetBase asset1 = new AssetBase(asset.FullID, asset.Name, asset.Type, asset.Metadata.CreatorID); - Copy(asset, asset1); - asset1.ID = url + asset.ID; - - AdjustIdentifiers(asset1.Metadata); - if (asset1.Metadata.Type == (sbyte)AssetType.Object) - asset1.Data = AdjustIdentifiers(asset.Data); - else - asset1.Data = asset.Data; + if (!url.EndsWith("/") && !url.EndsWith("=")) + url = url + "/"; - string id = m_scene.AssetService.Store(asset1); - if (id == string.Empty) - { - m_log.DebugFormat("[HG ASSET MAPPER]: Asset server {0} did not accept {1}", url, asset.ID); - success = false; - } - else - m_log.DebugFormat("[HG ASSET MAPPER]: Posted copy of asset {0} from local asset server to {1}", asset1.ID, url); - } - return success; + if (asset == null) + { + m_log.Warn("[HG ASSET MAPPER]: Tried to post asset to remote server, but asset not in local cache."); + return false; } + + // See long comment in AssetCache.AddAsset + if (asset.Temporary || asset.Local) + return true; + + // We need to copy the asset into a new asset, because + // we need to set its ID to be URL+UUID, so that the + // HGAssetService dispatches it to the remote grid. + // It's not pretty, but the best that can be done while + // not having a global naming infrastructure + AssetBase asset1 = new AssetBase(asset.FullID, asset.Name, asset.Type, asset.Metadata.CreatorID); + Copy(asset, asset1); + asset1.ID = url + asset.ID; + + AdjustIdentifiers(asset1.Metadata); + if (asset1.Metadata.Type == (sbyte)AssetType.Object) + asset1.Data = AdjustIdentifiers(asset.Data); else - m_log.Warn("[HG ASSET MAPPER]: Tried to post asset to remote server, but asset not in local cache."); + asset1.Data = asset.Data; - return false; + string id = m_scene.AssetService.Store(asset1); + if (String.IsNullOrEmpty(id)) + { + m_log.DebugFormat("[HG ASSET MAPPER]: Asset server {0} did not accept {1}", url, asset.ID); + return false; + } + else { + m_log.DebugFormat("[HG ASSET MAPPER]: Posted copy of asset {0} from local asset server to {1}", asset1.ID, url); + return true; + } } private void Copy(AssetBase from, AssetBase to) @@ -165,7 +174,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess private void AdjustIdentifiers(AssetMetadata meta) { - if (meta.CreatorID != null && meta.CreatorID != string.Empty) + if (!string.IsNullOrEmpty(meta.CreatorID)) { UUID uuid = UUID.Zero; UUID.TryParse(meta.CreatorID, out uuid); @@ -181,49 +190,10 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess return Utils.StringToBytes(RewriteSOP(xml)); } - protected string RewriteSOP(string xml) + protected string RewriteSOP(string xmlData) { - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - XmlNodeList sops = doc.GetElementsByTagName("SceneObjectPart"); - - foreach (XmlNode sop in sops) - { - UserAccount creator = null; - bool hasCreatorData = false; - XmlNodeList nodes = sop.ChildNodes; - foreach (XmlNode node in nodes) - { - if (node.Name == "CreatorID") - { - UUID uuid = UUID.Zero; - UUID.TryParse(node.InnerText, out uuid); - creator = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, uuid); - } - if (node.Name == "CreatorData" && node.InnerText != null && node.InnerText != string.Empty) - hasCreatorData = true; - - //if (node.Name == "OwnerID") - //{ - // UserAccount owner = GetUser(node.InnerText); - // if (owner != null) - // node.InnerText = m_ProfileServiceURL + "/" + node.InnerText + "/" + owner.FirstName + " " + owner.LastName; - //} - } - - if (!hasCreatorData && creator != null) - { - XmlElement creatorData = doc.CreateElement("CreatorData"); - creatorData.InnerText = m_HomeURI + ";" + creator.FirstName + " " + creator.LastName; - sop.AppendChild(creatorData); - } - } - - using (StringWriter wr = new StringWriter()) - { - doc.Save(wr); - return wr.ToString(); - } +// Console.WriteLine("Input XML [{0}]", xmlData); + return ExternalRepresentationUtils.RewriteSOP(xmlData, m_scene.Name, m_HomeURI, m_scene.UserAccountService, m_scene.RegionInfo.ScopeID); } @@ -251,12 +221,13 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess // The act of gathering UUIDs downloads some assets from the remote server // but not all... - Dictionary ids = new Dictionary(); HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, userAssetURL); - uuidGatherer.GatherAssetUuids(assetID, (AssetType)meta.Type, ids); - m_log.DebugFormat("[HG ASSET MAPPER]: Preparing to get {0} assets", ids.Count); + uuidGatherer.AddForInspection(assetID); + uuidGatherer.GatherAll(); + + m_log.DebugFormat("[HG ASSET MAPPER]: Preparing to get {0} assets", uuidGatherer.GatheredUuids.Count); bool success = true; - foreach (UUID uuid in ids.Keys) + foreach (UUID uuid in uuidGatherer.GatheredUuids.Keys) if (FetchAsset(userAssetURL, uuid) == null) success = false; @@ -267,39 +238,92 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess m_log.DebugFormat("[HG ASSET MAPPER]: Successfully got item {0} from asset server {1}", assetID, userAssetURL); } - public void Post(UUID assetID, UUID ownerID, string userAssetURL) { - // Post the item from the local AssetCache onto the remote asset server - // and place an entry in m_assetMap + m_log.DebugFormat("[HG ASSET MAPPER]: Starting to send asset {0} with children to asset server {1}", assetID, userAssetURL); + + // Find all the embedded assets - m_log.Debug("[HG ASSET MAPPER]: Posting object " + assetID + " to asset server " + userAssetURL); AssetBase asset = m_scene.AssetService.Get(assetID.ToString()); - if (asset != null) + if (asset == null) + { + m_log.DebugFormat("[HG ASSET MAPPER]: Something wrong with asset {0}, it could not be found", assetID); + return; + } + + HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, string.Empty); + uuidGatherer.AddForInspection(asset.FullID); + uuidGatherer.GatherAll(); + + // Check which assets already exist in the destination server + + string url = userAssetURL; + if (!url.EndsWith("/") && !url.EndsWith("=")) + url = url + "/"; + + string[] remoteAssetIDs = new string[uuidGatherer.GatheredUuids.Count]; + int i = 0; + foreach (UUID id in uuidGatherer.GatheredUuids.Keys) + remoteAssetIDs[i++] = url + id.ToString(); + + bool[] exist = m_scene.AssetService.AssetsExist(remoteAssetIDs); + + var existSet = new HashSet(); + i = 0; + foreach (UUID id in uuidGatherer.GatheredUuids.Keys) + { + if (exist[i]) + existSet.Add(id.ToString()); + ++i; + } + + // Send only those assets which don't already exist in the destination server + + bool success = true; + + foreach (UUID uuid in uuidGatherer.GatheredUuids.Keys) { - Dictionary ids = new Dictionary(); - HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, string.Empty); - uuidGatherer.GatherAssetUuids(asset.FullID, (AssetType)asset.Type, ids); - bool success = false; - foreach (UUID uuid in ids.Keys) + if (!existSet.Contains(uuid.ToString())) { asset = m_scene.AssetService.Get(uuid.ToString()); if (asset == null) + { m_log.DebugFormat("[HG ASSET MAPPER]: Could not find asset {0}", uuid); + } else - success = PostAsset(userAssetURL, asset); + { + try + { + success &= PostAsset(userAssetURL, asset); + } + catch (Exception e) + { + m_log.Error( + string.Format( + "[HG ASSET MAPPER]: Failed to post asset {0} (type {1}, length {2}) referenced from {3} to {4} with exception ", + asset.ID, asset.Type, asset.Data.Length, assetID, userAssetURL), + e); + + // For debugging purposes for now we will continue to throw the exception up the stack as was already happening. However, after + // debugging we may want to simply report the failure if we can tell this is due to a failure + // with a particular asset and not a destination network failure where all asset posts will fail (and + // generate large amounts of log spam). + throw e; + } + } } - - // maybe all pieces got there... - if (!success) - m_log.DebugFormat("[HG ASSET MAPPER]: Problems posting item {0} to asset server {1}", assetID, userAssetURL); else - m_log.DebugFormat("[HG ASSET MAPPER]: Successfully posted item {0} to asset server {1}", assetID, userAssetURL); - + { + m_log.DebugFormat( + "[HG ASSET MAPPER]: Didn't post asset {0} referenced from {1} because it already exists in asset server {2}", + uuid, assetID, userAssetURL); + } } - else - m_log.DebugFormat("[HG ASSET MAPPER]: Something wrong with asset {0}, it could not be found", assetID); + if (!success) + m_log.DebugFormat("[HG ASSET MAPPER]: Problems sending asset {0} with children to asset server {1}", assetID, userAssetURL); + else + m_log.DebugFormat("[HG ASSET MAPPER]: Successfully sent asset {0} with children to asset server {1}", assetID, userAssetURL); } #endregion diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGInventoryAccessModule.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGInventoryAccessModule.cs index 964efda..582b267 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGInventoryAccessModule.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGInventoryAccessModule.cs @@ -62,6 +62,17 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess private string m_ThisGatekeeper; private bool m_RestrictInventoryAccessAbroad; + private bool m_bypassPermissions = true; + + // This simple check makes it possible to support grids in which all the simulators + // share all central services of the Robust server EXCEPT assets. In other words, + // grids where the simulators' assets are kept in one DB and the users' inventory assets + // are kept on another. When users rez items from inventory or take objects from world, + // an HG-like asset copy takes place between the 2 servers, the world asset server and + // the user's asset server. + private bool m_CheckSeparateAssets = false; + private string m_LocalAssetsURL = string.Empty; + // private bool m_Initialized = false; #region INonSharedRegionModule @@ -88,16 +99,26 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess IConfig thisModuleConfig = source.Configs["HGInventoryAccessModule"]; if (thisModuleConfig != null) { - // legacy configuration [obsolete] - m_HomeURI = thisModuleConfig.GetString("ProfileServerURI", string.Empty); - // preferred - m_HomeURI = thisModuleConfig.GetString("HomeURI", m_HomeURI); + m_HomeURI = Util.GetConfigVarFromSections(source, "HomeURI", + new string[] { "Startup", "Hypergrid", "HGInventoryAccessModule" }, String.Empty); + m_ThisGatekeeper = Util.GetConfigVarFromSections(source, "GatekeeperURI", + new string[] { "Startup", "Hypergrid", "HGInventoryAccessModule" }, String.Empty); + // Legacy. Renove soon! + m_ThisGatekeeper = thisModuleConfig.GetString("Gatekeeper", m_ThisGatekeeper); + m_OutboundPermission = thisModuleConfig.GetBoolean("OutboundPermission", true); - m_ThisGatekeeper = thisModuleConfig.GetString("Gatekeeper", string.Empty); m_RestrictInventoryAccessAbroad = thisModuleConfig.GetBoolean("RestrictInventoryAccessAbroad", true); + m_CheckSeparateAssets = thisModuleConfig.GetBoolean("CheckSeparateAssets", false); + m_LocalAssetsURL = thisModuleConfig.GetString("RegionHGAssetServerURI", string.Empty); + m_LocalAssetsURL = m_LocalAssetsURL.Trim(new char[] { '/' }); + } else m_log.Warn("[HG INVENTORY ACCESS MODULE]: HGInventoryAccessModule configs not found. ProfileServerURI not set!"); + + m_bypassPermissions = !Util.GetConfigVarFromSections(source, "serverside_object_permissions", + new string[] { "Startup", "Permissions" }, true); + } } } @@ -109,9 +130,14 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess base.AddRegion(scene); m_assMapper = new HGAssetMapper(scene, m_HomeURI); - scene.EventManager.OnNewInventoryItemUploadComplete += UploadInventoryItem; + scene.EventManager.OnNewInventoryItemUploadComplete += PostInventoryAsset; scene.EventManager.OnTeleportStart += TeleportStart; scene.EventManager.OnTeleportFail += TeleportFail; + + // We're fgoing to enforce some stricter permissions if Outbound is false + scene.Permissions.OnTakeObject += CanTakeObject; + scene.Permissions.OnTakeCopyObject += CanTakeObject; + scene.Permissions.OnTransferUserInventory += OnTransferUserInventory; } #endregion @@ -133,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess if (sp is ScenePresence) { AgentCircuitData aCircuit = ((ScenePresence)sp).Scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId); - if ((aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0) + if (aCircuit != null && (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0) { if (m_RestrictInventoryAccessAbroad) { @@ -183,8 +209,11 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess } } - public void UploadInventoryItem(UUID avatarID, UUID assetID, string name, int userlevel) + public void PostInventoryAsset(UUID avatarID, AssetType type, UUID assetID, string name, int userlevel) { + if (type == AssetType.Link) + return; + string userAssetServer = string.Empty; if (IsForeignUser(avatarID, out userAssetServer) && userAssetServer != string.Empty && m_OutboundPermission) { @@ -219,18 +248,32 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess { UUID newAssetID = base.CapsUpdateInventoryItemAsset(remoteClient, itemID, data); - UploadInventoryItem(remoteClient.AgentId, newAssetID, "", 0); + PostInventoryAsset(remoteClient.AgentId, AssetType.Unknown, newAssetID, "", 0); return newAssetID; } + /// + /// UpdateInventoryItemAsset + /// + public override bool UpdateInventoryItemAsset(UUID ownerID, InventoryItemBase item, AssetBase asset) + { + if (base.UpdateInventoryItemAsset(ownerID, item, asset)) + { + PostInventoryAsset(ownerID, (AssetType)asset.Type, asset.FullID, asset.Name, 0); + return true; + } + + return false; + } + /// /// Used in DeleteToInventory /// protected override void ExportAsset(UUID agentID, UUID assetID) { if (!assetID.Equals(UUID.Zero)) - UploadInventoryItem(agentID, assetID, "", 0); + PostInventoryAsset(agentID, AssetType.Unknown, assetID, "", 0); else m_log.Debug("[HGScene]: Scene.Inventory did not create asset"); } @@ -242,7 +285,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess UUID RayTargetID, byte BypassRayCast, bool RayEndIsIntersection, bool RezSelected, bool RemoveItem, UUID fromTaskID, bool attachment) { - m_log.DebugFormat("[HGScene] RezObject itemID={0} fromTaskID={1}", itemID, fromTaskID); + m_log.DebugFormat("[HGScene]: RezObject itemID={0} fromTaskID={1}", itemID, fromTaskID); //if (fromTaskID.Equals(UUID.Zero)) //{ @@ -268,50 +311,98 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess SceneObjectGroup sog = base.RezObject(remoteClient, itemID, RayEnd, RayStart, RayTargetID, BypassRayCast, RayEndIsIntersection, RezSelected, RemoveItem, fromTaskID, attachment); - if (sog == null) - remoteClient.SendAgentAlertMessage("Unable to rez: problem accessing inventory or locating assets", false); - return sog; } public override void TransferInventoryAssets(InventoryItemBase item, UUID sender, UUID receiver) { - string userAssetServer = string.Empty; - if (IsForeignUser(sender, out userAssetServer) && userAssetServer != string.Empty) - m_assMapper.Get(item.AssetID, sender, userAssetServer); + string senderAssetServer = string.Empty; + string receiverAssetServer = string.Empty; + bool isForeignSender, isForeignReceiver; + isForeignSender = IsForeignUser(sender, out senderAssetServer); + isForeignReceiver = IsForeignUser(receiver, out receiverAssetServer); + + // They're both local. Nothing to do. + if (!isForeignSender && !isForeignReceiver) + return; + + // At least one of them is foreign. + // If both users have the same asset server, no need to transfer the asset + if (senderAssetServer.Equals(receiverAssetServer)) + { + m_log.DebugFormat("[HGScene]: Asset transfer between foreign users, but they have the same server. No transfer."); + return; + } + + if (isForeignSender && senderAssetServer != string.Empty) + m_assMapper.Get(item.AssetID, sender, senderAssetServer); - if (IsForeignUser(receiver, out userAssetServer) && userAssetServer != string.Empty && m_OutboundPermission) - m_assMapper.Post(item.AssetID, receiver, userAssetServer); + if (isForeignReceiver && receiverAssetServer != string.Empty && m_OutboundPermission) + m_assMapper.Post(item.AssetID, receiver, receiverAssetServer); } public override bool IsForeignUser(UUID userID, out string assetServerURL) { assetServerURL = string.Empty; - if (UserManagementModule != null && !UserManagementModule.IsLocalGridUser(userID)) - { // foreign - ScenePresence sp = null; - if (m_Scene.TryGetScenePresence(userID, out sp)) + if (UserManagementModule != null) + { + if (!m_CheckSeparateAssets) { - AgentCircuitData aCircuit = m_Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); - if (aCircuit.ServiceURLs.ContainsKey("AssetServerURI")) - { - assetServerURL = aCircuit.ServiceURLs["AssetServerURI"].ToString(); - assetServerURL = assetServerURL.Trim(new char[] { '/' }); + if (!UserManagementModule.IsLocalGridUser(userID)) + { // foreign + ScenePresence sp = null; + if (m_Scene.TryGetScenePresence(userID, out sp)) + { + AgentCircuitData aCircuit = m_Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); + if (aCircuit != null && aCircuit.ServiceURLs != null && aCircuit.ServiceURLs.ContainsKey("AssetServerURI")) + { + assetServerURL = aCircuit.ServiceURLs["AssetServerURI"].ToString(); + assetServerURL = assetServerURL.Trim(new char[] { '/' }); + } + } + else + { + assetServerURL = UserManagementModule.GetUserServerURL(userID, "AssetServerURI"); + assetServerURL = assetServerURL.Trim(new char[] { '/' }); + } + return true; } } else { - assetServerURL = UserManagementModule.GetUserServerURL(userID, "AssetServerURI"); - assetServerURL = assetServerURL.Trim(new char[] { '/' }); + if (IsLocalInventoryAssetsUser(userID, out assetServerURL)) + { + m_log.DebugFormat("[HGScene]: user {0} has local assets {1}", userID, assetServerURL); + return false; + } + else + { + m_log.DebugFormat("[HGScene]: user {0} has foreign assets {1}", userID, assetServerURL); + return true; + } } - return true; } - return false; } + private bool IsLocalInventoryAssetsUser(UUID uuid, out string assetsURL) + { + assetsURL = UserManagementModule.GetUserServerURL(uuid, "AssetServerURI"); + if (assetsURL == string.Empty) + { + AgentCircuitData agent = m_Scene.AuthenticateHandler.GetAgentCircuitData(uuid); + if (agent != null) + { + assetsURL = agent.ServiceURLs["AssetServerURI"].ToString(); + assetsURL = assetsURL.Trim(new char[] { '/' }); + } + } + return m_LocalAssetsURL.Equals(assetsURL); + } + + protected override InventoryItemBase GetItem(UUID agentID, UUID itemID) { InventoryItemBase item = base.GetItem(agentID, itemID); @@ -346,7 +437,15 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess InventoryFolderBase root = m_Scene.InventoryService.GetRootFolder(client.AgentId); InventoryCollection content = m_Scene.InventoryService.GetFolderContent(client.AgentId, root.ID); - inv.SendBulkUpdateInventory(content.Folders.ToArray(), content.Items.ToArray()); + List keep = new List(); + + foreach (InventoryFolderBase f in content.Folders) + { + if (f.Name != "My Suitcase" && f.Name != "Current Outfit") + keep.Add(f); + } + + inv.SendBulkUpdateInventory(keep.ToArray(), content.Items.ToArray()); } } } @@ -379,7 +478,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess foreach (InventoryFolderBase f in content.Folders) { - if (f.Name != "My Suitcase") + if (f.Name != "My Suitcase" && f.Name != "Current Outfit") { f.Name = f.Name + " (Unavailable)"; keep.Add(f); @@ -404,5 +503,36 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess } #endregion + + #region Permissions + + private bool CanTakeObject(UUID objectID, UUID stealer, Scene scene) + { + if (m_bypassPermissions) return true; + + if (!m_OutboundPermission && !UserManagementModule.IsLocalGridUser(stealer)) + { + SceneObjectGroup sog = null; + if (m_Scene.TryGetSceneObjectGroup(objectID, out sog) && sog.OwnerID == stealer) + return true; + + return false; + } + + return true; + } + + private bool OnTransferUserInventory(UUID itemID, UUID userID, UUID recipientID) + { + if (m_bypassPermissions) return true; + + if (!m_OutboundPermission && !UserManagementModule.IsLocalGridUser(recipientID)) + return false; + + return true; + } + + + #endregion } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs index 8b7c16e..5a9efb8 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs @@ -47,6 +47,7 @@ using OpenMetaverse; using log4net; using Nini.Config; using Mono.Addins; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Framework.InventoryAccess { @@ -202,7 +203,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess m_Scene.AssetService.Store(asset); m_Scene.CreateNewInventoryItem( remoteClient, remoteClient.AgentId.ToString(), string.Empty, folderID, - name, description, 0, callbackID, asset, invType, nextOwnerMask, creationDate); + name, description, 0, callbackID, asset.FullID, asset.Type, invType, nextOwnerMask, creationDate); } else { @@ -259,7 +260,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess return UUID.Zero; } - remoteClient.SendAgentAlertMessage("Notecard saved", false); + remoteClient.SendAlertMessage("Notecard saved"); } else if ((InventoryType)item.InvType == InventoryType.LSL) { @@ -269,7 +270,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess return UUID.Zero; } - remoteClient.SendAgentAlertMessage("Script saved", false); + remoteClient.SendAlertMessage("Script saved"); } AssetBase asset = @@ -291,7 +292,34 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess return UUID.Zero; } - + + public virtual bool UpdateInventoryItemAsset(UUID ownerID, InventoryItemBase item, AssetBase asset) + { + if (item != null && item.Owner == ownerID && asset != null) + { +// m_log.DebugFormat( +// "[INVENTORY ACCESS MODULE]: Updating item {0} {1} with new asset {2}", +// item.Name, item.ID, asset.ID); + + item.AssetID = asset.FullID; + item.Description = asset.Description; + item.Name = asset.Name; + item.AssetType = asset.Type; + item.InvType = (int)InventoryType.Object; + + m_Scene.AssetService.Store(asset); + m_Scene.InventoryService.UpdateItem(item); + + return true; + } + else + { + m_log.ErrorFormat("[INVENTORY ACCESS MODULE]: Given invalid item for inventory update: {0}", + (item == null || asset == null? "null item or asset" : "wrong owner")); + return false; + } + } + public virtual List CopyToInventory( DeRezAction action, UUID folderID, List objectGroups, IClientAPI remoteClient, bool asAttachment) @@ -352,23 +380,32 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess bool asAttachment) { CoalescedSceneObjects coa = new CoalescedSceneObjects(UUID.Zero); - Dictionary originalPositions = new Dictionary(); +// Dictionary originalPositions = new Dictionary(); + + Dictionary group2Keyframe = new Dictionary(); foreach (SceneObjectGroup objectGroup in objlist) { - Vector3 inventoryStoredPosition = new Vector3 - (((objectGroup.AbsolutePosition.X > (int)Constants.RegionSize) - ? 250 - : objectGroup.AbsolutePosition.X) - , - (objectGroup.AbsolutePosition.Y > (int)Constants.RegionSize) - ? 250 - : objectGroup.AbsolutePosition.Y, - objectGroup.AbsolutePosition.Z); - - originalPositions[objectGroup.UUID] = objectGroup.AbsolutePosition; + if (objectGroup.RootPart.KeyframeMotion != null) + { + objectGroup.RootPart.KeyframeMotion.Pause(); + group2Keyframe.Add(objectGroup, objectGroup.RootPart.KeyframeMotion); + objectGroup.RootPart.KeyframeMotion = null; + } - objectGroup.AbsolutePosition = inventoryStoredPosition; +// Vector3 inventoryStoredPosition = new Vector3 +// (((objectGroup.AbsolutePosition.X > (int)Constants.RegionSize) +// ? 250 +// : objectGroup.AbsolutePosition.X) +// , +// (objectGroup.AbsolutePosition.Y > (int)Constants.RegionSize) +// ? 250 +// : objectGroup.AbsolutePosition.Y, +// objectGroup.AbsolutePosition.Z); +// +// originalPositions[objectGroup.UUID] = objectGroup.AbsolutePosition; +// +// objectGroup.AbsolutePosition = inventoryStoredPosition; // Make sure all bits but the ones we want are clear // on take. @@ -377,7 +414,8 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess objectGroup.RootPart.NextOwnerMask &= ((uint)PermissionMask.Copy | (uint)PermissionMask.Transfer | - (uint)PermissionMask.Modify); + (uint)PermissionMask.Modify | + (uint)PermissionMask.Export); objectGroup.RootPart.NextOwnerMask |= (uint)PermissionMask.Move; @@ -395,9 +433,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess else itemXml = SceneObjectSerializer.ToOriginalXmlFormat(objlist[0], !asAttachment); - // Restore the position of each group now that it has been stored to inventory. - foreach (SceneObjectGroup objectGroup in objlist) - objectGroup.AbsolutePosition = originalPositions[objectGroup.UUID]; +// // Restore the position of each group now that it has been stored to inventory. +// foreach (SceneObjectGroup objectGroup in objlist) +// objectGroup.AbsolutePosition = originalPositions[objectGroup.UUID]; InventoryItemBase item = CreateItemForObject(action, remoteClient, objlist[0], folderID); @@ -407,17 +445,28 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess if (item == null) return null; + + item.CreatorId = objlist[0].RootPart.CreatorID.ToString(); + item.CreatorData = objlist[0].RootPart.CreatorData; - // Can't know creator is the same, so null it in inventory if (objlist.Count > 1) { - item.CreatorId = UUID.Zero.ToString(); item.Flags = (uint)InventoryItemFlags.ObjectHasMultipleItems; + + // If the objects have different creators then don't specify a creator at all + foreach (SceneObjectGroup objectGroup in objlist) + { + if ((objectGroup.RootPart.CreatorID.ToString() != item.CreatorId) + || (objectGroup.RootPart.CreatorData.ToString() != item.CreatorData)) + { + item.CreatorId = UUID.Zero.ToString(); + item.CreatorData = string.Empty; + break; + } + } } else { - item.CreatorId = objlist[0].RootPart.CreatorID.ToString(); - item.CreatorData = objlist[0].RootPart.CreatorData; item.SaleType = objlist[0].RootPart.ObjectSaleType; item.SalePrice = objlist[0].RootPart.SalePrice; } @@ -438,13 +487,13 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess } else { - AddPermissions(item, objlist[0], objlist, remoteClient); - item.CreationDate = Util.UnixTimeSinceEpoch(); item.Description = asset.Description; item.Name = asset.Name; item.AssetType = asset.Type; + AddPermissions(item, objlist[0], objlist, remoteClient); + m_Scene.AddInventoryItem(item); if (remoteClient != null && item.Owner == remoteClient.AgentId) @@ -461,6 +510,13 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess } } + // Restore KeyframeMotion + foreach (SceneObjectGroup objectGroup in group2Keyframe.Keys) + { + objectGroup.RootPart.KeyframeMotion = group2Keyframe[objectGroup]; + objectGroup.RootPart.KeyframeMotion.Start(); + } + // This is a hook to do some per-asset post-processing for subclasses that need that if (remoteClient != null) ExportAsset(remoteClient.AgentId, asset.FullID); @@ -485,49 +541,65 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess InventoryItemBase item, SceneObjectGroup so, List objsForEffectivePermissions, IClientAPI remoteClient) { - uint effectivePerms = (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify | PermissionMask.Move) | 7; + uint effectivePerms = (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify | PermissionMask.Move | PermissionMask.Export) | 7; + uint allObjectsNextOwnerPerms = 0x7fffffff; + uint allObjectsEveryOnePerms = 0x7fffffff; + uint allObjectsGroupPerms = 0x7fffffff; + foreach (SceneObjectGroup grp in objsForEffectivePermissions) + { effectivePerms &= grp.GetEffectivePermissions(); + allObjectsNextOwnerPerms &= grp.RootPart.NextOwnerMask; + allObjectsEveryOnePerms &= grp.RootPart.EveryoneMask; + allObjectsGroupPerms &= grp.RootPart.GroupMask; + } effectivePerms |= (uint)PermissionMask.Move; + //PermissionsUtil.LogPermissions(item.Name, "Before AddPermissions", item.BasePermissions, item.CurrentPermissions, item.NextPermissions); + if (remoteClient != null && (remoteClient.AgentId != so.RootPart.OwnerID) && m_Scene.Permissions.PropagatePermissions()) { + // Changing ownership, so apply the "Next Owner" permissions to all of the + // inventory item's permissions. + uint perms = effectivePerms; - uint nextPerms = (perms & 7) << 13; - if ((nextPerms & (uint)PermissionMask.Copy) == 0) - perms &= ~(uint)PermissionMask.Copy; - if ((nextPerms & (uint)PermissionMask.Transfer) == 0) - perms &= ~(uint)PermissionMask.Transfer; - if ((nextPerms & (uint)PermissionMask.Modify) == 0) - perms &= ~(uint)PermissionMask.Modify; - - item.BasePermissions = perms & so.RootPart.NextOwnerMask; + PermissionsUtil.ApplyFoldedPermissions(effectivePerms, ref perms); + + item.BasePermissions = perms & allObjectsNextOwnerPerms; item.CurrentPermissions = item.BasePermissions; - item.NextPermissions = perms & so.RootPart.NextOwnerMask; - item.EveryOnePermissions = so.RootPart.EveryoneMask & so.RootPart.NextOwnerMask; - item.GroupPermissions = so.RootPart.GroupMask & so.RootPart.NextOwnerMask; + item.NextPermissions = perms & allObjectsNextOwnerPerms; + item.EveryOnePermissions = allObjectsEveryOnePerms & allObjectsNextOwnerPerms; + item.GroupPermissions = allObjectsGroupPerms & allObjectsNextOwnerPerms; - // Magic number badness. Maybe this deserves an enum. - // bit 4 (16) is the "Slam" bit, it means treat as passed - // and apply next owner perms on rez - item.CurrentPermissions |= 16; // Slam! + // apply next owner perms on rez + item.CurrentPermissions |= SceneObjectGroup.SLAM; } else { + // Not changing ownership. + // In this case we apply the permissions in the object's items ONLY to the inventory + // item's "Next Owner" permissions, but NOT to its "Current", "Base", etc. permissions. + // E.g., if the object contains a No-Transfer item then the item's "Next Owner" + // permissions are also No-Transfer. + PermissionsUtil.ApplyFoldedPermissions(effectivePerms, ref allObjectsNextOwnerPerms); + item.BasePermissions = effectivePerms; item.CurrentPermissions = effectivePerms; - item.NextPermissions = so.RootPart.NextOwnerMask & effectivePerms; - item.EveryOnePermissions = so.RootPart.EveryoneMask & effectivePerms; - item.GroupPermissions = so.RootPart.GroupMask & effectivePerms; + item.NextPermissions = allObjectsNextOwnerPerms & effectivePerms; + item.EveryOnePermissions = allObjectsEveryOnePerms & effectivePerms; + item.GroupPermissions = allObjectsGroupPerms & effectivePerms; item.CurrentPermissions &= ((uint)PermissionMask.Copy | (uint)PermissionMask.Transfer | (uint)PermissionMask.Modify | (uint)PermissionMask.Move | + (uint)PermissionMask.Export | 7); // Preserve folded permissions - } - + } + + //PermissionsUtil.LogPermissions(item.Name, "After AddPermissions", item.BasePermissions, item.CurrentPermissions, item.NextPermissions); + return item; } @@ -542,6 +614,10 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess protected InventoryItemBase CreateItemForObject( DeRezAction action, IClientAPI remoteClient, SceneObjectGroup so, UUID folderID) { +// m_log.DebugFormat( +// "[BASIC INVENTORY ACCESS MODULE]: Creating item for object {0} {1} for folder {2}, action {3}", +// so.Name, so.UUID, folderID, action); +// // Get the user info of the item destination // UUID userID = UUID.Zero; @@ -619,18 +695,18 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess if (remoteClient == null || so.OwnerID != remoteClient.AgentId) { - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.LostAndFound); } else { - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.Trash); } } else if (action == DeRezAction.Return) { // Dump to lost + found unconditionally // - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.LostAndFound); } if (folderID == UUID.Zero && folder == null) @@ -639,21 +715,22 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess { // Deletes go to trash by default // - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.Trash); } else { - if (remoteClient == null || so.OwnerID != remoteClient.AgentId) + if (remoteClient == null || so.RootPart.OwnerID != remoteClient.AgentId) { // Taking copy of another person's item. Take to // Objects folder. - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.Object); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.Object); + so.FromFolderID = UUID.Zero; } else { // Catch all. Use lost & found // - folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.LostAndFound); } } } @@ -663,10 +740,16 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess // if (action == DeRezAction.Take || action == DeRezAction.TakeCopy) { - if (so.FromFolderID != UUID.Zero && userID == remoteClient.AgentId) + if (so.FromFolderID != UUID.Zero && so.RootPart.OwnerID == remoteClient.AgentId) { InventoryFolderBase f = new InventoryFolderBase(so.FromFolderID, userID); folder = m_Scene.InventoryService.GetFolder(f); + + if(folder.Type == 14 || folder.Type == 16) + { + // folder.Type = 6; + folder = m_Scene.InventoryService.GetFolderForType(userID, FolderType.Object); + } } } @@ -722,7 +805,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess UUID RayTargetID, byte BypassRayCast, bool RayEndIsIntersection, bool RezSelected, bool RemoveItem, UUID fromTaskID, bool attachment) { - AssetBase rezAsset = m_Scene.AssetService.Get(assetID.ToString()); + AssetBase rezAsset = m_Scene.AssetService.Get(assetID.ToString()); if (rezAsset == null) { @@ -731,12 +814,14 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess m_log.WarnFormat( "[InventoryAccessModule]: Could not find asset {0} for item {1} {2} for {3} in RezObject()", assetID, item.Name, item.ID, remoteClient.Name); + remoteClient.SendAgentAlertMessage(string.Format("Unable to rez: could not find asset {0} for item {1}.", assetID, item.Name), false); } else { m_log.WarnFormat( "[INVENTORY ACCESS MODULE]: Could not find asset {0} for {1} in RezObject()", assetID, remoteClient.Name); + remoteClient.SendAgentAlertMessage(string.Format("Unable to rez: could not find asset {0}.", assetID), false); } return null; @@ -744,67 +829,34 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess SceneObjectGroup group = null; - string xmlData = Utils.BytesToString(rezAsset.Data); - List objlist = new List(); - List veclist = new List(); + List objlist; + List veclist; + Vector3 bbox; + float offsetHeight; byte bRayEndIsIntersection = (byte)(RayEndIsIntersection ? 1 : 0); Vector3 pos; - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xmlData); - XmlElement e = (XmlElement)doc.SelectSingleNode("/CoalescedObject"); - if (e == null || attachment) // Single - { - SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - - objlist.Add(g); - veclist.Add(new Vector3(0, 0, 0)); + bool single + = m_Scene.GetObjectsToRez( + rezAsset.Data, attachment, out objlist, out veclist, out bbox, out offsetHeight); - float offsetHeight = 0; + if (single) + { pos = m_Scene.GetNewRezLocation( RayStart, RayEnd, RayTargetID, Quaternion.Identity, - BypassRayCast, bRayEndIsIntersection, true, g.GetAxisAlignedBoundingBox(out offsetHeight), false); + BypassRayCast, bRayEndIsIntersection, true, bbox, false); pos.Z += offsetHeight; } else { - XmlElement coll = (XmlElement)e; - float bx = Convert.ToSingle(coll.GetAttribute("x")); - float by = Convert.ToSingle(coll.GetAttribute("y")); - float bz = Convert.ToSingle(coll.GetAttribute("z")); - Vector3 bbox = new Vector3(bx, by, bz); - pos = m_Scene.GetNewRezLocation(RayStart, RayEnd, RayTargetID, Quaternion.Identity, BypassRayCast, bRayEndIsIntersection, true, bbox, false); - pos -= bbox / 2; - - XmlNodeList groups = e.SelectNodes("SceneObjectGroup"); - foreach (XmlNode n in groups) - { - SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(n.OuterXml); - - objlist.Add(g); - XmlElement el = (XmlElement)n; - - string rawX = el.GetAttribute("offsetx"); - string rawY = el.GetAttribute("offsety"); - string rawZ = el.GetAttribute("offsetz"); -// -// m_log.DebugFormat( -// "[INVENTORY ACCESS MODULE]: Converting coalesced object {0} offset <{1}, {2}, {3}>", -// g.Name, rawX, rawY, rawZ); - - float x = Convert.ToSingle(rawX); - float y = Convert.ToSingle(rawY); - float z = Convert.ToSingle(rawZ); - veclist.Add(new Vector3(x, y, z)); - } } - if (item != null && !DoPreRezWhenFromItem(remoteClient, item, objlist, pos, attachment)) + if (item != null && !DoPreRezWhenFromItem(remoteClient, item, objlist, pos, veclist, attachment)) return null; for (int i = 0; i < objlist.Count; i++) @@ -823,11 +875,23 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess m_log.Debug("[INVENTORY ACCESS MODULE]: Object has UUID.Zero! Position 3"); } - foreach (SceneObjectPart part in group.Parts) + // if this was previously an attachment and is now being rezzed, + // save the old attachment info. + if (group.IsAttachment == false && group.RootPart.Shape.State != 0) { - // Make the rezzer the owner, as this is not necessarily set correctly in the serialized asset. - part.LastOwnerID = part.OwnerID; - part.OwnerID = remoteClient.AgentId; + group.RootPart.AttachedPos = group.AbsolutePosition; + group.RootPart.Shape.LastAttachPoint = (byte)group.AttachmentPoint; + } + + if (item == null) + { + // Change ownership. Normally this is done in DoPreRezWhenFromItem(), but in this case we must do it here. + foreach (SceneObjectPart part in group.Parts) + { + // Make the rezzer the owner, as this is not necessarily set correctly in the serialized asset. + part.LastOwnerID = part.OwnerID; + part.OwnerID = remoteClient.AgentId; + } } if (!attachment) @@ -855,7 +919,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess // one full update during the attachment // process causes some clients to fail to display the // attachment properly. - m_Scene.AddNewSceneObject(group, true, false); + m_Scene.AddNewSceneObject(group, !attachment, false); // if attachment we set it's asset id so object updates // can reflect that, if not, we set it's position in world. @@ -902,10 +966,15 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess /// /// /// + /// + /// List of vector position adjustments for a coalesced objects. For ordinary objects + /// this list will contain just Vector3.Zero. The order of adjustments must match the order of objlist + /// /// /// true if we can processed with rezzing, false if we need to abort private bool DoPreRezWhenFromItem( - IClientAPI remoteClient, InventoryItemBase item, List objlist, Vector3 pos, bool isAttachment) + IClientAPI remoteClient, InventoryItemBase item, List objlist, + Vector3 pos, List veclist, bool isAttachment) { UUID fromUserInventoryItemId = UUID.Zero; @@ -928,28 +997,29 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess } } - int primcount = 0; - foreach (SceneObjectGroup g in objlist) - primcount += g.PrimCount; - - if (!m_Scene.Permissions.CanRezObject( - primcount, remoteClient.AgentId, pos) - && !isAttachment) + for (int i = 0; i < objlist.Count; i++) { - // The client operates in no fail mode. It will - // have already removed the item from the folder - // if it's no copy. - // Put it back if it's not an attachment - // - if (((item.CurrentPermissions & (uint)PermissionMask.Copy) == 0) && (!isAttachment)) - remoteClient.SendBulkUpdateInventory(item); + SceneObjectGroup g = objlist[i]; - ILandObject land = m_Scene.LandChannel.GetLandObject(pos.X, pos.Y); - remoteClient.SendAlertMessage(string.Format( - "Can't rez object '{0}' at <{1:F3}, {2:F3}, {3:F3}> on parcel '{4}' in region {5}.", - item.Name, pos.X, pos.Y, pos.Z, land != null ? land.LandData.Name : "Unknown", m_Scene.RegionInfo.RegionName)); + if (!m_Scene.Permissions.CanRezObject( + g.PrimCount, remoteClient.AgentId, pos + veclist[i]) + && !isAttachment) + { + // The client operates in no fail mode. It will + // have already removed the item from the folder + // if it's no copy. + // Put it back if it's not an attachment + // + if (((item.CurrentPermissions & (uint)PermissionMask.Copy) == 0) && (!isAttachment)) + remoteClient.SendBulkUpdateInventory(item); - return false; + ILandObject land = m_Scene.LandChannel.GetLandObject(pos.X, pos.Y); + remoteClient.SendAlertMessage(string.Format( + "Can't rez object '{0}' at <{1:F3}, {2:F3}, {3:F3}> on parcel '{4}' in region {5}.", + item.Name, pos.X, pos.Y, pos.Z, land != null ? land.LandData.Name : "Unknown", m_Scene.Name)); + + return false; + } } for (int i = 0; i < objlist.Count; i++) @@ -977,44 +1047,19 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess // "[INVENTORY ACCESS MODULE]: rootPart.OwnedID {0}, item.Owner {1}, item.CurrentPermissions {2:X}", // rootPart.OwnerID, item.Owner, item.CurrentPermissions); - if ((rootPart.OwnerID != item.Owner) || - (item.CurrentPermissions & 16) != 0) + if ((rootPart.OwnerID != item.Owner) || (item.CurrentPermissions & SceneObjectGroup.SLAM) != 0) { //Need to kill the for sale here rootPart.ObjectSaleType = 0; rootPart.SalePrice = 10; - - if (m_Scene.Permissions.PropagatePermissions()) - { - foreach (SceneObjectPart part in so.Parts) - { - if ((item.Flags & (uint)InventoryItemFlags.ObjectHasMultipleItems) == 0) - { - part.EveryoneMask = item.EveryOnePermissions; - part.NextOwnerMask = item.NextPermissions; - } - part.GroupMask = 0; // DO NOT propagate here - } - - so.ApplyNextOwnerPermissions(); - } } - + foreach (SceneObjectPart part in so.Parts) { part.FromUserInventoryItemID = fromUserInventoryItemId; - - if ((part.OwnerID != item.Owner) || - (item.CurrentPermissions & 16) != 0) - { - part.Inventory.ChangeInventoryOwner(item.Owner); - part.GroupMask = 0; // DO NOT propagate here - } - - part.EveryoneMask = item.EveryOnePermissions; - part.NextOwnerMask = item.NextPermissions; + part.ApplyPermissionsOnRez(item, true, m_Scene); } - + rootPart.TrimPermissions(); if (isAttachment) @@ -1150,4 +1195,4 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess #endregion } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs new file mode 100644 index 0000000..007ff63 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs @@ -0,0 +1,146 @@ +/* + * 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.Threading; +using System.Xml; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.XEngine; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests +{ + [TestFixture] + public class HGAssetMapperTests : OpenSimTestCase + { + [Test] + public void TestPostAssetRewrite() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + XEngine xengine = new OpenSim.Region.ScriptEngine.XEngine.XEngine(); + xengine.DebugLevel = 1; + + IniConfigSource configSource = new IniConfigSource(); + + IConfig startupConfig = configSource.AddConfig("Startup"); + startupConfig.Set("DefaultScriptEngine", "XEngine"); + + IConfig xEngineConfig = configSource.AddConfig("XEngine"); + xEngineConfig.Set("Enabled", "true"); + xEngineConfig.Set("StartDelay", "0"); + xEngineConfig.Set("AppDomainLoading", "false"); + + string homeUrl = "http://hg.HomeTestPostAssetRewriteGrid.com"; + string foreignUrl = "http://hg.ForeignTestPostAssetRewriteGrid.com"; + int soIdTail = 0x1; + UUID assetId = TestHelpers.ParseTail(0x10); + UUID userId = TestHelpers.ParseTail(0x100); + UUID sceneId = TestHelpers.ParseTail(0x1000); + string userFirstName = "TestPostAsset"; + string userLastName = "Rewrite"; + int soPartsCount = 3; + + Scene scene = new SceneHelpers().SetupScene("TestPostAssetRewriteScene", sceneId, 1000, 1000, configSource); + SceneHelpers.SetupSceneModules(scene, configSource, xengine); + scene.StartScripts(); + + HGAssetMapper hgam = new HGAssetMapper(scene, homeUrl); + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, "password"); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, soPartsCount, ua.PrincipalID, "part", soIdTail); + RezScript( + scene, so.UUID, "default { state_entry() { llSay(0, \"Hello World\"); } }", "item1", ua.PrincipalID); + + AssetBase asset = AssetHelpers.CreateAsset(assetId, so); + asset.CreatorID = foreignUrl; + hgam.PostAsset(foreignUrl, asset); + + // Check transformed asset. + AssetBase ncAssetGet = scene.AssetService.Get(assetId.ToString()); + Assert.AreEqual(foreignUrl, ncAssetGet.CreatorID); + string xmlData = Utils.BytesToString(ncAssetGet.Data); + XmlDocument ncAssetGetXmlDoc = new XmlDocument(); + ncAssetGetXmlDoc.LoadXml(xmlData); + +// Console.WriteLine(ncAssetGetXmlDoc.OuterXml); + + XmlNodeList creatorDataNodes = ncAssetGetXmlDoc.GetElementsByTagName("CreatorData"); + + Assert.AreEqual(soPartsCount, creatorDataNodes.Count); + //Console.WriteLine("creatorDataNodes {0}", creatorDataNodes.Count); + + foreach (XmlNode creatorDataNode in creatorDataNodes) + { + Assert.AreEqual( + string.Format("{0};{1} {2}", homeUrl, ua.FirstName, ua.LastName), creatorDataNode.InnerText); + } + + // Check that saved script nodes have attributes + XmlNodeList savedScriptStateNodes = ncAssetGetXmlDoc.GetElementsByTagName("SavedScriptState"); + + Assert.AreEqual(1, savedScriptStateNodes.Count); + Assert.AreEqual(1, savedScriptStateNodes[0].Attributes.Count); + XmlNode uuidAttribute = savedScriptStateNodes[0].Attributes.GetNamedItem("UUID"); + Assert.NotNull(uuidAttribute); + // XXX: To check the actual UUID attribute we would have to do some work to retreive the UUID of the task + // item created earlier. + } + + private void RezScript(Scene scene, UUID soId, string script, string itemName, UUID userId) + { + InventoryItemBase itemTemplate = new InventoryItemBase(); + // itemTemplate.ID = itemId; + itemTemplate.Name = itemName; + itemTemplate.Folder = soId; + itemTemplate.InvType = (int)InventoryType.LSL; + + // XXX: Ultimately it would be better to be able to directly manipulate the script engine to rez a script + // immediately for tests rather than chunter through it's threaded mechanisms. + AutoResetEvent chatEvent = new AutoResetEvent(false); + + scene.EventManager.OnChatFromWorld += (s, c) => + { +// Console.WriteLine("Got chat [{0}]", c.Message); + chatEvent.Set(); + }; + + scene.RezNewScript(userId, itemTemplate, script); + +// Console.WriteLine("HERE"); + Assert.IsTrue(chatEvent.WaitOne(60000), "Chat event in HGAssetMapperTests.RezScript not received"); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs index ac25a93..1d91165 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs @@ -37,14 +37,12 @@ using OpenSim.Data; using OpenSim.Framework; using OpenSim.Framework.Serialization; using OpenSim.Framework.Serialization.External; -using OpenSim.Framework.Communications; using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; using OpenSim.Region.CoreModules.Framework.InventoryAccess; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests { @@ -109,8 +107,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests item1.AssetID = asset1.FullID; item1.ID = item1Id; InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, m_userId, "Objects")[0]; + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, m_userId, "Objects")[0]; item1.Folder = objsFolder.ID; + item1.Flags |= (uint)InventoryItemFlags.ObjectHasMultipleItems; m_scene.AddInventoryItem(item1); SceneObjectGroup so @@ -159,7 +158,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests item1.AssetID = asset1.FullID; item1.ID = item1Id; InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, m_userId, "Objects")[0]; + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, m_userId, "Objects")[0]; item1.Folder = objsFolder.ID; m_scene.AddInventoryItem(item1); diff --git a/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs b/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs index ec22146..862f0b7 100644 --- a/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Library/LibraryModule.cs @@ -30,7 +30,6 @@ using System.IO; using System.Reflection; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; using OpenSim.Region.Framework; @@ -43,6 +42,7 @@ using OpenMetaverse; using log4net; using Mono.Addins; using Nini.Config; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Framework.Library { @@ -175,7 +175,7 @@ namespace OpenSim.Region.CoreModules.Framework.Library m_log.InfoFormat("[LIBRARY MODULE]: Loading library archive {0} ({1})...", iarFileName, simpleName); simpleName = GetInventoryPathFromName(simpleName); - InventoryArchiveReadRequest archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, simpleName, iarFileName, false); + InventoryArchiveReadRequest archread = new InventoryArchiveReadRequest(m_MockScene.InventoryService, m_MockScene.AssetService, m_MockScene.UserAccountService, uinfo, simpleName, iarFileName, false); try { HashSet nodes = archread.Execute(); @@ -184,7 +184,7 @@ namespace OpenSim.Region.CoreModules.Framework.Library // didn't find the subfolder with the given name; place it on the top m_log.InfoFormat("[LIBRARY MODULE]: Didn't find {0} in library. Placing archive on the top level", simpleName); archread.Close(); - archread = new InventoryArchiveReadRequest(m_MockScene, uinfo, "/", iarFileName, false); + archread = new InventoryArchiveReadRequest(m_MockScene.InventoryService, m_MockScene.AssetService, m_MockScene.UserAccountService, uinfo, "/", iarFileName, false); archread.Execute(); } diff --git a/OpenSim/Region/CoreModules/Framework/Library/LocalInventoryService.cs b/OpenSim/Region/CoreModules/Framework/Library/LocalInventoryService.cs index 49589fd..e1e1838 100644 --- a/OpenSim/Region/CoreModules/Framework/Library/LocalInventoryService.cs +++ b/OpenSim/Region/CoreModules/Framework/Library/LocalInventoryService.cs @@ -65,7 +65,7 @@ namespace OpenSim.Region.CoreModules.Framework.Library { InventoryFolderImpl folder = null; InventoryCollection inv = new InventoryCollection(); - inv.UserID = m_Library.Owner; + inv.OwnerID = m_Library.Owner; if (folderID != m_Library.ID) { @@ -87,6 +87,34 @@ namespace OpenSim.Region.CoreModules.Framework.Library return inv; } + public virtual InventoryCollection[] GetMultipleFoldersContent(UUID principalID, UUID[] folderIDs) + { + InventoryCollection[] invColl = new InventoryCollection[folderIDs.Length]; + int i = 0; + foreach (UUID fid in folderIDs) + { + invColl[i++] = GetFolderContent(principalID, fid); + } + + return invColl; + } + + public virtual InventoryItemBase[] GetMultipleItems(UUID principalID, UUID[] itemIDs) + { + InventoryItemBase[] itemColl = new InventoryItemBase[itemIDs.Length]; + int i = 0; + InventoryItemBase item = new InventoryItemBase(); + item.Owner = principalID; + foreach (UUID fid in itemIDs) + { + item.ID = fid; + itemColl[i++] = GetItem(item); + } + + return itemColl; + } + + /// /// Add a new folder to the user's inventory /// @@ -142,28 +170,12 @@ namespace OpenSim.Region.CoreModules.Framework.Library public List GetInventorySkeleton(UUID userId) { return null; } /// - /// Synchronous inventory fetch. - /// - /// - /// - public InventoryCollection GetUserInventory(UUID userID) { return null; } - - /// - /// Request the inventory for a user. This is an asynchronous operation that will call the callback when the - /// inventory has been received - /// - /// - /// - public void GetUserInventory(UUID userID, InventoryReceiptCallback callback) { } - - - /// /// Gets the user folder for the given folder-type /// /// /// /// - public InventoryFolderBase GetFolderForType(UUID userID, AssetType type) { return null; } + public InventoryFolderBase GetFolderForType(UUID userID, FolderType type) { return null; } /// diff --git a/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs b/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs index d84460a..64feec1 100644 --- a/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs @@ -33,6 +33,7 @@ using log4net; using Nini.Config; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Servers; using OpenSim.Region.CoreModules.Framework.Monitoring.Alerts; using OpenSim.Region.CoreModules.Framework.Monitoring.Monitors; @@ -100,6 +101,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring "/monitorstats/" + Uri.EscapeDataString(m_scene.RegionInfo.RegionName), StatsPage); AddMonitors(); + RegisterStatsManagerRegionStatistics(); } public void RemoveRegion(Scene scene) @@ -109,6 +111,9 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring MainServer.Instance.RemoveHTTPHandler("GET", "/monitorstats/" + m_scene.RegionInfo.RegionID); MainServer.Instance.RemoveHTTPHandler("GET", "/monitorstats/" + Uri.EscapeDataString(m_scene.RegionInfo.RegionName)); + + UnRegisterStatsManagerRegionStatistics(); + m_scene = null; } @@ -399,6 +404,45 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring { m_log.Error("[Monitor] " + reporter.Name + " for " + m_scene.RegionInfo.RegionName + " reports " + reason + " (Fatal: " + fatal + ")"); } + + private List registeredStats = new List(); + private void MakeStat(string pName, string pUnitName, Action act) + { + Stat tempStat = new Stat(pName, pName, pName, pUnitName, "scene", m_scene.RegionInfo.RegionName, StatType.Pull, act, StatVerbosity.Info); + StatsManager.RegisterStat(tempStat); + registeredStats.Add(tempStat); + } + private void RegisterStatsManagerRegionStatistics() + { + MakeStat("RootAgents", "avatars", (s) => { s.Value = m_scene.SceneGraph.GetRootAgentCount(); }); + MakeStat("ChildAgents", "avatars", (s) => { s.Value = m_scene.SceneGraph.GetChildAgentCount(); }); + MakeStat("TotalPrims", "objects", (s) => { s.Value = m_scene.SceneGraph.GetTotalObjectsCount(); }); + MakeStat("ActivePrims", "objects", (s) => { s.Value = m_scene.SceneGraph.GetActiveObjectsCount(); }); + MakeStat("ActiveScripts", "scripts", (s) => { s.Value = m_scene.SceneGraph.GetActiveScriptsCount(); }); + + MakeStat("TimeDilation", "sec/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[0]; }); + MakeStat("SimFPS", "fps", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[1]; }); + MakeStat("PhysicsFPS", "fps", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[2]; }); + MakeStat("AgentUpdates", "updates/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[3]; }); + MakeStat("FrameTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[8]; }); + MakeStat("NetTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[9]; }); + MakeStat("OtherTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[12]; }); + MakeStat("PhysicsTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[10]; }); + MakeStat("AgentTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[16]; }); + MakeStat("ImageTime", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[11]; }); + MakeStat("ScriptLines", "lines/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[20]; }); + MakeStat("SimSpareMS", "ms/sec", (s) => { s.Value = m_scene.StatsReporter.LastReportedSimStats[21]; }); + } + + private void UnRegisterStatsManagerRegionStatistics() + { + foreach (Stat stat in registeredStats) + { + StatsManager.DeregisterStat(stat); + stat.Dispose(); + } + registeredStats.Clear(); + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/Search/BasicSearchModule.cs b/OpenSim/Region/CoreModules/Framework/Search/BasicSearchModule.cs new file mode 100644 index 0000000..3849996 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/Search/BasicSearchModule.cs @@ -0,0 +1,199 @@ +/* + * 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.Threading; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Monitoring; +using OpenSim.Region.ClientStack.LindenUDP; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors.Hypergrid; + +using OpenMetaverse; +using OpenMetaverse.Packets; +using log4net; +using Nini.Config; +using Mono.Addins; + +using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags; + +namespace OpenSim.Region.CoreModules.Framework.Search +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "BasicSearchModule")] + public class BasicSearchModule : ISharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected bool m_Enabled; + protected List m_Scenes = new List(); + + private IGroupsModule m_GroupsService = null; + + #region ISharedRegionModule + + public void Initialise(IConfigSource config) + { + string umanmod = config.Configs["Modules"].GetString("SearchModule", Name); + if (umanmod == Name) + { + m_Enabled = true; + m_log.DebugFormat("[BASIC SEARCH MODULE]: {0} is enabled", Name); + } + } + + public bool IsSharedModule + { + get { return true; } + } + + public virtual string Name + { + get { return "BasicSearchModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void AddRegion(Scene scene) + { + if (m_Enabled) + { + m_Scenes.Add(scene); + + scene.EventManager.OnMakeRootAgent += new Action(EventManager_OnMakeRootAgent); + scene.EventManager.OnMakeChildAgent += new EventManager.OnMakeChildAgentDelegate(EventManager_OnMakeChildAgent); + } + } + + public void RemoveRegion(Scene scene) + { + if (m_Enabled) + { + m_Scenes.Remove(scene); + + scene.EventManager.OnMakeRootAgent -= new Action(EventManager_OnMakeRootAgent); + scene.EventManager.OnMakeChildAgent -= new EventManager.OnMakeChildAgentDelegate(EventManager_OnMakeChildAgent); + } + } + + public void RegionLoaded(Scene s) + { + if (!m_Enabled) + return; + + if (m_GroupsService == null) + { + m_GroupsService = s.RequestModuleInterface(); + + // No Groups Service Connector, then group search won't work... + if (m_GroupsService == null) + m_log.Warn("[BASIC SEARCH MODULE]: Could not get IGroupsModule"); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + m_Scenes.Clear(); + } + + #endregion ISharedRegionModule + + + #region Event Handlers + + void EventManager_OnMakeRootAgent(ScenePresence sp) + { + sp.ControllingClient.OnDirFindQuery += OnDirFindQuery; + } + + void EventManager_OnMakeChildAgent(ScenePresence sp) + { + sp.ControllingClient.OnDirFindQuery -= OnDirFindQuery; + } + + void OnDirFindQuery(IClientAPI remoteClient, UUID queryID, string queryText, uint queryFlags, int queryStart) + { + queryText = queryText.Trim(); + + if (((DirFindFlags)queryFlags & DirFindFlags.People) == DirFindFlags.People) + { + if (string.IsNullOrEmpty(queryText)) + remoteClient.SendDirPeopleReply(queryID, new DirPeopleReplyData[0]); + + List accounts = m_Scenes[0].UserAccountService.GetUserAccounts(m_Scenes[0].RegionInfo.ScopeID, queryText); + DirPeopleReplyData[] hits = new DirPeopleReplyData[accounts.Count]; + int i = 0; + foreach (UserAccount acc in accounts) + { + DirPeopleReplyData d = new DirPeopleReplyData(); + d.agentID = acc.PrincipalID; + d.firstName = acc.FirstName; + d.lastName = acc.LastName; + d.online = false; + + hits[i++] = d; + } + + // TODO: This currently ignores pretty much all the query flags including Mature and sort order + remoteClient.SendDirPeopleReply(queryID, hits); + } + else if (((DirFindFlags)queryFlags & DirFindFlags.Groups) == DirFindFlags.Groups) + { + if (m_GroupsService == null) + { + m_log.Warn("[BASIC SEARCH MODULE]: Groups service is not available. Unable to search groups."); + remoteClient.SendAlertMessage("Groups search is not enabled"); + return; + } + + if (string.IsNullOrEmpty(queryText)) + remoteClient.SendDirGroupsReply(queryID, new DirGroupsReplyData[0]); + + // TODO: This currently ignores pretty much all the query flags including Mature and sort order + remoteClient.SendDirGroupsReply(queryID, m_GroupsService.FindGroups(remoteClient, queryText).ToArray()); + } + + } + + #endregion Event Handlers + + } + +} diff --git a/OpenSim/Region/CoreModules/Framework/ServiceThrottle/ServiceThrottleModule.cs b/OpenSim/Region/CoreModules/Framework/ServiceThrottle/ServiceThrottleModule.cs new file mode 100644 index 0000000..3abacbd --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/ServiceThrottle/ServiceThrottleModule.cs @@ -0,0 +1,256 @@ +/* + * 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.Threading; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework.Monitoring; +using OpenSim.Region.Framework.Scenes; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +namespace OpenSim.Region.CoreModules.Framework +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GridServiceThrottleModule")] + public class ServiceThrottleModule : ISharedRegionModule, IServiceThrottleModule + { + private static readonly ILog m_log = LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + private readonly List m_scenes = new List(); + private System.Timers.Timer m_timer = new System.Timers.Timer(); + + private Queue m_RequestQueue = new Queue(); + private Dictionary> m_Pending = new Dictionary>(); + private int m_Interval; + + #region ISharedRegionModule + + public void Initialise(IConfigSource config) + { + m_Interval = Util.GetConfigVarFromSections(config, "Interval", new string[] { "ServiceThrottle" }, 5000); + + m_timer = new System.Timers.Timer(); + m_timer.AutoReset = false; + m_timer.Enabled = true; + m_timer.Interval = 15000; // 15 secs at first + m_timer.Elapsed += ProcessQueue; + m_timer.Start(); + + //WorkManager.StartThread( + // ProcessQueue, + // "GridServiceRequestThread", + // ThreadPriority.BelowNormal, + // true, + // false); + } + + public void AddRegion(Scene scene) + { + lock (m_scenes) + { + m_scenes.Add(scene); + scene.RegisterModuleInterface(this); + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; + } + } + + public void RegionLoaded(Scene scene) + { + } + + public void RemoveRegion(Scene scene) + { + lock (m_scenes) + { + m_scenes.Remove(scene); + scene.EventManager.OnNewClient -= OnNewClient; + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "ServiceThrottleModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion ISharedRegionMOdule + + #region Events + + void OnNewClient(IClientAPI client) + { + client.OnRegionHandleRequest += OnRegionHandleRequest; + } + + void OnMakeRootAgent(ScenePresence obj) + { + lock (m_timer) + { + if (!m_timer.Enabled) + { + m_timer.Interval = m_Interval; + m_timer.Enabled = true; + m_timer.Start(); + } + } + } + + public void OnRegionHandleRequest(IClientAPI client, UUID regionID) + { + //m_log.DebugFormat("[SERVICE THROTTLE]: RegionHandleRequest {0}", regionID); + ulong handle = 0; + if (IsLocalRegionHandle(regionID, out handle)) + { + client.SendRegionHandle(regionID, handle); + return; + } + + Action action = delegate + { + GridRegion r = m_scenes[0].GridService.GetRegionByUUID(UUID.Zero, regionID); + + if (r != null && r.RegionHandle != 0) + client.SendRegionHandle(regionID, r.RegionHandle); + }; + + Enqueue("region", regionID.ToString(), action); + } + + #endregion Events + + #region IServiceThrottleModule + + public void Enqueue(string category, string itemid, Action continuation) + { + lock (m_RequestQueue) + { + if (m_Pending.ContainsKey(category)) + { + if (m_Pending[category].Contains(itemid)) + // Don't enqueue, it's already pending + return; + } + else + m_Pending.Add(category, new List()); + + m_Pending[category].Add(itemid); + + m_RequestQueue.Enqueue(delegate + { + lock (m_RequestQueue) + m_Pending[category].Remove(itemid); + + continuation(); + }); + } + } + + #endregion IServiceThrottleModule + + #region Process Continuation Queue + + private void ProcessQueue(object sender, System.Timers.ElapsedEventArgs e) + { + //m_log.DebugFormat("[YYY]: Process queue with {0} continuations", m_RequestQueue.Count); + + while (m_RequestQueue.Count > 0) + { + Action continuation = null; + lock (m_RequestQueue) + continuation = m_RequestQueue.Dequeue(); + + if (continuation != null) + continuation(); + } + + if (AreThereRootAgents()) + { + lock (m_timer) + { + m_timer.Interval = 1000; // 1 sec + m_timer.Enabled = true; + m_timer.Start(); + } + } + else + lock (m_timer) + m_timer.Enabled = false; + + } + + #endregion Process Continuation Queue + + #region Misc + + private bool IsLocalRegionHandle(UUID regionID, out ulong regionHandle) + { + regionHandle = 0; + foreach (Scene s in m_scenes) + if (s.RegionInfo.RegionID == regionID) + { + regionHandle = s.RegionInfo.RegionHandle; + return true; + } + return false; + } + + private bool AreThereRootAgents() + { + foreach (Scene s in m_scenes) + { + foreach (ScenePresence sp in s.GetScenePresences()) + if (!sp.IsChildAgent) + return true; + } + + return false; + } + + #endregion Misc + } + +} diff --git a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/BinaryLoggingModule.cs b/OpenSim/Region/CoreModules/Framework/Statistics/Logging/BinaryLoggingModule.cs index fb74cc6..f3436d1 100644 --- a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/BinaryLoggingModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Statistics/Logging/BinaryLoggingModule.cs @@ -57,7 +57,7 @@ namespace OpenSim.Region.CoreModules.Framework.Statistics.Logging try { IConfig statConfig = source.Configs["Statistics.Binary"]; - if (statConfig.Contains("enabled") && statConfig.GetBoolean("enabled")) + if (statConfig != null && statConfig.Contains("enabled") && statConfig.GetBoolean("enabled")) { if (statConfig.Contains("collect_region_stats")) { diff --git a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs b/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs deleted file mode 100755 index fd8d5e3..0000000 --- a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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.Text; -using log4net; - -namespace OpenSim.Region.CoreModules.Framework.Statistics.Logging -{ - /// - /// Class for writing a high performance, high volume log file. - /// Sometimes, to debug, one has a high volume logging to do and the regular - /// log file output is not appropriate. - /// Create a new instance with the parameters needed and - /// call Write() to output a line. Call Close() when finished. - /// If created with no parameters, it will not log anything. - /// - public class LogWriter : IDisposable - { - public bool Enabled { get; private set; } - - private string m_logDirectory = "."; - private int m_logMaxFileTimeMin = 5; // 5 minutes - public String LogFileHeader { get; set; } - - private StreamWriter m_logFile = null; - private TimeSpan m_logFileLife; - private DateTime m_logFileEndTime; - private Object m_logFileWriteLock = new Object(); - - // set externally when debugging. If let 'null', this does not write any error messages. - public ILog ErrorLogger = null; - private string LogHeader = "[LOG WRITER]"; - - /// - /// Create a log writer that will not write anything. Good for when not enabled - /// but the write statements are still in the code. - /// - public LogWriter() - { - Enabled = false; - m_logFile = null; - } - - /// - /// Create a log writer instance. - /// - /// The directory to create the log file in. May be 'null' for default. - /// The characters that begin the log file name. May be 'null' for default. - /// Maximum age of a log file in minutes. If zero, will set default. - public LogWriter(string dir, string headr, int maxFileTime) - { - m_logDirectory = dir == null ? "." : dir; - - LogFileHeader = headr == null ? "log-" : headr; - - m_logMaxFileTimeMin = maxFileTime; - if (m_logMaxFileTimeMin < 1) - m_logMaxFileTimeMin = 5; - - m_logFileLife = new TimeSpan(0, m_logMaxFileTimeMin, 0); - m_logFileEndTime = DateTime.Now + m_logFileLife; - - Enabled = true; - } - - public void Dispose() - { - this.Close(); - } - - public void Close() - { - Enabled = false; - if (m_logFile != null) - { - m_logFile.Close(); - m_logFile.Dispose(); - m_logFile = null; - } - } - - public void Write(string line, params object[] args) - { - if (!Enabled) return; - Write(String.Format(line, args)); - } - - public void Flush() - { - if (!Enabled) return; - if (m_logFile != null) - { - m_logFile.Flush(); - } - } - - public void Write(string line) - { - if (!Enabled) return; - try - { - lock (m_logFileWriteLock) - { - DateTime now = DateTime.Now; - if (m_logFile == null || now > m_logFileEndTime) - { - if (m_logFile != null) - { - m_logFile.Close(); - m_logFile.Dispose(); - m_logFile = null; - } - - // First log file or time has expired, start writing to a new log file - m_logFileEndTime = now + m_logFileLife; - string path = (m_logDirectory.Length > 0 ? m_logDirectory - + System.IO.Path.DirectorySeparatorChar.ToString() : "") - + String.Format("{0}{1}.log", LogFileHeader, now.ToString("yyyyMMddHHmmss")); - m_logFile = new StreamWriter(File.Open(path, FileMode.Append, FileAccess.Write)); - } - if (m_logFile != null) - { - StringBuilder buff = new StringBuilder(line.Length + 25); - buff.Append(now.ToString("yyyyMMddHHmmssfff")); - // buff.Append(now.ToString("yyyyMMddHHmmss")); - buff.Append(","); - buff.Append(line); - buff.Append("\r\n"); - m_logFile.Write(buff.ToString()); - } - } - } - catch (Exception e) - { - if (ErrorLogger != null) - { - ErrorLogger.ErrorFormat("{0}: FAILURE WRITING TO LOGFILE: {1}", LogHeader, e); - } - Enabled = false; - } - return; - } - } -} diff --git a/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs b/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs index 4ef57fe..7b89c2c 100644 --- a/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs +++ b/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs @@ -50,16 +50,15 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - #region ISharedRegionModule public new void Initialise(IConfigSource config) { - string umanmod = config.Configs["Modules"].GetString("UserManagementModule", base.Name); + string umanmod = config.Configs["Modules"].GetString("UserManagementModule", null); if (umanmod == Name) { m_Enabled = true; - RegisterConsoleCmds(); + Init(); m_log.DebugFormat("[USER MANAGEMENT MODULE]: {0} is enabled", Name); } } @@ -71,7 +70,7 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement #endregion ISharedRegionModule - protected override void AddAdditionalUsers(UUID avatarID, string query, List users) + protected override void AddAdditionalUsers(string query, List users) { if (query.Contains("@")) // First.Last@foo.com, maybe? { @@ -131,7 +130,17 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement } UserAgentServiceConnector uasConn = new UserAgentServiceConnector(uriStr); - UUID userID = uasConn.GetUUID(names[0], names[1]); + + UUID userID = UUID.Zero; + try + { + userID = uasConn.GetUUID(names[0], names[1]); + } + catch (Exception e) + { + m_log.Debug("[USER MANAGEMENT MODULE]: GetUUID call failed ", e); + } + if (!userID.Equals(UUID.Zero)) { UserData ud = new UserData(); diff --git a/OpenSim/Region/CoreModules/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs b/OpenSim/Region/CoreModules/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs new file mode 100644 index 0000000..4e3b7e5 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs @@ -0,0 +1,75 @@ +/* + * 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 Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.UserManagement; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Framework.UserManagement.Tests +{ + [TestFixture] + public class HGUserManagementModuleTests : OpenSimTestCase + { + /// + /// Test that a new HG agent (i.e. one without a user account) has their name cached in the UMM upon creation. + /// + [Test] + public void TestCachedUserNameForNewAgent() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + HGUserManagementModule hgumm = new HGUserManagementModule(); + UUID userId = TestHelpers.ParseStem("11"); + string firstName = "Fred"; + string lastName = "Astaire"; + string homeUri = "example.com"; + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("UserManagementModule", hgumm.Name); + + SceneHelpers sceneHelpers = new SceneHelpers(); + TestScene scene = sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(scene, config, hgumm); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + acd.firstname = firstName; + acd.lastname = lastName; + acd.ServiceURLs["HomeURI"] = "http://" + homeUri; + + SceneHelpers.AddScenePresence(scene, acd); + + string name = hgumm.GetUserName(userId); + Assert.That(name, Is.EqualTo(string.Format("{0}.{1} @{2}", firstName, lastName, homeUri))); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs b/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs index 77e8b00..7ecbd26 100644 --- a/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs +++ b/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs @@ -28,9 +28,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Threading; using OpenSim.Framework; using OpenSim.Framework.Console; +using OpenSim.Framework.Monitoring; using OpenSim.Region.ClientStack.LindenUDP; using OpenSim.Region.Framework; using OpenSim.Region.Framework.Interfaces; @@ -44,28 +46,24 @@ using log4net; using Nini.Config; using Mono.Addins; +using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags; + namespace OpenSim.Region.CoreModules.Framework.UserManagement { - public class UserData - { - public UUID Id { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public string HomeURL { get; set; } - public Dictionary ServerURLs { get; set; } - } - [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UserManagementModule")] - public class UserManagementModule : ISharedRegionModule, IUserManagement + public class UserManagementModule : ISharedRegionModule, IUserManagement, IPeople { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected bool m_Enabled; protected List m_Scenes = new List(); + protected IServiceThrottleModule m_ServiceThrottle; // The cache protected Dictionary m_UserCache = new Dictionary(); + protected bool m_DisplayChangingHomeURI = false; + #region ISharedRegionModule public void Initialise(IConfigSource config) @@ -74,9 +72,20 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement if (umanmod == Name) { m_Enabled = true; - RegisterConsoleCmds(); + Init(); m_log.DebugFormat("[USER MANAGEMENT MODULE]: {0} is enabled", Name); } + + if(!m_Enabled) + { + return; + } + + IConfig userManagementConfig = config.Configs["UserManagement"]; + if (userManagementConfig == null) + return; + + m_DisplayChangingHomeURI = userManagementConfig.GetBoolean("DisplayChangingHomeURI", false); } public bool IsSharedModule @@ -98,9 +107,13 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement { if (m_Enabled) { - m_Scenes.Add(scene); + lock (m_Scenes) + { + m_Scenes.Add(scene); + } scene.RegisterModuleInterface(this); + scene.RegisterModuleInterface(this); scene.EventManager.OnNewClient += new EventManager.OnNewClientDelegate(EventManager_OnNewClient); scene.EventManager.OnPrimsLoaded += new EventManager.PrimsLoaded(EventManager_OnPrimsLoaded); } @@ -111,12 +124,17 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement if (m_Enabled) { scene.UnregisterModuleInterface(this); - m_Scenes.Remove(scene); + lock (m_Scenes) + { + m_Scenes.Remove(scene); + } } } public void RegionLoaded(Scene s) { + if (m_Enabled && m_ServiceThrottle == null) + m_ServiceThrottle = s.RequestModuleInterface(); } public void PostInitialise() @@ -125,7 +143,10 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement public void Close() { - m_Scenes.Clear(); + lock (m_Scenes) + { + m_Scenes.Clear(); + } lock (m_UserCache) m_UserCache.Clear(); @@ -133,7 +154,7 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement #endregion ISharedRegionModule - + #region Event Handlers void EventManager_OnPrimsLoaded(Scene s) @@ -143,7 +164,6 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement s.ForEachSOG(delegate(SceneObjectGroup sog) { CacheCreators(sog); }); } - void EventManager_OnNewClient(IClientAPI client) { client.OnConnectionClosed += new Action(HandleConnectionClosed); @@ -157,21 +177,52 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement client.OnAvatarPickerRequest -= new AvatarPickerRequest(HandleAvatarPickerRequest); } - void HandleUUIDNameRequest(UUID uuid, IClientAPI remote_client) + void HandleUUIDNameRequest(UUID uuid, IClientAPI client) { +// m_log.DebugFormat( +// "[USER MANAGEMENT MODULE]: Handling request for name binding of UUID {0} from {1}", +// uuid, remote_client.Name); + if (m_Scenes[0].LibraryService != null && (m_Scenes[0].LibraryService.LibraryRootFolder.Owner == uuid)) { - remote_client.SendNameReply(uuid, "Mr", "OpenSim"); + client.SendNameReply(uuid, "Mr", "OpenSim"); } else { - string[] names = GetUserNames(uuid); - if (names.Length == 2) + UserData user; + /* bypass that continuation here when entry is already available */ + lock (m_UserCache) { - //m_log.DebugFormat("[XXX] HandleUUIDNameRequest {0} is {1} {2}", uuid, names[0], names[1]); - remote_client.SendNameReply(uuid, names[0], names[1]); + if (m_UserCache.TryGetValue(uuid, out user)) + { + if (!user.IsUnknownUser && user.HasGridUserTried) + { + client.SendNameReply(uuid, user.FirstName, user.LastName); + return; + } + } } + // Not found in cache, queue continuation + m_ServiceThrottle.Enqueue("name", uuid.ToString(), delegate + { + //m_log.DebugFormat("[YYY]: Name request {0}", uuid); + + // As least upto September 2013, clients permanently cache UUID -> Name bindings. Some clients + // appear to clear this when the user asks it to clear the cache, but others may not. + // + // So to avoid clients + // (particularly Hypergrid clients) permanently binding "Unknown User" to a given UUID, we will + // instead drop the request entirely. + if (GetUser(uuid, out user)) + { + client.SendNameReply(uuid, user.FirstName, user.LastName); + } +// else +// m_log.DebugFormat( +// "[USER MANAGEMENT MODULE]: No bound name for {0} found, ignoring request from {1}", +// uuid, client.Name); + }); } } @@ -181,29 +232,7 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement m_log.DebugFormat("[USER MANAGEMENT MODULE]: HandleAvatarPickerRequest for {0}", query); - // searhc the user accounts service - List accs = m_Scenes[0].UserAccountService.GetUserAccounts(m_Scenes[0].RegionInfo.ScopeID, query); - - List users = new List(); - if (accs != null) - { - foreach (UserAccount acc in accs) - { - UserData ud = new UserData(); - ud.FirstName = acc.FirstName; - ud.LastName = acc.LastName; - ud.Id = acc.PrincipalID; - users.Add(ud); - } - } - - // search the local cache - foreach (UserData data in m_UserCache.Values) - if (users.Find(delegate(UserData d) { return d.Id == data.Id; }) == null && - (data.FirstName.StartsWith(query) || data.LastName.StartsWith(query))) - users.Add(data); - - AddAdditionalUsers(avatarID, query, users); + List users = GetUserData(query, 500, 1); AvatarPickerReplyPacket replyPacket = (AvatarPickerReplyPacket)PacketPool.Instance.GetPacket(PacketType.AvatarPickerReply); // TODO: don't create new blocks if recycling an old packet @@ -249,60 +278,62 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement client.SendAvatarPickerReply(agent_data, data_args); } - protected virtual void AddAdditionalUsers(UUID avatarID, string query, List users) + protected virtual void AddAdditionalUsers(string query, List users) { } #endregion Event Handlers - private void CacheCreators(SceneObjectGroup sog) - { - //m_log.DebugFormat("[USER MANAGEMENT MODULE]: processing {0} {1}; {2}", sog.RootPart.Name, sog.RootPart.CreatorData, sog.RootPart.CreatorIdentification); - AddUser(sog.RootPart.CreatorID, sog.RootPart.CreatorData); - - foreach (SceneObjectPart sop in sog.Parts) - { - AddUser(sop.CreatorID, sop.CreatorData); - foreach (TaskInventoryItem item in sop.TaskInventory.Values) - AddUser(item.CreatorID, item.CreatorData); - } - } + #region IPeople - private string[] GetUserNames(UUID uuid) + public List GetUserData(string query, int page_size, int page_number) { - string[] returnstring = new string[2]; + // search the user accounts service + List accs = m_Scenes[0].UserAccountService.GetUserAccounts(m_Scenes[0].RegionInfo.ScopeID, query); - lock (m_UserCache) + List users = new List(); + if (accs != null) { - if (m_UserCache.ContainsKey(uuid)) + foreach (UserAccount acc in accs) { - returnstring[0] = m_UserCache[uuid].FirstName; - returnstring[1] = m_UserCache[uuid].LastName; - return returnstring; + UserData ud = new UserData(); + ud.FirstName = acc.FirstName; + ud.LastName = acc.LastName; + ud.Id = acc.PrincipalID; + ud.HasGridUserTried = true; + ud.IsUnknownUser = false; + users.Add(ud); } } - UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(UUID.Zero, uuid); - - if (account != null) + // search the local cache + foreach (UserData data in m_UserCache.Values) { - returnstring[0] = account.FirstName; - returnstring[1] = account.LastName; + if (data.Id != UUID.Zero && !data.IsUnknownUser && + users.Find(delegate(UserData d) { return d.Id == data.Id; }) == null && + (data.FirstName.ToLower().StartsWith(query.ToLower()) || data.LastName.ToLower().StartsWith(query.ToLower()))) + users.Add(data); + } - UserData user = new UserData(); - user.FirstName = account.FirstName; - user.LastName = account.LastName; + AddAdditionalUsers(query, users); - lock (m_UserCache) - m_UserCache[uuid] = user; - } - else + return users; + + } + + #endregion IPeople + + private void CacheCreators(SceneObjectGroup sog) + { + //m_log.DebugFormat("[USER MANAGEMENT MODULE]: processing {0} {1}; {2}", sog.RootPart.Name, sog.RootPart.CreatorData, sog.RootPart.CreatorIdentification); + AddUser(sog.RootPart.CreatorID, sog.RootPart.CreatorData); + + foreach (SceneObjectPart sop in sog.Parts) { - returnstring[0] = "Unknown"; - returnstring[1] = "User"; + AddUser(sop.CreatorID, sop.CreatorData); + foreach (TaskInventoryItem item in sop.TaskInventory.Values) + AddUser(item.CreatorID, item.CreatorData); } - - return returnstring; } #region IUserManagement @@ -338,55 +369,56 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement public string GetUserName(UUID uuid) { - string[] names = GetUserNames(uuid); - if (names.Length == 2) - { - string firstname = names[0]; - string lastname = names[1]; - - return firstname + " " + lastname; - - } - return "(hippos)"; + UserData user; + GetUser(uuid, out user); + return user.FirstName + " " + user.LastName; } public string GetUserHomeURL(UUID userID) { - lock (m_UserCache) + UserData user; + if(GetUser(userID, out user)) { - if (m_UserCache.ContainsKey(userID)) - return m_UserCache[userID].HomeURL; + return user.HomeURL; } - return string.Empty; } public string GetUserServerURL(UUID userID, string serverType) { UserData userdata; - lock (m_UserCache) - m_UserCache.TryGetValue(userID, out userdata); + if(!GetUser(userID, out userdata)) + { + return string.Empty; + } + + if (userdata.ServerURLs != null && userdata.ServerURLs.ContainsKey(serverType) && userdata.ServerURLs[serverType] != null) + { + return userdata.ServerURLs[serverType].ToString(); + } - if (userdata != null) + if (!string.IsNullOrEmpty(userdata.HomeURL)) { // m_log.DebugFormat("[USER MANAGEMENT MODULE]: Requested url type {0} for {1}", serverType, userID); - if (userdata.ServerURLs != null && userdata.ServerURLs.ContainsKey(serverType) && userdata.ServerURLs[serverType] != null) + UserAgentServiceConnector uConn = new UserAgentServiceConnector(userdata.HomeURL); + try { - return userdata.ServerURLs[serverType].ToString(); + userdata.ServerURLs = uConn.GetServerURLs(userID); } - - if (userdata.HomeURL != null && userdata.HomeURL != string.Empty) + catch(System.Net.WebException e) { - //m_log.DebugFormat( - // "[USER MANAGEMENT MODULE]: Did not find url type {0} so requesting urls from '{1}' for {2}", - // serverType, userdata.HomeURL, userID); - - UserAgentServiceConnector uConn = new UserAgentServiceConnector(userdata.HomeURL); - userdata.ServerURLs = uConn.GetServerURLs(userID); - if (userdata.ServerURLs != null && userdata.ServerURLs.ContainsKey(serverType) && userdata.ServerURLs[serverType] != null) - return userdata.ServerURLs[serverType].ToString(); + m_log.DebugFormat("[USER MANAGEMENT MODULE]: GetServerURLs call failed {0}", e.Message); + userdata.ServerURLs = new Dictionary(); } + catch (Exception e) + { + m_log.Debug("[USER MANAGEMENT MODULE]: GetServerURLs call failed ", e); + userdata.ServerURLs = new Dictionary(); + } + + if (userdata.ServerURLs != null && userdata.ServerURLs.ContainsKey(serverType) && userdata.ServerURLs[serverType] != null) + return userdata.ServerURLs[serverType].ToString(); } return string.Empty; @@ -394,13 +426,15 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement public string GetUserUUI(UUID userID) { - UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(m_Scenes[0].RegionInfo.ScopeID, userID); - if (account != null) - return userID.ToString(); + string uui; + GetUserUUI(userID, out uui); + return uui; + } + public bool GetUserUUI(UUID userID, out string uui) + { UserData ud; - lock (m_UserCache) - m_UserCache.TryGetValue(userID, out ud); + bool result = GetUser(userID, out ud); if (ud != null) { @@ -414,121 +448,251 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement first = parts[0]; last = parts[1]; } - return userID + ";" + homeURL + ";" + first + " " + last; + uui = userID + ";" + homeURL + ";" + first + " " + last; } } - return userID.ToString(); + uui = userID.ToString(); + return result; } - public void AddUser(UUID uuid, string first, string last) + #region Cache Management + public bool GetUser(UUID uuid, out UserData userdata) { lock (m_UserCache) { - if (m_UserCache.ContainsKey(uuid)) - return; + if (m_UserCache.TryGetValue(uuid, out userdata)) + { + if (userdata.HasGridUserTried) + { + return true; + } + } + else + { + userdata = new UserData(); + userdata.HasGridUserTried = false; + userdata.Id = uuid; + userdata.FirstName = "Unknown"; + userdata.LastName = "UserUMMAU42"; + userdata.HomeURL = string.Empty; + userdata.IsUnknownUser = true; + userdata.HasGridUserTried = false; + } + } + + /* BEGIN: do not wrap this code in any lock here + * There are HTTP calls in here. + */ + if (!userdata.HasGridUserTried) + { + /* rewrite here */ + UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(m_Scenes[0].RegionInfo.ScopeID, uuid); + if (account != null) + { + userdata.FirstName = account.FirstName; + userdata.LastName = account.LastName; + userdata.HomeURL = string.Empty; + userdata.IsUnknownUser = false; + userdata.HasGridUserTried = true; + } } - UserData user = new UserData(); - user.Id = uuid; - user.FirstName = first; - user.LastName = last; + if (!userdata.HasGridUserTried) + { + GridUserInfo uInfo = null; + if (null != m_Scenes[0].GridUserService) + { + uInfo = m_Scenes[0].GridUserService.GetGridUserInfo(uuid.ToString()); + } + if (uInfo != null) + { + string url, first, last, tmp; + UUID u; + if(uInfo.UserID.Length <= 36) + { + /* not a UUI */ + } + else if (Util.ParseUniversalUserIdentifier(uInfo.UserID, out u, out url, out first, out last, out tmp)) + { + if (url != string.Empty) + { + userdata.FirstName = first.Replace(" ", ".") + "." + last.Replace(" ", "."); + userdata.HomeURL = url; + try + { + userdata.LastName = "@" + new Uri(url).Authority; + userdata.IsUnknownUser = false; + } + catch + { + userdata.LastName = "@unknown"; + } + userdata.HasGridUserTried = true; + } + } + else + m_log.DebugFormat("[USER MANAGEMENT MODULE]: Unable to parse UUI {0}", uInfo.UserID); + } + } + /* END: do not wrap this code in any lock here */ - AddUserInternal(user); + lock (m_UserCache) + { + m_UserCache[uuid] = userdata; + } + return !userdata.IsUnknownUser; } - public void AddUser(UUID uuid, string first, string last, string homeURL) + public void AddUser(UUID uuid, string first, string last) { - //m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, first {1}, last {2}, url {3}", uuid, first, last, homeURL); - if (homeURL == string.Empty) - return; - - AddUser(uuid, homeURL + ";" + first + " " + last); + lock(m_UserCache) + { + if(!m_UserCache.ContainsKey(uuid)) + { + UserData user = new UserData(); + user.Id = uuid; + user.FirstName = first; + user.LastName = last; + user.IsUnknownUser = false; + user.HasGridUserTried = false; + m_UserCache.Add(uuid, user); + } + } } - public void AddUser (UUID id, string creatorData) + public void AddUser(UUID uuid, string first, string last, string homeURL) { - //m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, creatorData {1}", id, creatorData); + //m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, first {1}, last {2}, url {3}", uuid, first, last, homeURL); UserData oldUser; - //lock the whole block - prevent concurrent update lock (m_UserCache) { - m_UserCache.TryGetValue (id, out oldUser); - if (oldUser != null) + if (m_UserCache.TryGetValue(uuid, out oldUser)) { - if (creatorData == null || creatorData == String.Empty) + if (!oldUser.IsUnknownUser) { - //ignore updates without creator data + if (homeURL != oldUser.HomeURL && m_DisplayChangingHomeURI) + { + m_log.DebugFormat("[USER MANAGEMENT MODULE]: Different HomeURI for {0} {1} ({2}): {3} and {4}", + first, last, uuid.ToString(), homeURL, oldUser.HomeURL); + } + /* no update needed */ return; } - //try update unknown users - //and creator's home URL's - if ((oldUser.FirstName == "Unknown" && !creatorData.Contains ("Unknown")) || (oldUser.HomeURL != null && !creatorData.StartsWith (oldUser.HomeURL))) + } + else if(!m_UserCache.ContainsKey(uuid)) + { + oldUser = new UserData(); + oldUser.HasGridUserTried = false; + oldUser.IsUnknownUser = false; + if (homeURL != string.Empty) { - m_UserCache.Remove (id); -// m_log.DebugFormat("[USER MANAGEMENT MODULE]: Re-adding user with id {0}, creatorData [{1}] and old HomeURL {2}", id, creatorData,oldUser.HomeURL); + oldUser.FirstName = first.Replace(" ", ".") + "." + last.Replace(" ", "."); + try + { + oldUser.LastName = "@" + new Uri(homeURL).Authority; + oldUser.IsUnknownUser = false; + } + catch + { + oldUser.LastName = "@unknown"; + } } else { - //we have already a valid user within the cache - return; + oldUser.FirstName = first; + oldUser.LastName = last; } + oldUser.HomeURL = homeURL; + oldUser.Id = uuid; + m_UserCache.Add(uuid, oldUser); } + } + } - UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount (m_Scenes [0].RegionInfo.ScopeID, id); + public void AddUser(UUID id, string creatorData) + { + // m_log.InfoFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, creatorData {1}", id, creatorData); - if (account != null) + if(string.IsNullOrEmpty(creatorData)) + { + AddUser(id, string.Empty, string.Empty, string.Empty); + } + else + { + string homeURL; + string firstname = string.Empty; + string lastname = string.Empty; + + //creatorData = ; + + string[] parts = creatorData.Split(';'); + if(parts.Length > 1) { - AddUser (id, account.FirstName, account.LastName); + string[] nameparts = parts[1].Split(' '); + firstname = nameparts[0]; + for(int xi = 1; xi < nameparts.Length; ++xi) + { + if(xi != 1) + { + lastname += " "; + } + lastname += nameparts[xi]; + } } else { - UserData user = new UserData (); - user.Id = id; - - if (creatorData != null && creatorData != string.Empty) + firstname = "Unknown"; + lastname = "UserUMMAU5"; + } + if (parts.Length >= 1) + { + homeURL = parts[0]; + if(Uri.IsWellFormedUriString(homeURL, UriKind.Absolute)) + { + AddUser(id, firstname, lastname, homeURL); + } + else { - //creatorData = ; + m_log.DebugFormat("[SCENE]: Unable to parse Uri {0} for CreatorID {1}", parts[0], creatorData); - string[] parts = creatorData.Split (';'); - if (parts.Length >= 1) + lock (m_UserCache) { - user.HomeURL = parts [0]; - try - { - Uri uri = new Uri (parts [0]); - user.LastName = "@" + uri.Authority; - } - catch (UriFormatException) + if(!m_UserCache.ContainsKey(id)) { - m_log.DebugFormat ("[SCENE]: Unable to parse Uri {0}", parts [0]); - user.LastName = "@unknown"; + UserData newUser = new UserData(); + newUser.Id = id; + newUser.FirstName = firstname + "." + lastname.Replace(' ', '.'); + newUser.LastName = "@unknown"; + newUser.HomeURL = string.Empty; + newUser.HasGridUserTried = false; + newUser.IsUnknownUser = true; /* we mark those users as Unknown user so a re-retrieve may be activated */ + m_UserCache.Add(id, newUser); } } - if (parts.Length >= 2) - user.FirstName = parts [1].Replace (' ', '.'); } - else + } + else + { + lock(m_UserCache) { - user.FirstName = "Unknown"; - user.LastName = "User"; + if(!m_UserCache.ContainsKey(id)) + { + UserData newUser = new UserData(); + newUser.Id = id; + newUser.FirstName = "Unknown"; + newUser.LastName = "UserUMMAU4"; + newUser.HomeURL = string.Empty; + newUser.IsUnknownUser = true; + newUser.HasGridUserTried = false; + m_UserCache.Add(id, newUser); + } } - - AddUserInternal (user); } } } - - void AddUserInternal(UserData user) - { - lock (m_UserCache) - m_UserCache[user.Id] = user; - - //m_log.DebugFormat( - // "[USER MANAGEMENT MODULE]: Added user {0} {1} {2} {3}", - // user.Id, user.FirstName, user.LastName, user.HomeURL); - } + #endregion public bool IsLocalGridUser(UUID uuid) { @@ -541,36 +705,95 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement #endregion IUserManagement + protected void Init() + { + AddUser(UUID.Zero, "Unknown", "User"); + RegisterConsoleCmds(); + } + protected void RegisterConsoleCmds() { MainConsole.Instance.Commands.AddCommand("Users", true, + "show name", + "show name ", + "Show the bindings between a single user UUID and a user name", + String.Empty, + HandleShowUser); + + MainConsole.Instance.Commands.AddCommand("Users", true, "show names", "show names", "Show the bindings between user UUIDs and user names", String.Empty, HandleShowUsers); + + MainConsole.Instance.Commands.AddCommand("Users", true, + "reset user cache", + "reset user cache", + "reset user cache to allow changed settings to be applied", + String.Empty, + HandleResetUserCache); } - private void HandleShowUsers(string module, string[] cmd) + private void HandleResetUserCache(string module, string[] cmd) { - lock (m_UserCache) + lock(m_UserCache) { - if (m_UserCache.Count == 0) - { - MainConsole.Instance.Output("No users found"); - return; - } - - MainConsole.Instance.Output("UUID User Name"); - MainConsole.Instance.Output("-----------------------------------------------------------------------------"); - foreach (KeyValuePair kvp in m_UserCache) - { - MainConsole.Instance.Output(String.Format("{0} {1} {2} ({3})", - kvp.Key, kvp.Value.FirstName, kvp.Value.LastName, kvp.Value.HomeURL)); - } - + m_UserCache.Clear(); + } + } + + private void HandleShowUser(string module, string[] cmd) + { + if (cmd.Length < 3) + { + MainConsole.Instance.OutputFormat("Usage: show name "); return; } + + UUID userId; + if (!ConsoleUtil.TryParseConsoleUuid(MainConsole.Instance, cmd[2], out userId)) + return; + + UserData ud; + + if(!GetUser(userId, out ud)) + { + MainConsole.Instance.OutputFormat("No name known for user with id {0}", userId); + return; + } + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("UUID", 36); + cdt.AddColumn("Name", 30); + cdt.AddColumn("HomeURL", 40); + cdt.AddRow(userId, string.Format("{0} {1}", ud.FirstName, ud.LastName), ud.HomeURL); + + MainConsole.Instance.Output(cdt.ToString()); } + + private void HandleShowUsers(string module, string[] cmd) + { + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("UUID", 36); + cdt.AddColumn("Name", 30); + cdt.AddColumn("HomeURL", 40); + cdt.AddColumn("Checked", 10); + + Dictionary copyDict; + lock(m_UserCache) + { + copyDict = new Dictionary(m_UserCache); + } + + foreach(KeyValuePair kvp in copyDict) + { + cdt.AddRow(kvp.Key, string.Format("{0} {1}", kvp.Value.FirstName, kvp.Value.LastName), kvp.Value.HomeURL, kvp.Value.HasGridUserTried ? "yes" : "no"); + } + + MainConsole.Instance.Output(cdt.ToString()); + } + } -} \ No newline at end of file + +} -- cgit v1.1