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. --- .../AssetTransaction/AgentAssetsTransactions.cs | 9 + .../AssetTransaction/AssetTransactionModule.cs | 2 +- .../Agent/AssetTransaction/AssetXferUploader.cs | 21 +- .../Agent/TextureSender/J2KDecoderModule.cs | 28 +- .../Region/CoreModules/Asset/CenomeAssetCache.cs | 17 +- OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs | 5 + .../Region/CoreModules/Asset/FlotsamAssetCache.cs | 400 ++-- .../CoreModules/Asset/GlynnTuckerAssetCache.cs | 5 + .../Asset/Tests/FlotsamAssetCacheTests.cs | 1 - .../Avatar/Attachments/AttachmentsModule.cs | 792 +++++--- .../Attachments/Tests/AttachmentsModuleTests.cs | 430 +++- .../Avatar/AvatarFactory/AvatarFactoryModule.cs | 544 +++++- .../Tests/AvatarFactoryModuleTests.cs | 93 +- .../Avatar/BakedTextures/XBakesModule.cs | 200 ++ .../Region/CoreModules/Avatar/Chat/ChatModule.cs | 168 +- .../Avatar/Chat/Tests/ChatModuleTests.cs | 285 +++ .../CoreModules/Avatar/Combat/CombatModule.cs | 5 + .../CoreModules/Avatar/Dialog/DialogModule.cs | 30 +- .../Avatar/Friends/CallingCardModule.cs | 5 +- .../CoreModules/Avatar/Friends/FriendsModule.cs | 50 +- .../Avatar/Friends/FriendsRequestHandler.cs | 31 +- .../CoreModules/Avatar/Friends/HGFriendsModule.cs | 20 +- .../Avatar/Friends/Tests/FriendModuleTests.cs | 1 - .../Region/CoreModules/Avatar/Gods/GodsModule.cs | 198 +- .../InstantMessage/HGMessageTransferModule.cs | 18 +- .../Avatar/InstantMessage/MessageTransferModule.cs | 170 +- .../Avatar/InstantMessage/MuteListModule.cs | 1 - .../Avatar/InstantMessage/OfflineMessageModule.cs | 10 +- .../Avatar/InstantMessage/PresenceModule.cs | 2 + .../Archiver/InventoryArchiveReadRequest.cs | 155 +- .../Inventory/Archiver/InventoryArchiveUtils.cs | 86 +- .../Archiver/InventoryArchiveWriteRequest.cs | 120 +- .../Inventory/Archiver/InventoryArchiverModule.cs | 106 +- .../Tests/InventoryArchiveLoadPathTests.cs | 360 ++++ .../Archiver/Tests/InventoryArchiveLoadTests.cs | 192 ++ .../Archiver/Tests/InventoryArchiveSaveTests.cs | 422 ++++ .../Archiver/Tests/InventoryArchiveTestCase.cs | 8 +- .../Archiver/Tests/InventoryArchiverTests.cs | 417 ---- .../Avatar/Inventory/Archiver/Tests/PathTests.cs | 477 ----- .../Inventory/Transfer/InventoryTransferModule.cs | 166 +- .../Transfer/Tests/InventoryTransferModuleTests.cs | 448 +++++ .../Region/CoreModules/Avatar/Lure/HGLureModule.cs | 23 +- .../Region/CoreModules/Avatar/Lure/LureModule.cs | 2 +- .../Avatar/Profile/BasicProfileModule.cs | 3 + .../Avatar/UserProfiles/UserProfileModule.cs | 1406 +++++++++++++ .../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 ++++-- .../CoreModules/Hypergrid/HGWorldMapModule.cs | 72 +- .../Region/CoreModules/Properties/AssemblyInfo.cs | 8 +- .../DynamicTexture/DynamicTextureModule.cs | 6 +- .../Scripting/EMailModules/EmailModule.cs | 4 +- .../Scripting/HttpRequest/ScriptsHttpRequests.cs | 355 +++- .../HttpRequest/Tests/ScriptsHttpRequestsTests.cs | 199 ++ .../CoreModules/Scripting/LSLHttp/UrlModule.cs | 96 +- .../Scripting/LoadImageURL/LoadImageURLModule.cs | 57 +- .../ScriptModuleComms/ScriptModuleCommsModule.cs | 23 +- .../VectorRender/Tests/VectorRenderModuleTests.cs | 5 +- .../Scripting/VectorRender/VectorRenderModule.cs | 21 +- .../Scripting/WorldComm/WorldCommModule.cs | 10 +- .../CoreModules/Scripting/XMLRPC/XMLRPCModule.cs | 31 +- .../LocalUserProfilesServiceConnector.cs | 228 +++ .../LocalAgentPreferencesServiceConnector.cs | 153 ++ .../RemoteAgentPreferencesServiceConnector.cs | 116 ++ .../ServiceConnectorsOut/Asset/HGAssetBroker.cs | 47 +- .../Asset/LocalAssetServiceConnector.cs | 13 +- .../Asset/Tests/AssetConnectorTests.cs | 46 +- .../Authorization/AuthorizationService.cs | 34 +- .../Grid/LocalGridServiceConnector.cs | 100 +- .../ServiceConnectorsOut/Grid/RegionCache.cs | 9 +- .../Grid/RemoteGridServiceConnector.cs | 57 +- .../Grid/Tests/GridConnectorsTests.cs | 3 +- .../GridUser/ActivityDetector.cs | 28 +- .../Inventory/HGInventoryBroker.cs | 131 +- .../Inventory/InventoryCache.cs | 84 +- .../Inventory/LocalInventoryServiceConnector.cs | 38 +- .../Inventory/RemoteXInventoryServiceConnector.cs | 50 +- .../MapImage/MapImageServiceModule.cs | 137 +- .../Neighbour/LocalNeighbourServiceConnector.cs | 4 +- .../Presence/PresenceDetector.cs | 5 +- .../Simulation/LocalSimulationConnector.cs | 86 +- .../Simulation/RemoteSimulationConnector.cs | 74 +- .../LocalUserAccountServiceConnector.cs | 10 +- .../UserAccounts/UserAccountCache.cs | 7 +- .../CoreModules/World/Access/AccessModule.cs | 10 +- .../World/Archiver/ArchiveReadRequest.cs | 314 ++- .../World/Archiver/ArchiveWriteRequest.cs | 84 +- .../CoreModules/World/Archiver/ArchiverModule.cs | 109 +- .../CoreModules/World/Archiver/AssetsArchiver.cs | 9 +- .../CoreModules/World/Archiver/AssetsRequest.cs | 36 +- .../World/Archiver/Tests/ArchiverTests.cs | 23 +- .../World/Estate/EstateManagementCommands.cs | 45 +- .../World/Estate/EstateManagementModule.cs | 519 +++-- .../World/Estate/EstateTerrainXferHandler.cs | 6 +- .../CoreModules/World/Estate/XEstateConnector.cs | 218 +++ .../CoreModules/World/Estate/XEstateModule.cs | 255 +++ .../World/Estate/XEstateRequestHandler.cs | 288 +++ .../Region/CoreModules/World/Land/DwellModule.cs | 22 +- .../Region/CoreModules/World/Land/LandChannel.cs | 5 + .../CoreModules/World/Land/LandManagementModule.cs | 938 ++++++--- .../Region/CoreModules/World/Land/LandObject.cs | 324 +-- .../CoreModules/World/Land/PrimCountModule.cs | 11 +- .../World/Land/Tests/LandManagementModuleTests.cs | 266 +++ .../World/Land/Tests/PrimCountModuleTests.cs | 1 - .../CoreModules/World/LegacyMap/MapImageModule.cs | 694 ++++--- .../World/LegacyMap/ShadedMapTileRenderer.cs | 41 +- .../World/LegacyMap/TexturedMapTileRenderer.cs | 64 +- .../World/LightShare/LightShareModule.cs | 7 +- .../CoreModules/World/Media/Moap/MoapModule.cs | 3 + .../World/Media/Moap/Tests/MoapTests.cs | 1 - .../World/Objects/BuySell/BuySellModule.cs | 21 +- .../World/Objects/Commands/ObjectCommandsModule.cs | 32 +- .../World/Permissions/PermissionsModule.cs | 153 +- .../World/Region/RegionCommandsModule.cs | 160 +- .../CoreModules/World/Region/RestartModule.cs | 32 +- .../World/Serialiser/SerialiseObjects.cs | 30 +- .../World/Serialiser/Tests/SerialiserTests.cs | 556 ++++-- .../Region/CoreModules/World/Sound/SoundModule.cs | 9 + OpenSim/Region/CoreModules/World/Sun/SunModule.cs | 225 +-- .../Terrain/Effects/DefaultTerrainGenerator.cs | 2 +- .../Terrain/FileLoaders/GenericSystemDrawing.cs | 2 +- .../CoreModules/World/Terrain/FileLoaders/LLRAW.cs | 235 +-- .../CoreModules/World/Terrain/FileLoaders/RAW32.cs | 13 +- .../World/Terrain/FileLoaders/Terragen.cs | 49 +- .../World/Terrain/FloodBrushes/NoiseArea.cs | 2 +- .../CoreModules/World/Terrain/ITerrainFeature.cs | 60 + .../CoreModules/World/Terrain/ITerrainModifier.cs | 77 + .../World/Terrain/Modifiers/FillModifier.cs | 93 + .../World/Terrain/Modifiers/LowerModifier.cs | 92 + .../World/Terrain/Modifiers/MaxModifier.cs | 92 + .../World/Terrain/Modifiers/MinModifier.cs | 92 + .../World/Terrain/Modifiers/NoiseModifier.cs | 108 + .../World/Terrain/Modifiers/RaiseModifier.cs | 92 + .../World/Terrain/Modifiers/SmoothModifier.cs | 131 ++ .../World/Terrain/PaintBrushes/NoiseSphere.cs | 2 +- .../CoreModules/World/Terrain/TerrainModifier.cs | 378 ++++ .../World/Terrain/TerrainModifierData.cs | 17 + .../CoreModules/World/Terrain/TerrainModule.cs | 814 ++++++-- .../World/Terrain/Tests/TerrainModuleTests.cs | 75 + .../CoreModules/World/Terrain/Tests/TerrainTest.cs | 36 +- .../CoreModules/World/Warp3DMap/TerrainSplat.cs | 436 +++-- .../World/Warp3DMap/Warp3DImageModule.cs | 260 ++- .../Region/CoreModules/World/Wind/WindModule.cs | 38 +- .../CoreModules/World/WorldMap/MapSearchModule.cs | 94 +- .../CoreModules/World/WorldMap/WorldMapModule.cs | 517 +++-- 163 files changed, 18936 insertions(+), 6213 deletions(-) create mode 100644 OpenSim/Region/CoreModules/Avatar/BakedTextures/XBakesModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Chat/Tests/ChatModuleTests.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs delete mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs delete mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/PathTests.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs 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 create mode 100644 OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs create mode 100644 OpenSim/Region/CoreModules/ServiceConnectorsIn/UserProfiles/LocalUserProfilesServiceConnector.cs create mode 100644 OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/LocalAgentPreferencesServiceConnector.cs create mode 100644 OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/RemoteAgentPreferencesServiceConnector.cs create mode 100644 OpenSim/Region/CoreModules/World/Estate/XEstateConnector.cs create mode 100644 OpenSim/Region/CoreModules/World/Estate/XEstateModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Estate/XEstateRequestHandler.cs create mode 100644 OpenSim/Region/CoreModules/World/Land/Tests/LandManagementModuleTests.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainFeature.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/FillModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/LowerModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/MaxModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/MinModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/NoiseModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/RaiseModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Modifiers/SmoothModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/TerrainModifier.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/TerrainModifierData.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainModuleTests.cs (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs index 0271738..f56d17d 100644 --- a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AgentAssetsTransactions.cs @@ -33,6 +33,7 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; +using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.CoreModules.Agent.AssetTransaction { @@ -119,6 +120,14 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction } else { + // Check if the xfer is a terrain xfer + IEstateModule estateModule = m_Scene.RequestModuleInterface(); + if (estateModule != null) + { + if (estateModule.IsTerrainXfer(xferID)) + return; + } + m_log.ErrorFormat( "[AGENT ASSET TRANSACTIONS]: Could not find uploader for xfer id {0}, packet id {1}, data length {2}", xferID, packetID, data.Length); diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs index d1ad74f..b67c0df 100644 --- a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetTransactionModule.cs @@ -42,7 +42,7 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction public class AssetTransactionModule : INonSharedRegionModule, IAgentAssetTransactions { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected Scene m_Scene; private bool m_dumpAssetsToFile = false; diff --git a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs index 11efe6d..5143204 100644 --- a/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs +++ b/OpenSim/Region/CoreModules/Agent/AssetTransaction/AssetXferUploader.cs @@ -31,8 +31,10 @@ using System.Reflection; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Agent.AssetTransaction { @@ -317,12 +319,14 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction m_asset.Description = item.Description; m_asset.Type = (sbyte)item.AssetType; - // We must always store the item at this point even if the asset hasn't finished uploading, in order - // to avoid a race condition when the appearance module retrieves the item to set the asset id in - // the AvatarAppearance structure. - item.AssetID = m_asset.FullID; - if (item.AssetID != UUID.Zero) + if (m_asset.FullID != UUID.Zero) + { + // We must always store the item at this point even if the asset hasn't finished uploading, in order + // to avoid a race condition when the appearance module retrieves the item to set the asset id in + // the AvatarAppearance structure. + item.AssetID = m_asset.FullID; m_Scene.InventoryService.UpdateItem(item); + } if (m_uploadState == UploadState.Complete) { @@ -375,6 +379,8 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction m_Scene.AssetService.Store(m_asset); m_transactions.RemoveXferUploader(m_transactionID); + + m_Scene.EventManager.TriggerOnNewInventoryItemUploadComplete(ourClient.AgentId, (AssetType)type, m_asset.FullID, m_asset.Name, 0); } /// @@ -406,8 +412,8 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction item.AssetType = type; item.InvType = invType; item.Folder = InventFolder; - item.BasePermissions = 0x7fffffff; - item.CurrentPermissions = 0x7fffffff; + item.BasePermissions = (uint)(PermissionMask.All | PermissionMask.Export); + item.CurrentPermissions = item.BasePermissions; item.GroupPermissions=0; item.EveryOnePermissions=0; item.NextPermissions = nextPerm; @@ -421,5 +427,6 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction m_transactions.RemoveXferUploader(m_transactionID); } + } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index 3764685..47dcbcd 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Reflection; using System.Text; @@ -166,7 +167,7 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender // Do Decode! if (decode) - Decode(assetID, j2kData); + Util.FireAndForget(delegate { Decode(assetID, j2kData); }, null, "J2KDecoderModule.BeginDecode"); } } @@ -182,6 +183,25 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender return DoJ2KDecode(assetID, j2kData, out layers, out components); } + public Image DecodeToImage(byte[] j2kData) + { + if (m_useCSJ2K) + return J2kImage.FromBytes(j2kData); + else + { + ManagedImage mimage; + Image image; + if (OpenJPEG.DecodeToImage(j2kData, out mimage, out image)) + { + mimage = null; + return image; + } + else + return null; + } + } + + #endregion IJ2KDecoder /// @@ -211,7 +231,11 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender { try { - List layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kData)); + List layerStarts; + using (MemoryStream ms = new MemoryStream(j2kData)) + { + layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(ms); + } if (layerStarts != null && layerStarts.Count > 0) { diff --git a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs index e40caec..ebec9d2 100644 --- a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs @@ -194,6 +194,14 @@ namespace OpenSim.Region.CoreModules.Asset #region IImprovedAssetCache Members + public bool Check(string id) + { + AssetBase asset; + + // XXX:This is probably not an efficient implementation. + return m_cache.TryGetValue(id, out asset); + } + /// /// Cache asset. /// @@ -308,9 +316,12 @@ namespace OpenSim.Region.CoreModules.Asset /// public void Close() { - m_enabled = false; - m_cache.Clear(); - m_cache = null; + if (m_enabled) + { + m_enabled = false; + m_cache.Clear(); + m_cache = null; + } } /// diff --git a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs index 9742a5c..f720748 100644 --- a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs @@ -112,6 +112,11 @@ namespace OpenSim.Region.CoreModules.Asset //////////////////////////////////////////////////////////// // IImprovedAssetCache // + public bool Check(string id) + { + // XXX This is probably not an efficient implementation. + return Get(id) != null; + } public void Cache(AssetBase asset) { diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index 8e800cb..7d9c9a9 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -31,26 +31,26 @@ using System; using System.IO; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Timers; - using log4net; using Nini.Config; using Mono.Addins; using OpenMetaverse; - using OpenSim.Framework; using OpenSim.Framework.Console; +using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; //[assembly: Addin("FlotsamAssetCache", "1.1")] -//[assembly: AddinDependency("OpenSim", "0.5")] +//[assembly: AddinDependency("OpenSim", "0.8.1")] namespace OpenSim.Region.CoreModules.Asset { @@ -76,8 +76,6 @@ namespace OpenSim.Region.CoreModules.Asset private static ulong m_RequestsForInprogress; private static ulong m_DiskHits; private static ulong m_MemoryHits; - private static double m_HitRateMemory; - private static double m_HitRateFile; #if WAIT_ON_INPROGRESS_REQUESTS private Dictionary m_CurrentlyWriting = new Dictionary(); @@ -251,23 +249,19 @@ namespace OpenSim.Region.CoreModules.Asset private void UpdateFileCache(string key, AssetBase asset) { - string filename = GetFileName(asset.ID); + string filename = GetFileName(key); try { - // If the file is already cached just update access time. + // If the file is already cached, don't cache it, just touch it so access time is updated if (File.Exists(filename)) { - lock (m_CurrentlyWriting) - { - if (!m_CurrentlyWriting.Contains(filename)) - File.SetLastAccessTime(filename, DateTime.Now); - } - } - else + UpdateFileLastAccessTime(filename); + } + else { // Once we start writing, make sure we flag that we're writing - // that object to the cache so that we don't try to write the + // that object to the cache so that we don't try to write the // same file multiple times. lock (m_CurrentlyWriting) { @@ -279,7 +273,7 @@ namespace OpenSim.Region.CoreModules.Asset else { m_CurrentlyWriting.Add(filename, new ManualResetEvent(false)); - } + } #else if (m_CurrentlyWriting.Contains(filename)) @@ -291,10 +285,11 @@ namespace OpenSim.Region.CoreModules.Asset m_CurrentlyWriting.Add(filename); } #endif + } Util.FireAndForget( - delegate { WriteFileCache(filename, asset); }); + delegate { WriteFileCache(filename, asset); }, null, "FlotsamAssetCache.UpdateFileCache"); } } catch (Exception e) @@ -321,6 +316,24 @@ namespace OpenSim.Region.CoreModules.Asset } /// + /// Updates the cached file with the current time. + /// + /// Filename. + /// true, if the update was successful, false otherwise. + private bool UpdateFileLastAccessTime(string filename) + { + try + { + File.SetLastAccessTime(filename, DateTime.Now); + return true; + } + catch + { + return false; + } + } + + /// /// Try to get an asset from the in-memory cache. /// /// @@ -335,31 +348,62 @@ namespace OpenSim.Region.CoreModules.Asset return asset; } + private bool CheckFromMemoryCache(string id) + { + return m_MemoryCache.Contains(id); + } + /// /// Try to get an asset from the file cache. /// /// - /// + /// An asset retrieved from the file cache. null if there was a problem retrieving an asset. private AssetBase GetFromFileCache(string id) - { + { + string filename = GetFileName(id); + +#if WAIT_ON_INPROGRESS_REQUESTS + // Check if we're already downloading this asset. If so, try to wait for it to + // download. + if (m_WaitOnInprogressTimeout > 0) + { + m_RequestsForInprogress++; + + ManualResetEvent waitEvent; + if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent)) + { + waitEvent.WaitOne(m_WaitOnInprogressTimeout); + return Get(id); + } + } +#else + // Track how often we have the problem that an asset is requested while + // it is still being downloaded by a previous request. + if (m_CurrentlyWriting.Contains(filename)) + { + m_RequestsForInprogress++; + return null; + } +#endif + AssetBase asset = null; - string filename = GetFileName(id); if (File.Exists(filename)) { - FileStream stream = null; try { - stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); - BinaryFormatter bformatter = new BinaryFormatter(); + using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryFormatter bformatter = new BinaryFormatter(); - asset = (AssetBase)bformatter.Deserialize(stream); + asset = (AssetBase)bformatter.Deserialize(stream); - m_DiskHits++; + m_DiskHits++; + } } catch (System.Runtime.Serialization.SerializationException e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); @@ -371,40 +415,40 @@ namespace OpenSim.Region.CoreModules.Asset } catch (Exception e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); } - finally - { - if (stream != null) - stream.Close(); - } } -#if WAIT_ON_INPROGRESS_REQUESTS - // Check if we're already downloading this asset. If so, try to wait for it to - // download. - if (m_WaitOnInprogressTimeout > 0) - { - m_RequestsForInprogress++; + return asset; + } - ManualResetEvent waitEvent; - if (m_CurrentlyWriting.TryGetValue(filename, out waitEvent)) + private bool CheckFromFileCache(string id) + { + bool found = false; + + string filename = GetFileName(id); + + if (File.Exists(filename)) + { + try { - waitEvent.WaitOne(m_WaitOnInprogressTimeout); - return Get(id); + using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (stream != null) + found = true; + } + } + catch (Exception e) + { + m_log.ErrorFormat( + "[FLOTSAM ASSET CACHE]: Failed to check file {0} for asset {1}. Exception {2} {3}", + filename, id, e.Message, e.StackTrace); } } -#else - // Track how often we have the problem that an asset is requested while - // it is still being downloaded by a previous request. - if (m_CurrentlyWriting.Contains(filename)) - { - m_RequestsForInprogress++; - } -#endif - return asset; + + return found; } public AssetBase Get(string id) @@ -426,23 +470,24 @@ namespace OpenSim.Region.CoreModules.Asset if (((m_LogLevel >= 1)) && (m_HitRateDisplay != 0) && (m_Requests % m_HitRateDisplay == 0)) { - m_HitRateFile = (double)m_DiskHits / m_Requests * 100.0; - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Cache Get :: {0} :: {1}", id, asset == null ? "Miss" : "Hit"); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: File Hit Rate {0}% for {1} requests", m_HitRateFile.ToString("0.00"), m_Requests); - - if (m_MemoryCacheEnabled) - { - m_HitRateMemory = (double)m_MemoryHits / m_Requests * 100.0; - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Memory Hit Rate {0}% for {1} requests", m_HitRateMemory.ToString("0.00"), m_Requests); - } - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} unnessesary requests due to requests for assets that are currently downloading.", m_RequestsForInprogress); + GenerateCacheHitReport().ForEach(l => m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0}", l)); } return asset; } + public bool Check(string id) + { + if (m_MemoryCacheEnabled && CheckFromMemoryCache(id)) + return true; + + if (m_FileCacheEnabled && CheckFromFileCache(id)) + return true; + return false; + } + public AssetBase GetCached(string id) { return Get(id); @@ -469,7 +514,7 @@ namespace OpenSim.Region.CoreModules.Asset } catch (Exception e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to expire cached file {0}. Exception {1} {2}", id, e.Message, e.StackTrace); } @@ -520,29 +565,39 @@ namespace OpenSim.Region.CoreModules.Asset /// private void CleanExpiredFiles(string dir, DateTime purgeLine) { - foreach (string file in Directory.GetFiles(dir)) + try { - if (File.GetLastAccessTime(file) < purgeLine) + foreach (string file in Directory.GetFiles(dir)) { - File.Delete(file); + if (File.GetLastAccessTime(file) < purgeLine) + { + File.Delete(file); + } } - } - // Recurse into lower tiers - foreach (string subdir in Directory.GetDirectories(dir)) - { - CleanExpiredFiles(subdir, purgeLine); - } + // Recurse into lower tiers + foreach (string subdir in Directory.GetDirectories(dir)) + { + CleanExpiredFiles(subdir, purgeLine); + } - // Check if a tier directory is empty, if so, delete it - int dirSize = Directory.GetFiles(dir).Length + Directory.GetDirectories(dir).Length; - if (dirSize == 0) - { - Directory.Delete(dir); + // Check if a tier directory is empty, if so, delete it + int dirSize = Directory.GetFiles(dir).Length + Directory.GetDirectories(dir).Length; + if (dirSize == 0) + { + Directory.Delete(dir); + } + else if (dirSize >= m_CacheWarnAt) + { + m_log.WarnFormat( + "[FLOTSAM ASSET CACHE]: Cache folder exceeded CacheWarnAt limit {0} {1}. Suggest increasing tiers, tier length, or reducing cache expiration", + dir, dirSize); + } } - else if (dirSize >= m_CacheWarnAt) + catch (Exception e) { - m_log.WarnFormat("[FLOTSAM ASSET CACHE]: Cache folder exceeded CacheWarnAt limit {0} {1}. Suggest increasing tiers, tier length, or reducing cache expiration", dir, dirSize); + m_log.Warn( + string.Format("[FLOTSAM ASSET CACHE]: Could not complete clean of expired files in {0}, exception ", dir), e); } } @@ -601,7 +656,7 @@ namespace OpenSim.Region.CoreModules.Asset } catch (IOException e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to write asset {0} to temporary location {1} (final {2}) on cache in {3}. Exception {4} {5}.", asset.ID, tempname, filename, directory, e.Message, e.StackTrace); @@ -680,17 +735,31 @@ namespace OpenSim.Region.CoreModules.Asset /// /// This notes the last time the Region had a deep asset scan performed on it. /// - /// - private void StampRegionStatusFile(UUID RegionID) + /// + private void StampRegionStatusFile(UUID regionID) { - string RegionCacheStatusFile = Path.Combine(m_CacheDirectory, "RegionStatus_" + RegionID.ToString() + ".fac"); - if (File.Exists(RegionCacheStatusFile)) + string RegionCacheStatusFile = Path.Combine(m_CacheDirectory, "RegionStatus_" + regionID.ToString() + ".fac"); + + try { - File.SetLastWriteTime(RegionCacheStatusFile, DateTime.Now); + if (File.Exists(RegionCacheStatusFile)) + { + File.SetLastWriteTime(RegionCacheStatusFile, DateTime.Now); + } + else + { + File.WriteAllText( + RegionCacheStatusFile, + "Please do not delete this file unless you are manually clearing your Flotsam Asset Cache."); + } } - else + catch (Exception e) { - File.WriteAllText(RegionCacheStatusFile, "Please do not delete this file unless you are manually clearing your Flotsam Asset Cache."); + m_log.Warn( + string.Format( + "[FLOTSAM ASSET CACHE]: Could not stamp region status file for region {0}. Exception ", + regionID), + e); } } @@ -707,32 +776,49 @@ namespace OpenSim.Region.CoreModules.Asset { UuidGatherer gatherer = new UuidGatherer(m_AssetService); - Dictionary assets = new Dictionary(); + Dictionary assetsFound = new Dictionary(); + foreach (Scene s in m_Scenes) { StampRegionStatusFile(s.RegionInfo.RegionID); s.ForEachSOG(delegate(SceneObjectGroup e) - { - gatherer.GatherAssetUuids(e, assets); - }); - } + { + gatherer.AddForInspection(e); + gatherer.GatherAll(); - foreach (UUID assetID in assets.Keys) - { - string filename = GetFileName(assetID.ToString()); + foreach (UUID assetID in gatherer.GatheredUuids.Keys) + { + if (!assetsFound.ContainsKey(assetID)) + { + string filename = GetFileName(assetID.ToString()); - if (File.Exists(filename)) - { - File.SetLastAccessTime(filename, DateTime.Now); - } - else if (storeUncached) - { - m_AssetService.Get(assetID.ToString()); - } + if (File.Exists(filename)) + { + UpdateFileLastAccessTime(filename); + } + else if (storeUncached) + { + AssetBase cachedAsset = m_AssetService.Get(assetID.ToString()); + if (cachedAsset == null && gatherer.GatheredUuids[assetID] != (sbyte)AssetType.Unknown) + assetsFound[assetID] = false; + else + assetsFound[assetID] = true; + } + } + else if (!assetsFound[assetID]) + { + m_log.DebugFormat( + "[FLOTSAM ASSET CACHE]: Could not find asset {0}, type {1} referenced by object {2} at {3} in scene {4} when pre-caching all scene assets", + assetID, gatherer.GatheredUuids[assetID], e.Name, e.AbsolutePosition, s.Name); + } + } + + gatherer.GatheredUuids.Clear(); + }); } - return assets.Keys.Count; + return assetsFound.Count; } /// @@ -748,7 +834,7 @@ namespace OpenSim.Region.CoreModules.Asset } catch (Exception e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Couldn't clear asset cache directory {0} from {1}. Exception {2} {3}", dir, m_CacheDirectory, e.Message, e.StackTrace); } @@ -762,52 +848,84 @@ namespace OpenSim.Region.CoreModules.Asset } catch (Exception e) { - m_log.ErrorFormat( + m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Couldn't clear asset cache file {0} from {1}. Exception {1} {2}", file, m_CacheDirectory, e.Message, e.StackTrace); } } } + private List GenerateCacheHitReport() + { + List outputLines = new List(); + + double fileHitRate = (double)m_DiskHits / m_Requests * 100.0; + outputLines.Add( + string.Format("File Hit Rate: {0}% for {1} requests", fileHitRate.ToString("0.00"), m_Requests)); + + if (m_MemoryCacheEnabled) + { + double memHitRate = (double)m_MemoryHits / m_Requests * 100.0; + + outputLines.Add( + string.Format("Memory Hit Rate: {0}% for {1} requests", memHitRate.ToString("0.00"), m_Requests)); + } + + outputLines.Add( + string.Format( + "Unnecessary requests due to requests for assets that are currently downloading: {0}", + m_RequestsForInprogress)); + + return outputLines; + } + #region Console Commands private void HandleConsoleCommand(string module, string[] cmdparams) { + ICommandConsole con = MainConsole.Instance; + if (cmdparams.Length >= 2) { string cmd = cmdparams[1]; + switch (cmd) { case "status": if (m_MemoryCacheEnabled) - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Memory Cache : {0} assets", m_MemoryCache.Count); + con.OutputFormat("Memory Cache: {0} assets", m_MemoryCache.Count); else - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Memory cache disabled"); + con.OutputFormat("Memory cache disabled"); if (m_FileCacheEnabled) { int fileCount = GetFileCacheCount(m_CacheDirectory); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: File Cache : {0} assets", fileCount); + con.OutputFormat("File Cache: {0} assets", fileCount); + } + else + { + con.Output("File cache disabled"); + } + + GenerateCacheHitReport().ForEach(l => con.Output(l)); + + if (m_FileCacheEnabled) + { + con.Output("Deep scans have previously been performed on the following regions:"); foreach (string s in Directory.GetFiles(m_CacheDirectory, "*.fac")) - { - m_log.Info("[FLOTSAM ASSET CACHE]: Deep scans have previously been performed on the following regions:"); - + { string RegionID = s.Remove(0,s.IndexOf("_")).Replace(".fac",""); DateTime RegionDeepScanTMStamp = File.GetLastWriteTime(s); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Region: {0}, {1}", RegionID, RegionDeepScanTMStamp.ToString("MM/dd/yyyy hh:mm:ss")); + con.OutputFormat("Region: {0}, {1}", RegionID, RegionDeepScanTMStamp.ToString("MM/dd/yyyy hh:mm:ss")); } } - else - { - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: File cache disabled"); - } break; case "clear": if (cmdparams.Length < 2) { - m_log.Warn("[FLOTSAM ASSET CACHE]: Usage is fcache clear [file] [memory]"); + con.Output("Usage is fcache clear [file] [memory]"); break; } @@ -831,11 +949,11 @@ namespace OpenSim.Region.CoreModules.Asset if (m_MemoryCacheEnabled) { m_MemoryCache.Clear(); - m_log.Info("[FLOTSAM ASSET CACHE]: Memory cache cleared."); + con.Output("Memory cache cleared."); } else { - m_log.Info("[FLOTSAM ASSET CACHE]: Memory cache not enabled."); + con.Output("Memory cache not enabled."); } } @@ -844,32 +962,31 @@ namespace OpenSim.Region.CoreModules.Asset if (m_FileCacheEnabled) { ClearFileCache(); - m_log.Info("[FLOTSAM ASSET CACHE]: File cache cleared."); + con.Output("File cache cleared."); } else { - m_log.Info("[FLOTSAM ASSET CACHE]: File cache not enabled."); + con.Output("File cache not enabled."); } } break; case "assets": - m_log.Info("[FLOTSAM ASSET CACHE]: Ensuring assets are cached for all scenes."); + con.Output("Ensuring assets are cached for all scenes."); - Util.FireAndForget(delegate { + WorkManager.RunInThread(delegate + { int assetReferenceTotal = TouchAllSceneAssets(true); - m_log.InfoFormat( - "[FLOTSAM ASSET CACHE]: Completed check with {0} assets.", - assetReferenceTotal); - }); + con.OutputFormat("Completed check with {0} assets.", assetReferenceTotal); + }, null, "TouchAllSceneAssets"); break; case "expire": if (cmdparams.Length < 3) { - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Invalid parameters for Expire, please specify a valid date & time", cmd); + con.OutputFormat("Invalid parameters for Expire, please specify a valid date & time", cmd); break; } @@ -887,28 +1004,27 @@ namespace OpenSim.Region.CoreModules.Asset if (!DateTime.TryParse(s_expirationDate, out expirationDate)) { - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} is not a valid date & time", cmd); + con.OutputFormat("{0} is not a valid date & time", cmd); break; } if (m_FileCacheEnabled) CleanExpiredFiles(m_CacheDirectory, expirationDate); else - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: File cache not active, not clearing."); + con.OutputFormat("File cache not active, not clearing."); break; default: - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: Unknown command {0}", cmd); + con.OutputFormat("Unknown command {0}", cmd); break; } } else if (cmdparams.Length == 1) { - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: fcache status - Display cache status"); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: fcache clearmem - Remove all assets cached in memory"); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: fcache clearfile - Remove all assets cached on disk"); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: fcache cachescenes - Attempt a deep cache of all assets in all scenes"); - m_log.InfoFormat("[FLOTSAM ASSET CACHE]: fcache - Purge assets older then the specified date & time"); + con.Output("fcache assets - Attempt a deep cache of all assets in all scenes"); + con.Output("fcache expire - Purge assets older then the specified date & time"); + con.Output("fcache clear [file] [memory] - Remove cached assets"); + con.Output("fcache status - Display cache status"); } } @@ -935,6 +1051,18 @@ namespace OpenSim.Region.CoreModules.Asset return true; } + public bool[] AssetsExist(string[] ids) + { + bool[] exist = new bool[ids.Length]; + + for (int i = 0; i < ids.Length; i++) + { + exist[i] = Check(ids[i]); + } + + return exist; + } + public string Store(AssetBase asset) { if (asset.FullID == UUID.Zero) diff --git a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs index 9592ca0..5f76ac2 100644 --- a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs @@ -115,6 +115,11 @@ namespace OpenSim.Region.CoreModules.Asset // IImprovedAssetCache // + public bool Check(string id) + { + return m_Cache.Contains(id); + } + public void Cache(AssetBase asset) { if (asset != null) diff --git a/OpenSim/Region/CoreModules/Asset/Tests/FlotsamAssetCacheTests.cs b/OpenSim/Region/CoreModules/Asset/Tests/FlotsamAssetCacheTests.cs index fd02b08..73e4431 100644 --- a/OpenSim/Region/CoreModules/Asset/Tests/FlotsamAssetCacheTests.cs +++ b/OpenSim/Region/CoreModules/Asset/Tests/FlotsamAssetCacheTests.cs @@ -39,7 +39,6 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Asset.Tests { diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index 58ed554..2f67c4e 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.IO; +using System.Threading; using System.Xml; using log4net; using Mono.Addins; @@ -48,6 +49,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { #region INonSharedRegionModule private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public int DebugLevel { get; set; } + + /// + /// Period to sleep per 100 prims in order to avoid CPU spikes when an avatar with many attachments logs in/changes + /// outfit or many avatars with a medium levels of attachments login/change outfit simultaneously. + /// + /// + /// A value of 0 will apply no pause. The pause is specified in milliseconds. + /// + public int ThrottlePer100PrimsRezzed { get; set; } private Scene m_scene; private IInventoryAccessModule m_invAccessModule; @@ -64,21 +76,127 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { IConfig config = source.Configs["Attachments"]; if (config != null) + { Enabled = config.GetBoolean("Enabled", true); + + ThrottlePer100PrimsRezzed = config.GetInt("ThrottlePer100PrimsRezzed", 0); + } else + { Enabled = true; + } } public void AddRegion(Scene scene) { m_scene = scene; - m_scene.RegisterModuleInterface(this); - if (Enabled) + { + // Only register module with scene if it is enabled. All callers check for a null attachments module. + // Ideally, there should be a null attachments module for when this core attachments module has been + // disabled. Registering only when enabled allows for other attachments module implementations. + m_scene.RegisterModuleInterface(this); m_scene.EventManager.OnNewClient += SubscribeToClientEvents; + m_scene.EventManager.OnStartScript += (localID, itemID) => HandleScriptStateChange(localID, true); + m_scene.EventManager.OnStopScript += (localID, itemID) => HandleScriptStateChange(localID, false); + + MainConsole.Instance.Commands.AddCommand( + "Debug", + false, + "debug attachments log", + "debug attachments log [0|1]", + "Turn on attachments debug logging", + " <= 0 - turns off debug logging\n" + + " >= 1 - turns on attachment message debug logging", + HandleDebugAttachmentsLog); + + MainConsole.Instance.Commands.AddCommand( + "Debug", + false, + "debug attachments throttle", + "debug attachments throttle ", + "Turn on attachments throttling.", + "This requires a millisecond value. " + + " == 0 - disable throttling.\n" + + " > 0 - sleeps for this number of milliseconds per 100 prims rezzed.", + HandleDebugAttachmentsThrottle); + + MainConsole.Instance.Commands.AddCommand( + "Debug", + false, + "debug attachments status", + "debug attachments status", + "Show current attachments debug status", + HandleDebugAttachmentsStatus); + } // TODO: Should probably be subscribing to CloseClient too, but this doesn't yet give us IClientAPI } + + private void HandleDebugAttachmentsLog(string module, string[] args) + { + int debugLevel; + + if (!(args.Length == 4 && int.TryParse(args[3], out debugLevel))) + { + MainConsole.Instance.OutputFormat("Usage: debug attachments log [0|1]"); + } + else + { + DebugLevel = debugLevel; + MainConsole.Instance.OutputFormat( + "Set attachments debug level to {0} in {1}", DebugLevel, m_scene.Name); + } + } + + private void HandleDebugAttachmentsThrottle(string module, string[] args) + { + int ms; + + if (args.Length == 4 && int.TryParse(args[3], out ms)) + { + ThrottlePer100PrimsRezzed = ms; + MainConsole.Instance.OutputFormat( + "Attachments rez throttle per 100 prims is now {0} in {1}", ThrottlePer100PrimsRezzed, m_scene.Name); + + return; + } + + MainConsole.Instance.OutputFormat("Usage: debug attachments throttle "); + } + + private void HandleDebugAttachmentsStatus(string module, string[] args) + { + MainConsole.Instance.OutputFormat("Settings for {0}", m_scene.Name); + MainConsole.Instance.OutputFormat("Debug logging level: {0}", DebugLevel); + MainConsole.Instance.OutputFormat("Throttle per 100 prims: {0}ms", ThrottlePer100PrimsRezzed); + } + + /// + /// Listen for client triggered running state changes so that we can persist the script's object if necessary. + /// + /// + /// + private void HandleScriptStateChange(uint localID, bool started) + { + SceneObjectGroup sog = m_scene.GetGroupByPrim(localID); + if (sog != null && sog.IsAttachment) + { + if (!started) + { + // FIXME: This is a convoluted way for working out whether the script state has changed to stop + // because it has been manually stopped or because the stop was called in UpdateDetachedObject() below + // This needs to be handled in a less tangled way. + ScenePresence sp = m_scene.GetScenePresence(sog.AttachedAvatar); + if (sp.ControllingClient.IsActive) + sog.HasGroupChanged = true; + } + else + { + sog.HasGroupChanged = true; + } + } + } public void RemoveRegion(Scene scene) { @@ -127,8 +245,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments string state = sog.GetStateSnapshot(); ad.AttachmentObjectStates.Add(state); sp.InTransitScriptStates.Add(state); - // Let's remove the scripts of the original object here - sog.RemoveScriptInstances(true); + + // Scripts of the originals will be removed when the Agent is successfully removed. + // sog.RemoveScriptInstances(true); } } } @@ -136,6 +255,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments public void CopyAttachments(AgentData ad, IScenePresence sp) { +// m_log.DebugFormat("[ATTACHMENTS MODULE]: Copying attachment data into {0} in {1}", sp.Name, m_scene.Name); + if (ad.AttachmentObjects != null && ad.AttachmentObjects.Count > 0) { lock (sp.AttachmentsSyncLock) @@ -146,16 +267,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { ((SceneObjectGroup)so).LocalId = 0; ((SceneObjectGroup)so).RootPart.ClearUpdateSchedule(); + +// m_log.DebugFormat( +// "[ATTACHMENTS MODULE]: Copying script state with {0} bytes for object {1} for {2} in {3}", +// ad.AttachmentObjectStates[i].Length, so.Name, sp.Name, m_scene.Name); + so.SetState(ad.AttachmentObjectStates[i++], m_scene); m_scene.IncomingCreateObject(Vector3.Zero, so); } } } - /// - /// RezAttachments. This should only be called upon login on the first region. - /// Attachment rezzings on crossings and TPs are done in a different way. - /// public void RezAttachments(IScenePresence sp) { if (!Enabled) @@ -164,15 +286,35 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (null == sp.Appearance) { m_log.WarnFormat("[ATTACHMENTS MODULE]: Appearance has not been initialized for agent {0}", sp.UUID); + return; } -// m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing any attachments for {0}", sp.Name); + if (sp.GetAttachments().Count > 0) + { + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Not doing simulator-side attachment rez for {0} in {1} as their viewer has already rezzed attachments", + m_scene.Name, sp.Name); + + return; + } + + if (DebugLevel > 0) + m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing any attachments for {0} from simulator-side", sp.Name); List attachments = sp.Appearance.GetAttachments(); + + // Let's get all items at once, so they get cached + UUID[] items = new UUID[attachments.Count]; + int i = 0; + foreach (AvatarAttachment attach in attachments) + items[i++] = attach.ItemID; + m_scene.InventoryService.GetMultipleItems(sp.UUID, items); + foreach (AvatarAttachment attach in attachments) { - uint p = (uint)attach.AttachPoint; + uint attachmentPt = (uint)attach.AttachPoint; // m_log.DebugFormat( // "[ATTACHMENTS MODULE]: Doing initial rez of attachment with itemID {0}, assetID {1}, point {2} for {3} in {4}", @@ -190,16 +332,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { // If we're an NPC then skip all the item checks and manipulations since we don't have an // inventory right now. - if (sp.PresenceType == PresenceType.Npc) - RezSingleAttachmentFromInventoryInternal(sp, UUID.Zero, attach.AssetID, p); - else - RezSingleAttachmentFromInventory(sp, attach.ItemID, p); + RezSingleAttachmentFromInventoryInternal( + sp, sp.PresenceType == PresenceType.Npc ? UUID.Zero : attach.ItemID, attach.AssetID, attachmentPt, true); } catch (Exception e) { - UUID agentId = (sp.ControllingClient == null) ? (UUID)null : sp.ControllingClient.AgentId; + UUID agentId = (sp.ControllingClient == null) ? default(UUID) : sp.ControllingClient.AgentId; m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}", - attach.ItemID, attach.AssetID, p, agentId, e.Message, e.StackTrace); + attach.ItemID, attach.AssetID, attachmentPt, agentId, e.Message, e.StackTrace); } } } @@ -209,14 +349,36 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return; -// m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name); + List attachments = sp.GetAttachments(); + + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Saving for {0} attachments for {1} in {2}", + attachments.Count, sp.Name, m_scene.Name); + + if (attachments.Count <= 0) + return; + + Dictionary scriptStates = new Dictionary(); + + foreach (SceneObjectGroup so in attachments) + { + // Scripts MUST be snapshotted before the object is + // removed from the scene because doing otherwise will + // clobber the run flag + // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from + // scripts performing attachment operations at the same time. Getting object states stops the scripts. + scriptStates[so] = PrepareScriptInstanceForSave(so, false); + +// m_log.DebugFormat( +// "[ATTACHMENTS MODULE]: For object {0} for {1} in {2} got saved state {3}", +// so.Name, sp.Name, m_scene.Name, scriptStates[so]); + } lock (sp.AttachmentsSyncLock) { - foreach (SceneObjectGroup so in sp.GetAttachments()) - { - UpdateDetachedObject(sp, so); - } + foreach (SceneObjectGroup so in attachments) + UpdateDetachedObject(sp, so, scriptStates[so]); sp.ClearAttachments(); } @@ -227,9 +389,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return; -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}", -// m_scene.RegionInfo.RegionName, sp.Name, silent); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}", + m_scene.RegionInfo.RegionName, sp.Name, silent); foreach (SceneObjectGroup sop in sp.GetAttachments()) { @@ -238,114 +401,149 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments sp.ClearAttachments(); } - - public bool AttachObject(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool temp) + + public bool AttachObject( + IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool append) { if (!Enabled) return false; - if (AttachObjectInternal(sp, group, attachmentPt, silent, temp)) - { - m_scene.EventManager.TriggerOnAttach(group.LocalId, group.FromItemID, sp.UUID); - return true; - } + group.DetachFromBackup(); - return false; + bool success = AttachObjectInternal(sp, group, attachmentPt, silent, addToInventory, false, append); + + if (!success) + group.AttachToBackup(); + + return success; } - - private bool AttachObjectInternal(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool temp) + + /// + /// Internal method which actually does all the work for attaching an object. + /// + /// The object attached. + /// + /// The object to attach. + /// + /// + /// If true then add object to user inventory. + /// If true then scripts are resumed on the attached object. + /// Append to attachment point rather than replace. + private bool AttachObjectInternal( + IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool resumeScripts, bool append) { - lock (sp.AttachmentsSyncLock) + if (group.GetSittingAvatarsCount() != 0) { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})", -// group.Name, group.LocalId, sp.Name, attachmentPt, silent); + if (DebugLevel > 0) + m_log.WarnFormat( + "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it", + group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount()); - if (group.GetSittingAvatarsCount() != 0) - { -// m_log.WarnFormat( -// "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it", -// group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount()); - - return false; - } - - if (sp.GetAttachments(attachmentPt).Contains(group)) - { - // m_log.WarnFormat( - // "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached", - // group.Name, group.LocalId, sp.Name, AttachmentPt); - - return false; - } - - Vector3 attachPos = group.AbsolutePosition; - - // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should - // be removed when that functionality is implemented in opensim - attachmentPt &= 0x7f; - - // If the attachment point isn't the same as the one previously used - // set it's offset position = 0 so that it appears on the attachment point - // and not in a weird location somewhere unknown. - if (attachmentPt != 0 && attachmentPt != group.AttachmentPoint) - { - attachPos = Vector3.Zero; - } - - // AttachmentPt 0 means the client chose to 'wear' the attachment. - if (attachmentPt == 0) + return false; + } + + Vector3 attachPos = group.AbsolutePosition; + // If the attachment point isn't the same as the one previously used + // set it's offset position = 0 so that it appears on the attachment point + // and not in a weird location somewhere unknown. + if (attachmentPt != (uint)AttachmentPoint.Default && attachmentPt != group.AttachmentPoint) + { + attachPos = Vector3.Zero; + } + + // if the attachment point is the same as previous, make sure we get the saved + // position info. + if (attachmentPt != 0 && attachmentPt == group.RootPart.Shape.LastAttachPoint) + { + attachPos = group.RootPart.AttachedPos; + } + + // AttachmentPt 0 means the client chose to 'wear' the attachment. + if (attachmentPt == (uint)AttachmentPoint.Default) + { + // Check object for stored attachment point + attachmentPt = group.AttachmentPoint; + } + + // if we didn't find an attach point, look for where it was last attached + if (attachmentPt == 0) + { + attachmentPt = (uint)group.RootPart.Shape.LastAttachPoint; + attachPos = group.RootPart.AttachedPos; + group.HasGroupChanged = true; + } + + // if we still didn't find a suitable attachment point....... + if (attachmentPt == 0) + { + // Stick it on left hand with Zero Offset from the attachment point. + attachmentPt = (uint)AttachmentPoint.LeftHand; + attachPos = Vector3.Zero; + } + + group.AttachmentPoint = attachmentPt; + group.AbsolutePosition = attachPos; + + List attachments = sp.GetAttachments(attachmentPt); + + if (attachments.Contains(group)) + { + if (DebugLevel > 0) + m_log.WarnFormat( + "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached", + group.Name, group.LocalId, sp.Name, attachmentPt); + + return false; + } + + // If we already have 5, remove the oldest until only 4 are left. Skip over temp ones + while (attachments.Count >= 5) + { + if (attachments[0].FromItemID != UUID.Zero) + DetachSingleAttachmentToInv(sp, attachments[0]); + attachments.RemoveAt(0); + } + + // If we're not appending, remove the rest as well + if (attachments.Count != 0 && !append) + { + foreach (SceneObjectGroup g in attachments) { - // Check object for stored attachment point - attachmentPt = group.AttachmentPoint; + if (g.FromItemID != UUID.Zero) + DetachSingleAttachmentToInv(sp, g); } + } + + lock (sp.AttachmentsSyncLock) + { + if (addToInventory && sp.PresenceType != PresenceType.Npc) + UpdateUserInventoryWithAttachment(sp, group, attachmentPt, append); - // if we still didn't find a suitable attachment point....... - if (attachmentPt == 0) + AttachToAgent(sp, group, attachmentPt, attachPos, silent); + + if (resumeScripts) { - // Stick it on left hand with Zero Offset from the attachment point. - attachmentPt = (uint)AttachmentPoint.LeftHand; - attachPos = Vector3.Zero; + // Fire after attach, so we don't get messy perms dialogs + // 4 == AttachedRez + group.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4); + group.ResumeScripts(); } - - group.AttachmentPoint = attachmentPt; - group.AbsolutePosition = attachPos; - if (sp.PresenceType != PresenceType.Npc) - UpdateUserInventoryWithAttachment(sp, group, attachmentPt, temp); - - AttachToAgent(sp, group, attachmentPt, attachPos, silent); + // Do this last so that event listeners have access to all the effects of the attachment + m_scene.EventManager.TriggerOnAttach(group.LocalId, group.FromItemID, sp.UUID); } return true; } - private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool temp) + private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool append) { - // Remove any previous attachments - List attachments = sp.GetAttachments(attachmentPt); - - // At the moment we can only deal with a single attachment - if (attachments.Count != 0) - { - if (attachments[0].FromItemID != UUID.Zero) - DetachSingleAttachmentToInvInternal(sp, attachments[0]); - // Error logging commented because UUID.Zero now means temp attachment -// else -// m_log.WarnFormat( -// "[ATTACHMENTS MODULE]: When detaching existing attachment {0} {1} at point {2} to make way for {3} {4} for {5}, couldn't find the associated item ID to adjust inventory attachment record!", -// attachments[0].Name, attachments[0].LocalId, attachmentPt, group.Name, group.LocalId, sp.Name); - } - // Add the new attachment to inventory if we don't already have it. - if (!temp) - { - UUID newAttachmentItemID = group.FromItemID; - if (newAttachmentItemID == UUID.Zero) - newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID; + UUID newAttachmentItemID = group.FromItemID; + if (newAttachmentItemID == UUID.Zero) + newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID; - ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group); - } + ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group, append); } public SceneObjectGroup RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt) @@ -353,41 +551,40 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return null; -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: RezSingleAttachmentFromInventory to point {0} from item {1} for {2}", -// (AttachmentPoint)AttachmentPt, itemID, sp.Name); - - // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should - // be removed when that functionality is implemented in opensim - AttachmentPt &= 0x7f; + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: RezSingleAttachmentFromInventory to point {0} from item {1} for {2} in {3}", + (AttachmentPoint)AttachmentPt, itemID, sp.Name, m_scene.Name); - // Viewer 2/3 sometimes asks to re-wear items that are already worn (and show up in it's inventory as such). - // This often happens during login - not sure the exact reason. - // For now, we will ignore the request. Unfortunately, this means that we need to dig through all the - // ScenePresence attachments. We can't use the data in AvatarAppearance because that's present at login - // before anything has actually been attached. + // We check the attachments in the avatar appearance here rather than the objects attached to the + // ScenePresence itself so that we can ignore calls by viewer 2/3 to attach objects on startup. We are + // already doing this in ScenePresence.MakeRootAgent(). Simulator-side attaching needs to be done + // because pre-outfit folder viewers (most version 1 viewers) require it. bool alreadyOn = false; - List existingAttachments = sp.GetAttachments(); - foreach (SceneObjectGroup so in existingAttachments) + List existingAttachments = sp.Appearance.GetAttachments(); + foreach (AvatarAttachment existingAttachment in existingAttachments) { - if (so.FromItemID == itemID) + if (existingAttachment.ItemID == itemID) { alreadyOn = true; break; } } -// if (sp.Appearance.GetAttachmentForItem(itemID) != null) if (alreadyOn) { -// m_log.WarnFormat( -// "[ATTACHMENTS MODULE]: Ignoring request by {0} to wear item {1} at {2} since it is already worn", -// sp.Name, itemID, AttachmentPt); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Ignoring request by {0} to wear item {1} at {2} since it is already worn", + sp.Name, itemID, AttachmentPt); return null; } - return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt); + bool append = (AttachmentPt & 0x80) != 0; + AttachmentPt &= 0x7f; + + return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, append); } public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List> rezlist) @@ -395,13 +592,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return; - // m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing multiple attachments from inventory for {0}", sp.Name); - lock (sp.AttachmentsSyncLock) + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Rezzing {0} attachments from inventory for {1} in {2}", + rezlist.Count, sp.Name, m_scene.Name); + + foreach (KeyValuePair rez in rezlist) { - foreach (KeyValuePair rez in rezlist) - { - RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value); - } + RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value); } } @@ -415,9 +613,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return; -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: DetachSingleAttachmentToGround() for {0}, object {1}", -// sp.UUID, soLocalId); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: DetachSingleAttachmentToGround() for {0}, object {1}", + sp.UUID, soLocalId); SceneObjectGroup so = m_scene.GetGroupByPrim(soLocalId); @@ -433,9 +632,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (inventoryID == UUID.Zero) return; -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: In DetachSingleAttachmentToGround(), object is {0} {1}, associated item is {2}", -// so.Name, so.LocalId, inventoryID); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: In DetachSingleAttachmentToGround(), object is {0} {1}, associated item is {2}", + so.Name, so.LocalId, inventoryID); lock (sp.AttachmentsSyncLock) { @@ -463,6 +663,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments so.ClearPartAttachmentData(); rootPart.ApplyPhysics(rootPart.GetEffectiveObjectFlags(), rootPart.VolumeDetectActive); so.HasGroupChanged = true; + so.RootPart.Shape.LastAttachPoint = (byte)so.AttachmentPoint; rootPart.Rezzed = DateTime.Now; rootPart.RemFlag(PrimFlags.TemporaryOnRez); so.AttachToBackup(); @@ -481,25 +682,38 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so) { + if (so.AttachedAvatar != sp.UUID) + { + m_log.WarnFormat( + "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}", + so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName); + + return; + } + + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Detaching object {0} {1} (FromItemID {2}) for {3} in {4}", + so.Name, so.LocalId, so.FromItemID, sp.Name, m_scene.Name); + + // Scripts MUST be snapshotted before the object is + // removed from the scene because doing otherwise will + // clobber the run flag + // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from + // scripts performing attachment operations at the same time. Getting object states stops the scripts. + string scriptedState = PrepareScriptInstanceForSave(so, true); + lock (sp.AttachmentsSyncLock) { // Save avatar attachment information // m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); - if (so.AttachedAvatar != sp.UUID) - { - m_log.WarnFormat( - "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}", - so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName); - - return; - } - bool changed = sp.Appearance.DetachAttachment(so.FromItemID); if (changed && m_scene.AvatarFactory != null) m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); - DetachSingleAttachmentToInvInternal(sp, so); + sp.RemoveAttachment(so); + UpdateDetachedObject(sp, so, scriptedState); } } @@ -588,15 +802,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments (sbyte)AssetType.Object, Utils.StringToBytes(sceneObjectXml), sp.UUID); - m_scene.AssetService.Store(asset); - item.AssetID = asset.FullID; - item.Description = asset.Description; - item.Name = asset.Name; - item.AssetType = asset.Type; - item.InvType = (int)InventoryType.Object; - - m_scene.InventoryService.UpdateItem(item); + if (m_invAccessModule != null) + m_invAccessModule.UpdateInventoryItemAsset(sp.UUID, item, asset); // If the name of the object has been changed whilst attached then we want to update the inventory // item in the viewer. @@ -606,12 +814,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments grp.HasGroupChanged = false; // Prevent it being saved over and over } -// else -// { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", -// grp.UUID, grp.AttachmentPoint); -// } + else if (DebugLevel > 0) + { + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", + grp.UUID, grp.AttachmentPoint); + } } /// @@ -629,11 +837,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments private void AttachToAgent( IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent) { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} in pt {2} pos {3} {4}", -// so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos); - - so.DetachFromBackup(); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} at pt {2} pos {3} {4} in {5}", + so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos, m_scene.Name); // Remove from database and parcel prim count m_scene.DeleteFromStorage(so.UUID); @@ -656,16 +863,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { if (so.HasPrivateAttachmentPoint) { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Killing private HUD {0} for avatars other than {1} at attachment point {2}", -// so.Name, sp.Name, so.AttachmentPoint); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Killing private HUD {0} for avatars other than {1} at attachment point {2}", + so.Name, sp.Name, so.AttachmentPoint); // As this scene object can now only be seen by the attaching avatar, tell everybody else in the // scene that it's no longer in their awareness. m_scene.ForEachClient( client => { if (client.AgentId != so.AttachedAvatar) - client.SendKillObject(m_scene.RegionInfo.RegionHandle, new List() { so.LocalId }); + client.SendKillObject(new List() { so.LocalId }); }); } @@ -692,14 +900,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (m_invAccessModule == null) return null; - // m_log.DebugFormat( - // "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}", - // grp.Name, grp.LocalId, remoteClient.Name); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}", + grp.Name, grp.LocalId, sp.Name); InventoryItemBase newItem = m_invAccessModule.CopyToInventory( DeRezAction.TakeCopy, - m_scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object).ID, + m_scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object).ID, new List { grp }, sp.ControllingClient, true)[0]; @@ -709,8 +918,32 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments return newItem; } - private string GetObjectScriptStates(SceneObjectGroup grp) + /// + /// Prepares the script instance for save. + /// + /// + /// This involves triggering the detach event and getting the script state (which also stops the script) + /// This MUST be done outside sp.AttachmentsSyncLock, since otherwise there is a chance of deadlock if a + /// running script is performing attachment operations. + /// + /// + /// The script state ready for persistence. + /// + /// + /// + /// + /// If true, then fire the script event before we save its state. + /// + private string PrepareScriptInstanceForSave(SceneObjectGroup grp, bool fireDetachEvent) { + if (fireDetachEvent) + { + m_scene.EventManager.TriggerOnAttach(grp.LocalId, grp.FromItemID, UUID.Zero); + + // Allow detach event time to do some work before stopping the script + Thread.Sleep(2); + } + using (StringWriter sw = new StringWriter()) { using (XmlTextWriter writer = new XmlTextWriter(sw)) @@ -722,7 +955,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments } } - private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so) + private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so, string scriptedState) { // Don't save attachments for HG visitors, it // messes up their inventory. When a HG visitor logs @@ -735,11 +968,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments && (m_scene.UserManagementModule == null || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID)); - // Scripts MUST be snapshotted before the object is - // removed from the scene because doing otherwise will - // clobber the run flag - string scriptedState = GetObjectScriptStates(so); - // Remove the object from the scene so no more updates // are sent. Doing this before the below changes will ensure // updates can't cause "HUD artefacts" @@ -763,91 +991,88 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments so.RemoveScriptInstances(true); } - private void DetachSingleAttachmentToInvInternal(IScenePresence sp, SceneObjectGroup so) + protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal( + IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, bool append) { - // m_log.DebugFormat("[ATTACHMENTS MODULE]: Detaching item {0} to inventory for {1}", itemID, sp.Name); + if (m_invAccessModule == null) + return null; - m_scene.EventManager.TriggerOnAttach(so.LocalId, so.FromItemID, UUID.Zero); - sp.RemoveAttachment(so); + SceneObjectGroup objatt; - UpdateDetachedObject(sp, so); - } + if (itemID != UUID.Zero) + objatt = m_invAccessModule.RezObject(sp.ControllingClient, + itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, + false, false, sp.UUID, true); + else + objatt = m_invAccessModule.RezObject(sp.ControllingClient, + null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, + false, false, sp.UUID, true); - private SceneObjectGroup RezSingleAttachmentFromInventoryInternal( - IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt) - { - if (m_invAccessModule == null) - return null; + if (objatt == null) + { + m_log.WarnFormat( + "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}", + itemID, sp.Name, attachmentPt); - lock (sp.AttachmentsSyncLock) + return null; + } + else if (itemID == UUID.Zero) { - SceneObjectGroup objatt; + // We need to have a FromItemID for multiple attachments on a single attach point to appear. This is + // true on Singularity 1.8.5 and quite possibly other viewers as well. As NPCs don't have an inventory + // we will satisfy this requirement by inserting a random UUID. + objatt.FromItemID = UUID.Random(); + } - if (itemID != UUID.Zero) - objatt = m_invAccessModule.RezObject(sp.ControllingClient, - itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, - false, false, sp.UUID, true); - else - objatt = m_invAccessModule.RezObject(sp.ControllingClient, - null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, - false, false, sp.UUID, true); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Rezzed single object {0} with {1} prims for attachment to {2} on point {3} in {4}", + objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name); + + // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller. + objatt.HasGroupChanged = false; + bool tainted = false; + if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint) + tainted = true; + + // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal + // course of events. If not, then it's probably not worth trying to recover the situation + // since this is more likely to trigger further exceptions and confuse later debugging. If + // exceptions can be thrown in expected error conditions (not NREs) then make this consistent + // since other normal error conditions will simply return false instead. + // This will throw if the attachment fails + try + { + AttachObjectInternal(sp, objatt, attachmentPt, false, true, true, append); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}", + objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace); - if (objatt != null) - { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Rezzed single object {0} for attachment to {1} on point {2} in {3}", -// objatt.Name, sp.Name, attachmentPt, m_scene.Name); - - // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller. - objatt.HasGroupChanged = false; - bool tainted = false; - if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint) - tainted = true; - - // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal - // course of events. If not, then it's probably not worth trying to recover the situation - // since this is more likely to trigger further exceptions and confuse later debugging. If - // exceptions can be thrown in expected error conditions (not NREs) then make this consistent - // since other normal error conditions will simply return false instead. - // This will throw if the attachment fails - try - { - AttachObjectInternal(sp, objatt, attachmentPt, false, false); - } - catch (Exception e) - { - m_log.ErrorFormat( - "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}", - objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace); - - // Make sure the object doesn't stick around and bail - sp.RemoveAttachment(objatt); - m_scene.DeleteSceneObject(objatt, false); - return null; - } + // Make sure the object doesn't stick around and bail + sp.RemoveAttachment(objatt); + m_scene.DeleteSceneObject(objatt, false); + return null; + } - if (tainted) - objatt.HasGroupChanged = true; + if (tainted) + objatt.HasGroupChanged = true; - // Fire after attach, so we don't get messy perms dialogs - // 4 == AttachedRez - objatt.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4); - objatt.ResumeScripts(); + if (ThrottlePer100PrimsRezzed > 0) + { + int throttleMs = (int)Math.Round((float)objatt.PrimCount / 100 * ThrottlePer100PrimsRezzed); - // Do this last so that event listeners have access to all the effects of the attachment - m_scene.EventManager.TriggerOnAttach(objatt.LocalId, itemID, sp.UUID); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Throttling by {0}ms after rez of {1} with {2} prims for attachment to {3} on point {4} in {5}", + throttleMs, objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name); - return objatt; - } - else - { - m_log.WarnFormat( - "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}", - itemID, sp.Name, attachmentPt); - } + Thread.Sleep(throttleMs); } - return null; + return objatt; } /// @@ -857,7 +1082,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments /// /// /// - private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att) + private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att, bool append) { // m_log.DebugFormat( // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}", @@ -880,12 +1105,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (item == null) return; - bool changed = sp.Appearance.SetAttachment((int)AttachmentPt, itemID, item.AssetID); + int attFlag = append ? 0x80 : 0; + bool changed = sp.Appearance.SetAttachment((int)AttachmentPt | attFlag, itemID, item.AssetID); if (changed && m_scene.AvatarFactory != null) { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Queueing appearance save for {0}, attachment {1} point {2} in ShowAttachInUserInventory()", -// sp.Name, att.Name, AttachmentPt); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Queueing appearance save for {0}, attachment {1} point {2} in ShowAttachInUserInventory()", + sp.Name, att.Name, AttachmentPt); m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); } @@ -900,9 +1127,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (!Enabled) return null; - // m_log.DebugFormat( - // "[ATTACHMENTS MODULE]: Rezzing attachment to point {0} from item {1} for {2}", - // (AttachmentPoint)AttachmentPt, itemID, remoteClient.Name); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Rezzing attachment to point {0} from item {1} for {2}", + (AttachmentPoint)AttachmentPt, itemID, remoteClient.Name); ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); @@ -933,9 +1161,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent) { -// m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})", -// objectLocalID, remoteClient.Name, AttachmentPt, silent); + if (DebugLevel > 0) + m_log.DebugFormat( + "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})", + objectLocalID, remoteClient.Name, AttachmentPt, silent); if (!Enabled) return; @@ -964,12 +1193,20 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments return; } - // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should - // be removed when that functionality is implemented in opensim + bool append = (AttachmentPt & 0x80) != 0; AttachmentPt &= 0x7f; // Calls attach with a Zero position - AttachObject(sp, part.ParentGroup, AttachmentPt, false, false); + if (AttachObject(sp, part.ParentGroup, AttachmentPt, false, true, append)) + { + if (DebugLevel > 0) + m_log.Debug( + "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId + + ", AttachmentPoint: " + AttachmentPt); + + // Save avatar attachment information + m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId); + } } catch (Exception e) { @@ -997,17 +1234,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); if (sp != null) { - lock (sp.AttachmentsSyncLock) + List attachments = sp.GetAttachments(); + + foreach (SceneObjectGroup group in attachments) { - List attachments = sp.GetAttachments(); - - foreach (SceneObjectGroup group in attachments) + if (group.FromItemID == itemID && group.FromItemID != UUID.Zero) { - if (group.FromItemID == itemID && group.FromItemID != UUID.Zero) - { - DetachSingleAttachmentToInv(sp, group); - return; - } + DetachSingleAttachmentToInv(sp, group); + return; } } } diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs index 0ee01c7..0ac3add 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs @@ -37,7 +37,8 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.CoreModules.Avatar.Attachments; using OpenSim.Region.CoreModules.Framework; using OpenSim.Region.CoreModules.Framework.EntityTransfer; @@ -51,7 +52,6 @@ using OpenSim.Region.ScriptEngine.Interfaces; using OpenSim.Region.ScriptEngine.XEngine; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests { @@ -130,7 +130,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests config.AddConfig("Modules"); config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); - modules.Add(new AttachmentsModule()); + AttachmentsModule attMod = new AttachmentsModule(); + attMod.DebugLevel = 1; + modules.Add(attMod); modules.Add(new BasicInventoryAccessModule()); } @@ -195,9 +197,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests string attName = "att"; SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, attName, sp.UUID); + Assert.That(so.Backup, Is.True); m_numberOfAttachEventsFired = 0; - scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, false); + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, true, false); // Check status on scene presence Assert.That(sp.HasAttachments(), Is.True); @@ -209,6 +212,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(attSo.IsAttachment); Assert.That(attSo.UsesPhysics, Is.False); Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.Backup, Is.False); // Check item status Assert.That( @@ -219,7 +223,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(attachmentItem, Is.Not.Null); Assert.That(attachmentItem.Name, Is.EqualTo(attName)); - InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object); + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); @@ -228,6 +232,120 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); } + [Test] + public void TestWearAttachmentFromGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + SceneObjectGroup so2 = SceneHelpers.AddSceneObject(scene, "att2", sp.UUID); + + { + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "att1", sp.UUID); + + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status + Assert.That( + sp.Appearance.GetAttachpoint(attSo.FromItemID), + Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(new InventoryItemBase(attSo.FromItemID)); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(2)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test wearing a different attachment from the ground. + { + scene.AttachmentsModule.AttachObject(sp, so2, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status + Assert.That( + sp.Appearance.GetAttachpoint(attSo.FromItemID), + Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(new InventoryItemBase(attSo.FromItemID)); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so2.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + + // Test rewearing an already worn attachment from ground. Nothing should happen. + { + scene.AttachmentsModule.AttachObject(sp, so2, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status + Assert.That( + sp.Appearance.GetAttachpoint(attSo.FromItemID), + Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(new InventoryItemBase(attSo.FromItemID)); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so2.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + } + /// /// Test that we do not attempt to attach an in-world object that someone else is sitting on. /// @@ -254,7 +372,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests sp2.AbsolutePosition = new Vector3(0, 0, 0); sp2.HandleAgentRequestSit(sp2.ControllingClient, sp2.UUID, so.UUID, Vector3.Zero); - scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, false); + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, true, false); Assert.That(sp.HasAttachments(), Is.False); Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); @@ -267,7 +385,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests public void TestRezAttachmentFromInventory() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); Scene scene = CreateTestScene(); UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); @@ -275,29 +393,141 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); - m_numberOfAttachEventsFired = 0; - scene.AttachmentsModule.RezSingleAttachmentFromInventory( - sp, attItem.ID, (uint)AttachmentPoint.Chest); + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); - // Check scene presence status - Assert.That(sp.HasAttachments(), Is.True); - List attachments = sp.GetAttachments(); - Assert.That(attachments.Count, Is.EqualTo(1)); - SceneObjectGroup attSo = attachments[0]; - Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); - Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); - Assert.That(attSo.IsAttachment); - Assert.That(attSo.UsesPhysics, Is.False); - Assert.That(attSo.IsTemporary, Is.False); + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.IsFalse(attSo.Backup); + + // Check appearance status + Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test attaching an already attached attachment + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); - // Check appearance status - Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); - Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check appearance status + Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + } - Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + /// + /// Test wearing an attachment from inventory, as opposed to explicit choosing the rez point + /// + [Test] + public void TestWearAttachmentFromInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); - // Check events - Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); + + InventoryItemBase attItem1 = CreateAttachmentItem(scene, ua1.PrincipalID, "att1", 0x10, 0x20); + InventoryItemBase attItem2 = CreateAttachmentItem(scene, ua1.PrincipalID, "att2", 0x11, 0x21); + + { + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem1.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem1.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status + Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attItem1.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test wearing a second attachment at the same position + // Until multiple attachments at one point is implemented, this will remove the first attachment + // This test relies on both attachments having the same default attachment point (in this case LeftHand + // since none other has been set). + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem2.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status + Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attItem2.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + + // Test wearing an already attached attachment + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem2.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status + Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attItem2.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } } /// @@ -315,7 +545,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, sp.UUID, "att-name", 0x10); TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript( - scene, + scene.AssetService, so.RootPart, "scriptItem", "default { attach(key id) { if (id != NULL_KEY) { llSay(0, \"Hello World\"); } } }"); @@ -372,7 +602,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(scene.InventoryService.GetItem(new InventoryItemBase(attItem.ID)), Is.Null); // Check object in scene - Assert.That(scene.GetSceneObjectGroup("att"), Is.Not.Null); + SceneObjectGroup soInScene = scene.GetSceneObjectGroup("att"); + Assert.That(soInScene, Is.Not.Null); + Assert.IsTrue(soInScene.Backup); // Check events Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); @@ -426,7 +658,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, sp.UUID, "att-name", 0x10); TaskInventoryItem scriptTaskItem = TaskInventoryHelpers.AddScript( - scene, + scene.AssetService, so.RootPart, "scriptItem", "default { attach(key id) { if (id != NULL_KEY) { llSay(0, \"Hello World\"); } } }"); @@ -490,7 +722,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests SceneObjectGroup rezzedAtt = presence.GetAttachments()[0]; m_numberOfAttachEventsFired = 0; - scene.IncomingCloseAgent(presence.UUID, false); + scene.CloseAgent(presence.UUID, false); // Check that we can't retrieve this attachment from the scene. Assert.That(scene.GetSceneObjectGroup(rezzedAtt.UUID), Is.Null); @@ -503,7 +735,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests public void TestRezAttachmentsOnAvatarEntrance() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); Scene scene = CreateTestScene(); UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); @@ -526,6 +758,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(attSo.IsAttachment); Assert.That(attSo.UsesPhysics, Is.False); Assert.That(attSo.IsTemporary, Is.False); + Assert.IsFalse(attSo.Backup); // Check appearance status List retreivedAttachments = presence.Appearance.GetAttachments(); @@ -569,12 +802,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); } +/* [Test] - public void TestSameSimulatorNeighbouringRegionsTeleport() + public void TestSameSimulatorNeighbouringRegionsTeleportV1() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + AttachmentsModule attModA = new AttachmentsModule(); AttachmentsModule attModB = new AttachmentsModule(); EntityTransferModule etmA = new EntityTransferModule(); @@ -603,8 +841,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests SceneHelpers.SetupSceneModules( sceneB, config, new CapabilitiesModule(), etmB, attModB, new BasicInventoryAccessModule()); + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + lscm.ServiceVersion = 0.1f; + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(sceneA, 0x1); - ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, ua1.PrincipalID, sh.SceneManager); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); beforeTeleportSp.AbsolutePosition = new Vector3(30, 31, 32); InventoryItemBase attItem = CreateAttachmentItem(sceneA, ua1.PrincipalID, "att", 0x10, 0x20); @@ -623,7 +870,119 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)beforeTeleportSp.ControllingClient).CompleteTeleportClientSide(); + destinationTestClients[0].CompleteMovement(); + + // Check attachments have made it into sceneB + ScenePresence afterTeleportSceneBSp = sceneB.GetScenePresence(ua1.PrincipalID); + + // This is appearance data, as opposed to actually rezzed attachments + List sceneBAttachments = afterTeleportSceneBSp.Appearance.GetAttachments(); + Assert.That(sceneBAttachments.Count, Is.EqualTo(1)); + Assert.That(sceneBAttachments[0].AttachPoint, Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(sceneBAttachments[0].ItemID, Is.EqualTo(attItem.ID)); + Assert.That(sceneBAttachments[0].AssetID, Is.EqualTo(attItem.AssetID)); + Assert.That(afterTeleportSceneBSp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment + List actualSceneBAttachments = afterTeleportSceneBSp.GetAttachments(); + Assert.That(actualSceneBAttachments.Count, Is.EqualTo(1)); + SceneObjectGroup actualSceneBAtt = actualSceneBAttachments[0]; + Assert.That(actualSceneBAtt.Name, Is.EqualTo(attItem.Name)); + Assert.That(actualSceneBAtt.AttachmentPoint, Is.EqualTo((uint)AttachmentPoint.Chest)); + Assert.IsFalse(actualSceneBAtt.Backup); + + Assert.That(sceneB.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check attachments have been removed from sceneA + ScenePresence afterTeleportSceneASp = sceneA.GetScenePresence(ua1.PrincipalID); + + // Since this is appearance data, it is still present on the child avatar! + List sceneAAttachments = afterTeleportSceneASp.Appearance.GetAttachments(); + Assert.That(sceneAAttachments.Count, Is.EqualTo(1)); + Assert.That(afterTeleportSceneASp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment, which should no longer exist + List actualSceneAAttachments = afterTeleportSceneASp.GetAttachments(); + Assert.That(actualSceneAAttachments.Count, Is.EqualTo(0)); + + Assert.That(sceneA.GetSceneObjectGroups().Count, Is.EqualTo(0)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } +*/ + + [Test] + public void TestSameSimulatorNeighbouringRegionsTeleportV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + AttachmentsModule attModA = new AttachmentsModule(); + AttachmentsModule attModB = new AttachmentsModule(); + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + modulesConfig.Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules( + sceneA, config, new CapabilitiesModule(), etmA, attModA, new BasicInventoryAccessModule()); + SceneHelpers.SetupSceneModules( + sceneB, config, new CapabilitiesModule(), etmB, attModB, new BasicInventoryAccessModule()); + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(sceneA, 0x1); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeTeleportSp.AbsolutePosition = new Vector3(30, 31, 32); + + Assert.That(destinationTestClients.Count, Is.EqualTo(1)); + Assert.That(destinationTestClients[0], Is.Not.Null); + + InventoryItemBase attItem = CreateAttachmentItem(sceneA, ua1.PrincipalID, "att", 0x10, 0x20); + + sceneA.AttachmentsModule.RezSingleAttachmentFromInventory( + beforeTeleportSp, attItem.ID, (uint)AttachmentPoint.Chest); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + // Here, we need to make clientA's receipt of SendRegionTeleport trigger clientB's CompleteMovement(). This + // is to operate the teleport V2 mechanism where the EntityTransferModule will first request the client to + // CompleteMovement to the region and then call UpdateAgent to the destination region to confirm the receipt + // Both these operations will occur on different threads and will wait for each other. + // We have to do this via ThreadPool directly since FireAndForget has been switched to sync for the V1 + // test protocol, where we are trying to avoid unpredictable async operations in regression tests. + tc.OnTestClientSendRegionTeleport + += (regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL) + => ThreadPool.UnsafeQueueUserWorkItem(o => destinationTestClients[0].CompleteMovement(), null); + + m_numberOfAttachEventsFired = 0; + sceneA.RequestTeleportLocation( + beforeTeleportSp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); // Check attachments have made it into sceneB ScenePresence afterTeleportSceneBSp = sceneB.GetScenePresence(ua1.PrincipalID); @@ -642,6 +1001,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests SceneObjectGroup actualSceneBAtt = actualSceneBAttachments[0]; Assert.That(actualSceneBAtt.Name, Is.EqualTo(attItem.Name)); Assert.That(actualSceneBAtt.AttachmentPoint, Is.EqualTo((uint)AttachmentPoint.Chest)); + Assert.IsFalse(actualSceneBAtt.Backup); Assert.That(sceneB.GetSceneObjectGroups().Count, Is.EqualTo(1)); @@ -663,4 +1023,4 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs index 0a69979..cfb082b 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -40,6 +40,7 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Mono.Addins; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory { @@ -54,6 +55,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory private int m_savetime = 5; // seconds to wait before saving changed appearance private int m_sendtime = 2; // seconds to wait before sending changed appearance + private bool m_reusetextures = false; private int m_checkTime = 500; // milliseconds to wait between checks for appearance updates private System.Timers.Timer m_updateTimer = new System.Timers.Timer(); @@ -72,6 +74,8 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory { m_savetime = Convert.ToInt32(appearanceConfig.GetString("DelayBeforeAppearanceSave",Convert.ToString(m_savetime))); m_sendtime = Convert.ToInt32(appearanceConfig.GetString("DelayBeforeAppearanceSend",Convert.ToString(m_sendtime))); + m_reusetextures = appearanceConfig.GetBoolean("ReuseTextures",m_reusetextures); + // m_log.InfoFormat("[AVFACTORY] configured for {0} save and {1} send",m_savetime,m_sendtime); } @@ -130,6 +134,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory client.OnRequestWearables += Client_OnRequestWearables; client.OnSetAppearance += Client_OnSetAppearance; client.OnAvatarNowWearing += Client_OnAvatarNowWearing; + client.OnCachedTextureRequest += Client_OnCachedTextureRequest; } #endregion @@ -140,9 +145,24 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory /// /// /// - public void SetAppearance(IScenePresence sp, AvatarAppearance appearance) + public void SetAppearance(IScenePresence sp, AvatarAppearance appearance, WearableCacheItem[] cacheItems) { - SetAppearance(sp, appearance.Texture, appearance.VisualParams); + SetAppearance(sp, appearance.Texture, appearance.VisualParams, cacheItems); + } + + + public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams, Vector3 avSize, WearableCacheItem[] cacheItems) + { + float oldoff = sp.Appearance.AvatarFeetOffset; + Vector3 oldbox = sp.Appearance.AvatarBoxSize; + + SetAppearance(sp, textureEntry, visualParams, cacheItems); + sp.Appearance.SetSize(avSize); + + float off = sp.Appearance.AvatarFeetOffset; + Vector3 box = sp.Appearance.AvatarBoxSize; + if (oldoff != off || oldbox != box) + ((ScenePresence)sp).SetSize(box, off); } /// @@ -151,7 +171,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory /// /// /// - public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams) + public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams, WearableCacheItem[] cacheItems) { // m_log.DebugFormat( // "[AVFACTORY]: start SetAppearance for {0}, te {1}, visualParams {2}", @@ -174,18 +194,27 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory // m_log.DebugFormat( // "[AVFACTORY]: Setting visual params for {0} to {1}", // client.Name, string.Join(", ", visualParamsStrings)); - +/* float oldHeight = sp.Appearance.AvatarHeight; changed = sp.Appearance.SetVisualParams(visualParams); if (sp.Appearance.AvatarHeight != oldHeight && sp.Appearance.AvatarHeight > 0) ((ScenePresence)sp).SetHeight(sp.Appearance.AvatarHeight); - } + */ +// float oldoff = sp.Appearance.AvatarFeetOffset; +// Vector3 oldbox = sp.Appearance.AvatarBoxSize; + changed = sp.Appearance.SetVisualParams(visualParams); +// float off = sp.Appearance.AvatarFeetOffset; +// Vector3 box = sp.Appearance.AvatarBoxSize; +// if(oldoff != off || oldbox != box) +// ((ScenePresence)sp).SetSize(box,off); + } + // Process the baked texture array if (textureEntry != null) { -// m_log.DebugFormat("[AVFACTORY]: Received texture update for {0} {1}", sp.Name, sp.UUID); + m_log.DebugFormat("[AVFACTORY]: Received texture update for {0} {1}", sp.Name, sp.UUID); // WriteBakedTexturesReport(sp, m_log.DebugFormat); @@ -222,7 +251,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory private void SendAppearance(ScenePresence sp) { // Send the appearance to everyone in the scene - sp.SendAppearanceToAllOtherAgents(); + sp.SendAppearanceToAllOtherClients(); // Send animations back to the avatar as well sp.Animator.SendAnimPack(); @@ -254,6 +283,17 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory return GetBakedTextureFaces(sp); } + public WearableCacheItem[] GetCachedItems(UUID agentId) + { + ScenePresence sp = m_scene.GetScenePresence(agentId); + WearableCacheItem[] items = sp.Appearance.WearableCacheItems; + //foreach (WearableCacheItem item in items) + //{ + + //} + return items; + } + public bool SaveBakedTextures(UUID agentId) { ScenePresence sp = m_scene.GetScenePresence(agentId); @@ -287,6 +327,9 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory if (asset != null) { + // Replace an HG ID with the simple asset ID so that we can persist textures for foreign HG avatars + asset.ID = asset.FullID.ToString(); + asset.Temporary = false; asset.Local = false; m_scene.AssetService.Store(asset); @@ -323,7 +366,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory public void QueueAppearanceSave(UUID agentid) { - // m_log.WarnFormat("[AVFACTORY]: Queue appearance save for {0}", agentid); +// m_log.DebugFormat("[AVFACTORY]: Queueing appearance save for {0}", agentid); // 10000 ticks per millisecond, 1000 milliseconds per second long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_savetime * 1000 * 10000); @@ -337,6 +380,53 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory public bool ValidateBakedTextureCache(IScenePresence sp) { bool defonly = true; // are we only using default textures + IImprovedAssetCache cache = m_scene.RequestModuleInterface(); + IBakedTextureModule bakedModule = m_scene.RequestModuleInterface(); + WearableCacheItem[] wearableCache = null; + + // Cache wearable data for teleport. + // Only makes sense if there's a bake module and a cache module + if (bakedModule != null && cache != null) + { + try + { + wearableCache = bakedModule.Get(sp.UUID); + } + catch (Exception) + { + + } + if (wearableCache != null) + { + for (int i = 0; i < wearableCache.Length; i++) + { + cache.Cache(wearableCache[i].TextureAsset); + } + } + } + /* + IBakedTextureModule bakedModule = m_scene.RequestModuleInterface(); + if (invService.GetRootFolder(userID) != null) + { + WearableCacheItem[] wearableCache = null; + if (bakedModule != null) + { + try + { + wearableCache = bakedModule.Get(userID); + appearance.WearableCacheItems = wearableCache; + appearance.WearableCacheItemsDirty = false; + foreach (WearableCacheItem item in wearableCache) + { + appearance.Texture.FaceTextures[item.TextureIndex].TextureID = item.TextureID; + } + } + catch (Exception) + { + + } + } + */ // Process the texture entry for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) @@ -344,10 +434,32 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory int idx = AvatarAppearance.BAKE_INDICES[i]; Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; - // if there is no texture entry, skip it + // No face, so lets check our baked service cache, teleport or login. if (face == null) - continue; - + { + if (wearableCache != null) + { + // If we find the an appearance item, set it as the textureentry and the face + WearableCacheItem searchitem = WearableCacheItem.SearchTextureIndex((uint) idx, wearableCache); + if (searchitem != null) + { + sp.Appearance.Texture.FaceTextures[idx] = sp.Appearance.Texture.CreateFace((uint) idx); + sp.Appearance.Texture.FaceTextures[idx].TextureID = searchitem.TextureID; + face = sp.Appearance.Texture.FaceTextures[idx]; + } + else + { + // if there is no texture entry and no baked cache, skip it + continue; + } + } + else + { + //No texture entry face and no cache. Skip this face. + continue; + } + } + // m_log.DebugFormat( // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", // face.TextureID, idx, client.Name, client.AgentId); @@ -374,6 +486,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory public int RequestRebake(IScenePresence sp, bool missingTexturesOnly) { int texturesRebaked = 0; +// IImprovedAssetCache cache = m_scene.RequestModuleInterface(); for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) { @@ -480,7 +593,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory if (sendTime < now) { - Util.FireAndForget(o => SendAppearance(avatarID)); + Util.FireAndForget(o => SendAppearance(avatarID), null, "AvatarFactoryModule.SendAppearance"); m_sendqueue.Remove(avatarID); } } @@ -498,7 +611,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory if (sendTime < now) { - Util.FireAndForget(o => SaveAppearance(avatarID)); + Util.FireAndForget(o => SaveAppearance(avatarID), null, "AvatarFactoryModule.SaveAppearance"); m_savequeue.Remove(avatarID); } } @@ -526,7 +639,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory return; } - // m_log.WarnFormat("[AVFACTORY] avatar {0} save appearance",agentid); +// m_log.DebugFormat("[AVFACTORY]: Saving appearance for avatar {0}", agentid); // This could take awhile since it needs to pull inventory // We need to do it at the point of save so that there is a sufficient delay for any upload of new body part/shape @@ -535,6 +648,14 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory // multiple save requests. SetAppearanceAssets(sp.UUID, sp.Appearance); +// List attachments = sp.Appearance.GetAttachments(); +// foreach (AvatarAttachment att in attachments) +// { +// m_log.DebugFormat( +// "[AVFACTORY]: For {0} saving attachment {1} at point {2}", +// sp.Name, att.ItemID, att.AttachPoint); +// } + m_scene.AvatarService.SetAppearance(agentid, sp.Appearance); // Trigger this here because it's the final step in the set/queue/save process for appearance setting. @@ -542,6 +663,12 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory m_scene.EventManager.TriggerAvatarAppearanceChanged(sp); } + /// + /// For a given set of appearance items, check whether the items are valid and add their asset IDs to + /// appearance data. + /// + /// + /// private void SetAppearanceAssets(UUID userID, AvatarAppearance appearance) { IInventoryService invService = m_scene.InventoryService; @@ -553,7 +680,13 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory for (int j = 0; j < appearance.Wearables[i].Count; j++) { if (appearance.Wearables[i][j].ItemID == UUID.Zero) + { + m_log.WarnFormat( + "[AVFACTORY]: Wearable item {0}:{1} for user {2} unexpectedly UUID.Zero. Ignoring.", + i, j, userID); + continue; + } // Ignore ruth's assets if (appearance.Wearables[i][j].ItemID == AvatarWearable.DefaultWearables[i][0].ItemID) @@ -568,7 +701,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory } else { - m_log.ErrorFormat( + m_log.WarnFormat( "[AVFACTORY]: Can't find inventory item {0} for {1}, setting to default", appearance.Wearables[i][j].ItemID, (WearableType)i); @@ -581,8 +714,311 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory { m_log.WarnFormat("[AVFACTORY]: user {0} has no inventory, appearance isn't going to work", userID); } + +// IInventoryService invService = m_scene.InventoryService; +// bool resetwearable = false; +// if (invService.GetRootFolder(userID) != null) +// { +// for (int i = 0; i < AvatarWearable.MAX_WEARABLES; i++) +// { +// for (int j = 0; j < appearance.Wearables[i].Count; j++) +// { +// // Check if the default wearables are not set +// if (appearance.Wearables[i][j].ItemID == UUID.Zero) +// { +// switch ((WearableType) i) +// { +// case WearableType.Eyes: +// case WearableType.Hair: +// case WearableType.Shape: +// case WearableType.Skin: +// //case WearableType.Underpants: +// TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); +// resetwearable = true; +// m_log.Warn("[AVFACTORY]: UUID.Zero Wearables, passing fake values."); +// resetwearable = true; +// break; +// +// } +// continue; +// } +// +// // Ignore ruth's assets except for the body parts! missing body parts fail avatar appearance on V1 +// if (appearance.Wearables[i][j].ItemID == AvatarWearable.DefaultWearables[i][0].ItemID) +// { +// switch ((WearableType)i) +// { +// case WearableType.Eyes: +// case WearableType.Hair: +// case WearableType.Shape: +// case WearableType.Skin: +// //case WearableType.Underpants: +// TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); +// +// m_log.WarnFormat("[AVFACTORY]: {0} Default Wearables, passing existing values.", (WearableType)i); +// resetwearable = true; +// break; +// +// } +// continue; +// } +// +// InventoryItemBase baseItem = new InventoryItemBase(appearance.Wearables[i][j].ItemID, userID); +// baseItem = invService.GetItem(baseItem); +// +// if (baseItem != null) +// { +// appearance.Wearables[i].Add(appearance.Wearables[i][j].ItemID, baseItem.AssetID); +// int unmodifiedWearableIndexForClosure = i; +// m_scene.AssetService.Get(baseItem.AssetID.ToString(), this, +// delegate(string x, object y, AssetBase z) +// { +// if (z == null) +// { +// TryAndRepairBrokenWearable( +// (WearableType)unmodifiedWearableIndexForClosure, invService, +// userID, appearance); +// } +// }); +// } +// else +// { +// m_log.ErrorFormat( +// "[AVFACTORY]: Can't find inventory item {0} for {1}, setting to default", +// appearance.Wearables[i][j].ItemID, (WearableType)i); +// +// TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); +// resetwearable = true; +// +// } +// } +// } +// +// // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... +// if (appearance.Wearables[(int) WearableType.Eyes] == null) +// { +// m_log.WarnFormat("[AVFACTORY]: {0} Eyes are Null, passing existing values.", (WearableType.Eyes)); +// +// TryAndRepairBrokenWearable(WearableType.Eyes, invService, userID, appearance); +// resetwearable = true; +// } +// else +// { +// if (appearance.Wearables[(int) WearableType.Eyes][0].ItemID == UUID.Zero) +// { +// m_log.WarnFormat("[AVFACTORY]: Eyes are UUID.Zero are broken, {0} {1}", +// appearance.Wearables[(int) WearableType.Eyes][0].ItemID, +// appearance.Wearables[(int) WearableType.Eyes][0].AssetID); +// TryAndRepairBrokenWearable(WearableType.Eyes, invService, userID, appearance); +// resetwearable = true; +// +// } +// +// } +// // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... +// if (appearance.Wearables[(int)WearableType.Shape] == null) +// { +// m_log.WarnFormat("[AVFACTORY]: {0} shape is Null, passing existing values.", (WearableType.Shape)); +// +// TryAndRepairBrokenWearable(WearableType.Shape, invService, userID, appearance); +// resetwearable = true; +// } +// else +// { +// if (appearance.Wearables[(int)WearableType.Shape][0].ItemID == UUID.Zero) +// { +// m_log.WarnFormat("[AVFACTORY]: Shape is UUID.Zero and broken, {0} {1}", +// appearance.Wearables[(int)WearableType.Shape][0].ItemID, +// appearance.Wearables[(int)WearableType.Shape][0].AssetID); +// TryAndRepairBrokenWearable(WearableType.Shape, invService, userID, appearance); +// resetwearable = true; +// +// } +// +// } +// // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... +// if (appearance.Wearables[(int)WearableType.Hair] == null) +// { +// m_log.WarnFormat("[AVFACTORY]: {0} Hair is Null, passing existing values.", (WearableType.Hair)); +// +// TryAndRepairBrokenWearable(WearableType.Hair, invService, userID, appearance); +// resetwearable = true; +// } +// else +// { +// if (appearance.Wearables[(int)WearableType.Hair][0].ItemID == UUID.Zero) +// { +// m_log.WarnFormat("[AVFACTORY]: Hair is UUID.Zero and broken, {0} {1}", +// appearance.Wearables[(int)WearableType.Hair][0].ItemID, +// appearance.Wearables[(int)WearableType.Hair][0].AssetID); +// TryAndRepairBrokenWearable(WearableType.Hair, invService, userID, appearance); +// resetwearable = true; +// +// } +// +// } +// // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... +// if (appearance.Wearables[(int)WearableType.Skin] == null) +// { +// m_log.WarnFormat("[AVFACTORY]: {0} Skin is Null, passing existing values.", (WearableType.Skin)); +// +// TryAndRepairBrokenWearable(WearableType.Skin, invService, userID, appearance); +// resetwearable = true; +// } +// else +// { +// if (appearance.Wearables[(int)WearableType.Skin][0].ItemID == UUID.Zero) +// { +// m_log.WarnFormat("[AVFACTORY]: Skin is UUID.Zero and broken, {0} {1}", +// appearance.Wearables[(int)WearableType.Skin][0].ItemID, +// appearance.Wearables[(int)WearableType.Skin][0].AssetID); +// TryAndRepairBrokenWearable(WearableType.Skin, invService, userID, appearance); +// resetwearable = true; +// +// } +// +// } +// if (resetwearable) +// { +// ScenePresence presence = null; +// if (m_scene.TryGetScenePresence(userID, out presence)) +// { +// presence.ControllingClient.SendWearables(presence.Appearance.Wearables, +// presence.Appearance.Serial++); +// } +// } +// +// } +// else +// { +// m_log.WarnFormat("[AVFACTORY]: user {0} has no inventory, appearance isn't going to work", userID); +// } + } + + private void TryAndRepairBrokenWearable(WearableType type, IInventoryService invService, UUID userID,AvatarAppearance appearance) + { + UUID defaultwearable = GetDefaultItem(type); + if (defaultwearable != UUID.Zero) + { + UUID newInvItem = UUID.Random(); + InventoryItemBase itembase = new InventoryItemBase(newInvItem, userID) + { + AssetID = + defaultwearable, + AssetType + = + (int) + FolderType + .BodyPart, + CreatorId + = + userID + .ToString + (), + //InvType = (int)InventoryType.Wearable, + + Description + = + "Failed Wearable Replacement", + Folder = + invService + .GetFolderForType + (userID, + FolderType + .BodyPart) + .ID, + Flags = (uint) type, + Name = Enum.GetName(typeof (WearableType), type), + BasePermissions = (uint) PermissionMask.Copy, + CurrentPermissions = (uint) PermissionMask.Copy, + EveryOnePermissions = (uint) PermissionMask.Copy, + GroupPermissions = (uint) PermissionMask.Copy, + NextPermissions = (uint) PermissionMask.Copy + }; + invService.AddItem(itembase); + UUID LinkInvItem = UUID.Random(); + itembase = new InventoryItemBase(LinkInvItem, userID) + { + AssetID = + newInvItem, + AssetType + = + (int) + AssetType + .Link, + CreatorId + = + userID + .ToString + (), + InvType = (int) InventoryType.Wearable, + + Description + = + "Failed Wearable Replacement", + Folder = + invService + .GetFolderForType + (userID, + FolderType + .CurrentOutfit) + .ID, + Flags = (uint) type, + Name = Enum.GetName(typeof (WearableType), type), + BasePermissions = (uint) PermissionMask.Copy, + CurrentPermissions = (uint) PermissionMask.Copy, + EveryOnePermissions = (uint) PermissionMask.Copy, + GroupPermissions = (uint) PermissionMask.Copy, + NextPermissions = (uint) PermissionMask.Copy + }; + invService.AddItem(itembase); + appearance.Wearables[(int)type] = new AvatarWearable(newInvItem, GetDefaultItem(type)); + ScenePresence presence = null; + if (m_scene.TryGetScenePresence(userID, out presence)) + { + m_scene.SendInventoryUpdate(presence.ControllingClient, + invService.GetFolderForType(userID, + FolderType + .CurrentOutfit), + false, true); + } + } } + private UUID GetDefaultItem(WearableType wearable) + { + // These are ruth + UUID ret = UUID.Zero; + switch (wearable) + { + case WearableType.Eyes: + ret = new UUID("4bb6fa4d-1cd2-498a-a84c-95c1a0e745a7"); + break; + case WearableType.Hair: + ret = new UUID("d342e6c0-b9d2-11dc-95ff-0800200c9a66"); + break; + case WearableType.Pants: + ret = new UUID("00000000-38f9-1111-024e-222222111120"); + break; + case WearableType.Shape: + ret = new UUID("66c41e39-38f9-f75a-024e-585989bfab73"); + break; + case WearableType.Shirt: + ret = new UUID("00000000-38f9-1111-024e-222222111110"); + break; + case WearableType.Skin: + ret = new UUID("77c41e39-38f9-f75a-024e-585989bbabbb"); + break; + case WearableType.Undershirt: + ret = new UUID("16499ebb-3208-ec27-2def-481881728f47"); + break; + case WearableType.Underpants: + ret = new UUID("4ac2e9c7-3671-d229-316a-67717730841d"); + break; + } + + return ret; + } #endregion #region Client Event Handlers @@ -592,12 +1028,17 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory /// private void Client_OnRequestWearables(IClientAPI client) { - // m_log.DebugFormat("[AVFACTORY]: Client_OnRequestWearables called for {0} ({1})", client.Name, client.AgentId); - ScenePresence sp = m_scene.GetScenePresence(client.AgentId); - if (sp != null) - client.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial++); - else - m_log.WarnFormat("[AVFACTORY]: Client_OnRequestWearables unable to find presence for {0}", client.AgentId); + Util.FireAndForget(delegate(object x) + { + Thread.Sleep(4000); + + // m_log.DebugFormat("[AVFACTORY]: Client_OnRequestWearables called for {0} ({1})", client.Name, client.AgentId); + ScenePresence sp = m_scene.GetScenePresence(client.AgentId); + if (sp != null) + client.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial++); + else + m_log.WarnFormat("[AVFACTORY]: Client_OnRequestWearables unable to find presence for {0}", client.AgentId); + }, null, "AvatarFactoryModule.OnClientRequestWearables"); } /// @@ -606,12 +1047,12 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory /// /// /// - private void Client_OnSetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams) + private void Client_OnSetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams, Vector3 avSize, WearableCacheItem[] cacheItems) { // m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance called for {0} ({1})", client.Name, client.AgentId); ScenePresence sp = m_scene.GetScenePresence(client.AgentId); if (sp != null) - SetAppearance(sp, textureEntry, visualParams); + SetAppearance(sp, textureEntry, visualParams,avSize, cacheItems); else m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance unable to find presence for {0}", client.AgentId); } @@ -659,6 +1100,61 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory QueueAppearanceSave(client.AgentId); } } + + /// + /// Respond to the cached textures request from the client + /// + /// + /// + /// + private void Client_OnCachedTextureRequest(IClientAPI client, int serial, List cachedTextureRequest) + { + // m_log.WarnFormat("[AVFACTORY]: Client_OnCachedTextureRequest called for {0} ({1})", client.Name, client.AgentId); + ScenePresence sp = m_scene.GetScenePresence(client.AgentId); + + List cachedTextureResponse = new List(); + foreach (CachedTextureRequestArg request in cachedTextureRequest) + { + UUID texture = UUID.Zero; + int index = request.BakedTextureIndex; + + if (m_reusetextures) + { + // this is the most insanely dumb way to do this... however it seems to + // actually work. if the appearance has been reset because wearables have + // changed then the texture entries are zero'd out until the bakes are + // uploaded. on login, if the textures exist in the cache (eg if you logged + // into the simulator recently, then the appearance will pull those and send + // them back in the packet and you won't have to rebake. if the textures aren't + // in the cache then the intial makeroot() call in scenepresence will zero + // them out. + // + // a better solution (though how much better is an open question) is to + // store the hashes in the appearance and compare them. Thats's coming. + + Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[index]; + if (face != null) + texture = face.TextureID; + + // m_log.WarnFormat("[AVFACTORY]: reuse texture {0} for index {1}",texture,index); + } + + CachedTextureResponseArg response = new CachedTextureResponseArg(); + response.BakedTextureIndex = index; + response.BakedTextureID = texture; + response.HostName = null; + + cachedTextureResponse.Add(response); + } + + // m_log.WarnFormat("[AVFACTORY]: serial is {0}",serial); + // The serial number appears to be used to match requests and responses + // in the texture transaction. We just send back the serial number + // that was provided in the request. The viewer bumps this for us. + client.SendCachedTextureResponse(sp, serial, cachedTextureResponse); + } + + #endregion public void WriteBakedTexturesReport(IScenePresence sp, ReportOutputAction outputAction) @@ -690,7 +1186,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory } bool bakedTextureValid = m_scene.AvatarFactory.ValidateBakedTextureCache(sp); - outputAction("{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "corrupt"); + outputAction("{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "incomplete"); } } } diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs index 1830d41..9513408 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs @@ -34,7 +34,6 @@ using OpenSim.Framework; using OpenSim.Region.CoreModules.Asset; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory { @@ -48,23 +47,103 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory public void TestSetAppearance() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); UUID userId = TestHelpers.ParseTail(0x1); + UUID bakedTextureID = TestHelpers.ParseTail(0x2); + // We need an asset cache because otherwise the LocalAssetServiceConnector will short-circuit directly + // to the AssetService, which will then store temporary and local assets permanently + CoreAssetCache assetCache = new CoreAssetCache(); + AvatarFactoryModule afm = new AvatarFactoryModule(); - TestScene scene = new SceneHelpers().SetupScene(); + TestScene scene = new SceneHelpers(assetCache).SetupScene(); SceneHelpers.SetupSceneModules(scene, afm); ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId); + // TODO: Use the actual BunchOfCaps functionality once we slot in the CapabilitiesModules + AssetBase bakedTextureAsset; + bakedTextureAsset + = new AssetBase( + bakedTextureID, "Test Baked Texture", (sbyte)AssetType.Texture, userId.ToString()); + bakedTextureAsset.Data = new byte[] { 2 }; // Not necessary to have a genuine JPEG2000 asset here yet + bakedTextureAsset.Temporary = true; + bakedTextureAsset.Local = true; + scene.AssetService.Store(bakedTextureAsset); + byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT]; for (byte i = 0; i < visualParams.Length; i++) visualParams[i] = i; - afm.SetAppearance(sp, new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)), visualParams); + Primitive.TextureEntry bakedTextureEntry = new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)); + uint eyesFaceIndex = (uint)AppearanceManager.BakeTypeToAgentTextureIndex(BakeType.Eyes); + Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); + + int rebakeRequestsReceived = 0; + ((TestClient)sp.ControllingClient).OnReceivedSendRebakeAvatarTextures += id => rebakeRequestsReceived++; + + // This is the alpha texture + eyesFace.TextureID = bakedTextureID; + afm.SetAppearance(sp, bakedTextureEntry, visualParams, null); + + Assert.That(rebakeRequestsReceived, Is.EqualTo(0)); + + AssetBase eyesBake = scene.AssetService.Get(bakedTextureID.ToString()); + Assert.That(eyesBake, Is.Not.Null); + Assert.That(eyesBake.Temporary, Is.True); + Assert.That(eyesBake.Local, Is.True); + } + + /// + /// Test appearance setting where the baked texture UUID are library alpha textures. + /// + /// + /// For a mesh avatar, it appears these 'baked textures' are used. So these should not trigger a request to + /// rebake. + /// + [Test] + public void TestSetAppearanceAlphaBakedTextures() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + UUID alphaTextureID = new UUID("3a367d1c-bef1-6d43-7595-e88c1e3aadb3"); + + // We need an asset cache because otherwise the LocalAssetServiceConnector will short-circuit directly + // to the AssetService, which will then store temporary and local assets permanently + CoreAssetCache assetCache = new CoreAssetCache(); + + AvatarFactoryModule afm = new AvatarFactoryModule(); + TestScene scene = new SceneHelpers(assetCache).SetupScene(); + SceneHelpers.SetupSceneModules(scene, afm); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId); + + AssetBase libraryAsset; + libraryAsset + = new AssetBase( + alphaTextureID, "Default Alpha Layer Texture", (sbyte)AssetType.Texture, userId.ToString()); + libraryAsset.Data = new byte[] { 2 }; // Not necessary to have a genuine JPEG2000 asset here yet + libraryAsset.Temporary = false; + libraryAsset.Local = false; + scene.AssetService.Store(libraryAsset); + + byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT]; + for (byte i = 0; i < visualParams.Length; i++) + visualParams[i] = i; + + Primitive.TextureEntry bakedTextureEntry = new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)); + uint eyesFaceIndex = (uint)AppearanceManager.BakeTypeToAgentTextureIndex(BakeType.Eyes); + Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); + + int rebakeRequestsReceived = 0; + ((TestClient)sp.ControllingClient).OnReceivedSendRebakeAvatarTextures += id => rebakeRequestsReceived++; - // TODO: Check baked texture - Assert.AreEqual(visualParams, sp.Appearance.VisualParams); + // This is the alpha texture + eyesFace.TextureID = alphaTextureID; + afm.SetAppearance(sp, bakedTextureEntry, visualParams, null); + + Assert.That(rebakeRequestsReceived, Is.EqualTo(0)); } [Test] @@ -102,7 +181,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); eyesFace.TextureID = eyesTextureId; - afm.SetAppearance(sp, bakedTextureEntry, visualParams); + afm.SetAppearance(sp, bakedTextureEntry, visualParams, null); afm.SaveBakedTextures(userId); // Dictionary bakedTextures = afm.GetBakedTextureFaces(userId); diff --git a/OpenSim/Region/CoreModules/Avatar/BakedTextures/XBakesModule.cs b/OpenSim/Region/CoreModules/Avatar/BakedTextures/XBakesModule.cs new file mode 100644 index 0000000..414f06a --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/BakedTextures/XBakesModule.cs @@ -0,0 +1,200 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using Nini.Config; +using System; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.ServiceAuth; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.Avatar.BakedTextures +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XBakes.Module")] + public class XBakesModule : INonSharedRegionModule, IBakedTextureModule + { + protected Scene m_Scene; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private UTF8Encoding enc = new UTF8Encoding(); + private string m_URL = String.Empty; + private static XmlSerializer m_serializer = new XmlSerializer(typeof(AssetBase)); + + private static IServiceAuth m_Auth; + + public void Initialise(IConfigSource configSource) + { + IConfig config = configSource.Configs["XBakes"]; + if (config == null) + return; + + m_URL = config.GetString("URL", String.Empty); + m_Auth = ServiceAuth.Create(configSource, "XBakes"); + } + + public void AddRegion(Scene scene) + { + // m_log.InfoFormat("[XBakes]: Enabled for region {0}", scene.RegionInfo.RegionName); + m_Scene = scene; + + scene.RegisterModuleInterface(this); + } + + public void RegionLoaded(Scene scene) + { + } + + public void RemoveRegion(Scene scene) + { + } + + public void Close() + { + } + + public string Name + { + get { return "XBakes.Module"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public WearableCacheItem[] Get(UUID id) + { + if (m_URL == String.Empty) + return null; + + int size = 0; + + using (RestClient rc = new RestClient(m_URL)) + { + List ret = new List(); + rc.AddResourcePath("bakes"); + rc.AddResourcePath(id.ToString()); + + rc.RequestMethod = "GET"; + + try + { + Stream s = rc.Request(m_Auth); + + using (XmlTextReader sr = new XmlTextReader(s)) + { + sr.ReadStartElement("BakedAppearance"); + while (sr.LocalName == "BakedTexture") + { + string sTextureIndex = sr.GetAttribute("TextureIndex"); + int lTextureIndex = Convert.ToInt32(sTextureIndex); + string sCacheId = sr.GetAttribute("CacheId"); + UUID lCacheId = UUID.Zero; + if (!(UUID.TryParse(sCacheId, out lCacheId))) + { + // ?? Nothing here + } + + ++size; + + sr.ReadStartElement("BakedTexture"); + AssetBase a = (AssetBase)m_serializer.Deserialize(sr); + ret.Add(new WearableCacheItem() { CacheId = lCacheId, TextureIndex = (uint)lTextureIndex, TextureAsset = a, TextureID = a.FullID }); + + sr.ReadEndElement(); + } + + m_log.DebugFormat("[XBakes]: read {0} textures for user {1}", ret.Count, id); + } + + return ret.ToArray(); + } + catch (XmlException) + { + return null; + } + } + } + + public void Store(UUID agentId, WearableCacheItem[] data) + { + if (m_URL == String.Empty) + return; + + MemoryStream reqStream; + + using (MemoryStream bakeStream = new MemoryStream()) + using (XmlTextWriter bakeWriter = new XmlTextWriter(bakeStream, null)) + { + bakeWriter.WriteStartElement(String.Empty, "BakedAppearance", String.Empty); + + for (int i = 0; i < data.Length; i++) + { + if (data[i] != null) + { + bakeWriter.WriteStartElement(String.Empty, "BakedTexture", String.Empty); + bakeWriter.WriteAttributeString(String.Empty, "TextureIndex", String.Empty, data[i].TextureIndex.ToString()); + bakeWriter.WriteAttributeString(String.Empty, "CacheId", String.Empty, data[i].CacheId.ToString()); + if (data[i].TextureAsset != null) + m_serializer.Serialize(bakeWriter, data[i].TextureAsset); + + bakeWriter.WriteEndElement(); + } + } + + bakeWriter.WriteEndElement(); + bakeWriter.Flush(); + + reqStream = new MemoryStream(bakeStream.ToArray()); + } + + RestClient rc = new RestClient(m_URL); + rc.AddResourcePath("bakes"); + rc.AddResourcePath(agentId.ToString()); + + rc.RequestMethod = "POST"; + + Util.FireAndForget( + delegate + { + rc.Request(reqStream, m_Auth); + m_log.DebugFormat("[XBakes]: stored {0} textures for user {1}", data.Length, agentId); + }, null, "XBakesModule.Store" + ); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs index 6d62ff0..f0b1e67 100644 --- a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs @@ -32,6 +32,7 @@ using log4net; using Nini.Config; using Mono.Addins; using OpenMetaverse; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -50,7 +51,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat private int m_saydistance = 20; private int m_shoutdistance = 100; private int m_whisperdistance = 10; - private List m_scenes = new List(); internal object m_syncy = new object(); @@ -61,18 +61,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat { m_config = config.Configs["Chat"]; - if (null == m_config) + if (m_config != null) { - m_log.Info("[CHAT]: no config found, plugin disabled"); - m_enabled = false; - return; - } - - if (!m_config.GetBoolean("enabled", true)) - { - m_log.Info("[CHAT]: plugin disabled by configuration"); - m_enabled = false; - return; + if (!m_config.GetBoolean("enabled", true)) + { + m_log.Info("[CHAT]: plugin disabled by configuration"); + m_enabled = false; + return; + } } m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); @@ -82,18 +78,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat public virtual void AddRegion(Scene scene) { - if (!m_enabled) return; + if (!m_enabled) + return; - lock (m_syncy) - { - if (!m_scenes.Contains(scene)) - { - m_scenes.Add(scene); - scene.EventManager.OnNewClient += OnNewClient; - scene.EventManager.OnChatFromWorld += OnChatFromWorld; - scene.EventManager.OnChatBroadcast += OnChatBroadcast; - } - } + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnChatFromWorld += OnChatFromWorld; + scene.EventManager.OnChatBroadcast += OnChatBroadcast; m_log.InfoFormat("[CHAT]: Initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, m_whisperdistance, m_saydistance, m_shoutdistance); @@ -101,22 +91,24 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat public virtual void RegionLoaded(Scene scene) { + if (!m_enabled) + return; + + ISimulatorFeaturesModule featuresModule = scene.RequestModuleInterface(); + + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + } public virtual void RemoveRegion(Scene scene) { - if (!m_enabled) return; + if (!m_enabled) + return; - lock (m_syncy) - { - if (m_scenes.Contains(scene)) - { - scene.EventManager.OnNewClient -= OnNewClient; - scene.EventManager.OnChatFromWorld -= OnChatFromWorld; - scene.EventManager.OnChatBroadcast -= OnChatBroadcast; - m_scenes.Remove(scene); - } - } + scene.EventManager.OnNewClient -= OnNewClient; + scene.EventManager.OnChatFromWorld -= OnChatFromWorld; + scene.EventManager.OnChatBroadcast -= OnChatBroadcast; } public virtual void Close() @@ -191,23 +183,16 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat UUID ownerID = UUID.Zero; UUID targetID = c.TargetUUID; string message = c.Message; - IScene scene = c.Scene; + Scene scene = (Scene)c.Scene; Vector3 fromPos = c.Position; - Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, - scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); + Vector3 regionPos = new Vector3(scene.RegionInfo.WorldLocX, scene.RegionInfo.WorldLocY, 0); if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel; switch (sourceType) { case ChatSourceType.Agent: - if (!(scene is Scene)) - { - m_log.WarnFormat("[CHAT]: scene {0} is not a Scene object, cannot obtain scene presence for {1}", - scene.RegionInfo.RegionName, c.Sender.AgentId); - return; - } - ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId); + ScenePresence avatar = scene.GetScenePresence(c.Sender.AgentId); fromPos = avatar.AbsolutePosition; fromName = avatar.Name; fromID = c.Sender.AgentId; @@ -234,36 +219,33 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat HashSet receiverIDs = new HashSet(); - foreach (Scene s in m_scenes) + if (targetID == UUID.Zero) { - if (targetID == UUID.Zero) - { - // This should use ForEachClient, but clients don't have a position. - // If camera is moved into client, then camera position can be used - s.ForEachRootScenePresence( - delegate(ScenePresence presence) - { - if (TrySendChatMessage( - presence, fromPos, regionPos, fromID, ownerID, fromName, c.Type, message, sourceType, false)) - receiverIDs.Add(presence.UUID); - } - ); - } - else - { - // This is a send to a specific client eg from llRegionSayTo - // no need to check distance etc, jand send is as say - ScenePresence presence = s.GetScenePresence(targetID); - if (presence != null && !presence.IsChildAgent) + // This should use ForEachClient, but clients don't have a position. + // If camera is moved into client, then camera position can be used + scene.ForEachScenePresence( + delegate(ScenePresence presence) { if (TrySendChatMessage( - presence, fromPos, regionPos, fromID, ownerID, fromName, ChatTypeEnum.Say, message, sourceType, true)) + presence, fromPos, regionPos, fromID, ownerID, fromName, c.Type, message, sourceType, false)) receiverIDs.Add(presence.UUID); } + ); + } + else + { + // This is a send to a specific client eg from llRegionSayTo + // no need to check distance etc, jand send is as say + ScenePresence presence = scene.GetScenePresence(targetID); + if (presence != null && !presence.IsChildAgent) + { + if (TrySendChatMessage( + presence, fromPos, regionPos, fromID, ownerID, fromName, ChatTypeEnum.Say, message, sourceType, true)) + receiverIDs.Add(presence.UUID); } } - (scene as Scene).EventManager.TriggerOnChatToClients( + scene.EventManager.TriggerOnChatToClients( fromID, receiverIDs, message, c.Type, fromPos, fromName, sourceType, ChatAudibleLevel.Fully); } @@ -288,17 +270,20 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat string fromName = c.From; UUID fromID = UUID.Zero; + UUID ownerID = UUID.Zero; ChatSourceType sourceType = ChatSourceType.Object; if (null != c.Sender) { ScenePresence avatar = (c.Scene as Scene).GetScenePresence(c.Sender.AgentId); fromID = c.Sender.AgentId; fromName = avatar.Name; + ownerID = c.Sender.AgentId; sourceType = ChatSourceType.Agent; } else if (c.SenderUUID != UUID.Zero) { - fromID = c.SenderUUID; + fromID = c.SenderUUID; + ownerID = ((SceneObjectPart)c.SenderObject).OwnerID; } // m_log.DebugFormat("[CHAT] Broadcast: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, cType, sourceType); @@ -316,7 +301,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat return; client.SendChatMessage( - c.Message, (byte)cType, CenterOfRegion, fromName, fromID, fromID, + c.Message, (byte)cType, CenterOfRegion, fromName, fromID, ownerID, (byte)sourceType, (byte)ChatAudibleLevel.Fully); receiverIDs.Add(client.AgentId); @@ -348,18 +333,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat UUID fromAgentID, UUID ownerID, string fromName, ChatTypeEnum type, string message, ChatSourceType src, bool ignoreDistance) { - // don't send stuff to child agents - if (presence.IsChildAgent) return false; - - Vector3 fromRegionPos = fromPos + regionPos; - Vector3 toRegionPos = presence.AbsolutePosition + - new Vector3(presence.Scene.RegionInfo.RegionLocX * Constants.RegionSize, - presence.Scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); - - int dis = (int)Util.GetDistanceTo(toRegionPos, fromRegionPos); + if (presence.LifecycleState != ScenePresenceState.Running) + return false; if (!ignoreDistance) { + Vector3 fromRegionPos = fromPos + regionPos; + Vector3 toRegionPos = presence.AbsolutePosition + + new Vector3(presence.Scene.RegionInfo.WorldLocX, presence.Scene.RegionInfo.WorldLocY, 0); + + int dis = (int)Util.GetDistanceTo(toRegionPos, fromRegionPos); + if (type == ChatTypeEnum.Whisper && dis > m_whisperdistance || type == ChatTypeEnum.Say && dis > m_saydistance || type == ChatTypeEnum.Shout && dis > m_shoutdistance) @@ -375,5 +359,33 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat return true; } + + #region SimulatorFeaturesRequest + + static OSDInteger m_SayRange, m_WhisperRange, m_ShoutRange; + + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + OSD extras = new OSDMap(); + if (features.ContainsKey("OpenSimExtras")) + extras = features["OpenSimExtras"]; + else + features["OpenSimExtras"] = extras; + + if (m_SayRange == null) + { + // Do this only once + m_SayRange = new OSDInteger(m_saydistance); + m_WhisperRange = new OSDInteger(m_whisperdistance); + m_ShoutRange = new OSDInteger(m_shoutdistance); + } + + ((OSDMap)extras)["say-range"] = m_SayRange; + ((OSDMap)extras)["whisper-range"] = m_WhisperRange; + ((OSDMap)extras)["shout-range"] = m_ShoutRange; + + } + + #endregion } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/Tests/ChatModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Chat/Tests/ChatModuleTests.cs new file mode 100644 index 0000000..3018d94 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Chat/Tests/ChatModuleTests.cs @@ -0,0 +1,285 @@ +/* + * 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 log4net.Config; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Avatar.Chat; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Chat.Tests +{ + [TestFixture] + public class ChatModuleTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // We must do this here so that child agent positions are updated in a predictable manner. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + private void SetupNeighbourRegions(TestScene sceneA, TestScene sceneB) + { + // XXX: HTTP server is not (and should not be) necessary for this test, though it's absence makes the + // CapabilitiesModule complain when it can't set up HTTP endpoints. + // BaseHttpServer httpServer = new BaseHttpServer(99999); + // MainServer.AddHttpServer(httpServer); + // MainServer.Instance = httpServer; + + // We need entity transfer modules so that when sp2 logs into the east region, the region calls + // EntityTransferModuleto set up a child agent on the west region. + // XXX: However, this is not an entity transfer so is misleading. + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Chat"); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA, new ChatModule()); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB, new ChatModule()); + } + + /// + /// Tests chat between neighbour regions on the east-west axis + /// + /// + /// Really, this is a combination of a child agent position update test and a chat range test. These need + /// to be separated later on. + /// + [Test] + public void TestInterRegionChatDistanceEastWest() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID sp1Uuid = TestHelpers.ParseTail(0x11); + UUID sp2Uuid = TestHelpers.ParseTail(0x12); + + Vector3 sp1Position = new Vector3(6, 128, 20); + Vector3 sp2Position = new Vector3(250, 128, 20); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneWest = sh.SetupScene("sceneWest", TestHelpers.ParseTail(0x1), 1000, 1000); + TestScene sceneEast = sh.SetupScene("sceneEast", TestHelpers.ParseTail(0x2), 1001, 1000); + + SetupNeighbourRegions(sceneWest, sceneEast); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(sceneEast, sp1Uuid); + TestClient sp1Client = (TestClient)sp1.ControllingClient; + + // If we don't set agents to flying, test will go wrong as they instantly fall to z = 0. + // TODO: May need to create special complete no-op test physics module rather than basic physics, since + // physics is irrelevant to this test. + sp1.Flying = true; + + // When sp1 logs in to sceneEast, it sets up a child agent in sceneWest and informs the sp2 client to + // make the connection. For this test, will simplify this chain by making the connection directly. + ScenePresence sp1Child = SceneHelpers.AddChildScenePresence(sceneWest, sp1Uuid); + TestClient sp1ChildClient = (TestClient)sp1Child.ControllingClient; + + sp1.AbsolutePosition = sp1Position; + + ScenePresence sp2 = SceneHelpers.AddScenePresence(sceneWest, sp2Uuid); + TestClient sp2Client = (TestClient)sp2.ControllingClient; + sp2.Flying = true; + + ScenePresence sp2Child = SceneHelpers.AddChildScenePresence(sceneEast, sp2Uuid); + TestClient sp2ChildClient = (TestClient)sp2Child.ControllingClient; + + sp2.AbsolutePosition = sp2Position; + + // We must update the scenes in order to make the root new root agents trigger position updates in their + // children. + sceneWest.Update(1); + sceneEast.Update(1); + + // Check child positions are correct. + Assert.AreEqual( + new Vector3(sp1Position.X + sceneEast.RegionInfo.RegionSizeX, sp1Position.Y, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + Assert.AreEqual( + new Vector3(sp2Position.X - sceneWest.RegionInfo.RegionSizeX, sp2Position.Y, sp2Position.Z), + sp2ChildClient.SceneAgent.AbsolutePosition); + + string receivedSp1ChatMessage = ""; + string receivedSp2ChatMessage = ""; + + sp1ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp1ChatMessage = message; + sp2ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp2ChatMessage = message; + + TestUserInRange(sp1Client, "ello darling", ref receivedSp2ChatMessage); + TestUserInRange(sp2Client, "fantastic cats", ref receivedSp1ChatMessage); + + sp1Position = new Vector3(30, 128, 20); + sp1.AbsolutePosition = sp1Position; + sceneEast.Update(1); + + // Check child position is correct. + Assert.AreEqual( + new Vector3(sp1Position.X + sceneEast.RegionInfo.RegionSizeX, sp1Position.Y, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + TestUserOutOfRange(sp1Client, "beef", ref receivedSp2ChatMessage); + TestUserOutOfRange(sp2Client, "lentils", ref receivedSp1ChatMessage); + } + + /// + /// Tests chat between neighbour regions on the north-south axis + /// + /// + /// Really, this is a combination of a child agent position update test and a chat range test. These need + /// to be separated later on. + /// + [Test] + public void TestInterRegionChatDistanceNorthSouth() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + UUID sp1Uuid = TestHelpers.ParseTail(0x11); + UUID sp2Uuid = TestHelpers.ParseTail(0x12); + + Vector3 sp1Position = new Vector3(128, 250, 20); + Vector3 sp2Position = new Vector3(128, 6, 20); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneNorth = sh.SetupScene("sceneNorth", TestHelpers.ParseTail(0x1), 1000, 1000); + TestScene sceneSouth = sh.SetupScene("sceneSouth", TestHelpers.ParseTail(0x2), 1000, 1001); + + SetupNeighbourRegions(sceneNorth, sceneSouth); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(sceneNorth, sp1Uuid); + TestClient sp1Client = (TestClient)sp1.ControllingClient; + + // If we don't set agents to flying, test will go wrong as they instantly fall to z = 0. + // TODO: May need to create special complete no-op test physics module rather than basic physics, since + // physics is irrelevant to this test. + sp1.Flying = true; + + // When sp1 logs in to sceneEast, it sets up a child agent in sceneNorth and informs the sp2 client to + // make the connection. For this test, will simplify this chain by making the connection directly. + ScenePresence sp1Child = SceneHelpers.AddChildScenePresence(sceneSouth, sp1Uuid); + TestClient sp1ChildClient = (TestClient)sp1Child.ControllingClient; + + sp1.AbsolutePosition = sp1Position; + + ScenePresence sp2 = SceneHelpers.AddScenePresence(sceneSouth, sp2Uuid); + TestClient sp2Client = (TestClient)sp2.ControllingClient; + sp2.Flying = true; + + ScenePresence sp2Child = SceneHelpers.AddChildScenePresence(sceneNorth, sp2Uuid); + TestClient sp2ChildClient = (TestClient)sp2Child.ControllingClient; + + sp2.AbsolutePosition = sp2Position; + + // We must update the scenes in order to make the root new root agents trigger position updates in their + // children. + sceneNorth.Update(1); + sceneSouth.Update(1); + + // Check child positions are correct. + Assert.AreEqual( + new Vector3(sp1Position.X, sp1Position.Y - sceneNorth.RegionInfo.RegionSizeY, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + Assert.AreEqual( + new Vector3(sp2Position.X, sp2Position.Y + sceneSouth.RegionInfo.RegionSizeY, sp2Position.Z), + sp2ChildClient.SceneAgent.AbsolutePosition); + + string receivedSp1ChatMessage = ""; + string receivedSp2ChatMessage = ""; + + sp1ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp1ChatMessage = message; + sp2ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp2ChatMessage = message; + + TestUserInRange(sp1Client, "ello darling", ref receivedSp2ChatMessage); + TestUserInRange(sp2Client, "fantastic cats", ref receivedSp1ChatMessage); + + sp1Position = new Vector3(30, 128, 20); + sp1.AbsolutePosition = sp1Position; + sceneNorth.Update(1); + + // Check child position is correct. + Assert.AreEqual( + new Vector3(sp1Position.X, sp1Position.Y - sceneNorth.RegionInfo.RegionSizeY, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + TestUserOutOfRange(sp1Client, "beef", ref receivedSp2ChatMessage); + TestUserOutOfRange(sp2Client, "lentils", ref receivedSp1ChatMessage); + } + + private void TestUserInRange(TestClient speakClient, string testMessage, ref string receivedMessage) + { + receivedMessage = ""; + + speakClient.Chat(0, ChatTypeEnum.Say, testMessage); + + Assert.AreEqual(testMessage, receivedMessage); + } + + private void TestUserOutOfRange(TestClient speakClient, string testMessage, ref string receivedMessage) + { + receivedMessage = ""; + + speakClient.Chat(0, ChatTypeEnum.Say, testMessage); + + Assert.AreNotEqual(testMessage, receivedMessage); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs b/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs index 343cdb5..fc23b72 100644 --- a/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs @@ -31,6 +31,7 @@ using Nini.Config; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; using OpenMetaverse; using Mono.Addins; @@ -182,6 +183,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Combat.CombatModule try { ILandObject obj = avatar.Scene.LandChannel.GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + + if (obj == null) + return; + if ((obj.LandData.Flags & (uint)ParcelFlags.AllowDamage) != 0 || avatar.Scene.RegionInfo.RegionSettings.AllowDamage) { diff --git a/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs b/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs index d26907b..a896897 100644 --- a/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs @@ -133,13 +133,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Dialog UUID objectID, UUID ownerID, string message, UUID textureID, int ch, string[] buttonlabels) { - UserAccount account = m_scene.UserAccountService.GetUserAccount( - m_scene.RegionInfo.ScopeID, ownerID); - string ownerFirstName, ownerLastName; - if (account != null) + string username = m_scene.UserManagementModule.GetUserName(ownerID); + string ownerFirstName, ownerLastName = String.Empty; + if (!String.IsNullOrEmpty(username)) { - ownerFirstName = account.FirstName; - ownerLastName = account.LastName; + string[] parts = username.Split(' '); + ownerFirstName = parts[0]; + if (parts.Length > 1) + ownerLastName = username.Split(' ')[1]; } else { @@ -170,17 +171,16 @@ namespace OpenSim.Region.CoreModules.Avatar.Dialog } public void SendTextBoxToUser(UUID avatarid, string message, - int chatChannel, string name, UUID objectid, UUID ownerid) + int chatChannel, string name, UUID objectid, UUID ownerID) { - UserAccount account = m_scene.UserAccountService.GetUserAccount( - m_scene.RegionInfo.ScopeID, ownerid); - string ownerFirstName, ownerLastName; - UUID ownerID = UUID.Zero; - if (account != null) + string username = m_scene.UserManagementModule.GetUserName(ownerID); + string ownerFirstName, ownerLastName = String.Empty; + if (!String.IsNullOrEmpty(username)) { - ownerFirstName = account.FirstName; - ownerLastName = account.LastName; - ownerID = account.PrincipalID; + string[] parts = username.Split(' '); + ownerFirstName = parts[0]; + if (parts.Length > 1) + ownerLastName = username.Split(' ')[1]; } else { diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/CallingCardModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/CallingCardModule.cs index 5ec0ea9..eb23e83 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/CallingCardModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/CallingCardModule.cs @@ -36,6 +36,7 @@ using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Mono.Addins; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Avatar.Friends { @@ -180,7 +181,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends if (folderID == UUID.Zero) { InventoryFolderBase folder = inv.GetFolderForType(userID, - AssetType.CallingCard); + FolderType.CallingCard); if (folder == null) // Nowhere to put it return UUID.Zero; @@ -236,7 +237,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends IInventoryService invService = m_Scenes[0].InventoryService; InventoryFolderBase trashFolder = - invService.GetFolderForType(client.AgentId, AssetType.TrashFolder); + invService.GetFolderForType(client.AgentId, FolderType.Trash); InventoryItemBase item = new InventoryItemBase(transactionID, client.AgentId); item = invService.GetItem(item); diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs index 8056030..08e7dd2 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs @@ -38,7 +38,6 @@ using OpenMetaverse; using Mono.Addins; using OpenSim.Framework; using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -94,6 +93,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends protected Dictionary m_Friends = new Dictionary(); /// + /// Maintain a record of clients that need to notify about their online status. This only + /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism. + /// + protected HashSet m_NeedsToNotifyStatus = new HashSet(); + + /// /// Maintain a record of viewers that need to be sent notifications for friends that are online. This only /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism. /// @@ -324,6 +329,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends private void OnMakeRootAgent(ScenePresence sp) { RecacheFriends(sp.ControllingClient); + + lock (m_NeedsToNotifyStatus) + { + if (m_NeedsToNotifyStatus.Remove(sp.UUID)) + { + // Inform the friends that this user is online. This can only be done once the client is a Root Agent. + StatusChange(sp.UUID, true); + } + } } private void OnClientLogin(IClientAPI client) @@ -331,8 +345,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends UUID agentID = client.AgentId; //m_log.DebugFormat("[XXX]: OnClientLogin!"); - // Inform the friends that this user is online - StatusChange(agentID, true); + + // Register that we need to send this user's status to friends. This can only be done + // once the client becomes a Root Agent, because as part of sending out the presence + // we also get back the presence of the HG friends, and we need to send that to the + // client, but that can only be done when the client is a Root Agent. + lock (m_NeedsToNotifyStatus) + m_NeedsToNotifyStatus.Add(agentID); // Register that we need to send the list of online friends to this user lock (m_NeedsListOfOnlineFriends) @@ -371,7 +390,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends foreach (string fid in outstanding) { UUID fromAgentID; - string firstname = "Unknown", lastname = "User"; + string firstname = "Unknown", lastname = "UserFMSFOIN"; if (!GetAgentInfo(client.Scene.RegionInfo.ScopeID, fid, out fromAgentID, out firstname, out lastname)) { m_log.DebugFormat("[FRIENDS MODULE]: skipping malformed friend {0}", fid); @@ -397,7 +416,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends protected virtual bool GetAgentInfo(UUID scopeID, string fid, out UUID agentID, out string first, out string last) { - first = "Unknown"; last = "User"; + first = "Unknown"; last = "UserFMGAI"; if (!UUID.TryParse(fid, out agentID)) return false; @@ -491,13 +510,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // Notify about this user status StatusNotify(friendList, agentID, online); - } + }, null, "FriendsModule.StatusChange" ); } } protected virtual void StatusNotify(List friendList, UUID userID, bool online) { + //m_log.DebugFormat("[FRIENDS]: Entering StatusNotify for {0}", userID); + List friendStringIds = friendList.ConvertAll(friend => friend.Friend); List remoteFriendStringIds = new List(); foreach (string friendStringId in friendStringIds) @@ -523,12 +544,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends foreach (PresenceInfo friendSession in friendSessions) { // let's guard against sessions-gone-bad - if (friendSession.RegionID != UUID.Zero) + if (friendSession != null && friendSession.RegionID != UUID.Zero) { + //m_log.DebugFormat("[FRIENDS]: Get region {0}", friendSession.RegionID); GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); - //m_log.DebugFormat("[FRIENDS]: Remote Notify to region {0}", region.RegionName); - m_FriendsSimConnector.StatusNotify(region, userID, friendSession.UserID, online); + if (region != null) + { + m_FriendsSimConnector.StatusNotify(region, userID, friendSession.UserID, online); + } } + //else + // m_log.DebugFormat("[FRIENDS]: friend session is null or the region is UUID.Zero"); } } @@ -685,7 +711,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // // Try local - if (LocalFriendshipTerminated(exfriendID)) + if (LocalFriendshipTerminated(client.AgentId, exfriendID)) return; PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { exfriendID.ToString() }); @@ -827,13 +853,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends return false; } - public bool LocalFriendshipTerminated(UUID exfriendID) + public bool LocalFriendshipTerminated(UUID userID, UUID exfriendID) { IClientAPI friendClient = LocateClientObject(exfriendID); if (friendClient != null) { // the friend in this sim as root agent - friendClient.SendTerminateFriend(exfriendID); + friendClient.SendTerminateFriend(userID); // update local cache RecacheFriends(friendClient); // we're done diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsRequestHandler.cs b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsRequestHandler.cs index 637beef..13512a2 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsRequestHandler.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsRequestHandler.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -42,19 +42,27 @@ using log4net; namespace OpenSim.Region.CoreModules.Avatar.Friends { - public class FriendsRequestHandler : BaseStreamHandler + public class FriendsRequestHandler : BaseStreamHandlerBasicDOSProtector { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private FriendsModule m_FriendsModule; public FriendsRequestHandler(FriendsModule fmodule) - : base("POST", "/friends") + : base("POST", "/friends", new BasicDosProtectorOptions() + { + AllowXForwardedFor = true, + ForgetTimeSpan = TimeSpan.FromMinutes(2), + MaxRequestsInTimeframe = 20, + ReportingName = "FRIENDSDOSPROTECTOR", + RequestTimeSpan = TimeSpan.FromSeconds(5), + ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod + }) { m_FriendsModule = fmodule; } - public override byte[] Handle( + protected override byte[] ProcessRequest( string path, Stream requestData, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { StreamReader sr = new StreamReader(requestData); @@ -193,7 +201,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends if (!UUID.TryParse(request["ToID"].ToString(), out toID)) return FailureResult(); - if (m_FriendsModule.LocalFriendshipTerminated(toID)) + if (m_FriendsModule.LocalFriendshipTerminated(fromID, toID)) return SuccessResult(); return FailureResult(); @@ -281,18 +289,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends rootElement.AppendChild(result); - return DocToBytes(doc); - } - - private byte[] DocToBytes(XmlDocument doc) - { - MemoryStream ms = new MemoryStream(); - XmlTextWriter xw = new XmlTextWriter(ms, null); - xw.Formatting = Formatting.Indented; - doc.WriteTo(xw); - xw.Flush(); - - return ms.ToArray(); + return Util.DocToBytes(doc); } #endregion diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs index bf5c0bb..27b7376 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs @@ -183,6 +183,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends if (Util.ParseUniversalUserIdentifier(finfo.Friend, out id, out url, out first, out last, out tmp)) { IUserManagement uMan = m_Scenes[0].RequestModuleInterface(); + m_log.DebugFormat("[HGFRIENDS MODULE]: caching {0}", finfo.Friend); uMan.AddUser(id, url + ";" + first + " " + last); } } @@ -238,6 +239,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends fList.Add(s.Substring(0, 36)); } + // FIXME: also query the presence status of friends in other grids (like in HGStatusNotifier.Notify()) + PresenceInfo[] presence = PresenceService.GetAgents(fList.ToArray()); foreach (PresenceInfo pi in presence) { @@ -251,7 +254,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends protected override void StatusNotify(List friendList, UUID userID, bool online) { -// m_log.DebugFormat("[HGFRIENDS MODULE]: Entering StatusNotify for {0}", userID); + //m_log.DebugFormat("[HGFRIENDS MODULE]: Entering StatusNotify for {0}", userID); // First, let's divide the friends on a per-domain basis Dictionary> friendsPerDomain = new Dictionary>(); @@ -293,7 +296,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends protected override bool GetAgentInfo(UUID scopeID, string fid, out UUID agentID, out string first, out string last) { - first = "Unknown"; last = "User"; + first = "Unknown"; last = "UserHGGAI"; if (base.GetAgentInfo(scopeID, fid, out agentID, out first, out last)) return true; @@ -349,7 +352,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends public override FriendInfo[] GetFriendsFromService(IClientAPI client) { -// m_log.DebugFormat("[HGFRIENDS MODULE]: Entering GetFriendsFromService for {0}", client.Name); + // m_log.DebugFormat("[HGFRIENDS MODULE]: Entering GetFriendsFromService for {0}", client.Name); Boolean agentIsLocal = true; if (UserManagementModule != null) agentIsLocal = UserManagementModule.IsLocalGridUser(client.AgentId); @@ -362,13 +365,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends AgentCircuitData agentClientCircuit = ((Scene)(client.Scene)).AuthenticateHandler.GetAgentCircuitData(client.CircuitCode); if (agentClientCircuit != null) { - //[XXX] string agentUUI = Util.ProduceUserUniversalIdentifier(agentClientCircuit); - + // Note that this is calling a different interface than base; this one calls with a string param! finfos = FriendsService.GetFriends(client.AgentId.ToString()); m_log.DebugFormat("[HGFRIENDS MODULE]: Fetched {0} local friends for visitor {1}", finfos.Length, client.AgentId.ToString()); } -// m_log.DebugFormat("[HGFRIENDS MODULE]: Exiting GetFriendsFromService for {0}", client.Name); + // m_log.DebugFormat("[HGFRIENDS MODULE]: Exiting GetFriendsFromService for {0}", client.Name); return finfos; } @@ -658,7 +660,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends FriendsService.Delete(friendUUI, agentID.ToString()); // notify the exfriend's service - Util.FireAndForget(delegate { Delete(exfriendID, agentID, friendUUI); }); + Util.FireAndForget( + delegate { Delete(exfriendID, agentID, friendUUI); }, null, "HGFriendsModule.DeleteFriendshipForeignFriend"); m_log.DebugFormat("[HGFRIENDS MODULE]: {0} terminated {1}", agentID, friendUUI); return true; @@ -676,7 +679,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends FriendsService.Delete(agentUUI, exfriendID.ToString()); // notify the agent's service? - Util.FireAndForget(delegate { Delete(agentID, exfriendID, agentUUI); }); + Util.FireAndForget( + delegate { Delete(agentID, exfriendID, agentUUI); }, null, "HGFriendsModule.DeleteFriendshipLocalFriend"); m_log.DebugFormat("[HGFRIENDS MODULE]: {0} terminated {1}", agentUUI, exfriendID); return true; diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/Tests/FriendModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Friends/Tests/FriendModuleTests.cs index 961117e..e6fd54e 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/Tests/FriendModuleTests.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/Tests/FriendModuleTests.cs @@ -35,7 +35,6 @@ using OpenSim.Framework; using OpenSim.Region.CoreModules.Avatar.Friends; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Avatar.Friends.Tests { diff --git a/OpenSim/Region/CoreModules/Avatar/Gods/GodsModule.cs b/OpenSim/Region/CoreModules/Avatar/Gods/GodsModule.cs index 5a7446f..3b6d970 100644 --- a/OpenSim/Region/CoreModules/Avatar/Gods/GodsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Gods/GodsModule.cs @@ -26,20 +26,37 @@ */ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Reflection; +using System.Web; +using System.Xml; +using log4net; +using Mono.Addins; using Nini.Config; using OpenMetaverse; +using OpenMetaverse.Messages.Linden; +using OpenMetaverse.StructuredData; using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; - -using Mono.Addins; +using Caps = OpenSim.Framework.Capabilities.Caps; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; namespace OpenSim.Region.CoreModules.Avatar.Gods { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GodsModule")] public class GodsModule : INonSharedRegionModule, IGodsModule { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// Special UUID for actions that apply to all agents private static readonly UUID ALL_AGENTS = new UUID("44e87126-e794-4ded-05b3-7c42da3d5cdb"); @@ -65,6 +82,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Gods m_scene = scene; m_scene.RegisterModuleInterface(this); m_scene.EventManager.OnNewClient += SubscribeToClientEvents; + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; + scene.EventManager.OnIncomingInstantMessage += + OnIncomingInstantMessage; } public void RemoveRegion(Scene scene) @@ -98,6 +118,47 @@ namespace OpenSim.Region.CoreModules.Avatar.Gods client.OnRequestGodlikePowers -= RequestGodlikePowers; } + private void OnRegisterCaps(UUID agentID, Caps caps) + { + string uri = "/CAPS/" + UUID.Random(); + + caps.RegisterHandler( + "UntrustedSimulatorMessage", + new RestStreamHandler("POST", uri, HandleUntrustedSimulatorMessage, "UntrustedSimulatorMessage", null)); + } + + private string HandleUntrustedSimulatorMessage(string request, + string path, string param, IOSHttpRequest httpRequest, + IOSHttpResponse httpResponse) + { + OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request); + + string message = osd["message"].AsString(); + + if (message == "GodKickUser") + { + OSDMap body = (OSDMap)osd["body"]; + OSDArray userInfo = (OSDArray)body["UserInfo"]; + OSDMap userData = (OSDMap)userInfo[0]; + + UUID agentID = userData["AgentID"].AsUUID(); + UUID godID = userData["GodID"].AsUUID(); + UUID godSessionID = userData["GodSessionID"].AsUUID(); + uint kickFlags = userData["KickFlags"].AsUInteger(); + string reason = userData["Reason"].AsString(); + ScenePresence god = m_scene.GetScenePresence(godID); + if (god == null || god.ControllingClient.SessionId != godSessionID) + return String.Empty; + + KickUser(godID, godSessionID, agentID, kickFlags, Util.StringToBytes1024(reason)); + } + else + { + m_log.ErrorFormat("[GOD]: Unhandled UntrustedSimulatorMessage: {0}", message); + } + return String.Empty; + } + public void RequestGodlikePowers( UUID agentID, UUID sessionID, UUID token, bool godLike, IClientAPI controllingClient) { @@ -146,76 +207,85 @@ namespace OpenSim.Region.CoreModules.Avatar.Gods /// The message to send to the user after it's been turned into a field public void KickUser(UUID godID, UUID sessionID, UUID agentID, uint kickflags, byte[] reason) { - UUID kickUserID = ALL_AGENTS; - + if (!m_scene.Permissions.IsGod(godID)) + return; + ScenePresence sp = m_scene.GetScenePresence(agentID); - if (sp != null || agentID == kickUserID) + if (sp == null && agentID != ALL_AGENTS) { - if (m_scene.Permissions.IsGod(godID)) + IMessageTransferModule transferModule = + m_scene.RequestModuleInterface(); + if (transferModule != null) { - if (kickflags == 0) - { - if (agentID == kickUserID) - { - string reasonStr = Utils.BytesToString(reason); - - m_scene.ForEachClient( - delegate(IClientAPI controller) - { - if (controller.AgentId != godID) - controller.Kick(reasonStr); - } - ); - - // This is a bit crude. It seems the client will be null before it actually stops the thread - // The thread will kill itself eventually :/ - // Is there another way to make sure *all* clients get this 'inter region' message? - m_scene.ForEachRootClient( - delegate(IClientAPI client) - { - if (client.AgentId != godID) - { - client.Close(); - } - } - ); - } - else - { - m_scene.SceneGraph.removeUserCount(!sp.IsChildAgent); + m_log.DebugFormat("[GODS]: Sending nonlocal kill for agent {0}", agentID); + transferModule.SendInstantMessage(new GridInstantMessage( + m_scene, godID, "God", agentID, (byte)250, false, + Utils.BytesToString(reason), UUID.Zero, true, + new Vector3(), new byte[] {(byte)kickflags}, true), + delegate(bool success) {} ); + } + return; + } - sp.ControllingClient.Kick(Utils.BytesToString(reason)); - sp.ControllingClient.Close(); - } - } - - if (kickflags == 1) - { - sp.AllowMovement = false; - if (DialogModule != null) - { - DialogModule.SendAlertToUser(agentID, Utils.BytesToString(reason)); - DialogModule.SendAlertToUser(godID, "User Frozen"); - } - } - - if (kickflags == 2) - { - sp.AllowMovement = true; - if (DialogModule != null) - { - DialogModule.SendAlertToUser(agentID, Utils.BytesToString(reason)); - DialogModule.SendAlertToUser(godID, "User Unfrozen"); - } - } + switch (kickflags) + { + case 0: + if (sp != null) + { + KickPresence(sp, Utils.BytesToString(reason)); } - else + else if (agentID == ALL_AGENTS) { - if (DialogModule != null) - DialogModule.SendAlertToUser(godID, "Kick request denied"); + m_scene.ForEachRootScenePresence( + delegate(ScenePresence p) + { + if (p.UUID != godID && (!m_scene.Permissions.IsGod(p.UUID))) + KickPresence(p, Utils.BytesToString(reason)); + } + ); } + break; + case 1: + if (sp != null) + { + sp.AllowMovement = false; + m_dialogModule.SendAlertToUser(agentID, Utils.BytesToString(reason)); + m_dialogModule.SendAlertToUser(godID, "User Frozen"); + } + break; + case 2: + if (sp != null) + { + sp.AllowMovement = true; + m_dialogModule.SendAlertToUser(agentID, Utils.BytesToString(reason)); + m_dialogModule.SendAlertToUser(godID, "User Unfrozen"); + } + break; + default: + break; + } + } + + private void KickPresence(ScenePresence sp, string reason) + { + if (sp.IsChildAgent) + return; + sp.ControllingClient.Kick(reason); + sp.Scene.CloseAgent(sp.UUID, true); + } + + private void OnIncomingInstantMessage(GridInstantMessage msg) + { + if (msg.dialog == (uint)250) // Nonlocal kick + { + UUID agentID = new UUID(msg.toAgentID); + string reason = msg.message; + UUID godID = new UUID(msg.fromAgentID); + uint kickMode = (uint)msg.binaryBucket[0]; + + KickUser(godID, UUID.Zero, agentID, kickMode, Util.StringToBytes1024(reason)); } } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs index 7bf19c2..a1b918a 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs @@ -210,10 +210,10 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage success = m_IMService.OutgoingInstantMessage(im, url, foreigner); if (!success && !foreigner) - HandleUndeliveredMessage(im, result); + HandleUndeliverableMessage(im, result); else result(success); - }); + }, null, "HGMessageTransferModule.SendInstantMessage"); return; } @@ -246,7 +246,7 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage return successful; } - protected void HandleUndeliveredMessage(GridInstantMessage im, MessageResultNotification result) + public void HandleUndeliverableMessage(GridInstantMessage im, MessageResultNotification result) { UndeliveredMessage handlerUndeliveredMessage = OnUndeliveredMessage; @@ -282,7 +282,17 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage string uasURL = circuit.ServiceURLs["HomeURI"].ToString(); m_log.DebugFormat("[HG MESSAGE TRANSFER]: getting UUI of user {0} from {1}", toAgent, uasURL); UserAgentServiceConnector uasConn = new UserAgentServiceConnector(uasURL); - return uasConn.GetUUI(fromAgent, toAgent); + + string agentUUI = string.Empty; + try + { + agentUUI = uasConn.GetUUI(fromAgent, toAgent); + } + catch (Exception e) { + m_log.Debug("[HG MESSAGE TRANSFER]: GetUUI call failed ", e); + } + + return agentUUI; } } } diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs index fa935cd..2462ff8 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs @@ -181,7 +181,7 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage SendGridInstantMessageViaXMLRPC(im, result); } - private void HandleUndeliveredMessage(GridInstantMessage im, MessageResultNotification result) + public void HandleUndeliverableMessage(GridInstantMessage im, MessageResultNotification result) { UndeliveredMessage handlerUndeliveredMessage = OnUndeliveredMessage; @@ -372,7 +372,7 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage gim.fromAgentName = fromAgentName; gim.fromGroup = fromGroup; gim.imSessionID = imSessionID.Guid; - gim.RegionID = UUID.Zero.Guid; // RegionID.Guid; + gim.RegionID = RegionID.Guid; gim.timestamp = timestamp; gim.toAgentID = toAgentID.Guid; gim.message = message; @@ -428,7 +428,7 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage /// /// delegate for sending a grid instant message asynchronously /// - public delegate void GridInstantMessageDelegate(GridInstantMessage im, MessageResultNotification result, UUID prevRegionID); + public delegate void GridInstantMessageDelegate(GridInstantMessage im, MessageResultNotification result); protected virtual void GridInstantMessageCompleted(IAsyncResult iar) { @@ -442,138 +442,87 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage { GridInstantMessageDelegate d = SendGridInstantMessageViaXMLRPCAsync; - d.BeginInvoke(im, result, UUID.Zero, GridInstantMessageCompleted, d); + d.BeginInvoke(im, result, GridInstantMessageCompleted, d); } /// - /// Recursive SendGridInstantMessage over XMLRPC method. - /// This is called from within a dedicated thread. - /// The first time this is called, prevRegionHandle will be 0 Subsequent times this is called from - /// itself, prevRegionHandle will be the last region handle that we tried to send. - /// If the handles are the same, we look up the user's location using the grid. - /// If the handles are still the same, we end. The send failed. + /// Internal SendGridInstantMessage over XMLRPC method. /// - /// - /// Pass in 0 the first time this method is called. It will be called recursively with the last - /// regionhandle tried - /// - protected virtual void SendGridInstantMessageViaXMLRPCAsync(GridInstantMessage im, MessageResultNotification result, UUID prevRegionID) + /// + /// This is called from within a dedicated thread. + /// + private void SendGridInstantMessageViaXMLRPCAsync(GridInstantMessage im, MessageResultNotification result) { UUID toAgentID = new UUID(im.toAgentID); - - PresenceInfo upd = null; - - bool lookupAgent = false; + UUID regionID; + bool needToLookupAgent; lock (m_UserRegionMap) + needToLookupAgent = !m_UserRegionMap.TryGetValue(toAgentID, out regionID); + + while (true) { - if (m_UserRegionMap.ContainsKey(toAgentID)) + if (needToLookupAgent) { - upd = new PresenceInfo(); - upd.RegionID = m_UserRegionMap[toAgentID]; + PresenceInfo[] presences = PresenceService.GetAgents(new string[] { toAgentID.ToString() }); - // We need to compare the current regionhandle with the previous region handle - // or the recursive loop will never end because it will never try to lookup the agent again - if (prevRegionID == upd.RegionID) - { - lookupAgent = true; - } - } - else - { - lookupAgent = true; - } - } - + UUID foundRegionID = UUID.Zero; - // Are we needing to look-up an agent? - if (lookupAgent) - { - // Non-cached user agent lookup. - PresenceInfo[] presences = PresenceService.GetAgents(new string[] { toAgentID.ToString() }); - if (presences != null && presences.Length > 0) - { - foreach (PresenceInfo p in presences) + if (presences != null) { - if (p.RegionID != UUID.Zero) + foreach (PresenceInfo p in presences) { - upd = p; - break; + if (p.RegionID != UUID.Zero) + { + foundRegionID = p.RegionID; + break; + } } } - } - if (upd != null) - { - // check if we've tried this before.. - // This is one way to end the recursive loop - // - if (upd.RegionID == prevRegionID) - { - // m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); - HandleUndeliveredMessage(im, result); - return; - } + // If not found or the found region is the same as the last lookup, then message is undeliverable + if (foundRegionID == UUID.Zero || foundRegionID == regionID) + break; + else + regionID = foundRegionID; } - else + + GridRegion reginfo = m_Scenes[0].GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, regionID); + if (reginfo == null) { - // m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); - HandleUndeliveredMessage(im, result); - return; + m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find region {0}", regionID); + break; } - } - if (upd != null) - { - GridRegion reginfo = m_Scenes[0].GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, - upd.RegionID); - if (reginfo != null) + // Try to send the message to the agent via the retrieved region. + Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(im); + msgdata["region_handle"] = 0; + bool imresult = doIMSending(reginfo, msgdata); + + // If the message delivery was successful, then cache the entry. + if (imresult) { - Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(im); - // Not actually used anymore, left in for compatibility - // Remove at next interface change - // - msgdata["region_handle"] = 0; - bool imresult = doIMSending(reginfo, msgdata); - if (imresult) - { - // IM delivery successful, so store the Agent's location in our local cache. - lock (m_UserRegionMap) - { - if (m_UserRegionMap.ContainsKey(toAgentID)) - { - m_UserRegionMap[toAgentID] = upd.RegionID; - } - else - { - m_UserRegionMap.Add(toAgentID, upd.RegionID); - } - } - result(true); - } - else + lock (m_UserRegionMap) { - // try again, but lookup user this time. - // Warning, this must call the Async version - // of this method or we'll be making thousands of threads - // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync - // The version that spawns the thread is SendGridInstantMessageViaXMLRPC - - // This is recursive!!!!! - SendGridInstantMessageViaXMLRPCAsync(im, result, - upd.RegionID); + m_UserRegionMap[toAgentID] = regionID; } + result(true); + return; } - else - { - m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find region {0}", upd.RegionID); - HandleUndeliveredMessage(im, result); - } - } - else - { - HandleUndeliveredMessage(im, result); + + // If we reach this point in the first iteration of the while, then we may have unsuccessfully tried + // to use a locally cached region ID. All subsequent attempts need to lookup agent details from + // the presence service. + needToLookupAgent = true; } + + // If we reached this point then the message was not deliverable. Remove the bad cache entry and + // signal the delivery failure. + lock (m_UserRegionMap) + m_UserRegionMap.Remove(toAgentID); + + // m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); + HandleUndeliverableMessage(im, result); } /// @@ -584,7 +533,6 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage /// Bool if the message was successfully delivered at the other side. protected virtual bool doIMSending(GridRegion reginfo, Hashtable xmlrpcdata) { - ArrayList SendParams = new ArrayList(); SendParams.Add(xmlrpcdata); XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams); @@ -672,7 +620,7 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage gim["position_x"] = msg.Position.X.ToString(); gim["position_y"] = msg.Position.Y.ToString(); gim["position_z"] = msg.Position.Z.ToString(); - gim["region_id"] = msg.RegionID.ToString(); + gim["region_id"] = new UUID(msg.RegionID).ToString(); gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket,Base64FormattingOptions.None); return gim; } diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MuteListModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MuteListModule.cs index 7ce2813..315d372 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MuteListModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MuteListModule.cs @@ -32,7 +32,6 @@ using Nini.Config; using Mono.Addins; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Framework.Client; using OpenSim.Region.Framework.Interfaces; diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs index 7d763fa..9cdb1c2 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs @@ -32,7 +32,6 @@ using Mono.Addins; using Nini.Config; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Framework.Client; using OpenSim.Region.Framework.Interfaces; @@ -182,7 +181,10 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage "POST", m_RestURL + "/RetrieveMessages/", client.AgentId); if (msglist == null) + { m_log.WarnFormat("[OFFLINE MESSAGING]: WARNING null message list."); + return; + } foreach (GridInstantMessage im in msglist) { @@ -223,12 +225,8 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage return; } - Scene scene = FindScene(new UUID(im.fromAgentID)); - if (scene == null) - scene = m_SceneList[0]; - bool success = SynchronousRestObjectRequester.MakeRequest( - "POST", m_RestURL+"/SaveMessage/", im); + "POST", m_RestURL+"/SaveMessage/", im, 10000); if (im.dialog == (byte)InstantMessageDialog.MessageFromAgent) { diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs index 4c678c2..c2440d8 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs @@ -49,8 +49,10 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType); +#pragma warning disable 0067 public event PresenceChange OnPresenceChange; public event BulkPresenceData OnBulkPresenceData; +#pragma warning restore 0067 protected List m_Scenes = new List(); diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs index ecbd07f..4a06fd1 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs @@ -61,16 +61,22 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver private UserAccount m_userInfo; private string m_invPath; + + /// + /// ID of this request + /// + protected UUID m_id; /// /// Do we want to merge this load with existing inventory? /// protected bool m_merge; - /// - /// We only use this to request modules - /// - protected Scene m_scene; + protected IInventoryService m_InventoryService; + protected IAssetService m_AssetService; + protected IUserAccountService m_UserAccountService; + + private InventoryArchiverModule m_module; /// /// The stream from which the inventory archive will be loaded. @@ -115,12 +121,29 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// Record the creator id that should be associated with an asset. This is used to adjust asset creator ids /// after OSP resolution (since OSP creators are only stored in the item /// - protected Dictionary m_creatorIdForAssetId = new Dictionary(); + protected Dictionary m_creatorIdForAssetId = new Dictionary(); + + public InventoryArchiveReadRequest( + IInventoryService inv, IAssetService assets, IUserAccountService uacc, UserAccount userInfo, string invPath, string loadPath, bool merge) + : this(UUID.Zero, null, + inv, + assets, + uacc, + userInfo, + invPath, + loadPath, + merge) + { + } public InventoryArchiveReadRequest( - Scene scene, UserAccount userInfo, string invPath, string loadPath, bool merge) + UUID id, InventoryArchiverModule module, IInventoryService inv, IAssetService assets, IUserAccountService uacc, UserAccount userInfo, string invPath, string loadPath, bool merge) : this( - scene, + id, + module, + inv, + assets, + uacc, userInfo, invPath, new GZipStream(ArchiveHelpers.GetStream(loadPath), CompressionMode.Decompress), @@ -129,13 +152,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public InventoryArchiveReadRequest( - Scene scene, UserAccount userInfo, string invPath, Stream loadStream, bool merge) + UUID id, InventoryArchiverModule module, IInventoryService inv, IAssetService assets, IUserAccountService uacc, UserAccount userInfo, string invPath, Stream loadStream, bool merge) { - m_scene = scene; + m_id = id; + m_InventoryService = inv; + m_AssetService = assets; + m_UserAccountService = uacc; m_merge = merge; m_userInfo = userInfo; m_invPath = invPath; m_loadStream = loadStream; + m_module = module; // FIXME: Do not perform this check since older versions of OpenSim do save the control file after other things // (I thought they weren't). We will need to bump the version number and perform this check on all @@ -158,11 +185,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { try { + Exception reportedException = null; + string filePath = "ERROR"; List folderCandidates - = InventoryArchiveUtils.FindFolderByPath( - m_scene.InventoryService, m_userInfo.PrincipalID, m_invPath); + = InventoryArchiveUtils.FindFoldersByPath( + m_InventoryService, m_userInfo.PrincipalID, m_invPath); if (folderCandidates.Count == 0) { @@ -194,14 +223,25 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } archive.Close(); - + m_log.DebugFormat( "[INVENTORY ARCHIVER]: Successfully loaded {0} assets with {1} failures", m_successfulAssetRestores, m_failedAssetRestores); - m_log.InfoFormat("[INVENTORY ARCHIVER]: Successfully loaded {0} items", m_successfulItemRestores); + + //Alicia: When this is called by LibraryModule or Tests, m_module will be null as event is not required + if(m_module != null) + m_module.TriggerInventoryArchiveLoaded(m_id, true, m_userInfo, m_invPath, m_loadStream, reportedException, m_successfulItemRestores); return m_loadedNodes; } + catch(Exception Ex) + { + // Trigger saved event with failed result and exception data + if (m_module != null) + m_module.TriggerInventoryArchiveLoaded(m_id, false, m_userInfo, m_invPath, m_loadStream, Ex, 0); + + return m_loadedNodes; + } finally { m_loadStream.Close(); @@ -296,8 +336,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // iar name and try to find that instead. string plainPath = ArchiveConstants.ExtractPlainPathFromIarPath(archivePath); List folderCandidates - = InventoryArchiveUtils.FindFolderByPath( - m_scene.InventoryService, m_userInfo.PrincipalID, plainPath); + = InventoryArchiveUtils.FindFoldersByPath( + m_InventoryService, m_userInfo.PrincipalID, plainPath); if (folderCandidates.Count != 0) { @@ -372,15 +412,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver newFolderName = InventoryArchiveUtils.UnescapeArchivePath(newFolderName); UUID newFolderId = UUID.Random(); - // Asset type has to be Unknown here rather than Folder, otherwise the created folder can't be - // deleted once the client has relogged. - // The root folder appears to be labelled AssetType.Folder (shows up as "Category" in the client) - // even though there is a AssetType.RootCategory destFolder = new InventoryFolderBase( - newFolderId, newFolderName, m_userInfo.PrincipalID, - (short)AssetType.Unknown, destFolder.ID, 1); - m_scene.InventoryService.AddFolder(destFolder); + newFolderId, newFolderName, m_userInfo.PrincipalID, + (short)FolderType.None, destFolder.ID, 1); + m_InventoryService.AddFolder(destFolder); // Record that we have now created this folder iarPathExisting += rawDirsToCreate[i] + "/"; @@ -406,7 +442,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // Don't use the item ID that's in the file item.ID = UUID.Random(); - UUID ospResolvedId = OspResolver.ResolveOspa(item.CreatorId, m_scene.UserAccountService); + UUID ospResolvedId = OspResolver.ResolveOspa(item.CreatorId, m_UserAccountService); if (UUID.Zero != ospResolvedId) // The user exists in this grid { // m_log.DebugFormat("[INVENTORY ARCHIVER]: Found creator {0} via OSPA resolution", ospResolvedId); @@ -418,7 +454,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver item.CreatorId = ospResolvedId.ToString(); item.CreatorData = string.Empty; } - else if (item.CreatorData == null || item.CreatorData == String.Empty) + else if (string.IsNullOrEmpty(item.CreatorData)) { item.CreatorId = m_userInfo.PrincipalID.ToString(); // item.CreatorIdAsUuid = new UUID(item.CreatorId); @@ -436,7 +472,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // relying on native tar tools. m_creatorIdForAssetId[item.AssetID] = item.CreatorIdAsUuid; - m_scene.AddInventoryItem(item); + if (!m_InventoryService.AddItem(item)) + m_log.WarnFormat("[INVENTORY ARCHIVER]: Unable to save item {0} in folder {1}", item.Name, item.Folder); return item; } @@ -479,52 +516,24 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { if (m_creatorIdForAssetId.ContainsKey(assetId)) { - string xmlData = Utils.BytesToString(data); - List sceneObjects = new List(); + data = SceneObjectSerializer.ModifySerializedObject(assetId, data, + sog => { + bool modified = false; + + foreach (SceneObjectPart sop in sog.Parts) + { + if (string.IsNullOrEmpty(sop.CreatorData)) + { + sop.CreatorID = m_creatorIdForAssetId[assetId]; + modified = true; + } + } + + return modified; + }); - CoalescedSceneObjects coa = null; - if (CoalescedSceneObjectsSerializer.TryFromXml(xmlData, out coa)) - { -// m_log.DebugFormat( -// "[INVENTORY ARCHIVER]: Loaded coalescence {0} has {1} objects", assetId, coa.Count); - - if (coa.Objects.Count == 0) - { - m_log.WarnFormat( - "[INVENTORY ARCHIVE READ REQUEST]: Aborting load of coalesced object from asset {0} as it has zero loaded components", - assetId); - return false; - } - - sceneObjects.AddRange(coa.Objects); - } - else - { - SceneObjectGroup deserializedObject = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - - if (deserializedObject != null) - { - sceneObjects.Add(deserializedObject); - } - else - { - m_log.WarnFormat( - "[INVENTORY ARCHIVE READ REQUEST]: Aborting load of object from asset {0} as deserialization failed", - assetId); - - return false; - } - } - - foreach (SceneObjectGroup sog in sceneObjects) - foreach (SceneObjectPart sop in sog.Parts) - if (sop.CreatorData == null || sop.CreatorData == "") - sop.CreatorID = m_creatorIdForAssetId[assetId]; - - if (coa != null) - data = Utils.StringToBytes(CoalescedSceneObjectsSerializer.ToXml(coa)); - else - data = Utils.StringToBytes(SceneObjectSerializer.ToOriginalXmlFormat(sceneObjects[0])); + if (data == null) + return false; } } @@ -533,7 +542,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver AssetBase asset = new AssetBase(assetId, "From IAR", assetType, UUID.Zero.ToString()); asset.Data = data; - m_scene.AssetService.Store(asset); + m_AssetService.Store(asset); return true; } @@ -546,7 +555,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver return false; } } - + /// /// Load control file /// @@ -652,4 +661,4 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver m_assetsLoaded = true; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveUtils.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveUtils.cs index 0d90a15..dbaf2aa 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveUtils.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveUtils.cs @@ -52,13 +52,82 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// /// Find a folder given a PATH_DELIMITER delimited path starting from a user's root folder /// + /// + /// This method does not handle paths that contain multiple delimitors + /// + /// FIXME: We have no way of distinguishing folders with the same path /// + /// FIXME: Delimitors which occur in names themselves are not currently escapable. + /// + /// + /// Inventory service to query + /// + /// + /// User id to search + /// + /// + /// The path to the required folder. + /// It this is empty or consists only of the PATH_DELIMTER then this folder itself is returned. + /// + /// The folder found. Please note that if there are multiple folders with the same name then an + /// unspecified one will be returned. If no such folder eixsts then null is returned + public static InventoryFolderBase FindFolderByPath( + IInventoryService inventoryService, UUID userId, string path) + { + List folders = FindFoldersByPath(inventoryService, userId, path); + + if (folders.Count == 0) + return null; + else + return folders[0]; + } + + /// + /// Find a folder given a PATH_DELIMITER delimited path starting from a given folder + /// + /// /// This method does not handle paths that contain multiple delimitors /// /// FIXME: We have no way of distinguishing folders with the same path /// /// FIXME: Delimitors which occur in names themselves are not currently escapable. + /// + /// + /// Inventory service to query + /// + /// + /// The folder from which the path starts + /// + /// + /// The path to the required folder. + /// It this is empty or consists only of the PATH_DELIMTER then this folder itself is returned. + /// + /// The folder found. Please note that if there are multiple folders with the same name then an + /// unspecified one will be returned. If no such folder eixsts then null is returned + public static InventoryFolderBase FindFolderByPath( + IInventoryService inventoryService, InventoryFolderBase startFolder, string path) + { + if (null == startFolder) + return null; + + List folders = FindFoldersByPath(inventoryService, startFolder, path); + + if (folders.Count == 0) + return null; + else + return folders[0]; + } + + /// + /// Find a set of folders given a PATH_DELIMITER delimited path starting from a user's root folder + /// + /// + /// This method does not handle paths that contain multiple delimitors + /// + /// FIXME: We have no way of distinguishing folders with the same path /// + /// FIXME: Delimitors which occur in names themselves are not currently escapable. + /// /// /// Inventory service to query /// @@ -70,7 +139,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// It this is empty or consists only of the PATH_DELIMTER then this folder itself is returned. /// /// An empty list if the folder is not found, otherwise a list of all folders that match the name - public static List FindFolderByPath( + public static List FindFoldersByPath( IInventoryService inventoryService, UUID userId, string path) { InventoryFolderBase rootFolder = inventoryService.GetRootFolder(userId); @@ -78,19 +147,19 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (null == rootFolder) return new List(); - return FindFolderByPath(inventoryService, rootFolder, path); + return FindFoldersByPath(inventoryService, rootFolder, path); } /// - /// Find a folder given a PATH_DELIMITER delimited path starting from this folder + /// Find a set of folders given a PATH_DELIMITER delimited path starting from this folder /// - /// + /// /// This method does not handle paths that contain multiple delimitors /// /// FIXME: We have no way of distinguishing folders with the same path. /// /// FIXME: Delimitors which occur in names themselves are not currently escapable. - /// + /// /// /// Inventory service to query /// @@ -102,7 +171,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// It this is empty or consists only of the PATH_DELIMTER then this folder itself is returned. /// /// An empty list if the folder is not found, otherwise a list of all folders that match the name - public static List FindFolderByPath( + public static List FindFoldersByPath( IInventoryService inventoryService, InventoryFolderBase startFolder, string path) { List foundFolders = new List(); @@ -133,12 +202,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver InventoryCollection contents = inventoryService.GetFolderContent(startFolder.Owner, startFolder.ID); +// m_log.DebugFormat( +// "Found {0} folders in {1} for {2}", contents.Folders.Count, startFolder.Name, startFolder.Owner); + foreach (InventoryFolderBase folder in contents.Folders) { if (folder.Name == components[0]) { if (components.Length > 1) - foundFolders.AddRange(FindFolderByPath(inventoryService, folder, components[1])); + foundFolders.AddRange(FindFoldersByPath(inventoryService, folder, components[1])); else foundFolders.Add(folder); } diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs index d0e88f6..f002ad7 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs @@ -34,6 +34,7 @@ using System.Xml; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Serialization; using OpenSim.Framework.Serialization.External; using OpenSim.Region.CoreModules.World.Archiver; @@ -42,6 +43,8 @@ using OpenSim.Services.Interfaces; using Ionic.Zlib; using GZipStream = Ionic.Zlib.GZipStream; using CompressionMode = Ionic.Zlib.CompressionMode; +using CompressionLevel = Ionic.Zlib.CompressionLevel; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { @@ -54,6 +57,22 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// public bool SaveAssets { get; set; } + /// + /// Determines which items will be included in the archive, according to their permissions. + /// Default is null, meaning no permission checks. + /// + public string FilterContent { get; set; } + + /// + /// Counter for inventory items saved to archive for passing to compltion event + /// + public int CountItems { get; set; } + + /// + /// Counter for inventory items skipped due to permission filter option for passing to compltion event + /// + public int CountFiltered { get; set; } + /// /// Used to select all inventory nodes in a folder but not the folder itself /// @@ -73,12 +92,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// /// ID of this request /// - protected Guid m_id; - - /// - /// Used to collect the uuids of the assets that we need to save into the archive - /// - protected Dictionary m_assetUuids = new Dictionary(); + protected UUID m_id; /// /// Used to collect the uuids of the users that we need to save into the archive @@ -94,7 +108,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// Constructor /// public InventoryArchiveWriteRequest( - Guid id, InventoryArchiverModule module, Scene scene, + UUID id, InventoryArchiverModule module, Scene scene, UserAccount userInfo, string invPath, string savePath) : this( id, @@ -110,7 +124,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// Constructor /// public InventoryArchiveWriteRequest( - Guid id, InventoryArchiverModule module, Scene scene, + UUID id, InventoryArchiverModule module, Scene scene, UserAccount userInfo, string invPath, Stream saveStream) { m_id = id; @@ -122,9 +136,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver m_assetGatherer = new UuidGatherer(m_scene.AssetService); SaveAssets = true; + FilterContent = null; } - protected void ReceivedAllAssets(ICollection assetsFoundUuids, ICollection assetsNotFoundUuids) + protected void ReceivedAllAssets(ICollection assetsFoundUuids, ICollection assetsNotFoundUuids, bool timedOut) { Exception reportedException = null; bool succeeded = true; @@ -143,8 +158,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver m_saveStream.Close(); } + if (timedOut) + { + succeeded = false; + reportedException = new Exception("Loading assets timed out"); + } + m_module.TriggerInventoryArchiveSaved( - m_id, succeeded, m_userInfo, m_invPath, m_saveStream, reportedException); + m_id, succeeded, m_userInfo, m_invPath, m_saveStream, reportedException, CountItems, CountFiltered); } protected void SaveInvItem(InventoryItemBase inventoryItem, string path, Dictionary options, IUserAccountService userAccountService) @@ -160,10 +181,26 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver "[INVENTORY ARCHIVER]: Skipping inventory item {0} {1} at {2}", inventoryItem.Name, inventoryItem.ID, path); } + + CountFiltered++; + return; } } + // Check For Permissions Filter Flags + if (!CanUserArchiveObject(m_userInfo.PrincipalID, inventoryItem)) + { + m_log.InfoFormat( + "[INVENTORY ARCHIVER]: Insufficient permissions, skipping inventory item {0} {1} at {2}", + inventoryItem.Name, inventoryItem.ID, path); + + // Count Items Excluded + CountFiltered++; + + return; + } + if (options.ContainsKey("verbose")) m_log.InfoFormat( "[INVENTORY ARCHIVER]: Saving item {0} {1} (asset UUID {2})", @@ -179,9 +216,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver AssetType itemAssetType = (AssetType)inventoryItem.AssetType; + // Count inventory items (different to asset count) + CountItems++; + // Don't chase down link asset items as they actually point to their target item IDs rather than an asset if (SaveAssets && itemAssetType != AssetType.Link && itemAssetType != AssetType.LinkFolder) - m_assetGatherer.GatherAssetUuids(inventoryItem.AssetID, (AssetType)inventoryItem.AssetType, m_assetUuids); + m_assetGatherer.AddForInspection(inventoryItem.AssetID); } /// @@ -237,6 +277,35 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } /// + /// Checks whether the user has permission to export an inventory item to an IAR. + /// + /// The user + /// The inventory item + /// Whether the user is allowed to export the object to an IAR + private bool CanUserArchiveObject(UUID UserID, InventoryItemBase InvItem) + { + if (FilterContent == null) + return true;// Default To Allow Export + + bool permitted = true; + + bool canCopy = (InvItem.CurrentPermissions & (uint)PermissionMask.Copy) != 0; + bool canTransfer = (InvItem.CurrentPermissions & (uint)PermissionMask.Transfer) != 0; + bool canMod = (InvItem.CurrentPermissions & (uint)PermissionMask.Modify) != 0; + + if (FilterContent.Contains("C") && !canCopy) + permitted = false; + + if (FilterContent.Contains("T") && !canTransfer) + permitted = false; + + if (FilterContent.Contains("M") && !canMod) + permitted = false; + + return permitted; + } + + /// /// Execute the inventory write request /// public void Execute(Dictionary options, IUserAccountService userAccountService) @@ -244,6 +313,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (options.ContainsKey("noassets") && (bool)options["noassets"]) SaveAssets = false; + // Set Permission filter if flag is set + if (options.ContainsKey("checkPermissions")) + { + Object temp; + if (options.TryGetValue("checkPermissions", out temp)) + FilterContent = temp.ToString().ToUpper(); + } + try { InventoryFolderBase inventoryFolder = null; @@ -266,6 +343,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver saveFolderContentsOnly = true; maxComponentIndex--; } + else if (maxComponentIndex == -1) + { + // If the user has just specified "/", then don't save the root "My Inventory" folder. This is + // more intuitive then requiring the user to specify "/*" for this. + saveFolderContentsOnly = true; + } m_invPath = String.Empty; for (int i = 0; i <= maxComponentIndex; i++) @@ -283,7 +366,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { m_invPath = m_invPath.Remove(m_invPath.LastIndexOf(InventoryFolderImpl.PATH_DELIMITER)); List candidateFolders - = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, rootFolder, m_invPath); + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, rootFolder, m_invPath); if (candidateFolders.Count > 0) inventoryFolder = candidateFolders[0]; } @@ -297,7 +380,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // We couldn't find the path indicated string errorMessage = string.Format("Aborted save. Could not find inventory path {0}", m_invPath); Exception e = new InventoryArchiverException(errorMessage); - m_module.TriggerInventoryArchiveSaved(m_id, false, m_userInfo, m_invPath, m_saveStream, e); + m_module.TriggerInventoryArchiveSaved(m_id, false, m_userInfo, m_invPath, m_saveStream, e, 0, 0); throw e; } @@ -335,22 +418,25 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver if (SaveAssets) { - m_log.DebugFormat("[INVENTORY ARCHIVER]: Saving {0} assets for items", m_assetUuids.Count); + m_assetGatherer.GatherAll(); + + m_log.DebugFormat( + "[INVENTORY ARCHIVER]: Saving {0} assets for items", m_assetGatherer.GatheredUuids.Count); AssetsRequest ar = new AssetsRequest( new AssetsArchiver(m_archiveWriter), - m_assetUuids, m_scene.AssetService, + m_assetGatherer.GatheredUuids, m_scene.AssetService, m_scene.UserAccountService, m_scene.RegionInfo.ScopeID, options, ReceivedAllAssets); - Util.FireAndForget(o => ar.Execute()); + WorkManager.RunInThread(o => ar.Execute(), null, string.Format("AssetsRequest ({0})", m_scene.Name)); } else { m_log.DebugFormat("[INVENTORY ARCHIVER]: Not saving assets since --noassets was specified"); - ReceivedAllAssets(new List(), new List()); + ReceivedAllAssets(new List(), new List(), false); } } catch (Exception) diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs index 849449b..8847414 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiverModule.cs @@ -34,7 +34,6 @@ using NDesk.Options; using Nini.Config; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Console; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -57,6 +56,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // public bool DisablePresenceChecks { get; set; } public event InventoryArchiveSaved OnInventoryArchiveSaved; + public event InventoryArchiveLoaded OnInventoryArchiveLoaded; /// /// The file to load and save inventory if no filename has been specified @@ -64,9 +64,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver protected const string DEFAULT_INV_BACKUP_FILENAME = "user-inventory.iar"; /// - /// Pending save completions initiated from the console + /// Pending save and load completions initiated from the console /// - protected List m_pendingConsoleSaves = new List(); + protected List m_pendingConsoleTasks = new List(); /// /// All scenes that this module knows about @@ -111,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { scene.RegisterModuleInterface(this); OnInventoryArchiveSaved += SaveInvConsoleCommandCompleted; + OnInventoryArchiveLoaded += LoadInvConsoleCommandCompleted; scene.AddCommand( "Archiving", this, "load iar", @@ -139,7 +140,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver + "-e|--exclude= don't save the inventory item in archive" + Environment.NewLine + "-f|--excludefolder= don't save contents of the folder in archive" + Environment.NewLine + "-v|--verbose extra debug messages.\n" - + "--noassets stops assets being saved to the IAR.", + + "--noassets stops assets being saved to the IAR." + + "--perm= stops items with insufficient permissions from being saved to the IAR.\n" + + " can contain one or more of these characters: \"C\" = Copy, \"T\" = Transfer, \"M\" = Modify.\n", HandleSaveInvConsoleCommand); m_aScene = scene; @@ -175,22 +178,34 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// Trigger the inventory archive saved event. /// protected internal void TriggerInventoryArchiveSaved( - Guid id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, - Exception reportedException) + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, + Exception reportedException, int SaveCount, int FilterCount) { InventoryArchiveSaved handlerInventoryArchiveSaved = OnInventoryArchiveSaved; if (handlerInventoryArchiveSaved != null) - handlerInventoryArchiveSaved(id, succeeded, userInfo, invPath, saveStream, reportedException); + handlerInventoryArchiveSaved(id, succeeded, userInfo, invPath, saveStream, reportedException, SaveCount , FilterCount); + } + + /// + /// Trigger the inventory archive loaded event. + /// + protected internal void TriggerInventoryArchiveLoaded( + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream loadStream, + Exception reportedException, int LoadCount) + { + InventoryArchiveLoaded handlerInventoryArchiveLoaded = OnInventoryArchiveLoaded; + if (handlerInventoryArchiveLoaded != null) + handlerInventoryArchiveLoaded(id, succeeded, userInfo, invPath, loadStream, reportedException, LoadCount); } public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream) + UUID id, string firstName, string lastName, string invPath, string pass, Stream saveStream) { return ArchiveInventory(id, firstName, lastName, invPath, pass, saveStream, new Dictionary()); } public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, Stream saveStream, + UUID id, string firstName, string lastName, string invPath, string pass, Stream saveStream, Dictionary options) { if (m_scenes.Count > 0) @@ -230,7 +245,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public bool ArchiveInventory( - Guid id, string firstName, string lastName, string invPath, string pass, string savePath, + UUID id, string firstName, string lastName, string invPath, string pass, string savePath, Dictionary options) { // if (!ConsoleUtil.CheckFileDoesNotExist(MainConsole.Instance, savePath)) @@ -272,13 +287,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver return false; } - public bool DearchiveInventory(string firstName, string lastName, string invPath, string pass, Stream loadStream) + public bool DearchiveInventory(UUID id, string firstName, string lastName, string invPath, string pass, Stream loadStream) { - return DearchiveInventory(firstName, lastName, invPath, pass, loadStream, new Dictionary()); + return DearchiveInventory(id, firstName, lastName, invPath, pass, loadStream, new Dictionary()); } public bool DearchiveInventory( - string firstName, string lastName, string invPath, string pass, Stream loadStream, + UUID id, string firstName, string lastName, string invPath, string pass, Stream loadStream, Dictionary options) { if (m_scenes.Count > 0) @@ -294,7 +309,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver try { - request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadStream, merge); + request = new InventoryArchiveReadRequest(id, this, m_aScene.InventoryService, m_aScene.AssetService, m_aScene.UserAccountService, userInfo, invPath, loadStream, merge); } catch (EntryPointNotFoundException e) { @@ -326,7 +341,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } public bool DearchiveInventory( - string firstName, string lastName, string invPath, string pass, string loadPath, + UUID id, string firstName, string lastName, string invPath, string pass, string loadPath, Dictionary options) { if (m_scenes.Count > 0) @@ -342,7 +357,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver try { - request = new InventoryArchiveReadRequest(m_aScene, userInfo, invPath, loadPath, merge); + request = new InventoryArchiveReadRequest(id, this, m_aScene.InventoryService, m_aScene.AssetService, m_aScene.UserAccountService, userInfo, invPath, loadPath, merge); } catch (EntryPointNotFoundException e) { @@ -378,6 +393,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver { try { + UUID id = UUID.Random(); + Dictionary options = new Dictionary(); OptionSet optionSet = new OptionSet().Add("m|merge", delegate (string v) { options["merge"] = v != null; }); @@ -400,10 +417,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver "[INVENTORY ARCHIVER]: Loading archive {0} to inventory path {1} for {2} {3}", loadPath, invPath, firstName, lastName); - if (DearchiveInventory(firstName, lastName, invPath, pass, loadPath, options)) - m_log.InfoFormat( - "[INVENTORY ARCHIVER]: Loaded archive {0} for {1} {2}", - loadPath, firstName, lastName); + lock (m_pendingConsoleTasks) + m_pendingConsoleTasks.Add(id); + + DearchiveInventory(id, firstName, lastName, invPath, pass, loadPath, options); } catch (InventoryArchiverException e) { @@ -417,7 +434,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// protected void HandleSaveInvConsoleCommand(string module, string[] cmdparams) { - Guid id = Guid.NewGuid(); + UUID id = UUID.Random(); Dictionary options = new Dictionary(); @@ -439,6 +456,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver options["excludefolders"] = new List(); ((List)options["excludefolders"]).Add(v); }); + ops.Add("perm=", delegate(string v) { options["checkPermissions"] = v; }); List mainParams = ops.Parse(cmdparams); @@ -464,8 +482,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver "[INVENTORY ARCHIVER]: Saving archive {0} using inventory path {1} for {2} {3}", savePath, invPath, firstName, lastName); - lock (m_pendingConsoleSaves) - m_pendingConsoleSaves.Add(id); + lock (m_pendingConsoleTasks) + m_pendingConsoleTasks.Add(id); ArchiveInventory(id, firstName, lastName, invPath, pass, savePath, options); } @@ -476,20 +494,24 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } private void SaveInvConsoleCommandCompleted( - Guid id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, - Exception reportedException) + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, + Exception reportedException, int SaveCount, int FilterCount) { - lock (m_pendingConsoleSaves) + lock (m_pendingConsoleTasks) { - if (m_pendingConsoleSaves.Contains(id)) - m_pendingConsoleSaves.Remove(id); + if (m_pendingConsoleTasks.Contains(id)) + m_pendingConsoleTasks.Remove(id); else return; } if (succeeded) { - m_log.InfoFormat("[INVENTORY ARCHIVER]: Saved archive for {0} {1}", userInfo.FirstName, userInfo.LastName); + // Report success and include item count and filter count (Skipped items due to --perm or --exclude switches) + if(FilterCount == 0) + m_log.InfoFormat("[INVENTORY ARCHIVER]: Saved archive with {0} items for {1} {2}", SaveCount, userInfo.FirstName, userInfo.LastName); + else + m_log.InfoFormat("[INVENTORY ARCHIVER]: Saved archive with {0} items for {1} {2}. Skipped {3} items due to exclude and/or perm switches", SaveCount, userInfo.FirstName, userInfo.LastName, FilterCount); } else { @@ -499,6 +521,30 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } } + private void LoadInvConsoleCommandCompleted( + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream loadStream, + Exception reportedException, int LoadCount) + { + lock (m_pendingConsoleTasks) + { + if (m_pendingConsoleTasks.Contains(id)) + m_pendingConsoleTasks.Remove(id); + else + return; + } + + if (succeeded) + { + m_log.InfoFormat("[INVENTORY ARCHIVER]: Loaded {0} items from archive {1} for {2} {3}", LoadCount, invPath, userInfo.FirstName, userInfo.LastName); + } + else + { + m_log.ErrorFormat( + "[INVENTORY ARCHIVER]: Archive load for {0} {1} failed - {2}", + userInfo.FirstName, userInfo.LastName, reportedException.Message); + } + } + /// /// Get user information for the given name. /// @@ -536,7 +582,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver } catch (Exception e) { - m_log.ErrorFormat("[INVENTORY ARCHIVER]: Could not authenticate password, {0}", e.Message); + m_log.ErrorFormat("[INVENTORY ARCHIVER]: Could not authenticate password, {0}", e); return null; } } diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs new file mode 100644 index 0000000..c2e645f --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs @@ -0,0 +1,360 @@ +/* + * 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 NUnit.Framework; +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Framework.Serialization; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveLoadPathTests : InventoryArchiveTestCase + { + /// + /// Test loading an IAR to various different inventory paths. + /// + [Test] + public void TestLoadIarToInventoryPaths() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SerialiserModule serialiserModule = new SerialiserModule(); + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + + // Annoyingly, we have to set up a scene even though inventory loading has nothing to do with a scene + Scene scene = new SceneHelpers().SetupScene(); + + SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); + + UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "meowfood"); + UserAccountHelpers.CreateUserWithInventory(scene, m_uaLL1, "hampshire"); + + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); + + // Now try loading to a root child folder + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xA", false); + MemoryStream archiveReadStream = new MemoryStream(m_iarStream.ToArray()); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "xA", "meowfood", archiveReadStream); + + InventoryItemBase foundItem2 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xA/" + m_item1Name); + Assert.That(foundItem2, Is.Not.Null, "Didn't find loaded item 2"); + + // Now try loading to a more deeply nested folder + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC", false); + archiveReadStream = new MemoryStream(archiveReadStream.ToArray()); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "xB/xC", "meowfood", archiveReadStream); + + InventoryItemBase foundItem3 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC/" + m_item1Name); + Assert.That(foundItem3, Is.Not.Null, "Didn't find loaded item 3"); + } + + /// + /// Test that things work when the load path specified starts with a slash + /// + [Test] + public void TestLoadIarPathStartsWithSlash() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SerialiserModule serialiserModule = new SerialiserModule(); + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); + + UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "password"); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/Objects", "password", m_iarStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath( + scene.InventoryService, m_uaMT.PrincipalID, "/Objects/" + m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1 in TestLoadIarFolderStartsWithSlash()"); + } + + [Test] + public void TestLoadIarPathWithEscapedChars() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + string itemName = "You & you are a mean/man/"; + string humanEscapedItemName = @"You & you are a mean\/man\/"; + string userPassword = "meowfood"; + + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, archiverModule); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, "meowfood"); + + // Create asset + SceneObjectGroup object1; + SceneObjectPart part1; + { + string partName = "part name"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); + Vector3 groupPosition = new Vector3(10, 20, 30); + Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); + Vector3 offsetPosition = new Vector3(5, 10, 15); + + part1 + = new SceneObjectPart( + ownerId, shape, groupPosition, rotationOffset, offsetPosition); + part1.Name = partName; + + object1 = new SceneObjectGroup(part1); + scene.AddNewSceneObject(object1, false); + } + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = itemName; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // LOAD ITEM + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + archiverModule.DearchiveInventory(UUID.Random(), userFirstName, userLastName, "Scripts", userPassword, archiveReadStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath( + scene.InventoryService, userId, "Scripts/Objects/" + humanEscapedItemName); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); +// Assert.That( +// foundItem1.CreatorId, Is.EqualTo(userUuid), +// "Loaded item non-uuid creator doesn't match that of the loading user"); + Assert.That( + foundItem1.Name, Is.EqualTo(itemName), + "Loaded item name doesn't match saved name"); + } + + /// + /// Test replication of an archive path to the user's inventory. + /// + [Test] + public void TestNewIarPath() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + Dictionary foldersCreated = new Dictionary(); + HashSet nodesLoaded = new HashSet(); + + string folder1Name = "1"; + string folder2aName = "2a"; + string folder2bName = "2b"; + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1Name, UUID.Random()); + string folder2aArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2aName, UUID.Random()); + string folder2bArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2bName, UUID.Random()); + + string iarPath1 = string.Join("", new string[] { folder1ArchiveName, folder2aArchiveName }); + string iarPath2 = string.Join("", new string[] { folder1ArchiveName, folder2bArchiveName }); + + { + // Test replication of path1 + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + iarPath1, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + foldersCreated, nodesLoaded); + + List folder1Candidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); + Assert.That(folder1Candidates.Count, Is.EqualTo(1)); + + InventoryFolderBase folder1 = folder1Candidates[0]; + List folder2aCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2aName); + Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); + } + + { + // Test replication of path2 + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + iarPath2, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + foldersCreated, nodesLoaded); + + List folder1Candidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); + Assert.That(folder1Candidates.Count, Is.EqualTo(1)); + + InventoryFolderBase folder1 = folder1Candidates[0]; + + List folder2aCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2aName); + Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); + + List folder2bCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2bName); + Assert.That(folder2bCandidates.Count, Is.EqualTo(1)); + } + } + + /// + /// Test replication of a partly existing archive path to the user's inventory. This should create + /// a duplicate path without the merge option. + /// + [Test] + public void TestPartExistingIarPath() + { + TestHelpers.InMethod(); + //log4net.Config.XmlConfigurator.Configure(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + string folder1ExistingName = "a"; + string folder2Name = "b"; + + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder( + scene.InventoryService, ua1.PrincipalID, folder1ExistingName, false); + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); + string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); + + string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); + + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + new Dictionary(), new HashSet()); + + List folder1PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + Assert.That(folder1PostCandidates.Count, Is.EqualTo(2)); + + // FIXME: Temporarily, we're going to do something messy to make sure we pick up the created folder. + InventoryFolderBase folder1Post = null; + foreach (InventoryFolderBase folder in folder1PostCandidates) + { + if (folder.ID != folder1.ID) + { + folder1Post = folder; + break; + } + } +// Assert.That(folder1Post.ID, Is.EqualTo(folder1.ID)); + + List folder2PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1Post, "b"); + Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); + } + + /// + /// Test replication of a partly existing archive path to the user's inventory. This should create + /// a merged path. + /// + [Test] + public void TestMergeIarPath() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + string folder1ExistingName = "a"; + string folder2Name = "b"; + + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder( + scene.InventoryService, ua1.PrincipalID, folder1ExistingName, false); + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); + string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); + + string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); + + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, folder1ExistingName, (Stream)null, true) + .ReplicateArchivePathToUserInventory( + itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + new Dictionary(), new HashSet()); + + List folder1PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + Assert.That(folder1PostCandidates.Count, Is.EqualTo(1)); + Assert.That(folder1PostCandidates[0].ID, Is.EqualTo(folder1.ID)); + + List folder2PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1PostCandidates[0], "b"); + Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); + } + } +} + diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs new file mode 100644 index 0000000..57b4f80 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs @@ -0,0 +1,192 @@ +/* + * 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 NUnit.Framework; +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Framework.Serialization; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveLoadTests : InventoryArchiveTestCase + { + protected TestScene m_scene; + protected InventoryArchiverModule m_archiverModule; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + SerialiserModule serialiserModule = new SerialiserModule(); + m_archiverModule = new InventoryArchiverModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, serialiserModule, m_archiverModule); + } + + [Test] + public void TestLoadCoalesecedItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "password"); + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/", "password", m_iarStream); + + InventoryItemBase coaItem + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_coaItemName); + + Assert.That(coaItem, Is.Not.Null, "Didn't find loaded item 1"); + + string assetXml = AssetHelpers.ReadAssetAsString(m_scene.AssetService, coaItem.AssetID); + + CoalescedSceneObjects coa; + bool readResult = CoalescedSceneObjectsSerializer.TryFromXml(assetXml, out coa); + + Assert.That(readResult, Is.True); + Assert.That(coa.Count, Is.EqualTo(2)); + + List coaObjects = coa.Objects; + Assert.That(coaObjects[0].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000120"))); + Assert.That(coaObjects[0].AbsolutePosition, Is.EqualTo(new Vector3(15, 30, 45))); + + Assert.That(coaObjects[1].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000140"))); + Assert.That(coaObjects[1].AbsolutePosition, Is.EqualTo(new Vector3(25, 50, 75))); + } + + /// + /// Test case where a creator account exists for the creator UUID embedded in item metadata and serialized + /// objects. + /// + [Test] + public void TestLoadIarCreatorAccountPresent() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "meowfood"); + + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/", "meowfood", m_iarStream); + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_item1Name); + + Assert.That( + foundItem1.CreatorId, Is.EqualTo(m_uaLL1.PrincipalID.ToString()), + "Loaded item non-uuid creator doesn't match original"); + Assert.That( + foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL1.PrincipalID), + "Loaded item uuid creator doesn't match original"); + Assert.That(foundItem1.Owner, Is.EqualTo(m_uaLL1.PrincipalID), + "Loaded item owner doesn't match inventory reciever"); + + AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); + string xmlData = Utils.BytesToString(asset1.Data); + SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); + + Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL1.PrincipalID)); + } + +// /// +// /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where +// /// an account exists with the same name as the creator, though not the same id. +// /// +// [Test] +// public void TestLoadIarV0_1SameNameCreator() +// { +// TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); +// +// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "meowfood"); +// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL2, "hampshire"); +// +// m_archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); +// InventoryItemBase foundItem1 +// = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); +// +// Assert.That( +// foundItem1.CreatorId, Is.EqualTo(m_uaLL2.PrincipalID.ToString()), +// "Loaded item non-uuid creator doesn't match original"); +// Assert.That( +// foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL2.PrincipalID), +// "Loaded item uuid creator doesn't match original"); +// Assert.That(foundItem1.Owner, Is.EqualTo(m_uaMT.PrincipalID), +// "Loaded item owner doesn't match inventory reciever"); +// +// AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); +// string xmlData = Utils.BytesToString(asset1.Data); +// SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); +// +// Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL2.PrincipalID)); +// } + + /// + /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where + /// the creator or an account with the creator's name does not exist within the system. + /// + [Test] + public void TestLoadIarV0_1AbsentCreator() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "password"); + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/", "password", m_iarStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); + Assert.That( + foundItem1.CreatorId, Is.EqualTo(m_uaMT.PrincipalID.ToString()), + "Loaded item non-uuid creator doesn't match that of the loading user"); + Assert.That( + foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaMT.PrincipalID), + "Loaded item uuid creator doesn't match that of the loading user"); + + AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); + string xmlData = Utils.BytesToString(asset1.Data); + SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); + + Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaMT.PrincipalID)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs new file mode 100644 index 0000000..7265405 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs @@ -0,0 +1,422 @@ +/* + * 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 NUnit.Framework; +using OpenMetaverse; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Framework.Serialization; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveSaveTests : InventoryArchiveTestCase + { + protected TestScene m_scene; + protected InventoryArchiverModule m_archiverModule; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + SerialiserModule serialiserModule = new SerialiserModule(); + m_archiverModule = new InventoryArchiverModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, serialiserModule, m_archiverModule); + } + + /// + /// Test that the IAR has the required files in the right order. + /// + /// + /// At the moment, the only thing that matters is that the control file is the very first one. + /// + [Test] + public void TestOrder() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + MemoryStream archiveReadStream = new MemoryStream(m_iarStreamBytes); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + byte[] data = tar.ReadEntry(out filePath, out tarEntryType); + Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); + + InventoryArchiveReadRequest iarr + = new InventoryArchiveReadRequest(UUID.Random(), null, null, null, null, null, null, (Stream)null, false); + iarr.LoadControlFile(filePath, data); + + Assert.That(iarr.ControlFileLoaded, Is.True); + } + + [Test] + public void TestSaveRootFolderToIar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = TestHelpers.ParseTail(0x20); + + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "/", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // Test created iar + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + +// InventoryArchiveUtils. + bool gotObjectsFolder = false; + + string objectsFolderName + = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + InventoryArchiveWriteRequest.CreateArchiveFolderName( + UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, userId, "Objects"))); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { +// Console.WriteLine("Got {0}", filePath); + + // Lazily, we only bother to look for the system objects folder created when we call CreateUserWithInventory() + // XXX: But really we need to stop all that stuff being created in tests or check for such folders + // more thoroughly + if (filePath == objectsFolderName) + gotObjectsFolder = true; + } + + Assert.That(gotObjectsFolder, Is.True); + } + + [Test] + public void TestSaveNonRootFolderToIar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = TestHelpers.ParseTail(0x20); + + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create base folder + InventoryFolderBase f1 + = UserInventoryHelpers.CreateInventoryFolder(m_scene.InventoryService, userId, "f1", true); + + // Create item1 + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, userId, "My Little Dog Object", 0x5); + InventoryItemBase i1 = UserInventoryHelpers.AddInventoryItem(m_scene, so1, 0x50, 0x60, "f1"); + + // Create embedded folder + InventoryFolderBase f1_1 + = UserInventoryHelpers.CreateInventoryFolder(m_scene.InventoryService, userId, "f1/f1.1", true); + + // Create embedded item + SceneObjectGroup so1_1 = SceneHelpers.CreateSceneObject(1, userId, "My Little Cat Object", 0x6); + InventoryItemBase i2 = UserInventoryHelpers.AddInventoryItem(m_scene, so1_1, 0x500, 0x600, "f1/f1.1"); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "f1", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // Test created iar + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + +// InventoryArchiveUtils. + bool gotf1 = false, gotf1_1 = false, gotso1 = false, gotso2 = false; + + string f1FileName + = string.Format("{0}{1}", ArchiveConstants.INVENTORY_PATH, InventoryArchiveWriteRequest.CreateArchiveFolderName(f1)); + string f1_1FileName + = string.Format("{0}{1}", f1FileName, InventoryArchiveWriteRequest.CreateArchiveFolderName(f1_1)); + string so1FileName + = string.Format("{0}{1}", f1FileName, InventoryArchiveWriteRequest.CreateArchiveItemName(i1)); + string so2FileName + = string.Format("{0}{1}", f1_1FileName, InventoryArchiveWriteRequest.CreateArchiveItemName(i2)); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { +// Console.WriteLine("Got {0}", filePath); + + if (filePath == f1FileName) + gotf1 = true; + else if (filePath == f1_1FileName) + gotf1_1 = true; + else if (filePath == so1FileName) + gotso1 = true; + else if (filePath == so2FileName) + gotso2 = true; + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotf1, Is.True); + Assert.That(gotf1_1, Is.True); + Assert.That(gotso1, Is.True); + Assert.That(gotso2, Is.True); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test saving a single inventory item to an IAR + /// (subject to change since there is no fixed format yet). + /// + [Test] + public void TestSaveItemToIar() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create asset + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + m_scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + //bool gotControlFile = false; + bool gotObject1File = false; + //bool gotObject2File = false; + string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); + string expectedObject1FilePath = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + expectedObject1FileName); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + +// Console.WriteLine("Reading archive"); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + Console.WriteLine("Got {0}", filePath); + +// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) +// { +// gotControlFile = true; +// } + + if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) + { +// string fileName = filePath.Remove(0, "Objects/".Length); +// +// if (fileName.StartsWith(part1.Name)) +// { + Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); + gotObject1File = true; +// } +// else if (fileName.StartsWith(part2.Name)) +// { +// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); +// gotObject2File = true; +// } + } + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotObject1File, Is.True, "No item1 file in archive"); +// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test saving a single inventory item to an IAR without its asset + /// + [Test] + public void TestSaveItemToIarNoAssets() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create asset + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + m_scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + + Dictionary options = new Dictionary(); + options.Add("noassets", true); + + // When we're not saving assets, archiving is being done synchronously. + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream, options); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + //bool gotControlFile = false; + bool gotObject1File = false; + //bool gotObject2File = false; + string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); + string expectedObject1FilePath = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + expectedObject1FileName); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + +// Console.WriteLine("Reading archive"); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + Console.WriteLine("Got {0}", filePath); + +// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) +// { +// gotControlFile = true; +// } + + if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) + { +// string fileName = filePath.Remove(0, "Objects/".Length); +// +// if (fileName.StartsWith(part1.Name)) +// { + Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); + gotObject1File = true; +// } +// else if (fileName.StartsWith(part2.Name)) +// { +// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); +// gotObject2File = true; +// } + } + else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + Assert.Fail("Found asset path in TestSaveItemToIarNoAssets()"); + } + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotObject1File, Is.True, "No item1 file in archive"); +// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); + + // TODO: Test presence of more files and contents of files. + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs index db78da9..519c697 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs @@ -36,14 +36,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.World.Serialiser; 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.Avatar.Inventory.Archiver.Tests { @@ -163,14 +161,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests scene.AddInventoryItem(coaItem); archiverModule.ArchiveInventory( - Guid.NewGuid(), m_uaLL1.FirstName, m_uaLL1.LastName, "/*", "hampshire", archiveWriteStream); + UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/*", "hampshire", archiveWriteStream); m_iarStreamBytes = archiveWriteStream.ToArray(); } protected void SaveCompleted( - Guid id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, - Exception reportedException) + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, + Exception reportedException, int SaveCount, int FilterCount) { mre.Set(); } diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs deleted file mode 100644 index 06f6e49..0000000 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/InventoryArchiverTests.cs +++ /dev/null @@ -1,417 +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.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using NUnit.Framework; -using OpenMetaverse; -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.World.Serialiser; -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.Avatar.Inventory.Archiver.Tests -{ - [TestFixture] - public class InventoryArchiverTests : InventoryArchiveTestCase - { - protected TestScene m_scene; - protected InventoryArchiverModule m_archiverModule; - - [SetUp] - public override void SetUp() - { - base.SetUp(); - - SerialiserModule serialiserModule = new SerialiserModule(); - m_archiverModule = new InventoryArchiverModule(); - - m_scene = new SceneHelpers().SetupScene(); - SceneHelpers.SetupSceneModules(m_scene, serialiserModule, m_archiverModule); - } - - [Test] - public void TestLoadCoalesecedItem() - { - TestHelpers.InMethod(); -// TestHelpers.EnableLogging(); - - UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "password"); - m_archiverModule.DearchiveInventory(m_uaLL1.FirstName, m_uaLL1.LastName, "/", "password", m_iarStream); - - InventoryItemBase coaItem - = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_coaItemName); - - Assert.That(coaItem, Is.Not.Null, "Didn't find loaded item 1"); - - string assetXml = AssetHelpers.ReadAssetAsString(m_scene.AssetService, coaItem.AssetID); - - CoalescedSceneObjects coa; - bool readResult = CoalescedSceneObjectsSerializer.TryFromXml(assetXml, out coa); - - Assert.That(readResult, Is.True); - Assert.That(coa.Count, Is.EqualTo(2)); - - List coaObjects = coa.Objects; - Assert.That(coaObjects[0].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000120"))); - Assert.That(coaObjects[0].AbsolutePosition, Is.EqualTo(new Vector3(15, 30, 45))); - - Assert.That(coaObjects[1].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000140"))); - Assert.That(coaObjects[1].AbsolutePosition, Is.EqualTo(new Vector3(25, 50, 75))); - } - - /// - /// Test that the IAR has the required files in the right order. - /// - /// - /// At the moment, the only thing that matters is that the control file is the very first one. - /// - [Test] - public void TestOrder() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - MemoryStream archiveReadStream = new MemoryStream(m_iarStreamBytes); - TarArchiveReader tar = new TarArchiveReader(archiveReadStream); - string filePath; - TarArchiveReader.TarEntryType tarEntryType; - - byte[] data = tar.ReadEntry(out filePath, out tarEntryType); - Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); - - InventoryArchiveReadRequest iarr - = new InventoryArchiveReadRequest(null, null, null, (Stream)null, false); - iarr.LoadControlFile(filePath, data); - - Assert.That(iarr.ControlFileLoaded, Is.True); - } - - /// - /// Test saving a single inventory item to an IAR - /// (subject to change since there is no fixed format yet). - /// - [Test] - public void TestSaveItemToIar() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - // Create user - string userFirstName = "Jock"; - string userLastName = "Stirrup"; - string userPassword = "troll"; - UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); - UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); - - // Create asset - UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); - SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); - - UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); - AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); - m_scene.AssetService.Store(asset1); - - // Create item - UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); - string item1Name = "My Little Dog"; - InventoryItemBase item1 = new InventoryItemBase(); - item1.Name = item1Name; - item1.AssetID = asset1.FullID; - item1.ID = item1Id; - InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, userId, "Objects")[0]; - item1.Folder = objsFolder.ID; - m_scene.AddInventoryItem(item1); - - MemoryStream archiveWriteStream = new MemoryStream(); - m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; - - mre.Reset(); - m_archiverModule.ArchiveInventory( - Guid.NewGuid(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream); - mre.WaitOne(60000, false); - - byte[] archive = archiveWriteStream.ToArray(); - MemoryStream archiveReadStream = new MemoryStream(archive); - TarArchiveReader tar = new TarArchiveReader(archiveReadStream); - - //bool gotControlFile = false; - bool gotObject1File = false; - //bool gotObject2File = false; - string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); - string expectedObject1FilePath = string.Format( - "{0}{1}", - ArchiveConstants.INVENTORY_PATH, - expectedObject1FileName); - - string filePath; - TarArchiveReader.TarEntryType tarEntryType; - -// Console.WriteLine("Reading archive"); - - while (tar.ReadEntry(out filePath, out tarEntryType) != null) - { - Console.WriteLine("Got {0}", filePath); - -// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) -// { -// gotControlFile = true; -// } - - if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) - { -// string fileName = filePath.Remove(0, "Objects/".Length); -// -// if (fileName.StartsWith(part1.Name)) -// { - Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); - gotObject1File = true; -// } -// else if (fileName.StartsWith(part2.Name)) -// { -// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); -// gotObject2File = true; -// } - } - } - -// Assert.That(gotControlFile, Is.True, "No control file in archive"); - Assert.That(gotObject1File, Is.True, "No item1 file in archive"); -// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); - - // TODO: Test presence of more files and contents of files. - } - - /// - /// Test saving a single inventory item to an IAR without its asset - /// - [Test] - public void TestSaveItemToIarNoAssets() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - // Create user - string userFirstName = "Jock"; - string userLastName = "Stirrup"; - string userPassword = "troll"; - UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); - UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); - - // Create asset - UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); - SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); - - UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); - AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); - m_scene.AssetService.Store(asset1); - - // Create item - UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); - string item1Name = "My Little Dog"; - InventoryItemBase item1 = new InventoryItemBase(); - item1.Name = item1Name; - item1.AssetID = asset1.FullID; - item1.ID = item1Id; - InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(m_scene.InventoryService, userId, "Objects")[0]; - item1.Folder = objsFolder.ID; - m_scene.AddInventoryItem(item1); - - MemoryStream archiveWriteStream = new MemoryStream(); - - Dictionary options = new Dictionary(); - options.Add("noassets", true); - - // When we're not saving assets, archiving is being done synchronously. - m_archiverModule.ArchiveInventory( - Guid.NewGuid(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream, options); - - byte[] archive = archiveWriteStream.ToArray(); - MemoryStream archiveReadStream = new MemoryStream(archive); - TarArchiveReader tar = new TarArchiveReader(archiveReadStream); - - //bool gotControlFile = false; - bool gotObject1File = false; - //bool gotObject2File = false; - string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); - string expectedObject1FilePath = string.Format( - "{0}{1}", - ArchiveConstants.INVENTORY_PATH, - expectedObject1FileName); - - string filePath; - TarArchiveReader.TarEntryType tarEntryType; - -// Console.WriteLine("Reading archive"); - - while (tar.ReadEntry(out filePath, out tarEntryType) != null) - { - Console.WriteLine("Got {0}", filePath); - -// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) -// { -// gotControlFile = true; -// } - - if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) - { -// string fileName = filePath.Remove(0, "Objects/".Length); -// -// if (fileName.StartsWith(part1.Name)) -// { - Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); - gotObject1File = true; -// } -// else if (fileName.StartsWith(part2.Name)) -// { -// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); -// gotObject2File = true; -// } - } - else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) - { - Assert.Fail("Found asset path in TestSaveItemToIarNoAssets()"); - } - } - -// Assert.That(gotControlFile, Is.True, "No control file in archive"); - Assert.That(gotObject1File, Is.True, "No item1 file in archive"); -// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); - - // TODO: Test presence of more files and contents of files. - } - - /// - /// Test case where a creator account exists for the creator UUID embedded in item metadata and serialized - /// objects. - /// - [Test] - public void TestLoadIarCreatorAccountPresent() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "meowfood"); - - m_archiverModule.DearchiveInventory(m_uaLL1.FirstName, m_uaLL1.LastName, "/", "meowfood", m_iarStream); - InventoryItemBase foundItem1 - = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_item1Name); - - Assert.That( - foundItem1.CreatorId, Is.EqualTo(m_uaLL1.PrincipalID.ToString()), - "Loaded item non-uuid creator doesn't match original"); - Assert.That( - foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL1.PrincipalID), - "Loaded item uuid creator doesn't match original"); - Assert.That(foundItem1.Owner, Is.EqualTo(m_uaLL1.PrincipalID), - "Loaded item owner doesn't match inventory reciever"); - - AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); - string xmlData = Utils.BytesToString(asset1.Data); - SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - - Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL1.PrincipalID)); - } - -// /// -// /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where -// /// an account exists with the same name as the creator, though not the same id. -// /// -// [Test] -// public void TestLoadIarV0_1SameNameCreator() -// { -// TestHelpers.InMethod(); -// TestHelpers.EnableLogging(); -// -// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "meowfood"); -// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL2, "hampshire"); -// -// m_archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); -// InventoryItemBase foundItem1 -// = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); -// -// Assert.That( -// foundItem1.CreatorId, Is.EqualTo(m_uaLL2.PrincipalID.ToString()), -// "Loaded item non-uuid creator doesn't match original"); -// Assert.That( -// foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL2.PrincipalID), -// "Loaded item uuid creator doesn't match original"); -// Assert.That(foundItem1.Owner, Is.EqualTo(m_uaMT.PrincipalID), -// "Loaded item owner doesn't match inventory reciever"); -// -// AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); -// string xmlData = Utils.BytesToString(asset1.Data); -// SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); -// -// Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL2.PrincipalID)); -// } - - /// - /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where - /// the creator or an account with the creator's name does not exist within the system. - /// - [Test] - public void TestLoadIarV0_1AbsentCreator() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "password"); - m_archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/", "password", m_iarStream); - - InventoryItemBase foundItem1 - = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); - - Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); - Assert.That( - foundItem1.CreatorId, Is.EqualTo(m_uaMT.PrincipalID.ToString()), - "Loaded item non-uuid creator doesn't match that of the loading user"); - Assert.That( - foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaMT.PrincipalID), - "Loaded item uuid creator doesn't match that of the loading user"); - - AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); - string xmlData = Utils.BytesToString(asset1.Data); - SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - - Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaMT.PrincipalID)); - } - } -} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/PathTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/PathTests.cs deleted file mode 100644 index 6eb3605..0000000 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/Tests/PathTests.cs +++ /dev/null @@ -1,477 +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.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using NUnit.Framework; -using OpenMetaverse; -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.World.Serialiser; -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.Avatar.Inventory.Archiver.Tests -{ - [TestFixture] - public class PathTests : InventoryArchiveTestCase - { - /// - /// Test saving an inventory path to a V0.1 OpenSim Inventory Archive - /// (subject to change since there is no fixed format yet). - /// - [Test] - public void TestSavePathToIarV0_1() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - InventoryArchiverModule archiverModule = new InventoryArchiverModule(); - - Scene scene = new SceneHelpers().SetupScene(); - SceneHelpers.SetupSceneModules(scene, archiverModule); - - // Create user - string userFirstName = "Jock"; - string userLastName = "Stirrup"; - string userPassword = "troll"; - UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); - UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, userPassword); - - // Create asset - SceneObjectGroup object1; - SceneObjectPart part1; - { - string partName = "My Little Dog Object"; - UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); - PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); - Vector3 groupPosition = new Vector3(10, 20, 30); - Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); - Vector3 offsetPosition = new Vector3(5, 10, 15); - - part1 = new SceneObjectPart(ownerId, shape, groupPosition, rotationOffset, offsetPosition); - part1.Name = partName; - - object1 = new SceneObjectGroup(part1); - scene.AddNewSceneObject(object1, false); - } - - UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); - AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); - scene.AssetService.Store(asset1); - - // Create item - UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); - InventoryItemBase item1 = new InventoryItemBase(); - item1.Name = "My Little Dog"; - item1.AssetID = asset1.FullID; - item1.ID = item1Id; - InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, userId, "Objects")[0]; - item1.Folder = objsFolder.ID; - scene.AddInventoryItem(item1); - - MemoryStream archiveWriteStream = new MemoryStream(); - archiverModule.OnInventoryArchiveSaved += SaveCompleted; - - // Test saving a particular path - mre.Reset(); - archiverModule.ArchiveInventory( - Guid.NewGuid(), userFirstName, userLastName, "Objects", userPassword, archiveWriteStream); - mre.WaitOne(60000, false); - - byte[] archive = archiveWriteStream.ToArray(); - MemoryStream archiveReadStream = new MemoryStream(archive); - TarArchiveReader tar = new TarArchiveReader(archiveReadStream); - - //bool gotControlFile = false; - bool gotObject1File = false; - //bool gotObject2File = false; - string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); - string expectedObject1FilePath = string.Format( - "{0}{1}{2}", - ArchiveConstants.INVENTORY_PATH, - InventoryArchiveWriteRequest.CreateArchiveFolderName(objsFolder), - expectedObject1FileName); - - string filePath; - TarArchiveReader.TarEntryType tarEntryType; - -// Console.WriteLine("Reading archive"); - - while (tar.ReadEntry(out filePath, out tarEntryType) != null) - { -// Console.WriteLine("Got {0}", filePath); - -// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) -// { -// gotControlFile = true; -// } - - if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) - { -// string fileName = filePath.Remove(0, "Objects/".Length); -// -// if (fileName.StartsWith(part1.Name)) -// { - Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); - gotObject1File = true; -// } -// else if (fileName.StartsWith(part2.Name)) -// { -// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); -// gotObject2File = true; -// } - } - } - -// Assert.That(gotControlFile, Is.True, "No control file in archive"); - Assert.That(gotObject1File, Is.True, "No item1 file in archive"); -// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); - - // TODO: Test presence of more files and contents of files. - } - - /// - /// Test loading an IAR to various different inventory paths. - /// - [Test] - public void TestLoadIarToInventoryPaths() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - SerialiserModule serialiserModule = new SerialiserModule(); - InventoryArchiverModule archiverModule = new InventoryArchiverModule(); - - // Annoyingly, we have to set up a scene even though inventory loading has nothing to do with a scene - Scene scene = new SceneHelpers().SetupScene(); - - SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); - - UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "meowfood"); - UserAccountHelpers.CreateUserWithInventory(scene, m_uaLL1, "hampshire"); - - archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); - InventoryItemBase foundItem1 - = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); - - Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); - - // Now try loading to a root child folder - UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xA"); - MemoryStream archiveReadStream = new MemoryStream(m_iarStream.ToArray()); - archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "xA", "meowfood", archiveReadStream); - - InventoryItemBase foundItem2 - = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xA/" + m_item1Name); - Assert.That(foundItem2, Is.Not.Null, "Didn't find loaded item 2"); - - // Now try loading to a more deeply nested folder - UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC"); - archiveReadStream = new MemoryStream(archiveReadStream.ToArray()); - archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "xB/xC", "meowfood", archiveReadStream); - - InventoryItemBase foundItem3 - = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC/" + m_item1Name); - Assert.That(foundItem3, Is.Not.Null, "Didn't find loaded item 3"); - } - - /// - /// Test that things work when the load path specified starts with a slash - /// - [Test] - public void TestLoadIarPathStartsWithSlash() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - SerialiserModule serialiserModule = new SerialiserModule(); - InventoryArchiverModule archiverModule = new InventoryArchiverModule(); - Scene scene = new SceneHelpers().SetupScene(); - SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); - - UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "password"); - archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/Objects", "password", m_iarStream); - - InventoryItemBase foundItem1 - = InventoryArchiveUtils.FindItemByPath( - scene.InventoryService, m_uaMT.PrincipalID, "/Objects/" + m_item1Name); - - Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1 in TestLoadIarFolderStartsWithSlash()"); - } - - [Test] - public void TestLoadIarPathWithEscapedChars() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - string itemName = "You & you are a mean/man/"; - string humanEscapedItemName = @"You & you are a mean\/man\/"; - string userPassword = "meowfood"; - - InventoryArchiverModule archiverModule = new InventoryArchiverModule(); - - Scene scene = new SceneHelpers().SetupScene(); - SceneHelpers.SetupSceneModules(scene, archiverModule); - - // Create user - string userFirstName = "Jock"; - string userLastName = "Stirrup"; - UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); - UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, "meowfood"); - - // Create asset - SceneObjectGroup object1; - SceneObjectPart part1; - { - string partName = "part name"; - UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); - PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); - Vector3 groupPosition = new Vector3(10, 20, 30); - Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); - Vector3 offsetPosition = new Vector3(5, 10, 15); - - part1 - = new SceneObjectPart( - ownerId, shape, groupPosition, rotationOffset, offsetPosition); - part1.Name = partName; - - object1 = new SceneObjectGroup(part1); - scene.AddNewSceneObject(object1, false); - } - - UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); - AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); - scene.AssetService.Store(asset1); - - // Create item - UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); - InventoryItemBase item1 = new InventoryItemBase(); - item1.Name = itemName; - item1.AssetID = asset1.FullID; - item1.ID = item1Id; - InventoryFolderBase objsFolder - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, userId, "Objects")[0]; - item1.Folder = objsFolder.ID; - scene.AddInventoryItem(item1); - - MemoryStream archiveWriteStream = new MemoryStream(); - archiverModule.OnInventoryArchiveSaved += SaveCompleted; - - mre.Reset(); - archiverModule.ArchiveInventory( - Guid.NewGuid(), userFirstName, userLastName, "Objects", userPassword, archiveWriteStream); - mre.WaitOne(60000, false); - - // LOAD ITEM - MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); - - archiverModule.DearchiveInventory(userFirstName, userLastName, "Scripts", userPassword, archiveReadStream); - - InventoryItemBase foundItem1 - = InventoryArchiveUtils.FindItemByPath( - scene.InventoryService, userId, "Scripts/Objects/" + humanEscapedItemName); - - Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); -// Assert.That( -// foundItem1.CreatorId, Is.EqualTo(userUuid), -// "Loaded item non-uuid creator doesn't match that of the loading user"); - Assert.That( - foundItem1.Name, Is.EqualTo(itemName), - "Loaded item name doesn't match saved name"); - } - - /// - /// Test replication of an archive path to the user's inventory. - /// - [Test] - public void TestNewIarPath() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - Scene scene = new SceneHelpers().SetupScene(); - UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); - - Dictionary foldersCreated = new Dictionary(); - HashSet nodesLoaded = new HashSet(); - - string folder1Name = "1"; - string folder2aName = "2a"; - string folder2bName = "2b"; - - string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1Name, UUID.Random()); - string folder2aArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2aName, UUID.Random()); - string folder2bArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2bName, UUID.Random()); - - string iarPath1 = string.Join("", new string[] { folder1ArchiveName, folder2aArchiveName }); - string iarPath2 = string.Join("", new string[] { folder1ArchiveName, folder2bArchiveName }); - - { - // Test replication of path1 - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) - .ReplicateArchivePathToUserInventory( - iarPath1, scene.InventoryService.GetRootFolder(ua1.PrincipalID), - foldersCreated, nodesLoaded); - - List folder1Candidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); - Assert.That(folder1Candidates.Count, Is.EqualTo(1)); - - InventoryFolderBase folder1 = folder1Candidates[0]; - List folder2aCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1, folder2aName); - Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); - } - - { - // Test replication of path2 - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) - .ReplicateArchivePathToUserInventory( - iarPath2, scene.InventoryService.GetRootFolder(ua1.PrincipalID), - foldersCreated, nodesLoaded); - - List folder1Candidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); - Assert.That(folder1Candidates.Count, Is.EqualTo(1)); - - InventoryFolderBase folder1 = folder1Candidates[0]; - - List folder2aCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1, folder2aName); - Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); - - List folder2bCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1, folder2bName); - Assert.That(folder2bCandidates.Count, Is.EqualTo(1)); - } - } - - /// - /// Test replication of a partly existing archive path to the user's inventory. This should create - /// a duplicate path without the merge option. - /// - [Test] - public void TestPartExistingIarPath() - { - TestHelpers.InMethod(); - //log4net.Config.XmlConfigurator.Configure(); - - Scene scene = new SceneHelpers().SetupScene(); - UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); - - string folder1ExistingName = "a"; - string folder2Name = "b"; - - InventoryFolderBase folder1 - = UserInventoryHelpers.CreateInventoryFolder( - scene.InventoryService, ua1.PrincipalID, folder1ExistingName); - - string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); - string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); - - string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); - - new InventoryArchiveReadRequest(scene, ua1, null, (Stream)null, false) - .ReplicateArchivePathToUserInventory( - itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), - new Dictionary(), new HashSet()); - - List folder1PostCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); - Assert.That(folder1PostCandidates.Count, Is.EqualTo(2)); - - // FIXME: Temporarily, we're going to do something messy to make sure we pick up the created folder. - InventoryFolderBase folder1Post = null; - foreach (InventoryFolderBase folder in folder1PostCandidates) - { - if (folder.ID != folder1.ID) - { - folder1Post = folder; - break; - } - } -// Assert.That(folder1Post.ID, Is.EqualTo(folder1.ID)); - - List folder2PostCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1Post, "b"); - Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); - } - - /// - /// Test replication of a partly existing archive path to the user's inventory. This should create - /// a merged path. - /// - [Test] - public void TestMergeIarPath() - { - TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - - Scene scene = new SceneHelpers().SetupScene(); - UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); - - string folder1ExistingName = "a"; - string folder2Name = "b"; - - InventoryFolderBase folder1 - = UserInventoryHelpers.CreateInventoryFolder( - scene.InventoryService, ua1.PrincipalID, folder1ExistingName); - - string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); - string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); - - string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); - - new InventoryArchiveReadRequest(scene, ua1, folder1ExistingName, (Stream)null, true) - .ReplicateArchivePathToUserInventory( - itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), - new Dictionary(), new HashSet()); - - List folder1PostCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); - Assert.That(folder1PostCandidates.Count, Is.EqualTo(1)); - Assert.That(folder1PostCandidates[0].ID, Is.EqualTo(folder1.ID)); - - List folder2PostCandidates - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, folder1PostCandidates[0], "b"); - Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); - } - } -} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs index bcb7f42..bba48cc 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs @@ -47,10 +47,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer /// private List m_Scenelist = new List(); -// private Dictionary m_AgentRegions = -// new Dictionary(); - private IMessageTransferModule m_TransferModule = null; + private IMessageTransferModule m_TransferModule; private bool m_Enabled = true; #region Region Module interface @@ -81,9 +79,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer // scene.RegisterModuleInterface(this); scene.EventManager.OnNewClient += OnNewClient; -// scene.EventManager.OnClientClosed += ClientLoggedOut; scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; -// scene.EventManager.OnSetRootAgentScene += OnSetRootAgentScene; } public void RegionLoaded(Scene scene) @@ -96,11 +92,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer m_log.Error("[INVENTORY TRANSFER]: No Message transfer module found, transfers will be local only"); m_Enabled = false; - m_Scenelist.Clear(); - scene.EventManager.OnNewClient -= OnNewClient; -// scene.EventManager.OnClientClosed -= ClientLoggedOut; +// m_Scenelist.Clear(); +// scene.EventManager.OnNewClient -= OnNewClient; scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage; -// scene.EventManager.OnSetRootAgentScene -= OnSetRootAgentScene; } } } @@ -108,9 +102,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer public void RemoveRegion(Scene scene) { scene.EventManager.OnNewClient -= OnNewClient; -// scene.EventManager.OnClientClosed -= ClientLoggedOut; scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage; -// scene.EventManager.OnSetRootAgentScene -= OnSetRootAgentScene; m_Scenelist.Remove(scene); } @@ -139,11 +131,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer // Inventory giving is conducted via instant message client.OnInstantMessage += OnInstantMessage; } - -// protected void OnSetRootAgentScene(UUID id, Scene scene) -// { -// m_AgentRegions[id] = scene; -// } private Scene FindClientScene(UUID agentId) { @@ -162,8 +149,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer private void OnInstantMessage(IClientAPI client, GridInstantMessage im) { // m_log.DebugFormat( -// "[INVENTORY TRANSFER]: {0} IM type received from {1}", -// (InstantMessageDialog)im.dialog, client.Name); +// "[INVENTORY TRANSFER]: {0} IM type received from client {1}. From={2} ({3}), To={4}", +// (InstantMessageDialog)im.dialog, client.Name, +// im.fromAgentID, im.fromAgentName, im.toAgentID); Scene scene = FindClientScene(client.AgentId); @@ -188,12 +176,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer { UUID folderID = new UUID(im.binaryBucket, 1); - m_log.DebugFormat("[INVENTORY TRANSFER]: Inserting original folder {0} "+ - "into agent {1}'s inventory", - folderID, new UUID(im.toAgentID)); + m_log.DebugFormat( + "[INVENTORY TRANSFER]: Inserting original folder {0} into agent {1}'s inventory", + folderID, new UUID(im.toAgentID)); - InventoryFolderBase folderCopy - = scene.GiveInventoryFolder(receipientID, client.AgentId, folderID, UUID.Zero); + InventoryFolderBase folderCopy + = scene.GiveInventoryFolder(client, receipientID, client.AgentId, folderID, UUID.Zero); if (folderCopy == null) { @@ -213,7 +201,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer user.ControllingClient.SendBulkUpdateInventory(folderCopy); // HACK!! - im.imSessionID = folderID.Guid; + // Insert the ID of the copied folder into the IM so that we know which item to move to trash if it + // is rejected. + // XXX: This is probably a misuse of the session ID slot. + im.imSessionID = copyID.Guid; } else { @@ -226,13 +217,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer "into agent {1}'s inventory", itemID, new UUID(im.toAgentID)); - InventoryItemBase itemCopy = scene.GiveInventoryItem( - new UUID(im.toAgentID), - client.AgentId, itemID); + string message; + InventoryItemBase itemCopy = scene.GiveInventoryItem(new UUID(im.toAgentID), client.AgentId, itemID, out message); if (itemCopy == null) { - client.SendAgentAlertMessage("Can't find item to give. Nothing given.", false); + client.SendAgentAlertMessage(message, false); return; } @@ -243,7 +233,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer user.ControllingClient.SendBulkUpdateInventory(itemCopy); // HACK!! - im.imSessionID = itemID.Guid; + // Insert the ID of the copied item into the IM so that we know which item to move to trash if it + // is rejected. + // XXX: This is probably a misuse of the session ID slot. + im.imSessionID = copyID.Guid; } // Send the IM to the recipient. The item is already @@ -379,7 +372,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer IInventoryService invService = scene.InventoryService; InventoryFolderBase trashFolder = - invService.GetFolderForType(client.AgentId, AssetType.TrashFolder); + invService.GetFolderForType(client.AgentId, FolderType.Trash); UUID inventoryID = new UUID(im.imSessionID); // The inventory item/folder, back from it's trip @@ -403,7 +396,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer { folder = new InventoryFolderBase(inventoryID, client.AgentId); folder = invService.GetFolder(folder); - + if (folder != null & trashFolder != null) { previousParentFolderID = folder.ParentID; @@ -454,90 +447,61 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer } } -// public bool NeedSceneCacheClear(UUID agentID, Scene scene) -// { -// if (!m_AgentRegions.ContainsKey(agentID)) -// { -// // Since we can get here two ways, we need to scan -// // the scenes here. This is somewhat more expensive -// // but helps avoid a nasty bug -// // -// -// foreach (Scene s in m_Scenelist) -// { -// ScenePresence presence; -// -// if (s.TryGetScenePresence(agentID, out presence)) -// { -// // If the agent is in this scene, then we -// // are being called twice in a single -// // teleport. This is wasteful of cycles -// // but harmless due to this 2nd level check -// // -// // If the agent is found in another scene -// // then the list wasn't current -// // -// // If the agent is totally unknown, then what -// // are we even doing here?? -// // -// if (s == scene) -// { -// //m_log.Debug("[INVTRANSFERMOD]: s == scene. Returning true in " + scene.RegionInfo.RegionName); -// return true; -// } -// else -// { -// //m_log.Debug("[INVTRANSFERMOD]: s != scene. Returning false in " + scene.RegionInfo.RegionName); -// return false; -// } -// } -// } -// //m_log.Debug("[INVTRANSFERMOD]: agent not in scene. Returning true in " + scene.RegionInfo.RegionName); -// return true; -// } -// -// // The agent is left in current Scene, so we must be -// // going to another instance -// // -// if (m_AgentRegions[agentID] == scene) -// { -// //m_log.Debug("[INVTRANSFERMOD]: m_AgentRegions[agentID] == scene. Returning true in " + scene.RegionInfo.RegionName); -// m_AgentRegions.Remove(agentID); -// return true; -// } -// -// // Another region has claimed the agent -// // -// //m_log.Debug("[INVTRANSFERMOD]: last resort. Returning false in " + scene.RegionInfo.RegionName); -// return false; -// } -// -// public void ClientLoggedOut(UUID agentID, Scene scene) -// { -// if (m_AgentRegions.ContainsKey(agentID)) -// m_AgentRegions.Remove(agentID); -// } - /// /// /// - /// - private void OnGridInstantMessage(GridInstantMessage msg) + /// + private void OnGridInstantMessage(GridInstantMessage im) { + // Check if it's a type of message that we should handle + if (!((im.dialog == (byte) InstantMessageDialog.InventoryOffered) + || (im.dialog == (byte) InstantMessageDialog.TaskInventoryOffered) + || (im.dialog == (byte) InstantMessageDialog.InventoryAccepted) + || (im.dialog == (byte) InstantMessageDialog.InventoryDeclined) + || (im.dialog == (byte) InstantMessageDialog.TaskInventoryDeclined))) + return; + + m_log.DebugFormat( + "[INVENTORY TRANSFER]: {0} IM type received from grid. From={1} ({2}), To={3}", + (InstantMessageDialog)im.dialog, im.fromAgentID, im.fromAgentName, im.toAgentID); + // Check if this is ours to handle // - Scene scene = FindClientScene(new UUID(msg.toAgentID)); + Scene scene = FindClientScene(new UUID(im.toAgentID)); if (scene == null) return; // Find agent to deliver to // - ScenePresence user = scene.GetScenePresence(new UUID(msg.toAgentID)); + ScenePresence user = scene.GetScenePresence(new UUID(im.toAgentID)); - // Just forward to local handling - OnInstantMessage(user.ControllingClient, msg); + if (user != null) + { + user.ControllingClient.SendInstantMessage(im); + + if (im.dialog == (byte)InstantMessageDialog.InventoryOffered) + { + AssetType assetType = (AssetType)im.binaryBucket[0]; + UUID inventoryID = new UUID(im.binaryBucket, 1); + + IInventoryService invService = scene.InventoryService; + InventoryNodeBase node = null; + if (AssetType.Folder == assetType) + { + InventoryFolderBase folder = new InventoryFolderBase(inventoryID, new UUID(im.toAgentID)); + node = invService.GetFolder(folder); + } + else + { + InventoryItemBase item = new InventoryItemBase(inventoryID, new UUID(im.toAgentID)); + node = invService.GetItem(item); + } + if (node != null) + user.ControllingClient.SendBulkUpdateInventory(node); + } + } } } } diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs new file mode 100644 index 0000000..7ddc396 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs @@ -0,0 +1,448 @@ +/* + * 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.Config; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Inventory.Transfer; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer.Tests +{ + [TestFixture] + public class InventoryTransferModuleTests : OpenSimTestCase + { + protected TestScene m_scene; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Messaging"); + config.Configs["Messaging"].Set("InventoryTransferModule", "InventoryTransferModule"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, config, new InventoryTransferModule()); + } + + [Test] + public void TestAcceptGivenItem() + { +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID itemId = TestHelpers.ParseTail(0x100); + UUID assetId = TestHelpers.ParseTail(0x200); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the object to test give + InventoryItemBase originalItem + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "givenObj", itemId, assetId, giverSp.UUID, InventoryType.Object); + + byte[] giveImBinaryBucket = new byte[17]; + byte[] itemIdBytes = itemId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + GridInstantMessage acceptIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryAccepted, + false, + "inventory accepted msg", + initialSessionId, + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(acceptIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryItemBase originalItemAfterGive + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterGive, Is.Not.Null); + Assert.That(originalItemAfterGive.ID, Is.EqualTo(originalItem.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, receiverSp.UUID, "Objects/givenObj"); + + Assert.That(receivedItem, Is.Not.Null); + Assert.That(receivedItem.ID, Is.Not.EqualTo(originalItem.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.DeleteItems(receiverSp.UUID, new List() { receivedItem.ID }); + + InventoryItemBase originalItemAfterDelete + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterDelete, Is.Not.Null); + + // TODO: Test scenario where giver deletes their item first. + } + + /// + /// Test user rejection of a given item. + /// + /// + /// A rejected item still ends up in the user's trash folder. + /// + [Test] + public void TestRejectGivenItem() + { +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID itemId = TestHelpers.ParseTail(0x100); + UUID assetId = TestHelpers.ParseTail(0x200); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the object to test give + InventoryItemBase originalItem + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "givenObj", itemId, assetId, giverSp.UUID, InventoryType.Object); + + GridInstantMessage receivedIm = null; + receiverClient.OnReceivedInstantMessage += im => receivedIm = im; + + byte[] giveImBinaryBucket = new byte[17]; + byte[] itemIdBytes = itemId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + // Session ID is now the created item ID (!) + GridInstantMessage rejectIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryDeclined, + false, + "inventory declined msg", + new UUID(receivedIm.imSessionID), + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(rejectIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryItemBase originalItemAfterGive + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterGive, Is.Not.Null); + Assert.That(originalItemAfterGive.ID, Is.EqualTo(originalItem.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, receiverSp.UUID, "Trash/givenObj"); + + InventoryFolderBase trashFolder + = m_scene.InventoryService.GetFolderForType(receiverSp.UUID, FolderType.Trash); + + Assert.That(receivedItem, Is.Not.Null); + Assert.That(receivedItem.ID, Is.Not.EqualTo(originalItem.ID)); + Assert.That(receivedItem.Folder, Is.EqualTo(trashFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.PurgeFolder(trashFolder); + + InventoryItemBase originalItemAfterDelete + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterDelete, Is.Not.Null); + } + + [Test] + public void TestAcceptGivenFolder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID folderId = TestHelpers.ParseTail(0x100); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + InventoryFolderBase originalFolder + = UserInventoryHelpers.CreateInventoryFolder( + m_scene.InventoryService, giverSp.UUID, folderId, "f1", true); + + byte[] giveImBinaryBucket = new byte[17]; + giveImBinaryBucket[0] = (byte)AssetType.Folder; + byte[] itemIdBytes = folderId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + GridInstantMessage acceptIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryAccepted, + false, + "inventory accepted msg", + initialSessionId, + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(acceptIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryFolderBase originalFolderAfterGive + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterGive, Is.Not.Null); + Assert.That(originalFolderAfterGive.ID, Is.EqualTo(originalFolder.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryFolderBase receivedFolder + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, receiverSp.UUID, "f1"); + + Assert.That(receivedFolder, Is.Not.Null); + Assert.That(receivedFolder.ID, Is.Not.EqualTo(originalFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.DeleteFolders(receiverSp.UUID, new List() { receivedFolder.ID }); + + InventoryFolderBase originalFolderAfterDelete + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterDelete, Is.Not.Null); + + // TODO: Test scenario where giver deletes their item first. + } + + /// + /// Test user rejection of a given item. + /// + /// + /// A rejected item still ends up in the user's trash folder. + /// + [Test] + public void TestRejectGivenFolder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID folderId = TestHelpers.ParseTail(0x100); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the folder to test give + InventoryFolderBase originalFolder + = UserInventoryHelpers.CreateInventoryFolder( + m_scene.InventoryService, giverSp.UUID, folderId, "f1", true); + + GridInstantMessage receivedIm = null; + receiverClient.OnReceivedInstantMessage += im => receivedIm = im; + + byte[] giveImBinaryBucket = new byte[17]; + giveImBinaryBucket[0] = (byte)AssetType.Folder; + byte[] itemIdBytes = folderId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + // Session ID is now the created item ID (!) + GridInstantMessage rejectIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryDeclined, + false, + "inventory declined msg", + new UUID(receivedIm.imSessionID), + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(rejectIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryFolderBase originalFolderAfterGive + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterGive, Is.Not.Null); + Assert.That(originalFolderAfterGive.ID, Is.EqualTo(originalFolder.ID)); + + // Test for folder successfully making it into the receiver's inventory + InventoryFolderBase receivedFolder + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, receiverSp.UUID, "Trash/f1"); + + InventoryFolderBase trashFolder + = m_scene.InventoryService.GetFolderForType(receiverSp.UUID, FolderType.Trash); + + Assert.That(receivedFolder, Is.Not.Null); + Assert.That(receivedFolder.ID, Is.Not.EqualTo(originalFolder.ID)); + Assert.That(receivedFolder.ParentID, Is.EqualTo(trashFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.PurgeFolder(trashFolder); + + InventoryFolderBase originalFolderAfterDelete + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterDelete, Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Lure/HGLureModule.cs b/OpenSim/Region/CoreModules/Avatar/Lure/HGLureModule.cs index 232a4fe..24286a4 100644 --- a/OpenSim/Region/CoreModules/Avatar/Lure/HGLureModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Lure/HGLureModule.cs @@ -65,7 +65,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Lure { m_Enabled = true; - m_ThisGridURL = config.Configs["Messaging"].GetString("Gatekeeper", string.Empty); + m_ThisGridURL = Util.GetConfigVarFromSections(config, "GatekeeperURI", + new string[] { "Startup", "Hypergrid", "Messaging" }, String.Empty); + // Legacy. Remove soon! + m_ThisGridURL = config.Configs["Messaging"].GetString("Gatekeeper", m_ThisGridURL); m_log.DebugFormat("[LURE MODULE]: {0} enabled", Name); } } @@ -151,7 +154,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Lure void OnIncomingInstantMessage(GridInstantMessage im) { - if (im.dialog == (byte)InstantMessageDialog.RequestTeleport) + if (im.dialog == (byte)InstantMessageDialog.RequestTeleport + || im.dialog == (byte)InstantMessageDialog.GodLikeRequestTeleport) { UUID sessionID = new UUID(im.imSessionID); @@ -235,16 +239,29 @@ namespace OpenSim.Region.CoreModules.Avatar.Lure GatekeeperServiceConnector gConn = new GatekeeperServiceConnector(); GridRegion gatekeeper = new GridRegion(); gatekeeper.ServerURI = url; - GridRegion finalDestination = gConn.GetHyperlinkRegion(gatekeeper, new UUID(im.RegionID)); + string homeURI = scene.GetAgentHomeURI(client.AgentId); + + string message; + GridRegion finalDestination = gConn.GetHyperlinkRegion(gatekeeper, new UUID(im.RegionID), client.AgentId, homeURI, out message); if (finalDestination != null) { ScenePresence sp = scene.GetScenePresence(client.AgentId); IEntityTransferModule transferMod = scene.RequestModuleInterface(); if (transferMod != null && sp != null) + { + if (message != null) + sp.ControllingClient.SendAgentAlertMessage(message, true); + transferMod.DoTeleport( sp, gatekeeper, finalDestination, im.Position + new Vector3(0.5f, 0.5f, 0f), Vector3.UnitX, teleportflags); + } + } + else + { + m_log.InfoFormat("[HG LURE MODULE]: Lure failed: {0}", message); + client.SendAgentAlertMessage(message, true); } } } diff --git a/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs b/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs index e4b0cfa..465ffbc 100644 --- a/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs @@ -165,7 +165,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Lure (uint)presence.AbsolutePosition.Y, (uint)Math.Ceiling(presence.AbsolutePosition.Z)); - m_log.DebugFormat("TP invite with message {0}", message); + m_log.DebugFormat("[LURE MODULE]: TP invite with message {0}, type {1}", message, lureType); GridInstantMessage m = new GridInstantMessage(scene, client.AgentId, client.FirstName+" "+client.LastName, targetid, diff --git a/OpenSim/Region/CoreModules/Avatar/Profile/BasicProfileModule.cs b/OpenSim/Region/CoreModules/Avatar/Profile/BasicProfileModule.cs index bf24030..2bb24ae 100644 --- a/OpenSim/Region/CoreModules/Avatar/Profile/BasicProfileModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Profile/BasicProfileModule.cs @@ -57,6 +57,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Profile public void Initialise(IConfigSource config) { + if(config.Configs["UserProfiles"] != null) + return; + m_log.DebugFormat("[PROFILE MODULE]: Basic Profile Module enabled"); m_Enabled = true; } diff --git a/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs b/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs new file mode 100644 index 0000000..c20369c --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/UserProfiles/UserProfileModule.cs @@ -0,0 +1,1406 @@ +/* + * 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 System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using Mono.Addins; +using OpenSim.Services.Connectors.Hypergrid; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Services.UserProfilesService; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using Microsoft.CSharp; + +namespace OpenSim.Region.CoreModules.Avatar.UserProfiles +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UserProfilesModule")] + public class UserProfileModule : IProfileModule, INonSharedRegionModule + { + /// + /// Logging + /// + static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // The pair of Dictionaries are used to handle the switching of classified ads + // by maintaining a cache of classified id to creator id mappings and an interest + // count. The entries are removed when the interest count reaches 0. + Dictionary m_classifiedCache = new Dictionary(); + Dictionary m_classifiedInterest = new Dictionary(); + + private JsonRpcRequestManager rpc = new JsonRpcRequestManager(); + + public Scene Scene + { + get; private set; + } + + /// + /// Gets or sets the ConfigSource. + /// + /// + /// The configuration + /// + public IConfigSource Config + { + get; + set; + } + + /// + /// Gets or sets the URI to the profile server. + /// + /// + /// The profile server URI. + /// + public string ProfileServerUri + { + get; + set; + } + + IProfileModule ProfileModule + { + get; set; + } + + IUserManagement UserManagementModule + { + get; set; + } + + /// + /// Gets or sets a value indicating whether this + /// is enabled. + /// + /// + /// true if enabled; otherwise, false. + /// + public bool Enabled + { + get; + set; + } + + public string MyGatekeeper + { + get; private set; + } + + + #region IRegionModuleBase implementation + /// + /// This is called to initialize the region module. For shared modules, this is called exactly once, after + /// creating the single (shared) instance. For non-shared modules, this is called once on each instance, after + /// the instace for the region has been created. + /// + /// + /// Source. + /// + public void Initialise(IConfigSource source) + { + Config = source; + ReplaceableInterface = typeof(IProfileModule); + + IConfig profileConfig = Config.Configs["UserProfiles"]; + + if (profileConfig == null) + { + m_log.Debug("[PROFILES]: UserProfiles disabled, no configuration"); + Enabled = false; + return; + } + + // If we find ProfileURL then we configure for FULL support + // else we setup for BASIC support + ProfileServerUri = profileConfig.GetString("ProfileServiceURL", ""); + if (ProfileServerUri == "") + { + Enabled = false; + return; + } + + m_log.Debug("[PROFILES]: Full Profiles Enabled"); + ReplaceableInterface = null; + Enabled = true; + + MyGatekeeper = Util.GetConfigVarFromSections(source, "GatekeeperURI", + new string[] { "Startup", "Hypergrid", "UserProfiles" }, String.Empty); + } + + /// + /// Adds the region. + /// + /// + /// Scene. + /// + public void AddRegion(Scene scene) + { + if(!Enabled) + return; + + Scene = scene; + Scene.RegisterModuleInterface(this); + Scene.EventManager.OnNewClient += OnNewClient; + Scene.EventManager.OnMakeRootAgent += HandleOnMakeRootAgent; + + UserManagementModule = Scene.RequestModuleInterface(); + } + + void HandleOnMakeRootAgent (ScenePresence obj) + { + if(obj.PresenceType == PresenceType.Npc) + return; + + Util.FireAndForget(delegate + { + GetImageAssets(((IScenePresence)obj).UUID); + }, null, "UserProfileModule.GetImageAssets"); + } + + /// + /// Removes the region. + /// + /// + /// Scene. + /// + public void RemoveRegion(Scene scene) + { + if(!Enabled) + return; + } + + /// + /// This will be called once for every scene loaded. In a shared module this will be multiple times in one + /// instance, while a nonshared module instance will only be called once. This method is called after AddRegion + /// has been called in all modules for that scene, providing an opportunity to request another module's + /// interface, or hook an event from another module. + /// + /// + /// Scene. + /// + public void RegionLoaded(Scene scene) + { + if(!Enabled) + return; + } + + /// + /// If this returns non-null, it is the type of an interface that this module intends to register. This will + /// cause the loader to defer loading of this module until all other modules have been loaded. If no other + /// module has registered the interface by then, this module will be activated, else it will remain inactive, + /// letting the other module take over. This should return non-null ONLY in modules that are intended to be + /// easily replaceable, e.g. stub implementations that the developer expects to be replaced by third party + /// provided modules. + /// + /// + /// The replaceable interface. + /// + public Type ReplaceableInterface + { + get; private set; + } + + /// + /// Called as the instance is closed. + /// + public void Close() + { + } + + /// + /// The name of the module + /// + /// + /// Gets the module name. + /// + public string Name + { + get { return "UserProfileModule"; } + } + #endregion IRegionModuleBase implementation + + #region Region Event Handlers + /// + /// Raises the new client event. + /// + /// + /// Client. + /// + void OnNewClient(IClientAPI client) + { + //Profile + client.OnRequestAvatarProperties += RequestAvatarProperties; + client.OnUpdateAvatarProperties += AvatarPropertiesUpdate; + client.OnAvatarInterestUpdate += AvatarInterestsUpdate; + + // Classifieds + client.AddGenericPacketHandler("avatarclassifiedsrequest", ClassifiedsRequest); + client.OnClassifiedInfoUpdate += ClassifiedInfoUpdate; + client.OnClassifiedInfoRequest += ClassifiedInfoRequest; + client.OnClassifiedDelete += ClassifiedDelete; + + // Picks + client.AddGenericPacketHandler("avatarpicksrequest", PicksRequest); + client.AddGenericPacketHandler("pickinforequest", PickInfoRequest); + client.OnPickInfoUpdate += PickInfoUpdate; + client.OnPickDelete += PickDelete; + + // Notes + client.AddGenericPacketHandler("avatarnotesrequest", NotesRequest); + client.OnAvatarNotesUpdate += NotesUpdate; + + // Preferences + client.OnUserInfoRequest += UserPreferencesRequest; + client.OnUpdateUserInfo += UpdateUserPreferences; + } + #endregion Region Event Handlers + + #region Classified + /// + /// + /// Handles the avatar classifieds request. + /// + /// + /// Sender. + /// + /// + /// Method. + /// + /// + /// Arguments. + /// + public void ClassifiedsRequest(Object sender, string method, List args) + { + if (!(sender is IClientAPI)) + return; + + IClientAPI remoteClient = (IClientAPI)sender; + + UUID targetID; + UUID.TryParse(args[0], out targetID); + + // Can't handle NPC yet... + ScenePresence p = FindPresence(targetID); + + if (null != p) + { + if (p.PresenceType == PresenceType.Npc) + return; + } + + string serverURI = string.Empty; + GetUserProfileServerURI(targetID, out serverURI); + UUID creatorId = UUID.Zero; + Dictionary classifieds = new Dictionary(); + + OSDMap parameters= new OSDMap(); + UUID.TryParse(args[0], out creatorId); + parameters.Add("creatorId", OSD.FromUUID(creatorId)); + OSD Params = (OSD)parameters; + if(!rpc.JsonRpcRequest(ref Params, "avatarclassifiedsrequest", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds); + return; + } + + parameters = (OSDMap)Params; + + OSDArray list = (OSDArray)parameters["result"]; + + + foreach(OSD map in list) + { + OSDMap m = (OSDMap)map; + UUID cid = m["classifieduuid"].AsUUID(); + string name = m["name"].AsString(); + + classifieds[cid] = name; + + lock (m_classifiedCache) + { + if (!m_classifiedCache.ContainsKey(cid)) + { + m_classifiedCache.Add(cid,creatorId); + m_classifiedInterest.Add(cid, 0); + } + + m_classifiedInterest[cid]++; + } + } + + remoteClient.SendAvatarClassifiedReply(new UUID(args[0]), classifieds); + } + + public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient) + { + UUID target = remoteClient.AgentId; + UserClassifiedAdd ad = new UserClassifiedAdd(); + ad.ClassifiedId = queryClassifiedID; + + lock (m_classifiedCache) + { + if (m_classifiedCache.ContainsKey(queryClassifiedID)) + { + target = m_classifiedCache[queryClassifiedID]; + + m_classifiedInterest[queryClassifiedID] --; + + if (m_classifiedInterest[queryClassifiedID] == 0) + { + m_classifiedInterest.Remove(queryClassifiedID); + m_classifiedCache.Remove(queryClassifiedID); + } + } + } + + string serverURI = string.Empty; + GetUserProfileServerURI(target, out serverURI); + + object Ad = (object)ad; + if(!rpc.JsonRpcRequest(ref Ad, "classifieds_info_query", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error getting classified info", false); + return; + } + ad = (UserClassifiedAdd) Ad; + + if(ad.CreatorId == UUID.Zero) + return; + + Vector3 globalPos = new Vector3(); + Vector3.TryParse(ad.GlobalPos, out globalPos); + + remoteClient.SendClassifiedInfoReply(ad.ClassifiedId, ad.CreatorId, (uint)ad.CreationDate, (uint)ad.ExpirationDate, + (uint)ad.Category, ad.Name, ad.Description, ad.ParcelId, (uint)ad.ParentEstate, + ad.SnapshotId, ad.SimName, globalPos, ad.ParcelName, ad.Flags, ad.Price); + + } + + /// + /// Classifieds info update. + /// + /// + /// Queryclassified I. + /// + /// + /// Query category. + /// + /// + /// Query name. + /// + /// + /// Query description. + /// + /// + /// Query parcel I. + /// + /// + /// Query parent estate. + /// + /// + /// Query snapshot I. + /// + /// + /// Query global position. + /// + /// + /// Queryclassified flags. + /// + /// + /// Queryclassified price. + /// + /// + /// Remote client. + /// + public void ClassifiedInfoUpdate(UUID queryclassifiedID, uint queryCategory, string queryName, string queryDescription, UUID queryParcelID, + uint queryParentEstate, UUID querySnapshotID, Vector3 queryGlobalPos, byte queryclassifiedFlags, + int queryclassifiedPrice, IClientAPI remoteClient) + { + Scene s = (Scene)remoteClient.Scene; + IMoneyModule money = s.RequestModuleInterface(); + + if (money != null) + { + if (!money.AmountCovered(remoteClient.AgentId, queryclassifiedPrice)) + { + remoteClient.SendAgentAlertMessage("You do not have enough money to create requested classified.", false); + return; + } + money.ApplyCharge(remoteClient.AgentId, queryclassifiedPrice, MoneyTransactionType.ClassifiedCharge); + } + + UserClassifiedAdd ad = new UserClassifiedAdd(); + + Vector3 pos = remoteClient.SceneAgent.AbsolutePosition; + ILandObject land = s.LandChannel.GetLandObject(pos.X, pos.Y); + ScenePresence p = FindPresence(remoteClient.AgentId); + + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + if (land == null) + { + ad.ParcelName = string.Empty; + } + else + { + ad.ParcelName = land.LandData.Name; + } + + ad.CreatorId = remoteClient.AgentId; + ad.ClassifiedId = queryclassifiedID; + ad.Category = Convert.ToInt32(queryCategory); + ad.Name = queryName; + ad.Description = queryDescription; + ad.ParentEstate = Convert.ToInt32(queryParentEstate); + ad.SnapshotId = querySnapshotID; + ad.SimName = remoteClient.Scene.RegionInfo.RegionName; + ad.GlobalPos = queryGlobalPos.ToString (); + ad.Flags = queryclassifiedFlags; + ad.Price = queryclassifiedPrice; + ad.ParcelId = p.currentParcelUUID; + + object Ad = ad; + + OSD.SerializeMembers(Ad); + + if(!rpc.JsonRpcRequest(ref Ad, "classified_update", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error updating classified", false); + return; + } + } + + /// + /// Classifieds delete. + /// + /// + /// Query classified I. + /// + /// + /// Remote client. + /// + public void ClassifiedDelete(UUID queryClassifiedID, IClientAPI remoteClient) + { + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + UUID classifiedId; + OSDMap parameters= new OSDMap(); + UUID.TryParse(queryClassifiedID.ToString(), out classifiedId); + parameters.Add("classifiedId", OSD.FromUUID(classifiedId)); + OSD Params = (OSD)parameters; + if(!rpc.JsonRpcRequest(ref Params, "classified_delete", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error classified delete", false); + return; + } + + parameters = (OSDMap)Params; + } + #endregion Classified + + #region Picks + /// + /// Handles the avatar picks request. + /// + /// + /// Sender. + /// + /// + /// Method. + /// + /// + /// Arguments. + /// + public void PicksRequest(Object sender, string method, List args) + { + if (!(sender is IClientAPI)) + return; + + IClientAPI remoteClient = (IClientAPI)sender; + + UUID targetId; + UUID.TryParse(args[0], out targetId); + + // Can't handle NPC yet... + ScenePresence p = FindPresence(targetId); + + if (null != p) + { + if (p.PresenceType == PresenceType.Npc) + return; + } + + string serverURI = string.Empty; + GetUserProfileServerURI(targetId, out serverURI); + + Dictionary picks = new Dictionary(); + + OSDMap parameters= new OSDMap(); + parameters.Add("creatorId", OSD.FromUUID(targetId)); + OSD Params = (OSD)parameters; + if(!rpc.JsonRpcRequest(ref Params, "avatarpicksrequest", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks); + return; + } + + parameters = (OSDMap)Params; + + OSDArray list = (OSDArray)parameters["result"]; + + foreach(OSD map in list) + { + OSDMap m = (OSDMap)map; + UUID cid = m["pickuuid"].AsUUID(); + string name = m["name"].AsString(); + + m_log.DebugFormat("[PROFILES]: PicksRequest {0}", name); + + picks[cid] = name; + } + remoteClient.SendAvatarPicksReply(new UUID(args[0]), picks); + } + + /// + /// Handles the pick info request. + /// + /// + /// Sender. + /// + /// + /// Method. + /// + /// + /// Arguments. + /// + public void PickInfoRequest(Object sender, string method, List args) + { + if (!(sender is IClientAPI)) + return; + + UUID targetID; + UUID.TryParse (args [0], out targetID); + string serverURI = string.Empty; + GetUserProfileServerURI (targetID, out serverURI); + + string theirGatekeeperURI; + GetUserGatekeeperURI (targetID, out theirGatekeeperURI); + + IClientAPI remoteClient = (IClientAPI)sender; + + UserProfilePick pick = new UserProfilePick (); + UUID.TryParse (args [0], out pick.CreatorId); + UUID.TryParse (args [1], out pick.PickId); + + + object Pick = (object)pick; + if (!rpc.JsonRpcRequest (ref Pick, "pickinforequest", serverURI, UUID.Random ().ToString ())) { + remoteClient.SendAgentAlertMessage ( + "Error selecting pick", false); + return; + } + pick = (UserProfilePick)Pick; + + Vector3 globalPos = new Vector3(Vector3.Zero); + + // Smoke and mirrors + if (pick.Gatekeeper == MyGatekeeper) + { + Vector3.TryParse(pick.GlobalPos,out globalPos); + } + else + { + // Setup the illusion + string region = string.Format("{0} {1}",pick.Gatekeeper,pick.SimName); + GridRegion target = Scene.GridService.GetRegionByName(Scene.RegionInfo.ScopeID, region); + + if(target == null) + { + // This is a dead or unreachable region + } + else + { + // Work our slight of hand + int x = target.RegionLocX; + int y = target.RegionLocY; + + dynamic synthX = globalPos.X - (globalPos.X/Constants.RegionSize) * Constants.RegionSize; + synthX += x; + globalPos.X = synthX; + + dynamic synthY = globalPos.Y - (globalPos.Y/Constants.RegionSize) * Constants.RegionSize; + synthY += y; + globalPos.Y = synthY; + } + } + + m_log.DebugFormat("[PROFILES]: PickInfoRequest: {0} : {1}", pick.Name.ToString(), pick.SnapshotId.ToString()); + + // Pull the rabbit out of the hat + remoteClient.SendPickInfoReply(pick.PickId,pick.CreatorId,pick.TopPick,pick.ParcelId,pick.Name, + pick.Desc,pick.SnapshotId,pick.ParcelName,pick.OriginalName,pick.SimName, + globalPos,pick.SortOrder,pick.Enabled); + } + + /// + /// Updates the userpicks + /// + /// + /// Remote client. + /// + /// + /// Pick I. + /// + /// + /// the creator of the pick + /// + /// + /// Top pick. + /// + /// + /// Name. + /// + /// + /// Desc. + /// + /// + /// Snapshot I. + /// + /// + /// Sort order. + /// + /// + /// Enabled. + /// + public void PickInfoUpdate(IClientAPI remoteClient, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled) + { + //TODO: See how this works with NPC, May need to test + m_log.DebugFormat("[PROFILES]: Start PickInfoUpdate Name: {0} PickId: {1} SnapshotId: {2}", name, pickID.ToString(), snapshotID.ToString()); + + UserProfilePick pick = new UserProfilePick(); + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + ScenePresence p = FindPresence(remoteClient.AgentId); + + Vector3 avaPos = p.AbsolutePosition; + // Getting the global position for the Avatar + Vector3 posGlobal = new Vector3(remoteClient.Scene.RegionInfo.WorldLocX + avaPos.X, + remoteClient.Scene.RegionInfo.WorldLocY + avaPos.Y, + avaPos.Z); + + string landParcelName = "My Parcel"; + UUID landParcelID = p.currentParcelUUID; + + ILandObject land = p.Scene.LandChannel.GetLandObject(avaPos.X, avaPos.Y); + + if (land != null) + { + // If land found, use parcel uuid from here because the value from SP will be blank if the avatar hasnt moved + landParcelName = land.LandData.Name; + landParcelID = land.LandData.GlobalID; + } + else + { + m_log.WarnFormat( + "[PROFILES]: PickInfoUpdate found no parcel info at {0},{1} in {2}", + avaPos.X, avaPos.Y, p.Scene.Name); + } + + + pick.PickId = pickID; + pick.CreatorId = creatorID; + pick.TopPick = topPick; + pick.Name = name; + pick.Desc = desc; + pick.ParcelId = landParcelID; + pick.SnapshotId = snapshotID; + pick.ParcelName = landParcelName; + pick.SimName = remoteClient.Scene.RegionInfo.RegionName; + pick.Gatekeeper = MyGatekeeper; + pick.GlobalPos = posGlobal.ToString(); + pick.SortOrder = sortOrder; + pick.Enabled = enabled; + + object Pick = (object)pick; + if(!rpc.JsonRpcRequest(ref Pick, "picks_update", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error updating pick", false); + return; + } + + m_log.DebugFormat("[PROFILES]: Finish PickInfoUpdate {0} {1}", pick.Name, pick.PickId.ToString()); + } + + /// + /// Delete a Pick + /// + /// + /// Remote client. + /// + /// + /// Query pick I. + /// + public void PickDelete(IClientAPI remoteClient, UUID queryPickID) + { + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + OSDMap parameters= new OSDMap(); + parameters.Add("pickId", OSD.FromUUID(queryPickID)); + OSD Params = (OSD)parameters; + if(!rpc.JsonRpcRequest(ref Params, "picks_delete", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error picks delete", false); + return; + } + } + #endregion Picks + + #region Notes + /// + /// Handles the avatar notes request. + /// + /// + /// Sender. + /// + /// + /// Method. + /// + /// + /// Arguments. + /// + public void NotesRequest(Object sender, string method, List args) + { + UserProfileNotes note = new UserProfileNotes(); + + if (!(sender is IClientAPI)) + return; + + IClientAPI remoteClient = (IClientAPI)sender; + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + note.UserId = remoteClient.AgentId; + UUID.TryParse(args[0], out note.TargetId); + + object Note = (object)note; + if(!rpc.JsonRpcRequest(ref Note, "avatarnotesrequest", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes); + return; + } + note = (UserProfileNotes) Note; + + remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes); + } + + /// + /// Avatars the notes update. + /// + /// + /// Remote client. + /// + /// + /// Query target I. + /// + /// + /// Query notes. + /// + public void NotesUpdate(IClientAPI remoteClient, UUID queryTargetID, string queryNotes) + { + UserProfileNotes note = new UserProfileNotes(); + + note.UserId = remoteClient.AgentId; + note.TargetId = queryTargetID; + note.Notes = queryNotes; + + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + object Note = note; + if(!rpc.JsonRpcRequest(ref Note, "avatar_notes_update", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error updating note", false); + return; + } + } + #endregion Notes + + #region User Preferences + /// + /// Updates the user preferences. + /// + /// + /// Im via email. + /// + /// + /// Visible. + /// + /// + /// Remote client. + /// + public void UpdateUserPreferences(bool imViaEmail, bool visible, IClientAPI remoteClient) + { + UserPreferences pref = new UserPreferences(); + + pref.UserId = remoteClient.AgentId; + pref.IMViaEmail = imViaEmail; + pref.Visible = visible; + + string serverURI = string.Empty; + bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + object Pref = pref; + if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_update", serverURI, UUID.Random().ToString())) + { + m_log.InfoFormat("[PROFILES]: UserPreferences update error"); + remoteClient.SendAgentAlertMessage("Error updating preferences", false); + return; + } + } + + /// + /// Users the preferences request. + /// + /// + /// Remote client. + /// + public void UserPreferencesRequest(IClientAPI remoteClient) + { + UserPreferences pref = new UserPreferences(); + + pref.UserId = remoteClient.AgentId; + + string serverURI = string.Empty; + bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + + object Pref = (object)pref; + if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_request", serverURI, UUID.Random().ToString())) + { +// m_log.InfoFormat("[PROFILES]: UserPreferences request error"); +// remoteClient.SendAgentAlertMessage("Error requesting preferences", false); + return; + } + pref = (UserPreferences) Pref; + + remoteClient.SendUserInfoReply(pref.IMViaEmail, pref.Visible, pref.EMail); + + } + #endregion User Preferences + + #region Avatar Properties + /// + /// Update the avatars interests . + /// + /// + /// Remote client. + /// + /// + /// Wantmask. + /// + /// + /// Wanttext. + /// + /// + /// Skillsmask. + /// + /// + /// Skillstext. + /// + /// + /// Languages. + /// + public void AvatarInterestsUpdate(IClientAPI remoteClient, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages) + { + UserProfileProperties prop = new UserProfileProperties(); + + prop.UserId = remoteClient.AgentId; + prop.WantToMask = (int)wantmask; + prop.WantToText = wanttext; + prop.SkillsMask = (int)skillsmask; + prop.SkillsText = skillstext; + prop.Language = languages; + + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + object Param = prop; + if(!rpc.JsonRpcRequest(ref Param, "avatar_interests_update", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error updating interests", false); + return; + } + } + + public void RequestAvatarProperties(IClientAPI remoteClient, UUID avatarID) + { + if (String.IsNullOrEmpty(avatarID.ToString()) || String.IsNullOrEmpty(remoteClient.AgentId.ToString())) + { + // Looking for a reason that some viewers are sending null Id's + m_log.DebugFormat("[PROFILES]: This should not happen remoteClient.AgentId {0} - avatarID {1}", remoteClient.AgentId, avatarID); + return; + } + + // Can't handle NPC yet... + ScenePresence p = FindPresence(avatarID); + + if (null != p) + { + if (p.PresenceType == PresenceType.Npc) + return; + } + + string serverURI = string.Empty; + bool foreign = GetUserProfileServerURI(avatarID, out serverURI); + + UserAccount account = null; + Dictionary userInfo; + + if (!foreign) + { + account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, avatarID); + } + else + { + userInfo = new Dictionary(); + } + + Byte[] charterMember = new Byte[1]; + string born = String.Empty; + uint flags = 0x00; + + if (null != account) + { + if (account.UserTitle == "") + { + charterMember[0] = (Byte)((account.UserFlags & 0xf00) >> 8); + } + else + { + charterMember = Utils.StringToBytes(account.UserTitle); + } + + born = Util.ToDateTime(account.Created).ToString( + "M/d/yyyy", CultureInfo.InvariantCulture); + flags = (uint)(account.UserFlags & 0xff); + } + else + { + if (GetUserAccountData(avatarID, out userInfo) == true) + { + if ((string)userInfo["user_title"] == "") + { + charterMember[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8); + } + else + { + charterMember = Utils.StringToBytes((string)userInfo["user_title"]); + } + + int val_born = (int)userInfo["user_created"]; + born = Util.ToDateTime(val_born).ToString( + "M/d/yyyy", CultureInfo.InvariantCulture); + + // picky, picky + int val_flags = (int)userInfo["user_flags"]; + flags = (uint)(val_flags & 0xff); + } + } + + UserProfileProperties props = new UserProfileProperties(); + string result = string.Empty; + + props.UserId = avatarID; + + if (!GetProfileData(ref props, foreign, out result)) + { +// m_log.DebugFormat("Error getting profile for {0}: {1}", avatarID, result); + return; + } + + remoteClient.SendAvatarProperties(props.UserId, props.AboutText, born, charterMember , props.FirstLifeText, flags, + props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId); + + + remoteClient.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText, (uint)props.SkillsMask, + props.SkillsText, props.Language); + } + + /// + /// Updates the avatar properties. + /// + /// + /// Remote client. + /// + /// + /// New profile. + /// + public void AvatarPropertiesUpdate(IClientAPI remoteClient, UserProfileData newProfile) + { + if (remoteClient.AgentId == newProfile.ID) + { + UserProfileProperties prop = new UserProfileProperties(); + + prop.UserId = remoteClient.AgentId; + prop.WebUrl = newProfile.ProfileUrl; + prop.ImageId = newProfile.Image; + prop.AboutText = newProfile.AboutText; + prop.FirstLifeImageId = newProfile.FirstLifeImage; + prop.FirstLifeText = newProfile.FirstLifeAboutText; + + string serverURI = string.Empty; + GetUserProfileServerURI(remoteClient.AgentId, out serverURI); + + object Prop = prop; + + if(!rpc.JsonRpcRequest(ref Prop, "avatar_properties_update", serverURI, UUID.Random().ToString())) + { + remoteClient.SendAgentAlertMessage( + "Error updating properties", false); + return; + } + + RequestAvatarProperties(remoteClient, newProfile.ID); + } + } + + /// + /// Gets the profile data. + /// + /// + /// The profile data. + /// + bool GetProfileData(ref UserProfileProperties properties, bool foreign, out string message) + { + // Can't handle NPC yet... + ScenePresence p = FindPresence(properties.UserId); + + if (null != p) + { + if (p.PresenceType == PresenceType.Npc) + { + message = "Id points to NPC"; + return false; + } + } + + string serverURI = string.Empty; + GetUserProfileServerURI(properties.UserId, out serverURI); + + // This is checking a friend on the home grid + // Not HG friend + if (String.IsNullOrEmpty(serverURI)) + { + message = "No Presence - foreign friend"; + return false; + } + + object Prop = (object)properties; + if (!rpc.JsonRpcRequest(ref Prop, "avatar_properties_request", serverURI, UUID.Random().ToString())) + { + // If it's a foreign user then try again using OpenProfile, in case that's what the grid is using + bool secondChanceSuccess = false; + if (foreign) + { + try + { + OpenProfileClient client = new OpenProfileClient(serverURI); + if (client.RequestAvatarPropertiesUsingOpenProfile(ref properties)) + secondChanceSuccess = true; + } + catch (Exception e) + { + m_log.Debug( + string.Format( + "[PROFILES]: Request using the OpenProfile API for user {0} to {1} failed", + properties.UserId, serverURI), + e); + + // Allow the return 'message' to say "JsonRpcRequest" and not "OpenProfile", because + // the most likely reason that OpenProfile failed is that the remote server + // doesn't support OpenProfile, and that's not very interesting. + } + } + + if (!secondChanceSuccess) + { + message = string.Format("JsonRpcRequest for user {0} to {1} failed", properties.UserId, serverURI); + m_log.DebugFormat("[PROFILES]: {0}", message); + + return false; + } + // else, continue below + } + + properties = (UserProfileProperties)Prop; + + message = "Success"; + return true; + } + #endregion Avatar Properties + + #region Utils + bool GetImageAssets(UUID avatarId) + { + string profileServerURI = string.Empty; + string assetServerURI = string.Empty; + + bool foreign = GetUserProfileServerURI(avatarId, out profileServerURI); + + if(!foreign) + return true; + + assetServerURI = UserManagementModule.GetUserServerURL(avatarId, "AssetServerURI"); + + if(string.IsNullOrEmpty(profileServerURI) || string.IsNullOrEmpty(assetServerURI)) + return false; + + OSDMap parameters= new OSDMap(); + parameters.Add("avatarId", OSD.FromUUID(avatarId)); + OSD Params = (OSD)parameters; + if(!rpc.JsonRpcRequest(ref Params, "image_assets_request", profileServerURI, UUID.Random().ToString())) + { + return false; + } + + parameters = (OSDMap)Params; + + if (parameters.ContainsKey("result")) + { + OSDArray list = (OSDArray)parameters["result"]; + + foreach (OSD asset in list) + { + OSDString assetId = (OSDString)asset; + + Scene.AssetService.Get(string.Format("{0}/{1}", assetServerURI, assetId.AsString())); + } + return true; + } + else + { + m_log.ErrorFormat("[PROFILES]: Problematic response for image_assets_request from {0}", profileServerURI); + return false; + } + } + + /// + /// Gets the user account data. + /// + /// + /// The user profile data. + /// + /// + /// If set to true user I. + /// + /// + /// If set to true user info. + /// + bool GetUserAccountData(UUID userID, out Dictionary userInfo) + { + Dictionary info = new Dictionary(); + + if (UserManagementModule.IsLocalGridUser(userID)) + { + // Is local + IUserAccountService uas = Scene.UserAccountService; + UserAccount account = uas.GetUserAccount(Scene.RegionInfo.ScopeID, userID); + + info["user_flags"] = account.UserFlags; + info["user_created"] = account.Created; + + if (!String.IsNullOrEmpty(account.UserTitle)) + info["user_title"] = account.UserTitle; + else + info["user_title"] = ""; + + userInfo = info; + + return false; + } + else + { + // Is Foreign + string home_url = UserManagementModule.GetUserServerURL(userID, "HomeURI"); + + if (String.IsNullOrEmpty(home_url)) + { + info["user_flags"] = 0; + info["user_created"] = 0; + info["user_title"] = "Unavailable"; + + userInfo = info; + return true; + } + + UserAgentServiceConnector uConn = new UserAgentServiceConnector(home_url); + + Dictionary account; + try + { + account = uConn.GetUserInfo(userID); + } + catch (Exception e) + { + m_log.Debug("[PROFILES]: GetUserInfo call failed ", e); + account = new Dictionary(); + } + + if (account.Count > 0) + { + if (account.ContainsKey("user_flags")) + info["user_flags"] = account["user_flags"]; + else + info["user_flags"] = ""; + + if (account.ContainsKey("user_created")) + info["user_created"] = account["user_created"]; + else + info["user_created"] = ""; + + info["user_title"] = "HG Visitor"; + } + else + { + info["user_flags"] = 0; + info["user_created"] = 0; + info["user_title"] = "HG Visitor"; + } + userInfo = info; + return true; + } + } + + /// + /// Gets the user gatekeeper server URI. + /// + /// + /// The user gatekeeper server URI. + /// + /// + /// If set to true user URI. + /// + /// + /// If set to true server URI. + /// + bool GetUserGatekeeperURI(UUID userID, out string serverURI) + { + bool local; + local = UserManagementModule.IsLocalGridUser(userID); + + if (!local) + { + serverURI = UserManagementModule.GetUserServerURL(userID, "GatekeeperURI"); + // Is Foreign + return true; + } + else + { + serverURI = MyGatekeeper; + // Is local + return false; + } + } + + /// + /// Gets the user profile server UR. + /// + /// + /// The user profile server UR. + /// + /// + /// If set to true user I. + /// + /// + /// If set to true server UR. + /// + bool GetUserProfileServerURI(UUID userID, out string serverURI) + { + bool local; + local = UserManagementModule.IsLocalGridUser(userID); + + if (!local) + { + serverURI = UserManagementModule.GetUserServerURL(userID, "ProfileServerURI"); + // Is Foreign + return true; + } + else + { + serverURI = ProfileServerUri; + // Is local + return false; + } + } + + /// + /// Finds the presence. + /// + /// + /// The presence. + /// + /// + /// Client I. + /// + ScenePresence FindPresence(UUID clientID) + { + ScenePresence p; + + p = Scene.GetScenePresence(clientID); + if (p != null && !p.IsChildAgent) + return p; + + return null; + } + #endregion Util + } +} 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 + +} diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs index e0921ad..ea5b34e 100644 --- a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs +++ b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs @@ -31,11 +31,13 @@ using System.Reflection; using log4net; using Nini.Config; using OpenMetaverse; +using OpenMetaverse.StructuredData; using Mono.Addins; using OpenSim.Framework; using OpenSim.Region.CoreModules.World.WorldMap; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; using GridRegion = OpenSim.Services.Interfaces.GridRegion; namespace OpenSim.Region.CoreModules.Hypergrid @@ -48,20 +50,63 @@ namespace OpenSim.Region.CoreModules.Hypergrid // Remember the map area that each client has been exposed to in this region private Dictionary> m_SeenMapBlocks = new Dictionary>(); + private string m_MapImageServerURL = string.Empty; + + private IUserManagement m_UserManagement; + #region INonSharedRegionModule Members - public override void Initialise(IConfigSource config) + public override void Initialise(IConfigSource source) { - IConfig startupConfig = config.Configs["Startup"]; - if (startupConfig.GetString("WorldMapModule", "WorldMap") == "HGWorldMap") + if (Util.GetConfigVarFromSections( + source, "WorldMapModule", new string[] { "Map", "Startup" }, "WorldMap") == "HGWorldMap") + { m_Enabled = true; + + m_MapImageServerURL = Util.GetConfigVarFromSections(source, "MapTileURL", new string[] {"LoginService", "HGWorldMap", "SimulatorFeatures"}); + + if (!string.IsNullOrEmpty(m_MapImageServerURL)) + { + m_MapImageServerURL = m_MapImageServerURL.Trim(); + if (!m_MapImageServerURL.EndsWith("/")) + m_MapImageServerURL = m_MapImageServerURL + "/"; + } + + + } } public override void AddRegion(Scene scene) { + if (!m_Enabled) + return; + base.AddRegion(scene); - scene.EventManager.OnClientClosed += new EventManager.ClientClosed(EventManager_OnClientClosed); + scene.EventManager.OnClientClosed += EventManager_OnClientClosed; + } + + public override void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + base.RegionLoaded(scene); + ISimulatorFeaturesModule featuresModule = m_scene.RequestModuleInterface(); + + if (featuresModule != null) + featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest; + + m_UserManagement = m_scene.RequestModuleInterface(); + + } + + public override void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.EventManager.OnClientClosed -= EventManager_OnClientClosed; } public override string Name @@ -82,10 +127,11 @@ namespace OpenSim.Region.CoreModules.Hypergrid foreach (MapBlockData b in mapBlocks) { b.Name = string.Empty; - b.Access = 254; // means 'simulator is offline'. We need this because the viewer ignores 255's + // Set 'simulator is offline'. We need this because the viewer ignores SimAccess.Unknown (255) + b.Access = (byte)SimAccess.Down; } - m_log.DebugFormat("[HG MAP]: Reseting {0} blocks", mapBlocks.Count); + m_log.DebugFormat("[HG MAP]: Resetting {0} blocks", mapBlocks.Count); sp.ControllingClient.SendMapBlock(mapBlocks, 0); m_SeenMapBlocks.Remove(clientID); } @@ -115,6 +161,20 @@ namespace OpenSim.Region.CoreModules.Hypergrid return mapBlocks; } + private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) + { + if (m_UserManagement != null && !string.IsNullOrEmpty(m_MapImageServerURL) && !m_UserManagement.IsLocalGridUser(agentID)) + { + OSD extras = new OSDMap(); + if (features.ContainsKey("OpenSimExtras")) + extras = features["OpenSimExtras"]; + else + features["OpenSimExtras"] = extras; + + ((OSDMap)extras)["map-server-url"] = m_MapImageServerURL; + + } + } } class MapArea diff --git a/OpenSim/Region/CoreModules/Properties/AssemblyInfo.cs b/OpenSim/Region/CoreModules/Properties/AssemblyInfo.cs index 5a8c4a2..63e3c92 100644 --- a/OpenSim/Region/CoreModules/Properties/AssemblyInfo.cs +++ b/OpenSim/Region/CoreModules/Properties/AssemblyInfo.cs @@ -30,9 +30,9 @@ using Mono.Addins; // Build Number // Revision // -[assembly: AssemblyVersion("0.7.5.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.8.3.*")] -[assembly: Addin("OpenSim.Region.CoreModules", "0.1")] -[assembly: AddinDependency("OpenSim", "0.5")] + +[assembly: Addin("OpenSim.Region.CoreModules", OpenSim.VersionInfo.VersionNumber)] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] diff --git a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs index 9d77b19..a686a4d 100644 --- a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs @@ -514,9 +514,11 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture scene.RegionInfo.RegionID.ToString()); asset.Data = assetData; asset.Description = String.Format("URL image : {0}", Url); - asset.Local = false; + if (asset.Description.Length > 128) + asset.Description = asset.Description.Substring(0, 128); + asset.Local = true; // dynamic images aren't saved in the assets server asset.Temporary = ((Disp & DISP_TEMP) != 0); - scene.AssetService.Store(asset); + scene.AssetService.Store(asset); // this will only save the asset in the local asset cache IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface(); if (cacheLayerDecode != null) diff --git a/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs index d943b20..4e7ad75 100644 --- a/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs @@ -213,8 +213,8 @@ namespace OpenSim.Region.CoreModules.Scripting.EmailModules if (part != null) { ObjectRegionName = s.RegionInfo.RegionName; - uint localX = (s.RegionInfo.RegionLocX * (int)Constants.RegionSize); - uint localY = (s.RegionInfo.RegionLocY * (int)Constants.RegionSize); + uint localX = s.RegionInfo.WorldLocX; + uint localY = s.RegionInfo.WorldLocY; ObjectRegionName = ObjectRegionName + " (" + localX + ", " + localY + ")"; return part; } diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs index a676971..9dfeb96 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -28,12 +28,15 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Mail; using System.Net.Security; +using System.Reflection; using System.Text; using System.Threading; using System.Security.Cryptography.X509Certificates; +using log4net; using Nini.Config; using OpenMetaverse; using OpenSim.Framework; @@ -91,10 +94,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")] public class HttpRequestModule : ISharedRegionModule, IHttpRequestModule { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private object HttpListLock = new object(); private int httpTimeout = 30000; private string m_name = "HttpScriptRequests"; + private OutboundUrlFilter m_outboundUrlFilter; private string m_proxyurl = ""; private string m_proxyexcepts = ""; @@ -132,10 +138,12 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return false; // Check for policy and execute it if defined +#pragma warning disable 0618 if (ServicePointManager.CertificatePolicy != null) { return ServicePointManager.CertificatePolicy.CheckValidationResult (sp, certificate, Request, 0); } +#pragma warning restore 0618 return true; } @@ -153,7 +161,9 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return UUID.Zero; } - public UUID StartHttpRequest(uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body) + public UUID StartHttpRequest( + uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body, + out HttpInitialRequestStatus status) { UUID reqID = UUID.Random(); HttpRequestClass htc = new HttpRequestClass(); @@ -187,10 +197,50 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest case (int)HttpRequestConstants.HTTP_VERIFY_CERT: htc.HttpVerifyCert = (int.Parse(parms[i + 1]) != 0); break; + + case (int)HttpRequestConstants.HTTP_VERBOSE_THROTTLE: + + // TODO implement me + break; + + case (int)HttpRequestConstants.HTTP_CUSTOM_HEADER: + //Parameters are in pairs and custom header takes + //arguments in pairs so adjust for header marker. + ++i; + + //Maximum of 8 headers are allowed based on the + //Second Life documentation for llHTTPRequest. + for (int count = 1; count <= 8; ++count) + { + //Not enough parameters remaining for a header? + if (parms.Length - i < 2) + break; + + //Have we reached the end of the list of headers? + //End is marked by a string with a single digit. + //We already know we have at least one parameter + //so it is safe to do this check at top of loop. + if (Char.IsDigit(parms[i][0])) + break; + + if (htc.HttpCustomHeaders == null) + htc.HttpCustomHeaders = new List(); + + htc.HttpCustomHeaders.Add(parms[i]); + htc.HttpCustomHeaders.Add(parms[i+1]); + + i += 2; + } + break; + + case (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE: + htc.HttpPragmaNoCache = (int.Parse(parms[i + 1]) != 0); + break; } } } - + + htc.RequestModule = this; htc.LocalID = localID; htc.ItemID = itemID; htc.Url = url; @@ -201,28 +251,68 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest htc.proxyurl = m_proxyurl; htc.proxyexcepts = m_proxyexcepts; + // Same number as default HttpWebRequest.MaximumAutomaticRedirections + htc.MaxRedirects = 50; + + if (StartHttpRequest(htc)) + { + status = HttpInitialRequestStatus.OK; + return htc.ReqID; + } + else + { + status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER; + return UUID.Zero; + } + } + + /// + /// Would a caller to this module be allowed to make a request to the given URL? + /// + /// + public bool CheckAllowed(Uri url) + { + return m_outboundUrlFilter.CheckAllowed(url); + } + + public bool StartHttpRequest(HttpRequestClass req) + { + if (!CheckAllowed(new Uri(req.Url))) + return false; + lock (HttpListLock) { - m_pendingRequests.Add(reqID, htc); + m_pendingRequests.Add(req.ReqID, req); } - htc.Process(); + req.Process(); - return reqID; + return true; } - public void StopHttpRequest(uint m_localID, UUID m_itemID) + public void StopHttpRequestsForScript(UUID id) { if (m_pendingRequests != null) { + List keysToRemove = null; + lock (HttpListLock) { - HttpRequestClass tmpReq; - if (m_pendingRequests.TryGetValue(m_itemID, out tmpReq)) + foreach (HttpRequestClass req in m_pendingRequests.Values) { - tmpReq.Stop(); - m_pendingRequests.Remove(m_itemID); + if (req.ItemID == id) + { + req.Stop(); + + if (keysToRemove == null) + keysToRemove = new List(); + + keysToRemove.Add(req.ReqID); + } } + + if (keysToRemove != null) + keysToRemove.ForEach(keyToRemove => m_pendingRequests.Remove(keyToRemove)); } } } @@ -240,19 +330,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest { lock (HttpListLock) { - foreach (UUID luid in m_pendingRequests.Keys) + foreach (HttpRequestClass req in m_pendingRequests.Values) { - HttpRequestClass tmpReq; - - if (m_pendingRequests.TryGetValue(luid, out tmpReq)) - { - if (tmpReq.Finished) - { - return tmpReq; - } - } + if (req.Finished) + return req; } } + return null; } @@ -279,6 +363,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config); + m_pendingRequests = new Dictionary(); } @@ -321,16 +407,27 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest #endregion } - public class HttpRequestClass: IServiceRequest + public class HttpRequestClass : IServiceRequest { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + // Constants for parameters // public const int HTTP_BODY_MAXLENGTH = 2; // public const int HTTP_METHOD = 0; // public const int HTTP_MIMETYPE = 1; // public const int HTTP_VERIFY_CERT = 3; + // public const int HTTP_VERBOSE_THROTTLE = 4; + // public const int HTTP_CUSTOM_HEADER = 5; + // public const int HTTP_PRAGMA_NO_CACHE = 6; + + /// + /// Module that made this request. + /// + public HttpRequestModule RequestModule { get; set; } + private bool _finished; public bool Finished - { + { get { return _finished; } } // public int HttpBodyMaxLen = 2048; // not implemented @@ -340,11 +437,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest public string HttpMIMEType = "text/plain;charset=utf-8"; public int HttpTimeout; public bool HttpVerifyCert = true; - private Thread httpThread; + //public bool HttpVerboseThrottle = true; // not implemented + public List HttpCustomHeaders = null; + public bool HttpPragmaNoCache = true; // Request info private UUID _itemID; - public UUID ItemID + public UUID ItemID { get { return _itemID; } set { _itemID = value; } @@ -358,9 +457,20 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest public DateTime Next; public string proxyurl; public string proxyexcepts; + + /// + /// Number of HTTP redirects that this request has been through. + /// + public int Redirects { get; private set; } + + /// + /// Maximum number of HTTP redirects allowed for this request. + /// + public int MaxRedirects { get; set; } + public string OutboundBody; private UUID _reqID; - public UUID ReqID + public UUID ReqID { get { return _reqID; } set { _reqID = value; } @@ -374,34 +484,19 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest public void Process() { - httpThread = new Thread(SendRequest); - httpThread.Name = "HttpRequestThread"; - httpThread.Priority = ThreadPriority.BelowNormal; - httpThread.IsBackground = true; - _finished = false; - httpThread.Start(); + SendRequest(); } - /* - * TODO: More work on the response codes. Right now - * returning 200 for success or 499 for exception - */ - public void SendRequest() { - HttpWebResponse response = null; - StringBuilder sb = new StringBuilder(); - byte[] buf = new byte[8192]; - string tempString = null; - int count = 0; - try { - Request = (HttpWebRequest) WebRequest.Create(Url); + Request = (HttpWebRequest)WebRequest.Create(Url); + Request.AllowAutoRedirect = false; Request.Method = HttpMethod; Request.ContentType = HttpMIMEType; - if(!HttpVerifyCert) + if (!HttpVerifyCert) { // We could hijack Connection Group Name to identify // a desired security exception. But at the moment we'll use a dummy header instead. @@ -412,41 +507,57 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest // { // Request.ConnectionGroupName="Verify"; // } - if (proxyurl != null && proxyurl.Length > 0) + + if (!HttpPragmaNoCache) + { + Request.Headers.Add("Pragma", "no-cache"); + } + + if (HttpCustomHeaders != null) + { + for (int i = 0; i < HttpCustomHeaders.Count; i += 2) + Request.Headers.Add(HttpCustomHeaders[i], + HttpCustomHeaders[i+1]); + } + + if (!string.IsNullOrEmpty(proxyurl)) { - if (proxyexcepts != null && proxyexcepts.Length > 0) + if (!string.IsNullOrEmpty(proxyexcepts)) { string[] elist = proxyexcepts.Split(';'); Request.Proxy = new WebProxy(proxyurl, true, elist); - } - else + } + else { Request.Proxy = new WebProxy(proxyurl, true); } } - foreach (KeyValuePair entry in ResponseHeaders) - if (entry.Key.ToLower().Equals("user-agent")) - Request.UserAgent = entry.Value; - else - Request.Headers[entry.Key] = entry.Value; + if (ResponseHeaders != null) + { + foreach (KeyValuePair entry in ResponseHeaders) + if (entry.Key.ToLower().Equals("user-agent") && Request is HttpWebRequest) + ((HttpWebRequest)Request).UserAgent = entry.Value; + else + Request.Headers[entry.Key] = entry.Value; + } // Encode outbound data - if (OutboundBody.Length > 0) + if (!string.IsNullOrEmpty(OutboundBody)) { byte[] data = Util.UTF8.GetBytes(OutboundBody); Request.ContentLength = data.Length; - Stream bstream = Request.GetRequestStream(); - bstream.Write(data, 0, data.Length); - bstream.Close(); + using (Stream bstream = Request.GetRequestStream()) + bstream.Write(data, 0, data.Length); } - Request.Timeout = HttpTimeout; try { - // execute the request - response = (HttpWebResponse) Request.GetResponse(); + IAsyncResult result = (IAsyncResult)Request.BeginGetResponse(ResponseCallback, null); + + ThreadPool.RegisterWaitForSingleObject( + result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), null, HttpTimeout, true); } catch (WebException e) { @@ -454,57 +565,127 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest { throw; } - response = (HttpWebResponse)e.Response; + + HttpWebResponse response = (HttpWebResponse)e.Response; + + Status = (int)response.StatusCode; + ResponseBody = response.StatusDescription; + _finished = true; } + } + catch (Exception e) + { +// m_log.Debug( +// string.Format("[SCRIPTS HTTP REQUESTS]: Exception on request to {0} for {1} ", Url, ItemID), e); - Status = (int)response.StatusCode; + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = e.Message; + _finished = true; + } + } - Stream resStream = response.GetResponseStream(); + private void ResponseCallback(IAsyncResult ar) + { + HttpWebResponse response = null; - do + try + { + try { - // fill the buffer with data - count = resStream.Read(buf, 0, buf.Length); - - // make sure we read some data - if (count != 0) + response = (HttpWebResponse)Request.EndGetResponse(ar); + } + catch (WebException e) + { + if (e.Status != WebExceptionStatus.ProtocolError) { - // translate from bytes to ASCII text - tempString = Util.UTF8.GetString(buf, 0, count); - - // continue building the string - sb.Append(tempString); + throw; } - } while (count > 0); // any more data to read? - ResponseBody = sb.ToString(); + response = (HttpWebResponse)e.Response; + } + + Status = (int)response.StatusCode; + + using (Stream stream = response.GetResponseStream()) + { + StreamReader reader = new StreamReader(stream, Encoding.UTF8); + ResponseBody = reader.ReadToEnd(); + } } catch (Exception e) { Status = (int)OSHttpStatusCode.ClientErrorJoker; ResponseBody = e.Message; - _finished = true; - return; +// m_log.Debug( +// string.Format("[SCRIPTS HTTP REQUESTS]: Exception on response to {0} for {1} ", Url, ItemID), e); } finally { if (response != null) response.Close(); + + // We need to resubmit + if ( + (Status == (int)HttpStatusCode.MovedPermanently + || Status == (int)HttpStatusCode.Found + || Status == (int)HttpStatusCode.SeeOther + || Status == (int)HttpStatusCode.TemporaryRedirect)) + { + if (Redirects >= MaxRedirects) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "Number of redirects exceeded max redirects"; + _finished = true; + } + else + { + string location = response.Headers["Location"]; + + if (location == null) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "HTTP redirect code but no location header"; + _finished = true; + } + else if (!RequestModule.CheckAllowed(new Uri(location))) + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = "URL from HTTP redirect blocked: " + location; + _finished = true; + } + else + { + Status = 0; + Url = response.Headers["Location"]; + Redirects++; + ResponseBody = null; + +// m_log.DebugFormat("Redirecting to [{0}]", Url); + + Process(); + } + } + } + else + { + _finished = true; + } } + } - _finished = true; + private void TimeoutCallback(object state, bool timedOut) + { + if (timedOut) + Request.Abort(); } public void Stop() { - try - { - httpThread.Abort(); - } - catch (Exception) - { - } +// m_log.DebugFormat("[SCRIPTS HTTP REQUESTS]: Stopping request to {0} for {1} ", Url, ItemID); + + if (Request != null) + Request.Abort(); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs new file mode 100644 index 0000000..d22487e --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.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.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using log4net.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Scripting.HttpRequest; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Scripting.HttpRequest.Tests +{ + class TestWebRequestCreate : IWebRequestCreate + { + public TestWebRequest NextRequest { get; set; } + + public WebRequest Create(Uri uri) + { +// NextRequest.RequestUri = uri; + + return NextRequest; + +// return new TestWebRequest(new SerializationInfo(typeof(TestWebRequest), new FormatterConverter()), new StreamingContext()); + } + } + + class TestWebRequest : WebRequest + { + public override string ContentType { get; set; } + public override string Method { get; set; } + + public Func OnEndGetResponse { get; set; } + + public TestWebRequest() : base() + { +// Console.WriteLine("created"); + } + +// public TestWebRequest(SerializationInfo serializationInfo, StreamingContext streamingContext) +// : base(serializationInfo, streamingContext) +// { +// Console.WriteLine("created"); +// } + + public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { +// Console.WriteLine("bish"); + TestAsyncResult tasr = new TestAsyncResult(); + callback(tasr); + + return tasr; + } + + public override WebResponse EndGetResponse(IAsyncResult asyncResult) + { +// Console.WriteLine("bosh"); + return OnEndGetResponse(asyncResult); + } + } + + class TestHttpWebResponse : HttpWebResponse + { + public string Response { get; set; } + +#pragma warning disable 0618 + public TestHttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base(serializationInfo, streamingContext) {} +#pragma warning restore 0618 + + public override Stream GetResponseStream() + { + return new MemoryStream(Encoding.UTF8.GetBytes(Response)); + } + } + + class TestAsyncResult : IAsyncResult + { + WaitHandle m_wh = new ManualResetEvent(true); + + object IAsyncResult.AsyncState + { + get { + throw new System.NotImplementedException (); + } + } + + WaitHandle IAsyncResult.AsyncWaitHandle + { + get { return m_wh; } + } + + bool IAsyncResult.CompletedSynchronously + { + get { return false; } + } + + bool IAsyncResult.IsCompleted + { + get { return true; } + } + } + + /// + /// Test script http request code. + /// + /// + /// This class uses some very hacky workarounds in order to mock HttpWebResponse which are Mono dependent (though + /// alternative code can be written to make this work for Windows). However, the value of being able to + /// regression test this kind of code is very high. + /// + [TestFixture] + public class ScriptsHttpRequestsTests : OpenSimTestCase + { + /// + /// Test what happens when we get a 404 response from a call. + /// +// [Test] + public void Test404Response() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + if (!Util.IsPlatformMono) + Assert.Ignore("Ignoring test since can only currently run on Mono"); + + string rawResponse = "boom"; + + TestWebRequestCreate twrc = new TestWebRequestCreate(); + + TestWebRequest twr = new TestWebRequest(); + //twr.OnEndGetResponse += ar => new TestHttpWebResponse(null, new StreamingContext()); + twr.OnEndGetResponse += ar => + { + SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new FormatterConverter()); + StreamingContext sc = new StreamingContext(); +// WebHeaderCollection headers = new WebHeaderCollection(); +// si.AddValue("m_HttpResponseHeaders", headers); + si.AddValue("uri", new Uri("test://arrg")); +// si.AddValue("m_Certificate", null); + si.AddValue("version", HttpVersion.Version11); + si.AddValue("statusCode", HttpStatusCode.NotFound); + si.AddValue("contentLength", 0); + si.AddValue("method", "GET"); + si.AddValue("statusDescription", "Not Found"); + si.AddValue("contentType", null); + si.AddValue("cookieCollection", new CookieCollection()); + + TestHttpWebResponse thwr = new TestHttpWebResponse(si, sc); + thwr.Response = rawResponse; + + throw new WebException("no message", null, WebExceptionStatus.ProtocolError, thwr); + }; + + twrc.NextRequest = twr; + + WebRequest.RegisterPrefix("test", twrc); + HttpRequestClass hr = new HttpRequestClass(); + hr.Url = "test://something"; + hr.SendRequest(); + + Assert.That(hr.Status, Is.EqualTo((int)HttpStatusCode.NotFound)); + Assert.That(hr.ResponseBody, Is.EqualTo(rawResponse)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs index a654477..99a3122 100644 --- a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs @@ -117,20 +117,21 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp /// private Dictionary m_UrlMap = new Dictionary(); - /// - /// Maximum number of external urls that can be set up by this module. - /// - private int m_TotalUrls = 100; - - private uint https_port = 0; + private uint m_HttpsPort = 0; private IHttpServer m_HttpServer = null; private IHttpServer m_HttpsServer = null; - private string m_ExternalHostNameForLSL = ""; - public string ExternalHostNameForLSL - { - get { return m_ExternalHostNameForLSL; } - } + public string ExternalHostNameForLSL { get; private set; } + + /// + /// The default maximum number of urls + /// + public const int DefaultTotalUrls = 100; + + /// + /// Maximum number of external urls that can be set up by this module. + /// + public int TotalUrls { get; set; } public Type ReplaceableInterface { @@ -144,16 +145,27 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp public void Initialise(IConfigSource config) { - m_ExternalHostNameForLSL = config.Configs["Network"].GetString("ExternalHostNameForLSL", System.Environment.MachineName); - bool ssl_enabled = config.Configs["Network"].GetBoolean("https_listener",false); + IConfig networkConfig = config.Configs["Network"]; + + if (networkConfig != null) + { + ExternalHostNameForLSL = config.Configs["Network"].GetString("ExternalHostNameForLSL", null); + + bool ssl_enabled = config.Configs["Network"].GetBoolean("https_listener", false); + + if (ssl_enabled) + m_HttpsPort = (uint)config.Configs["Network"].GetInt("https_port", (int)m_HttpsPort); + } - if (ssl_enabled) - https_port = (uint) config.Configs["Network"].GetInt("https_port",0); + if (ExternalHostNameForLSL == null) + ExternalHostNameForLSL = System.Environment.MachineName; IConfig llFunctionsConfig = config.Configs["LL-Functions"]; if (llFunctionsConfig != null) - m_TotalUrls = llFunctionsConfig.GetInt("max_external_urls_per_simulator", m_TotalUrls); + TotalUrls = llFunctionsConfig.GetInt("max_external_urls_per_simulator", DefaultTotalUrls); + else + TotalUrls = DefaultTotalUrls; } public void PostInitialise() @@ -169,9 +181,9 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp m_HttpServer = MainServer.Instance; // // We can use the https if it is enabled - if (https_port > 0) + if (m_HttpsPort > 0) { - m_HttpsServer = MainServer.GetHttpServer(https_port); + m_HttpsServer = MainServer.GetHttpServer(m_HttpsPort); } } @@ -204,12 +216,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (m_UrlMap) { - if (m_UrlMap.Count >= m_TotalUrls) + if (m_UrlMap.Count >= TotalUrls) { engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); return urlcode; } - string url = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + "/lslhttp/" + urlcode.ToString() + "/"; + string url = "http://" + ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + "/lslhttp/" + urlcode.ToString() + "/"; UrlData urlData = new UrlData(); urlData.hostID = host.UUID; @@ -223,9 +235,10 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp string uri = "/lslhttp/" + urlcode.ToString() + "/"; - m_HttpServer.AddPollServiceHTTPHandler( - uri, - new PollServiceEventArgs(HttpRequestHandler, HasEvents, GetEvents, NoEvents, urlcode)); + PollServiceEventArgs args + = new PollServiceEventArgs(HttpRequestHandler, uri, HasEvents, GetEvents, NoEvents, urlcode, 25000); + args.Type = PollServiceEventArgs.EventType.LslHttp; + m_HttpServer.AddPollServiceHTTPHandler(uri, args); m_log.DebugFormat( "[URL MODULE]: Set up incoming request url {0} for {1} in {2} {3}", @@ -249,12 +262,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (m_UrlMap) { - if (m_UrlMap.Count >= m_TotalUrls) + if (m_UrlMap.Count >= TotalUrls) { engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); return urlcode; } - string url = "https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + "/lslhttps/" + urlcode.ToString() + "/"; + string url = "https://" + ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + "/lslhttps/" + urlcode.ToString() + "/"; UrlData urlData = new UrlData(); urlData.hostID = host.UUID; @@ -268,9 +281,10 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp string uri = "/lslhttps/" + urlcode.ToString() + "/"; - m_HttpsServer.AddPollServiceHTTPHandler( - uri, - new PollServiceEventArgs(HttpRequestHandler, HasEvents, GetEvents, NoEvents, urlcode)); + PollServiceEventArgs args + = new PollServiceEventArgs(HttpRequestHandler, uri, HasEvents, GetEvents, NoEvents, urlcode, 25000); + args.Type = PollServiceEventArgs.EventType.LslHttp; + m_HttpsServer.AddPollServiceHTTPHandler(uri, args); m_log.DebugFormat( "[URL MODULE]: Set up incoming secure request url {0} for {1} in {2} {3}", @@ -328,8 +342,22 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp if (m_RequestMap.ContainsKey(request)) { UrlData urlData = m_RequestMap[request]; + string responseBody = body; + if (urlData.requests[request].responseType.Equals("text/plain")) + { + string value; + if (urlData.requests[request].headers.TryGetValue("user-agent", out value)) + { + if (value != null && value.IndexOf("MSIE") >= 0) + { + // wrap the html escaped response if the target client is IE + // It ignores "text/plain" if the body is html + responseBody = "" + System.Web.HttpUtility.HtmlEncode(body) + ""; + } + } + } urlData.requests[request].responseCode = status; - urlData.requests[request].responseBody = body; + urlData.requests[request].responseBody = responseBody; //urlData.requests[request].ev.Set(); urlData.requests[request].requestDone =true; } @@ -363,7 +391,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp public int GetFreeUrls() { lock (m_UrlMap) - return m_TotalUrls - m_UrlMap.Count; + return TotalUrls - m_UrlMap.Count; } public void ScriptRemoved(UUID itemID) @@ -490,7 +518,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp } } - private Hashtable GetEvents(UUID requestID, UUID sessionID, string request) + private Hashtable GetEvents(UUID requestID, UUID sessionID) { Hashtable response; @@ -565,9 +593,9 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp string url; if (is_ssl) - url = "https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp; + url = "https://" + ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp; else - url = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp; + url = "http://" + ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp; // Avoid a race - the request URL may have been released via llRequestUrl() whilst this // request was being processed. @@ -642,4 +670,4 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp ScriptRemoved(itemID); } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs index 65737fa..d45962f 100644 --- a/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs @@ -32,6 +32,7 @@ using System.Net; using Nini.Config; using OpenMetaverse; using OpenMetaverse.Imaging; +using OpenSim.Framework; using OpenSim.Region.CoreModules.Scripting.DynamicTexture; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -50,6 +51,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL private Scene m_scene; private IDynamicTextureManager m_textureManager; + private OutboundUrlFilter m_outboundUrlFilter; private string m_proxyurl = ""; private string m_proxyexcepts = ""; @@ -88,8 +90,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL public bool AsyncConvertUrl(UUID id, string url, string extraParams) { - MakeHttpRequest(url, id); - return true; + return MakeHttpRequest(url, id); } public bool AsyncConvertData(UUID id, string bodyData, string extraParams) @@ -110,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL public void Initialise(IConfigSource config) { + m_outboundUrlFilter = new OutboundUrlFilter("Script dynamic texture image module", config); m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); } @@ -157,13 +159,17 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL #endregion - private void MakeHttpRequest(string url, UUID requestID) + private bool MakeHttpRequest(string url, UUID requestID) { - WebRequest request = HttpWebRequest.Create(url); + if (!m_outboundUrlFilter.CheckAllowed(new Uri(url))) + return false; + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.AllowAutoRedirect = false; - if (m_proxyurl != null && m_proxyurl.Length > 0) + if (!string.IsNullOrEmpty(m_proxyurl)) { - if (m_proxyexcepts != null && m_proxyexcepts.Length > 0) + if (!string.IsNullOrEmpty(m_proxyexcepts)) { string[] elist = m_proxyexcepts.Split(';'); request.Proxy = new WebProxy(m_proxyurl, true, elist); @@ -174,12 +180,14 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL } } - RequestState state = new RequestState((HttpWebRequest) request, requestID); + RequestState state = new RequestState(request, requestID); // IAsyncResult result = request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1)); state.TimeOfRequest = (int) t.TotalSeconds; + + return true; } private void HttpRequestReturn(IAsyncResult result) @@ -195,10 +203,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL Stream stream = null; byte[] imageJ2000 = new byte[0]; Size newSize = new Size(0, 0); + HttpWebResponse response = null; try { - HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); + response = (HttpWebResponse)request.EndGetResponse(result); if (response != null && response.StatusCode == HttpStatusCode.OK) { stream = response.GetResponseStream(); @@ -262,18 +271,32 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL finally { if (stream != null) - { stream.Close(); - } - } - m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}", - imageJ2000.Length, state.RequestID); + if (response != null) + response.Close(); - m_textureManager.ReturnData( - state.RequestID, - new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture( - request.RequestUri, null, imageJ2000, newSize, false)); + if ( + response.StatusCode == HttpStatusCode.MovedPermanently + || response.StatusCode == HttpStatusCode.Found + || response.StatusCode == HttpStatusCode.SeeOther + || response.StatusCode == HttpStatusCode.TemporaryRedirect) + { + string redirectedUrl = response.Headers["Location"]; + + MakeHttpRequest(redirectedUrl, state.RequestID); + } + else + { + m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}", + imageJ2000.Length, state.RequestID); + + m_textureManager.ReturnData( + state.RequestID, + new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture( + request.RequestUri, null, imageJ2000, newSize, false)); + } + } } #region Nested type: RequestState diff --git a/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs b/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs index f6e1d39..6da2222 100644 --- a/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs @@ -41,10 +41,11 @@ using System.Linq.Expressions; namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "ScriptModuleCommsModule")] - class ScriptModuleCommsModule : INonSharedRegionModule, IScriptModuleComms + public class ScriptModuleCommsModule : INonSharedRegionModule, IScriptModuleComms { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[MODULE COMMS]"; private Dictionary m_constants = new Dictionary(); @@ -148,7 +149,7 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms MethodInfo mi = GetMethodInfoFromType(target.GetType(), meth, true); if (mi == null) { - m_log.WarnFormat("[MODULE COMMANDS] Failed to register method {0}", meth); + m_log.WarnFormat("{0} Failed to register method {1}", LogHeader, meth); return; } @@ -163,9 +164,9 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms public void RegisterScriptInvocation(object target, MethodInfo mi) { - m_log.DebugFormat("[MODULE COMMANDS] Register method {0} from type {1}", mi.Name, (target is Type) ? ((Type)target).Name : target.GetType().Name); +// m_log.DebugFormat("[MODULE COMMANDS] Register method {0} from type {1}", mi.Name, (target is Type) ? ((Type)target).Name : target.GetType().Name); - Type delegateType; + Type delegateType = typeof(void); List typeArgs = mi.GetParameters() .Select(p => p.ParameterType) .ToList(); @@ -176,8 +177,16 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms } else { - typeArgs.Add(mi.ReturnType); - delegateType = Expression.GetFuncType(typeArgs.ToArray()); + try + { + typeArgs.Add(mi.ReturnType); + delegateType = Expression.GetFuncType(typeArgs.ToArray()); + } + catch (Exception e) + { + m_log.ErrorFormat("{0} Failed to create function signature. Most likely more than 5 parameters. Method={1}. Error={2}", + LogHeader, mi.Name, e); + } } Delegate fcall; @@ -323,7 +332,7 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms /// public void RegisterConstant(string cname, object value) { - m_log.DebugFormat("[MODULE COMMANDS] register constant <{0}> with value {1}",cname,value.ToString()); +// m_log.DebugFormat("[MODULE COMMANDS] register constant <{0}> with value {1}",cname,value.ToString()); lock (m_constants) { m_constants.Add(cname,value); diff --git a/OpenSim/Region/CoreModules/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs b/OpenSim/Region/CoreModules/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs index 41baccc..ed255bf 100644 --- a/OpenSim/Region/CoreModules/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs +++ b/OpenSim/Region/CoreModules/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs @@ -40,7 +40,6 @@ using OpenSim.Region.CoreModules.Scripting.VectorRender; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests { @@ -152,7 +151,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests TestHelpers.InMethod(); string dtText - = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://localhost/shouldnotexist.png"; + = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://0.0.0.0/shouldnotexist.png"; SetupScene(false); SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); @@ -307,7 +306,7 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests TestHelpers.InMethod(); string dtText - = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://localhost/shouldnotexist.png"; + = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://0.0.0.0/shouldnotexist.png"; SetupScene(true); SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); diff --git a/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs b/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs index 689e8a7..4cecd85 100644 --- a/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs @@ -516,6 +516,9 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender foreach (string line in GetLines(data, dataDelim)) { string nextLine = line.Trim(); + +// m_log.DebugFormat("[VECTOR RENDER MODULE]: Processing line '{0}'", nextLine); + //replace with switch, or even better, do some proper parsing if (nextLine.StartsWith("MoveTo")) { @@ -829,6 +832,8 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender float y = Convert.ToSingle(yVal, CultureInfo.InvariantCulture); PointF point = new PointF(x, y); points[i / 2] = point; + +// m_log.DebugFormat("[VECTOR RENDER MODULE]: Got point {0}", points[i / 2]); } } } @@ -838,13 +843,17 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender try { WebRequest request = HttpWebRequest.Create(url); -//Ckrinke: Comment out for now as 'str' is unused. Bring it back into play later when it is used. -//Ckrinke Stream str = null; - HttpWebResponse response = (HttpWebResponse)(request).GetResponse(); - if (response.StatusCode == HttpStatusCode.OK) + + using (HttpWebResponse response = (HttpWebResponse)(request).GetResponse()) { - Bitmap image = new Bitmap(response.GetResponseStream()); - return image; + if (response.StatusCode == HttpStatusCode.OK) + { + using (Stream s = response.GetResponseStream()) + { + Bitmap image = new Bitmap(s); + return image; + } + } } } catch { } diff --git a/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs index 2c2c99c..3484387 100644 --- a/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs @@ -379,15 +379,9 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm if (sp.IsChildAgent) return; - // Send message to the avatar. // Channel zero only goes to the avatar - // non zero channel messages only go to the attachments - if (channel == 0) - { - m_scene.SimChatToAgent(target, Utils.StringToBytes(msg), - pos, name, id, false); - } - else + // non zero channel messages only go to the attachments of the avatar. + if (channel != 0) { List attachments = sp.GetAttachments(); if (attachments.Count == 0) diff --git a/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs index 385f5ad..87f4277 100644 --- a/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs @@ -36,6 +36,7 @@ using Nini.Config; using Nwc.XmlRpc; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; @@ -111,13 +112,15 @@ namespace OpenSim.Region.CoreModules.Scripting.XMLRPC m_rpcPending = new Dictionary(); m_rpcPendingResponses = new Dictionary(); m_pendingSRDResponses = new Dictionary(); - - try - { - m_remoteDataPort = config.Configs["XMLRPC"].GetInt("XmlRpcPort", m_remoteDataPort); - } - catch (Exception) + if (config.Configs["XMLRPC"] != null) { + try + { + m_remoteDataPort = config.Configs["XMLRPC"].GetInt("XmlRpcPort", m_remoteDataPort); + } + catch (Exception) + { + } } } @@ -654,12 +657,8 @@ namespace OpenSim.Region.CoreModules.Scripting.XMLRPC public void Process() { - httpThread = new Thread(SendRequest); - httpThread.Name = "HttpRequestThread"; - httpThread.Priority = ThreadPriority.BelowNormal; - httpThread.IsBackground = true; _finished = false; - httpThread.Start(); + httpThread = WorkManager.StartThread(SendRequest, "HttpRequestThread", ThreadPriority.BelowNormal, true, false); } /* @@ -675,7 +674,7 @@ namespace OpenSim.Region.CoreModules.Scripting.XMLRPC // if not, use as method name UUID parseUID; string mName = "llRemoteData"; - if ((Channel != null) && (Channel != "")) + if (!string.IsNullOrEmpty(Channel)) if (!UUID.TryParse(Channel, out parseUID)) mName = Channel; else @@ -731,13 +730,19 @@ namespace OpenSim.Region.CoreModules.Scripting.XMLRPC } _finished = true; + + Watchdog.RemoveThread(); } public void Stop() { try { - httpThread.Abort(); + if (httpThread != null) + { + Watchdog.AbortThread(httpThread.ManagedThreadId); + httpThread = null; + } } catch (Exception) { diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsIn/UserProfiles/LocalUserProfilesServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsIn/UserProfiles/LocalUserProfilesServiceConnector.cs new file mode 100644 index 0000000..4701ee6 --- /dev/null +++ b/OpenSim/Region/CoreModules/ServiceConnectorsIn/UserProfiles/LocalUserProfilesServiceConnector.cs @@ -0,0 +1,228 @@ + +/* + * 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 log4net; +using Mono.Addins; +using Nini.Config; +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Server.Base; +using OpenSim.Server.Handlers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Profile +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "LocalUserProfilesServicesConnector")] + public class LocalUserProfilesServicesConnector : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + private Dictionary regions = new Dictionary(); + + public IUserProfilesService ServiceModule + { + get; private set; + } + + public bool Enabled + { + get; private set; + } + + public string Name + { + get + { + return "LocalUserProfilesServicesConnector"; + } + } + + public string ConfigName + { + get; private set; + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public LocalUserProfilesServicesConnector() + { + m_log.Debug("[LOCAL USERPROFILES SERVICE CONNECTOR]: LocalUserProfileServicesConnector no params"); + } + + public LocalUserProfilesServicesConnector(IConfigSource source) + { + m_log.Debug("[LOCAL USERPROFILES SERVICE CONNECTOR]: LocalUserProfileServicesConnector instantiated directly."); + InitialiseService(source); + } + + public void InitialiseService(IConfigSource source) + { + ConfigName = "UserProfilesService"; + + // Instantiate the request handler + IHttpServer Server = MainServer.Instance; + + IConfig config = source.Configs[ConfigName]; + if (config == null) + { + m_log.Error("[LOCAL USERPROFILES SERVICE CONNECTOR]: UserProfilesService missing from OpenSim.ini"); + return; + } + + if(!config.GetBoolean("Enabled",false)) + { + Enabled = false; + return; + } + + Enabled = true; + + string serviceDll = config.GetString("LocalServiceModule", + String.Empty); + + if (serviceDll == String.Empty) + { + m_log.Error("[LOCAL USERPROFILES SERVICE CONNECTOR]: No LocalServiceModule named in section UserProfilesService"); + return; + } + + Object[] args = new Object[] { source, ConfigName }; + ServiceModule = + ServerUtils.LoadPlugin(serviceDll, + args); + + if (ServiceModule == null) + { + m_log.Error("[LOCAL USERPROFILES SERVICE CONNECTOR]: Can't load user profiles service"); + return; + } + + Enabled = true; + + JsonRpcProfileHandlers handler = new JsonRpcProfileHandlers(ServiceModule); + + Server.AddJsonRPCHandler("avatarclassifiedsrequest", handler.AvatarClassifiedsRequest); + Server.AddJsonRPCHandler("classified_update", handler.ClassifiedUpdate); + Server.AddJsonRPCHandler("classifieds_info_query", handler.ClassifiedInfoRequest); + Server.AddJsonRPCHandler("classified_delete", handler.ClassifiedDelete); + Server.AddJsonRPCHandler("avatarpicksrequest", handler.AvatarPicksRequest); + Server.AddJsonRPCHandler("pickinforequest", handler.PickInfoRequest); + Server.AddJsonRPCHandler("picks_update", handler.PicksUpdate); + Server.AddJsonRPCHandler("picks_delete", handler.PicksDelete); + Server.AddJsonRPCHandler("avatarnotesrequest", handler.AvatarNotesRequest); + Server.AddJsonRPCHandler("avatar_notes_update", handler.NotesUpdate); + Server.AddJsonRPCHandler("avatar_properties_request", handler.AvatarPropertiesRequest); + Server.AddJsonRPCHandler("avatar_properties_update", handler.AvatarPropertiesUpdate); + Server.AddJsonRPCHandler("avatar_interests_update", handler.AvatarInterestsUpdate); + Server.AddJsonRPCHandler("user_preferences_update", handler.UserPreferenecesUpdate); + Server.AddJsonRPCHandler("user_preferences_request", handler.UserPreferencesRequest); + Server.AddJsonRPCHandler("image_assets_request", handler.AvatarImageAssetsRequest); + Server.AddJsonRPCHandler("user_data_request", handler.RequestUserAppData); + Server.AddJsonRPCHandler("user_data_update", handler.UpdateUserAppData); + + } + + #region ISharedRegionModule implementation + + void ISharedRegionModule.PostInitialise() + { + if(!Enabled) + return; + } + + #endregion + + #region IRegionModuleBase implementation + + void IRegionModuleBase.Initialise(IConfigSource source) + { + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + string name = moduleConfig.GetString("UserProfilesServices", ""); + if (name == Name) + { + InitialiseService(source); + m_log.Info("[LOCAL USERPROFILES SERVICE CONNECTOR]: Local user profiles connector enabled"); + } + } + } + + void IRegionModuleBase.Close() + { + return; + } + + void IRegionModuleBase.AddRegion(Scene scene) + { + if (!Enabled) + return; + + lock (regions) + { + if (regions.ContainsKey(scene.RegionInfo.RegionID)) + m_log.ErrorFormat("[LOCAL USERPROFILES SERVICE CONNECTOR]: simulator seems to have more than one region with the same UUID. Please correct this!"); + else + regions.Add(scene.RegionInfo.RegionID, scene); + } + } + + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!Enabled) + return; + + lock (regions) + { + if (regions.ContainsKey(scene.RegionInfo.RegionID)) + regions.Remove(scene.RegionInfo.RegionID); + } + } + + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!Enabled) + return; + } + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/LocalAgentPreferencesServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/LocalAgentPreferencesServiceConnector.cs new file mode 100644 index 0000000..41ae53f --- /dev/null +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/LocalAgentPreferencesServiceConnector.cs @@ -0,0 +1,153 @@ +/* + * 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 OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; + +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.AgentPreferences +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "LocalAgentPreferencesServicesConnector")] + public class LocalAgentPreferencesServicesConnector : ISharedRegionModule, IAgentPreferencesService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IAgentPreferencesService m_AgentPreferencesService; + private bool m_Enabled = false; + + #region ISharedRegionModule + + public Type ReplaceableInterface + { + get { return null; } + } + + public string Name + { + get { return "LocalAgentPreferencesServicesConnector"; } + } + + public void Initialise(IConfigSource source) + { + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + string name = moduleConfig.GetString("AgentPreferencesServices", ""); + if (name == Name) + { + IConfig userConfig = source.Configs["AgentPreferencesService"]; + if (userConfig == null) + { + m_log.Error("[AGENT PREFERENCES CONNECTOR]: AgentPreferencesService missing from OpenSim.ini"); + return; + } + + string serviceDll = userConfig.GetString("LocalServiceModule", String.Empty); + + if (String.IsNullOrEmpty(serviceDll)) + { + m_log.Error("[AGENT PREFERENCES CONNECTOR]: No AgentPreferencesModule named in section AgentPreferencesService"); + return; + } + + Object[] args = new Object[] { source }; + m_AgentPreferencesService = ServerUtils.LoadPlugin(serviceDll, args); + + if (m_AgentPreferencesService == null) + { + m_log.Error("[AGENT PREFERENCES CONNECTOR]: Can't load agent preferences service"); + return; + } + m_Enabled = true; + m_log.Info("[AGENT PREFERENCES CONNECTOR]: Local agent preferences connector enabled"); + } + } + } + + public void PostInitialise() + { + if (!m_Enabled) + return; + } + + public void Close() + { + if (!m_Enabled) + return; + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + } + + #endregion ISharedRegionModule + + #region IAgentPreferencesService + + public AgentPrefs GetAgentPreferences(UUID principalID) + { + return m_AgentPreferencesService.GetAgentPreferences(principalID); + } + + public bool StoreAgentPreferences(AgentPrefs data) + { + return m_AgentPreferencesService.StoreAgentPreferences(data); + } + + public string GetLang(UUID principalID) + { + return m_AgentPreferencesService.GetLang(principalID); + } + + #endregion IAgentPreferencesService + } +} diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/RemoteAgentPreferencesServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/RemoteAgentPreferencesServiceConnector.cs new file mode 100644 index 0000000..ad9544a --- /dev/null +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/AgentPreferences/RemoteAgentPreferencesServiceConnector.cs @@ -0,0 +1,116 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +using System; +using System.Collections.Generic; +using System.Reflection; + +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors; + +using OpenMetaverse; +using log4net; +using Mono.Addins; +using Nini.Config; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.AgentPreferences +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RemoteAgentPreferencesServicesConnector")] + public class RemoteAgentPreferencesServicesConnector : AgentPreferencesServicesConnector, + ISharedRegionModule, IAgentPreferencesService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + + public Type ReplaceableInterface + { + get { return null; } + } + + public string Name + { + get { return "RemoteAgentPreferencesServicesConnector"; } + } + + public override void Initialise(IConfigSource source) + { + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + string name = moduleConfig.GetString("AgentPreferencesServices", ""); + if (name == Name) + { + IConfig userConfig = source.Configs["AgentPreferencesService"]; + if (userConfig == null) + { + m_log.Error("[AGENT PREFERENCES CONNECTOR]: AgentPreferencesService missing from OpenSim.ini"); + return; + } + + m_Enabled = true; + + base.Initialise(source); + + m_log.Info("[AGENT PREFERENCES CONNECTOR]: Remote agent preferences enabled"); + } + } + } + + public void PostInitialise() + { + /* no op */ + } + + public void Close() + { + /* no op */ + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + /* no op */ + } + + public void RegionLoaded(Scene scene) + { + /* no op */ + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/HGAssetBroker.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/HGAssetBroker.cs index d221d68..7fcfc74 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/HGAssetBroker.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/HGAssetBroker.cs @@ -69,6 +69,13 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset get { return "HGAssetBroker"; } } + public HGAssetBroker() {} + + public HGAssetBroker(IConfigSource config) + { + Initialise(config); + } + public void Initialise(IConfigSource source) { IConfig moduleConfig = source.Configs["Modules"]; @@ -288,7 +295,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset if (asset != null) { - Util.FireAndForget(delegate { handler(id, sender, asset); }); + Util.FireAndForget(delegate { handler(id, sender, asset); }, null, "HGAssetBroker.GotFromCache"); return true; } @@ -312,6 +319,23 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset } } + public virtual bool[] AssetsExist(string[] ids) + { + int numHG = 0; + foreach (string id in ids) + { + if (IsHG(id)) + ++numHG; + } + + if (numHG == 0) + return m_GridService.AssetsExist(ids); + else if (numHG == ids.Length) + return m_HGService.AssetsExist(ids); + else + throw new Exception("[HG ASSET CONNECTOR]: AssetsExist: all the assets must be either local or foreign"); + } + public string Store(AssetBase asset) { bool isHG = IsHG(asset.ID); @@ -322,14 +346,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset // a copy of the local asset. m_Cache.Cache(asset); - if (asset.Temporary || asset.Local) + if (asset.Local) { if (m_Cache != null) m_Cache.Cache(asset); return asset.ID; } - string id = string.Empty; + string id; if (IsHG(asset.ID)) { if (m_AssetPerms.AllowedExport(asset.Type)) @@ -340,18 +364,15 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset else id = m_GridService.Store(asset); - if (id != String.Empty) - { - // Placing this here, so that this work with old asset servers that don't send any reply back - // SynchronousRestObjectRequester returns somethins that is not an empty string - if (id != null) - asset.ID = id; + if (String.IsNullOrEmpty(id)) + return string.Empty; + + asset.ID = id; - if (m_Cache != null) - m_Cache.Cache(asset); - } - return id; + if (m_Cache != null) + m_Cache.Cache(asset); + return id; } public bool UpdateContent(string id, byte[] data) diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/LocalAssetServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/LocalAssetServiceConnector.cs index 480cd69..5f34450 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/LocalAssetServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/LocalAssetServiceConnector.cs @@ -236,7 +236,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset if (asset != null) { - Util.FireAndForget(delegate { handler(id, sender, asset); }); + Util.FireAndForget( + o => handler(id, sender, asset), null, "LocalAssetServiceConnector.GotFromCacheCallback"); return true; } } @@ -249,16 +250,22 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset // if (null == a) // m_log.WarnFormat("[LOCAL ASSET SERVICES CONNECTOR]: Could not asynchronously find asset with id {0}", id); - Util.FireAndForget(delegate { handler(assetID, s, a); }); + Util.FireAndForget( + o => handler(assetID, s, a), null, "LocalAssetServiceConnector.GotFromServiceCallback"); }); } + public bool[] AssetsExist(string[] ids) + { + return m_AssetService.AssetsExist(ids); + } + public string Store(AssetBase asset) { if (m_Cache != null) m_Cache.Cache(asset); - if (asset.Temporary || asset.Local) + if (asset.Local) { // m_log.DebugFormat( // "[LOCAL ASSET SERVICE CONNECTOR]: Returning asset {0} {1} without querying database since status Temporary = {2}, Local = {3}", diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs index 1982473..4f75191 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs @@ -42,7 +42,7 @@ using OpenSim.Tests.Common; namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests { [TestFixture] - public class AssetConnectorsTests : OpenSimTestCase + public class AssetConnectorTests : OpenSimTestCase { [Test] public void TestAddAsset() @@ -77,7 +77,6 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests // TODO: Add cache and check that this does receive a copy of the asset } - [Test] public void TestAddTemporaryAsset() { TestHelpers.InMethod(); @@ -93,8 +92,45 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); lasc.Initialise(config); + // If it is remote, it should be stored + AssetBase a2 = AssetHelpers.CreateNotecardAsset(); + a2.Local = false; + a2.Temporary = true; + + lasc.Store(a2); + + AssetBase retreivedA2 = lasc.Get(a2.ID); + Assert.That(retreivedA2.ID, Is.EqualTo(a2.ID)); + Assert.That(retreivedA2.Metadata.ID, Is.EqualTo(a2.Metadata.ID)); + Assert.That(retreivedA2.Data.Length, Is.EqualTo(a2.Data.Length)); + + AssetMetadata retrievedA2Metadata = lasc.GetMetadata(a2.ID); + Assert.That(retrievedA2Metadata.ID, Is.EqualTo(a2.ID)); + + byte[] retrievedA2Data = lasc.GetData(a2.ID); + Assert.That(retrievedA2Data.Length, Is.EqualTo(a2.Data.Length)); + + // TODO: Add cache and check that this does receive a copy of the asset + } + + [Test] + public void TestAddLocalAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); + lasc.Initialise(config); + AssetBase a1 = AssetHelpers.CreateNotecardAsset(); - a1.Temporary = true; + a1.Local = true; lasc.Store(a1); @@ -106,7 +142,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests } [Test] - public void TestAddLocalAsset() + public void TestAddTemporaryLocalAsset() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -121,8 +157,10 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); lasc.Initialise(config); + // If it is local, it should not be stored AssetBase a1 = AssetHelpers.CreateNotecardAsset(); a1.Local = true; + a1.Temporary = true; lasc.Store(a1); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Authorization/AuthorizationService.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Authorization/AuthorizationService.cs index 4470799..93dff1f 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Authorization/AuthorizationService.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Authorization/AuthorizationService.cs @@ -89,35 +89,43 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Authorization public bool IsAuthorizedForRegion( string user, string firstName, string lastName, string regionID, out string message) { - message = "authorized"; - // This should not happen if (m_Scene.RegionInfo.RegionID.ToString() != regionID) { m_log.WarnFormat("[AuthorizationService]: Service for region {0} received request to authorize for region {1}", m_Scene.RegionInfo.RegionID, regionID); - return true; + message = string.Format("Region {0} received request to authorize for region {1}", m_Scene.RegionInfo.RegionID, regionID); + return false; } if (m_accessValue == AccessFlags.None) + { + message = "Authorized"; return true; + } UUID userID = new UUID(user); - bool authorized = true; - if ((m_accessValue & AccessFlags.DisallowForeigners) == AccessFlags.DisallowForeigners) + + if ((m_accessValue & AccessFlags.DisallowForeigners) != 0) { - authorized = m_UserManagement.IsLocalGridUser(userID); - if (!authorized) - message = "no foreigner users allowed in this region"; + if (!m_UserManagement.IsLocalGridUser(userID)) + { + message = "No foreign users allowed in this region"; + return false; + } } - if (authorized && (m_accessValue & AccessFlags.DisallowResidents) == AccessFlags.DisallowResidents) + + if ((m_accessValue & AccessFlags.DisallowResidents) != 0) { - authorized = m_Scene.Permissions.IsGod(userID) | m_Scene.Permissions.IsAdministrator(userID); - if (!authorized) - message = "only Admins and Managers allowed in this region"; + if (!(m_Scene.Permissions.IsGod(userID) || m_Scene.Permissions.IsAdministrator(userID))) + { + message = "Only Admins and Managers allowed in this region"; + return false; + } } - return authorized; + message = "Authorized"; + return true; } } diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs index c0c2ca7..1f782f5 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs @@ -48,6 +48,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[LOCAL GRID SERVICE CONNECTOR]"; private IGridService m_GridService; private Dictionary m_LocalCache = new Dictionary(); @@ -56,11 +57,12 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid public LocalGridServicesConnector() { + m_log.DebugFormat("{0} LocalGridServicesConnector no parms.", LogHeader); } public LocalGridServicesConnector(IConfigSource source) { - m_log.Debug("[LOCAL GRID SERVICE CONNECTOR]: LocalGridServicesConnector instantiated directly."); + m_log.DebugFormat("{0} LocalGridServicesConnector instantiated directly.", LogHeader); InitialiseService(source); } @@ -92,15 +94,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid private void InitialiseService(IConfigSource source) { - IConfig assetConfig = source.Configs["GridService"]; - if (assetConfig == null) + IConfig config = source.Configs["GridService"]; + if (config == null) { m_log.Error("[LOCAL GRID SERVICE CONNECTOR]: GridService missing from OpenSim.ini"); return; } - string serviceDll = assetConfig.GetString("LocalServiceModule", - String.Empty); + string serviceDll = config.GetString("LocalServiceModule", String.Empty); if (serviceDll == String.Empty) { @@ -142,10 +143,13 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid scene.RegisterModuleInterface(this); - if (m_LocalCache.ContainsKey(scene.RegionInfo.RegionID)) - m_log.ErrorFormat("[LOCAL GRID SERVICE CONNECTOR]: simulator seems to have more than one region with the same UUID. Please correct this!"); - else - m_LocalCache.Add(scene.RegionInfo.RegionID, new RegionCache(scene)); + lock (m_LocalCache) + { + if (m_LocalCache.ContainsKey(scene.RegionInfo.RegionID)) + m_log.ErrorFormat("[LOCAL GRID SERVICE CONNECTOR]: simulator seems to have more than one region with the same UUID. Please correct this!"); + else + m_LocalCache.Add(scene.RegionInfo.RegionID, new RegionCache(scene)); + } } public void RemoveRegion(Scene scene) @@ -153,8 +157,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid if (!m_Enabled) return; - m_LocalCache[scene.RegionInfo.RegionID].Clear(); - m_LocalCache.Remove(scene.RegionInfo.RegionID); + lock (m_LocalCache) + { + m_LocalCache[scene.RegionInfo.RegionID].Clear(); + m_LocalCache.Remove(scene.RegionInfo.RegionID); + } } public void RegionLoaded(Scene scene) @@ -185,23 +192,59 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return m_GridService.GetRegionByUUID(scopeID, regionID); } + // Get a region given its base coordinates. + // NOTE: this is NOT 'get a region by some point in the region'. The coordinate MUST + // be the base coordinate of the region. public GridRegion GetRegionByPosition(UUID scopeID, int x, int y) { GridRegion region = null; + uint regionX = Util.WorldToRegionLoc((uint)x); + uint regionY = Util.WorldToRegionLoc((uint)y); + + // Sanity check + if ((Util.RegionToWorldLoc(regionX) != (uint)x) || (Util.RegionToWorldLoc(regionY) != (uint)y)) + { + m_log.WarnFormat("{0} GetRegionByPosition. Bad position requested: not the base of the region. Requested Pos=<{1},{2}>, Should Be=<{3},{4}>", + LogHeader, x, y, Util.RegionToWorldLoc(regionX), Util.RegionToWorldLoc(regionY)); + } // First see if it's a neighbour, even if it isn't on this sim. // Neighbour data is cached in memory, so this is fast - foreach (RegionCache rcache in m_LocalCache.Values) + + lock (m_LocalCache) { - region = rcache.GetRegionByPosition(x, y); - if (region != null) + foreach (RegionCache rcache in m_LocalCache.Values) { - return region; + region = rcache.GetRegionByPosition(x, y); + if (region != null) + { + m_log.DebugFormat("{0} GetRegionByPosition. Found region {1} in cache (of region {2}). Pos=<{3},{4}>", + LogHeader, region.RegionName, rcache.RegionName, + Util.WorldToRegionLoc((uint)region.RegionLocX), Util.WorldToRegionLoc((uint)region.RegionLocY)); + break; + } } } // Then try on this sim (may be a lookup in DB if this is using MySql). - return m_GridService.GetRegionByPosition(scopeID, x, y); + if (region == null) + { + region = m_GridService.GetRegionByPosition(scopeID, x, y); + + if (region == null) + { + m_log.DebugFormat("{0} GetRegionByPosition. Region not found by grid service. Pos=<{1},{2}>", + LogHeader, regionX, regionY); + } + else + { + m_log.DebugFormat("{0} GetRegionByPosition. Got region {1} from grid service. Pos=<{2},{3}>", + LogHeader, region.RegionName, + Util.WorldToRegionLoc((uint)region.RegionLocX), Util.WorldToRegionLoc((uint)region.RegionLocY)); + } + } + + return region; } public GridRegion GetRegionByName(UUID scopeID, string regionName) @@ -224,6 +267,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return m_GridService.GetDefaultRegions(scopeID); } + public List GetDefaultHypergridRegions(UUID scopeID) + { + return m_GridService.GetDefaultHypergridRegions(scopeID); + } + public List GetFallbackRegions(UUID scopeID, int x, int y) { return m_GridService.GetFallbackRegions(scopeID, x, y); @@ -239,21 +287,29 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return m_GridService.GetRegionFlags(scopeID, regionID); } + public Dictionary GetExtraFeatures() + { + return m_GridService.GetExtraFeatures(); + } + #endregion public void HandleShowNeighboursCommand(string module, string[] cmdparams) { System.Text.StringBuilder caps = new System.Text.StringBuilder(); - foreach (KeyValuePair kvp in m_LocalCache) + lock (m_LocalCache) { - caps.AppendFormat("*** Neighbours of {0} ({1}) ***\n", kvp.Value.RegionName, kvp.Key); - List regions = kvp.Value.GetNeighbours(); - foreach (GridRegion r in regions) - caps.AppendFormat(" {0} @ {1}-{2}\n", r.RegionName, r.RegionLocX / Constants.RegionSize, r.RegionLocY / Constants.RegionSize); + foreach (KeyValuePair kvp in m_LocalCache) + { + caps.AppendFormat("*** Neighbours of {0} ({1}) ***\n", kvp.Value.RegionName, kvp.Key); + List regions = kvp.Value.GetNeighbours(); + foreach (GridRegion r in regions) + caps.AppendFormat(" {0} @ {1}-{2}\n", r.RegionName, Util.WorldToRegionLoc((uint)r.RegionLocX), Util.WorldToRegionLoc((uint)r.RegionLocY)); + } } MainConsole.Instance.Output(caps.ToString()); } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionCache.cs index 9172536..ae76288 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionCache.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionCache.cs @@ -66,7 +66,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return; m_log.DebugFormat("[REGION CACHE]: (on region {0}) Region {1} is up @ {2}-{3}", - m_scene.RegionInfo.RegionName, otherRegion.RegionName, otherRegion.RegionLocX / Constants.RegionSize, otherRegion.RegionLocY / Constants.RegionSize); + m_scene.RegionInfo.RegionName, otherRegion.RegionName, Util.WorldToRegionLoc((uint)otherRegion.RegionLocX), Util.WorldToRegionLoc((uint)otherRegion.RegionLocY)); m_neighbours[otherRegion.RegionHandle] = otherRegion; } @@ -82,11 +82,16 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return new List(m_neighbours.Values); } + // Get a region given its base coordinates (in meters). + // NOTE: this is NOT 'get a region by some point in the region'. The coordinate MUST + // be the base coordinate of the region. + // The snapping is technically unnecessary but is harmless because regions are always + // multiples of the legacy region size (256). public GridRegion GetRegionByPosition(int x, int y) { uint xsnap = (uint)(x / Constants.RegionSize) * Constants.RegionSize; uint ysnap = (uint)(y / Constants.RegionSize) * Constants.RegionSize; - ulong handle = Utils.UIntsToLong(xsnap, ysnap); + ulong handle = Util.RegionWorldLocToHandle(xsnap, ysnap); if (m_neighbours.ContainsKey(handle)) return m_neighbours[handle]; diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RemoteGridServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RemoteGridServiceConnector.cs index b2646ba..85073fc 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RemoteGridServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RemoteGridServiceConnector.cs @@ -28,6 +28,7 @@ using log4net; using Mono.Addins; using System; +using System.Collections; using System.Collections.Generic; using System.Reflection; using Nini.Config; @@ -186,18 +187,41 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return rinfo; } + // Get a region given its base world coordinates (in meters). + // NOTE: this is NOT 'get a region by some point in the region'. The coordinate MUST + // be the base coordinate of the region. + // The coordinates are world coords (meters), NOT region units. public GridRegion GetRegionByPosition(UUID scopeID, int x, int y) { + ulong regionHandle = Util.RegionWorldLocToHandle((uint)x, (uint)y); + uint regionX = Util.WorldToRegionLoc((uint)x); + uint regionY = Util.WorldToRegionLoc((uint)y); + + // Sanity check + if ((Util.RegionToWorldLoc(regionX) != (uint)x) || (Util.RegionToWorldLoc(regionY) != (uint)y)) + { + m_log.WarnFormat("[REMOTE GRID CONNECTOR]: GetRegionByPosition. Bad position requested: not the base of the region. Requested Pos=<{0},{1}>, Should Be=<{2},{3}>", + x, y, Util.RegionToWorldLoc(regionX), Util.RegionToWorldLoc(regionY)); + } + bool inCache = false; - GridRegion rinfo = m_RegionInfoCache.Get(scopeID, Util.UIntsToLong((uint)x, (uint)y), out inCache); + GridRegion rinfo = m_RegionInfoCache.Get(scopeID, regionHandle, out inCache); if (inCache) + { + //m_log.DebugFormat("[REMOTE GRID CONNECTOR]: GetRegionByPosition. Found region {0} in cache. Pos=<{1},{2}>, RegionHandle={3}", + // (rinfo == null) ? "" : rinfo.RegionName, regionX, regionY, (rinfo == null) ? regionHandle : rinfo.RegionHandle); return rinfo; + } rinfo = m_LocalGridService.GetRegionByPosition(scopeID, x, y); if (rinfo == null) rinfo = m_RemoteGridService.GetRegionByPosition(scopeID, x, y); m_RegionInfoCache.Cache(rinfo); + + //m_log.DebugFormat("[REMOTE GRID CONNECTOR]: GetRegionByPosition. Added region {0} to the cache. Pos=<{1},{2}>, RegionHandle={3}", + // (rinfo == null) ? "" : rinfo.RegionName, regionX, regionY, (rinfo == null) ? regionHandle : rinfo.RegionHandle); + return rinfo; } @@ -277,6 +301,26 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return rinfo; } + public List GetDefaultHypergridRegions(UUID scopeID) + { + List rinfo = m_LocalGridService.GetDefaultHypergridRegions(scopeID); + //m_log.DebugFormat("[REMOTE GRID CONNECTOR]: Local GetDefaultHypergridRegions {0} found {1} regions", name, rinfo.Count); + List grinfo = m_RemoteGridService.GetDefaultHypergridRegions(scopeID); + + if (grinfo != null) + { + //m_log.DebugFormat("[REMOTE GRID CONNECTOR]: Remote GetDefaultHypergridRegions {0} found {1} regions", name, grinfo.Count); + foreach (GridRegion r in grinfo) + { + m_RegionInfoCache.Cache(r); + if (rinfo.Find(delegate(GridRegion gr) { return gr.RegionID == r.RegionID; }) == null) + rinfo.Add(r); + } + } + + return rinfo; + } + public List GetFallbackRegions(UUID scopeID, int x, int y) { List rinfo = m_LocalGridService.GetFallbackRegions(scopeID, x, y); @@ -325,6 +369,17 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return flags; } + + public Dictionary GetExtraFeatures() + { + Dictionary extraFeatures; + extraFeatures = m_LocalGridService.GetExtraFeatures(); + + if (extraFeatures.Count == 0) + extraFeatures = m_RemoteGridService.GetExtraFeatures(); + + return extraFeatures; + } #endregion } } diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs index 4338133..25ae689 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs @@ -34,6 +34,7 @@ using log4net.Config; using Nini.Config; using NUnit.Framework; using OpenMetaverse; + using OpenSim.Framework; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid; using OpenSim.Region.Framework.Scenes; @@ -141,7 +142,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid.Tests Assert.IsNotNull(result, "Retrieved GetRegionByUUID is null"); Assert.That(result.RegionID, Is.EqualTo(new UUID(1)), "Retrieved region's UUID does not match"); - result = m_LocalConnector.GetRegionByPosition(UUID.Zero, 1000 * (int)Constants.RegionSize, 1000 * (int)Constants.RegionSize); + result = m_LocalConnector.GetRegionByPosition(UUID.Zero, (int)Util.RegionToWorldLoc(1000), (int)Util.RegionToWorldLoc(1000)); Assert.IsNotNull(result, "Retrieved GetRegionByPosition is null"); Assert.That(result.RegionLocX, Is.EqualTo(1000 * (int)Constants.RegionSize), "Retrieved region's position does not match"); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/GridUser/ActivityDetector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/GridUser/ActivityDetector.cs index 221f815..2238c90 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/GridUser/ActivityDetector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/GridUser/ActivityDetector.cs @@ -67,10 +67,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.GridUser { if (sp.PresenceType != PresenceType.Npc) { - string userid = sp.Scene.UserManagementModule.GetUserUUI(sp.UUID); + string userid; //m_log.DebugFormat("[ACTIVITY DETECTOR]: Detected root presence {0} in {1}", userid, sp.Scene.RegionInfo.RegionName); - m_GridUserService.SetLastPosition( - userid, UUID.Zero, sp.Scene.RegionInfo.RegionID, sp.AbsolutePosition, sp.Lookat); + if (sp.Scene.UserManagementModule.GetUserUUI(sp.UUID, out userid)) + { + /* we only setposition on known agents that have a valid lookup */ + m_GridUserService.SetLastPosition( + userid, UUID.Zero, sp.Scene.RegionInfo.RegionID, sp.AbsolutePosition, sp.Lookat); + } } } @@ -81,20 +85,28 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.GridUser public void OnConnectionClose(IClientAPI client) { + if (client == null) + return; + if (client.SceneAgent == null) + return; + if (client.SceneAgent.IsChildAgent) return; - string userId = client.AgentId.ToString(); + string userId; + /* without scene we cannot logout correctly at all since we do not know how to send the loggedout message then */ if (client.Scene is Scene) { Scene s = (Scene)client.Scene; userId = s.UserManagementModule.GetUserUUI(client.AgentId); + if(s.UserManagementModule.GetUserUUI(client.AgentId, out userId)) + { + m_GridUserService.LoggedOut( + userId, client.SessionId, client.Scene.RegionInfo.RegionID, + client.SceneAgent.AbsolutePosition, client.SceneAgent.Lookat); + } } - //m_log.DebugFormat("[ACTIVITY DETECTOR]: Detected client logout {0} in {1}", userId, client.Scene.RegionInfo.RegionName); - m_GridUserService.LoggedOut( - userId, client.SessionId, client.Scene.RegionInfo.RegionID, - client.SceneAgent.AbsolutePosition, client.SceneAgent.Lookat); } } } diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/HGInventoryBroker.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/HGInventoryBroker.cs index e474ef6..48f228a 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/HGInventoryBroker.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/HGInventoryBroker.cs @@ -62,6 +62,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory private InventoryCache m_Cache = new InventoryCache(); + /// + /// Used to serialize inventory requests. + /// + private object m_Lock = new object(); + protected IUserManagement m_UserManagement; protected IUserManagement UserManagementModule { @@ -233,13 +238,18 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory if (sp != null) { AgentCircuitData aCircuit = scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode); + if (aCircuit == null) + return; + if (aCircuit.ServiceURLs == null) + return; + if (aCircuit.ServiceURLs.ContainsKey("InventoryServerURI")) { inventoryURL = aCircuit.ServiceURLs["InventoryServerURI"].ToString(); if (inventoryURL != null && inventoryURL != string.Empty) { inventoryURL = inventoryURL.Trim(new char[] { '/' }); - m_InventoryURLs.Add(userID, inventoryURL); + m_InventoryURLs[userID] = inventoryURL; m_log.DebugFormat("[HG INVENTORY CONNECTOR]: Added {0} to the cache of inventory URLs", inventoryURL); return; } @@ -254,7 +264,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory if (sp == null) { inventoryURL = UserManagementModule.GetUserServerURL(userID, "InventoryServerURI"); - if (inventoryURL != null && inventoryURL != string.Empty) + if (!string.IsNullOrEmpty(inventoryURL)) { inventoryURL = inventoryURL.Trim(new char[] { '/' }); m_InventoryURLs.Add(userID, inventoryURL); @@ -296,7 +306,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory public bool CreateUserInventory(UUID userID) { - return m_LocalGridInventoryService.CreateUserInventory(userID); + lock (m_Lock) + return m_LocalGridInventoryService.CreateUserInventory(userID); } public List GetInventorySkeleton(UUID userID) @@ -304,36 +315,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetInventorySkeleton(userID); + lock (m_Lock) + return m_LocalGridInventoryService.GetInventorySkeleton(userID); IInventoryService connector = GetConnector(invURL); return connector.GetInventorySkeleton(userID); } - public InventoryCollection GetUserInventory(UUID userID) - { - string invURL = GetInventoryServiceURL(userID); - m_log.DebugFormat("[HG INVENTORY CONNECTOR]: GetUserInventory for {0} {1}", userID, invURL); - - if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetUserInventory(userID); - - InventoryCollection c = m_Cache.GetUserInventory(userID); - if (c != null) - return c; - - IInventoryService connector = GetConnector(invURL); - c = connector.GetUserInventory(userID); - - m_Cache.Cache(userID, c); - return c; - } - - public void GetUserInventory(UUID userID, InventoryReceiptCallback callback) - { - } - public InventoryFolderBase GetRootFolder(UUID userID) { //m_log.DebugFormat("[HG INVENTORY CONNECTOR]: GetRootFolder for {0}", userID); @@ -344,7 +333,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetRootFolder(userID); + lock (m_Lock) + return m_LocalGridInventoryService.GetRootFolder(userID); IInventoryService connector = GetConnector(invURL); @@ -355,7 +345,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return root; } - public InventoryFolderBase GetFolderForType(UUID userID, AssetType type) + public InventoryFolderBase GetFolderForType(UUID userID, FolderType type) { //m_log.DebugFormat("[HG INVENTORY CONNECTOR]: GetFolderForType {0} type {1}", userID, type); InventoryFolderBase f = m_Cache.GetFolderForType(userID, type); @@ -365,7 +355,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetFolderForType(userID, type); + lock (m_Lock) + return m_LocalGridInventoryService.GetFolderForType(userID, type); IInventoryService connector = GetConnector(invURL); @@ -383,7 +374,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetFolderContent(userID, folderID); + lock (m_Lock) + return m_LocalGridInventoryService.GetFolderContent(userID, folderID); InventoryCollection c = m_Cache.GetFolderContent(userID, folderID); if (c != null) @@ -393,8 +385,27 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory } IInventoryService connector = GetConnector(invURL); + return connector.GetFolderContent(userID, folderID); + } + public InventoryCollection[] GetMultipleFoldersContent(UUID userID, UUID[] folderIDs) + { + string invURL = GetInventoryServiceURL(userID); + + if (invURL == null) // not there, forward to local inventory connector to resolve + lock (m_Lock) + return m_LocalGridInventoryService.GetMultipleFoldersContent(userID, folderIDs); + + else + { + InventoryCollection[] coll = new InventoryCollection[folderIDs.Length]; + int i = 0; + foreach (UUID fid in folderIDs) + coll[i++] = GetFolderContent(userID, fid); + + return coll; + } } public List GetFolderItems(UUID userID, UUID folderID) @@ -404,7 +415,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetFolderItems(userID, folderID); + lock (m_Lock) + return m_LocalGridInventoryService.GetFolderItems(userID, folderID); List items = m_Cache.GetFolderItems(userID, folderID); if (items != null) @@ -414,8 +426,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory } IInventoryService connector = GetConnector(invURL); - return connector.GetFolderItems(userID, folderID); + return connector.GetFolderItems(userID, folderID); } public bool AddFolder(InventoryFolderBase folder) @@ -428,7 +440,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(folder.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.AddFolder(folder); + lock (m_Lock) + return m_LocalGridInventoryService.AddFolder(folder); IInventoryService connector = GetConnector(invURL); @@ -445,7 +458,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(folder.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.UpdateFolder(folder); + lock (m_Lock) + return m_LocalGridInventoryService.UpdateFolder(folder); IInventoryService connector = GetConnector(invURL); @@ -464,7 +478,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(ownerID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.DeleteFolders(ownerID, folderIDs); + lock (m_Lock) + return m_LocalGridInventoryService.DeleteFolders(ownerID, folderIDs); IInventoryService connector = GetConnector(invURL); @@ -481,7 +496,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(folder.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.MoveFolder(folder); + lock (m_Lock) + return m_LocalGridInventoryService.MoveFolder(folder); IInventoryService connector = GetConnector(invURL); @@ -498,7 +514,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(folder.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.PurgeFolder(folder); + lock (m_Lock) + return m_LocalGridInventoryService.PurgeFolder(folder); IInventoryService connector = GetConnector(invURL); @@ -515,7 +532,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(item.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.AddItem(item); + lock (m_Lock) + return m_LocalGridInventoryService.AddItem(item); IInventoryService connector = GetConnector(invURL); @@ -532,7 +550,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(item.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.UpdateItem(item); + lock (m_Lock) + return m_LocalGridInventoryService.UpdateItem(item); IInventoryService connector = GetConnector(invURL); @@ -551,7 +570,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(ownerID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.MoveItems(ownerID, items); + lock (m_Lock) + return m_LocalGridInventoryService.MoveItems(ownerID, items); IInventoryService connector = GetConnector(invURL); @@ -570,7 +590,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(ownerID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.DeleteItems(ownerID, itemIDs); + lock (m_Lock) + return m_LocalGridInventoryService.DeleteItems(ownerID, itemIDs); IInventoryService connector = GetConnector(invURL); @@ -586,13 +607,31 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(item.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetItem(item); + lock (m_Lock) + return m_LocalGridInventoryService.GetItem(item); IInventoryService connector = GetConnector(invURL); return connector.GetItem(item); } + public InventoryItemBase[] GetMultipleItems(UUID userID, UUID[] itemIDs) + { + if (itemIDs == null) + return new InventoryItemBase[0]; + //m_log.Debug("[HG INVENTORY CONNECTOR]: GetItem " + item.ID); + + string invURL = GetInventoryServiceURL(userID); + + if (invURL == null) // not there, forward to local inventory connector to resolve + lock (m_Lock) + return m_LocalGridInventoryService.GetMultipleItems(userID, itemIDs); + + IInventoryService connector = GetConnector(invURL); + + return connector.GetMultipleItems(userID, itemIDs); + } + public InventoryFolderBase GetFolder(InventoryFolderBase folder) { if (folder == null) @@ -603,7 +642,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(folder.Owner); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetFolder(folder); + lock (m_Lock) + return m_LocalGridInventoryService.GetFolder(folder); IInventoryService connector = GetConnector(invURL); @@ -627,7 +667,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory string invURL = GetInventoryServiceURL(userID); if (invURL == null) // not there, forward to local inventory connector to resolve - return m_LocalGridInventoryService.GetAssetPermissions(userID, assetID); + lock (m_Lock) + return m_LocalGridInventoryService.GetAssetPermissions(userID, assetID); IInventoryService connector = GetConnector(invURL); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/InventoryCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/InventoryCache.cs index 1e434b9..3195e6b 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/InventoryCache.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/InventoryCache.cs @@ -1,23 +1,52 @@ -using System; -using System.Collections.Generic; +/* + * 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.Threading; using OpenSim.Framework; using OpenMetaverse; namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory { + /// + /// Cache root and system inventory folders to reduce number of potentially remote inventory calls and associated holdups. + /// public class InventoryCache { private const double CACHE_EXPIRATION_SECONDS = 3600.0; // 1 hour private static ExpiringCache m_RootFolders = new ExpiringCache(); - private static ExpiringCache> m_FolderTypes = new ExpiringCache>(); + private static ExpiringCache> m_FolderTypes = new ExpiringCache>(); private static ExpiringCache m_Inventories = new ExpiringCache(); public void Cache(UUID userID, InventoryFolderBase root) { - lock (m_RootFolders) - m_RootFolders.AddOrUpdate(userID, root, CACHE_EXPIRATION_SECONDS); + m_RootFolders.AddOrUpdate(userID, root, CACHE_EXPIRATION_SECONDS); } public InventoryFolderBase GetRootFolder(UUID userID) @@ -29,29 +58,37 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return null; } - public void Cache(UUID userID, AssetType type, InventoryFolderBase folder) + public void Cache(UUID userID, FolderType type, InventoryFolderBase folder) { - lock (m_FolderTypes) + Dictionary ff = null; + if (!m_FolderTypes.TryGetValue(userID, out ff)) + { + ff = new Dictionary(); + m_FolderTypes.Add(userID, ff, CACHE_EXPIRATION_SECONDS); + } + + // We need to lock here since two threads could potentially retrieve the same dictionary + // and try to add a folder for that type simultaneously. Dictionary<>.Add() is not described as thread-safe in the SDK + // even if the folders are identical. + lock (ff) { - Dictionary ff = null; - if (!m_FolderTypes.TryGetValue(userID, out ff)) - { - ff = new Dictionary(); - m_FolderTypes.Add(userID, ff, CACHE_EXPIRATION_SECONDS); - } if (!ff.ContainsKey(type)) ff.Add(type, folder); } } - public InventoryFolderBase GetFolderForType(UUID userID, AssetType type) + public InventoryFolderBase GetFolderForType(UUID userID, FolderType type) { - Dictionary ff = null; + Dictionary ff = null; if (m_FolderTypes.TryGetValue(userID, out ff)) { InventoryFolderBase f = null; - if (ff.TryGetValue(type, out f)) - return f; + + lock (ff) + { + if (ff.TryGetValue(type, out f)) + return f; + } } return null; @@ -59,16 +96,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory public void Cache(UUID userID, InventoryCollection inv) { - lock (m_Inventories) - m_Inventories.AddOrUpdate(userID, inv, 120); - } - - public InventoryCollection GetUserInventory(UUID userID) - { - InventoryCollection inv = null; - if (m_Inventories.TryGetValue(userID, out inv)) - return inv; - return null; + m_Inventories.AddOrUpdate(userID, inv, 120); } public InventoryCollection GetFolderContent(UUID userID, UUID folderID) @@ -78,7 +106,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory if (m_Inventories.TryGetValue(userID, out inv)) { c = new InventoryCollection(); - c.UserID = userID; + c.OwnerID = userID; c.Folders = inv.Folders.FindAll(delegate(InventoryFolderBase f) { diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/LocalInventoryServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/LocalInventoryServiceConnector.cs index ec5751d..20d4e02 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/LocalInventoryServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/LocalInventoryServiceConnector.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; using System.Reflection; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Data; using OpenSim.Server.Base; using OpenSim.Region.Framework.Interfaces; @@ -164,22 +165,12 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return m_InventoryService.GetInventorySkeleton(userId); } - public InventoryCollection GetUserInventory(UUID id) - { - return m_InventoryService.GetUserInventory(id); - } - - public void GetUserInventory(UUID userID, InventoryReceiptCallback callback) - { - m_InventoryService.GetUserInventory(userID, callback); - } - public InventoryFolderBase GetRootFolder(UUID userID) { return m_InventoryService.GetRootFolder(userID); } - public InventoryFolderBase GetFolderForType(UUID userID, AssetType type) + public InventoryFolderBase GetFolderForType(UUID userID, FolderType type) { return m_InventoryService.GetFolderForType(userID, type); } @@ -193,16 +184,30 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory // Protect ourselves against the caller subsequently modifying the items list List items = new List(invCol.Items); - Util.FireAndForget(delegate + WorkManager.RunInThread(delegate { foreach (InventoryItemBase item in items) - UserManager.AddUser(item.CreatorIdAsUuid, item.CreatorData); - }); + if (!string.IsNullOrEmpty(item.CreatorData)) + UserManager.AddUser(item.CreatorIdAsUuid, item.CreatorData); + }, null, string.Format("GetFolderContent (user {0}, folder {1})", userID, folderID)); } return invCol; } + 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 List GetFolderItems(UUID userID, UUID folderID) { return m_InventoryService.GetFolderItems(userID, folderID); @@ -302,6 +307,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return item; } + public InventoryItemBase[] GetMultipleItems(UUID userID, UUID[] itemIDs) + { + return m_InventoryService.GetMultipleItems(userID, itemIDs); + } + public InventoryFolderBase GetFolder(InventoryFolderBase folder) { return m_InventoryService.GetFolder(folder); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/RemoteXInventoryServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/RemoteXInventoryServiceConnector.cs index 2d3ba82..978b9d9 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/RemoteXInventoryServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Inventory/RemoteXInventoryServiceConnector.cs @@ -172,21 +172,12 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return m_RemoteConnector.GetInventorySkeleton(userId); } - public InventoryCollection GetUserInventory(UUID userID) - { - return m_RemoteConnector.GetUserInventory(userID); - } - - public void GetUserInventory(UUID userID, InventoryReceiptCallback callback) - { - } - public InventoryFolderBase GetRootFolder(UUID userID) { return m_RemoteConnector.GetRootFolder(userID); } - public InventoryFolderBase GetFolderForType(UUID userID, AssetType type) + public InventoryFolderBase GetFolderForType(UUID userID, FolderType type) { return m_RemoteConnector.GetFolderForType(userID, type); } @@ -195,22 +186,29 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory { InventoryCollection invCol = m_RemoteConnector.GetFolderContent(userID, folderID); - if (invCol != null && UserManager != null) - { - // Protect ourselves against the caller subsequently modifying the items list - List items = new List(invCol.Items); - - if (items != null && items.Count > 0) - Util.FireAndForget(delegate - { - foreach (InventoryItemBase item in items) - UserManager.AddUser(item.CreatorIdAsUuid, item.CreatorData); - }); - } + // Commenting this for now, because it's causing more grief than good + //if (invCol != null && UserManager != null) + //{ + // // Protect ourselves against the caller subsequently modifying the items list + // List items = new List(invCol.Items); + + // if (items != null && items.Count > 0) + // //Util.FireAndForget(delegate + // //{ + // foreach (InventoryItemBase item in items) + // if (!string.IsNullOrEmpty(item.CreatorData)) + // UserManager.AddUser(item.CreatorIdAsUuid, item.CreatorData); + // //}); + //} return invCol; } + public virtual InventoryCollection[] GetMultipleFoldersContent(UUID principalID, UUID[] folderIDs) + { + return m_RemoteConnector.GetMultipleFoldersContent(principalID, folderIDs); + } + public List GetFolderItems(UUID userID, UUID folderID) { return m_RemoteConnector.GetFolderItems(userID, folderID); @@ -305,6 +303,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory return m_RemoteConnector.GetItem(item); } + public InventoryItemBase[] GetMultipleItems(UUID userID, UUID[] itemIDs) + { + if (itemIDs == null) + return new InventoryItemBase[0]; + + return m_RemoteConnector.GetMultipleItems(userID, itemIDs); + } + public InventoryFolderBase GetFolder(InventoryFolderBase folder) { //m_log.DebugFormat("[XINVENTORY CONNECTOR]: GetFolder {0}", folder.ID); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/MapImage/MapImageServiceModule.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/MapImage/MapImageServiceModule.cs index a839086..08d6bdd 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/MapImage/MapImageServiceModule.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/MapImage/MapImageServiceModule.cs @@ -53,10 +53,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage /// [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MapImageServiceModule")] - public class MapImageServiceModule : ISharedRegionModule + public class MapImageServiceModule : IMapImageUploadModule, ISharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[MAP IMAGE SERVICE MODULE]:"; private bool m_enabled = false; private IMapImageService m_MapService; @@ -65,7 +66,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage private int m_refreshtime = 0; private int m_lastrefresh = 0; - private System.Timers.Timer m_refreshTimer = new System.Timers.Timer(); + private System.Timers.Timer m_refreshTimer; #region ISharedRegionModule @@ -75,7 +76,6 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage public void Close() { } public void PostInitialise() { } - /// /// /// @@ -94,14 +94,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage return; int refreshminutes = Convert.ToInt32(config.GetString("RefreshTime")); - if (refreshminutes <= 0) + + // if refresh is less than zero, disable the module + if (refreshminutes < 0) { - m_log.WarnFormat("[MAP IMAGE SERVICE MODULE]: No refresh time given in config. Module disabled."); + m_log.WarnFormat("[MAP IMAGE SERVICE MODULE]: Negative refresh time given in config. Module disabled."); return; } - m_refreshtime = refreshminutes * 60 * 1000; // convert from minutes to ms - string service = config.GetString("LocalServiceModule", string.Empty); if (service == string.Empty) { @@ -116,15 +116,25 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage m_log.WarnFormat("[MAP IMAGE SERVICE MODULE]: Unable to load LocalServiceModule from {0}. MapService module disabled. Please fix the configuration.", service); return; } + + // we don't want the timer if the interval is zero, but we still want this module enables + if(refreshminutes > 0) + { + m_refreshtime = refreshminutes * 60 * 1000; // convert from minutes to ms + + m_refreshTimer = new System.Timers.Timer(); + m_refreshTimer.Enabled = true; + m_refreshTimer.AutoReset = true; + m_refreshTimer.Interval = m_refreshtime; + m_refreshTimer.Elapsed += new ElapsedEventHandler(HandleMaptileRefresh); - m_refreshTimer.Enabled = true; - m_refreshTimer.AutoReset = true; - m_refreshTimer.Interval = m_refreshtime; - m_refreshTimer.Elapsed += new ElapsedEventHandler(HandleMaptileRefresh); - - m_log.InfoFormat("[MAP IMAGE SERVICE MODULE]: enabled with refresh time {0}min and service object {1}", + m_log.InfoFormat("[MAP IMAGE SERVICE MODULE]: enabled with refresh time {0} min and service object {1}", refreshminutes, service); - + } + else + { + m_log.InfoFormat("[MAP IMAGE SERVICE MODULE]: enabled with no refresh and service object {0}", service); + } m_enabled = true; } @@ -133,7 +143,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage /// public void AddRegion(Scene scene) { - if (! m_enabled) + if (!m_enabled) return; // Every shared region module has to maintain an indepedent list of @@ -141,7 +151,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage lock (m_scenes) m_scenes[scene.RegionInfo.RegionID] = scene; - scene.EventManager.OnRegionReadyStatusChange += s => { if (s.Ready) UploadMapTile(s); }; + // v2 Map generation on startup is now handled by scene to allow bmp to be shared with + // v1 service and not generate map tiles twice as was previous behavior + //scene.EventManager.OnRegionReadyStatusChange += s => { if (s.Ready) UploadMapTile(s); }; + + scene.RegisterModuleInterface(this); } /// @@ -188,42 +202,101 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.MapImage m_lastrefresh = Util.EnvironmentTickCount(); } + public void UploadMapTile(IScene scene, Bitmap mapTile) + { + if (mapTile == null) + { + m_log.WarnFormat("{0} Cannot upload null image", LogHeader); + return; + } + + m_log.DebugFormat("{0} Upload maptile for {1}", LogHeader, scene.Name); + + // mapTile.Save( // DEBUG DEBUG + // String.Format("maptiles/raw-{0}-{1}-{2}.jpg", regionName, scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY), + // ImageFormat.Jpeg); + // If the region/maptile is legacy sized, just upload the one tile like it has always been done + if (mapTile.Width == Constants.RegionSize && mapTile.Height == Constants.RegionSize) + { + ConvertAndUploadMaptile(mapTile, + scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY, + scene.RegionInfo.RegionName); + } + else + { + // For larger regions (varregion) we must cut the region image into legacy sized + // pieces since that is how the maptile system works. + // Note the assumption that varregions are always a multiple of legacy size. + for (uint xx = 0; xx < mapTile.Width; xx += Constants.RegionSize) + { + for (uint yy = 0; yy < mapTile.Height; yy += Constants.RegionSize) + { + // Images are addressed from the upper left corner so have to do funny + // math to pick out the sub-tile since regions are numbered from + // the lower left. + Rectangle rect = new Rectangle( + (int)xx, + mapTile.Height - (int)yy - (int)Constants.RegionSize, + (int)Constants.RegionSize, (int)Constants.RegionSize); + using (Bitmap subMapTile = mapTile.Clone(rect, mapTile.PixelFormat)) + { + ConvertAndUploadMaptile(subMapTile, + scene.RegionInfo.RegionLocX + (xx / Constants.RegionSize), + scene.RegionInfo.RegionLocY + (yy / Constants.RegionSize), + scene.Name); + } + } + } + } + } + /// /// /// private void UploadMapTile(IScene scene) { - m_log.DebugFormat("[MAP IMAGE SERVICE MODULE]: upload maptile for {0}", scene.RegionInfo.RegionName); - // Create a JPG map tile and upload it to the AddMapTile API - byte[] jpgData = Utils.EmptyBytes; IMapImageGenerator tileGenerator = scene.RequestModuleInterface(); if (tileGenerator == null) { - m_log.Warn("[MAP IMAGE SERVICE MODULE]: Cannot upload PNG map tile without an ImageGenerator"); + m_log.WarnFormat("{0} Cannot upload map tile without an ImageGenerator", LogHeader); return; } - using (Image mapTile = tileGenerator.CreateMapTile()) + using (Bitmap mapTile = tileGenerator.CreateMapTile()) { - using (MemoryStream stream = new MemoryStream()) + if (mapTile != null) + { + UploadMapTile(scene, mapTile); + } + else { - mapTile.Save(stream, ImageFormat.Jpeg); - jpgData = stream.ToArray(); + m_log.WarnFormat("{0} Tile image generation failed", LogHeader); } } + } - if (jpgData == Utils.EmptyBytes) + private void ConvertAndUploadMaptile(Image tileImage, uint locX, uint locY, string regionName) + { + byte[] jpgData = Utils.EmptyBytes; + + using (MemoryStream stream = new MemoryStream()) { - m_log.WarnFormat("[MAP IMAGE SERVICE MODULE]: Tile image generation failed"); - return; + tileImage.Save(stream, ImageFormat.Jpeg); + jpgData = stream.ToArray(); } - - string reason = string.Empty; - if (!m_MapService.AddMapTile((int)scene.RegionInfo.RegionLocX, (int)scene.RegionInfo.RegionLocY, jpgData, out reason)) + if (jpgData != Utils.EmptyBytes) + { + string reason = string.Empty; + if (!m_MapService.AddMapTile((int)locX, (int)locY, jpgData, out reason)) + { + m_log.DebugFormat("{0} Unable to upload tile image for {1} at {2}-{3}: {4}", LogHeader, + regionName, locX, locY, reason); + } + } + else { - m_log.DebugFormat("[MAP IMAGE SERVICE MODULE]: Unable to upload tile image for {0} at {1}-{2}: {3}", - scene.RegionInfo.RegionName, scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY, reason); + m_log.WarnFormat("{0} Tile image generation failed for region {1}", LogHeader, regionName); } } } diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Neighbour/LocalNeighbourServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Neighbour/LocalNeighbourServiceConnector.cs index fd89428..bda354f 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Neighbour/LocalNeighbourServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Neighbour/LocalNeighbourServiceConnector.cs @@ -125,14 +125,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Neighbour public OpenSim.Services.Interfaces.GridRegion HelloNeighbour(ulong regionHandle, RegionInfo thisRegion) { uint x, y; - Utils.LongToUInts(regionHandle, out x, out y); + Util.RegionHandleToRegionLoc(regionHandle, out x, out y); foreach (Scene s in m_Scenes) { if (s.RegionInfo.RegionHandle == regionHandle) { m_log.DebugFormat("[LOCAL NEIGHBOUR SERVICE CONNECTOR]: HelloNeighbour from region {0} to neighbour {1} at {2}-{3}", - thisRegion.RegionName, s.Name, x / Constants.RegionSize, y / Constants.RegionSize); + thisRegion.RegionName, s.Name, x, y ); //m_log.Debug("[NEIGHBOUR CONNECTOR]: Found region to SendHelloNeighbour"); return s.IncomingHelloNeighbour(thisRegion); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/PresenceDetector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/PresenceDetector.cs index 172bea1..50c252c 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/PresenceDetector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Presence/PresenceDetector.cs @@ -69,7 +69,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence public void OnMakeRootAgent(ScenePresence sp) { // m_log.DebugFormat("[PRESENCE DETECTOR]: Detected root presence {0} in {1}", sp.UUID, sp.Scene.RegionInfo.RegionName); - m_PresenceService.ReportAgent(sp.ControllingClient.SessionId, sp.Scene.RegionInfo.RegionID); + if (sp.PresenceType != PresenceType.Npc) + m_PresenceService.ReportAgent(sp.ControllingClient.SessionId, sp.Scene.RegionInfo.RegionID); } public void OnNewClient(IClientAPI client) @@ -79,7 +80,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence public void OnConnectionClose(IClientAPI client) { - if (!client.SceneAgent.IsChildAgent) + if (client != null && client.SceneAgent != null && !client.SceneAgent.IsChildAgent) { // m_log.DebugFormat("[PRESENCE DETECTOR]: Detected client logout {0} in {1}", client.AgentId, client.Scene.RegionInfo.RegionName); m_PresenceService.LogoutAgent(client.SessionId); diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs index 3c18074..cc8203e 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs @@ -46,11 +46,6 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// - /// Version of this service - /// - private const string m_Version = "SIMULATION/0.1"; - - /// /// Map region ID to scene. /// private Dictionary m_scenes = new Dictionary(); @@ -62,28 +57,27 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation #region Region Module interface - public void Initialise(IConfigSource config) + public void Initialise(IConfigSource configSource) { - IConfig moduleConfig = config.Configs["Modules"]; + IConfig moduleConfig = configSource.Configs["Modules"]; if (moduleConfig != null) { string name = moduleConfig.GetString("SimulationServices", ""); if (name == Name) { - //IConfig userConfig = config.Configs["SimulationService"]; - //if (userConfig == null) - //{ - // m_log.Error("[AVATAR CONNECTOR]: SimulationService missing from OpenSim.ini"); - // return; - //} + InitialiseService(configSource); m_ModuleEnabled = true; - m_log.Info("[SIMULATION CONNECTOR]: Local simulation enabled"); + m_log.Info("[LOCAL SIMULATION CONNECTOR]: Local simulation enabled."); } } } + public void InitialiseService(IConfigSource configSource) + { + } + public void PostInitialise() { } @@ -160,7 +154,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation #endregion - #region ISimulation + #region ISimulationService public IScene GetScene(UUID regionId) { @@ -191,7 +185,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation * Agent-related communications */ - public bool CreateAgent(GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) + public bool CreateAgent(GridRegion source, GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) { if (destination == null) { @@ -203,7 +197,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation if (m_scenes.ContainsKey(destination.RegionID)) { // m_log.DebugFormat("[LOCAL SIMULATION CONNECTOR]: Found region {0} to send SendCreateChildAgent", destination.RegionName); - return m_scenes[destination.RegionID].NewUserConnection(aCircuit, teleportFlags, out reason); + return m_scenes[destination.RegionID].NewUserConnection(aCircuit, teleportFlags, source, out reason); } reason = "Did not find region " + destination.RegionName; @@ -219,16 +213,19 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation { // m_log.DebugFormat( // "[LOCAL SIMULATION CONNECTOR]: Found region {0} {1} to send AgentUpdate", -// s.RegionInfo.RegionName, destination.RegionHandle); +// destination.RegionName, destination.RegionID); - return m_scenes[destination.RegionID].IncomingChildAgentDataUpdate(cAgentData); + return m_scenes[destination.RegionID].IncomingUpdateChildAgent(cAgentData); } -// m_log.DebugFormat("[LOCAL COMMS]: Did not find region {0} for ChildAgentUpdate", regionHandle); +// m_log.DebugFormat( +// "[LOCAL COMMS]: Did not find region {0} {1} for ChildAgentUpdate", +// destination.RegionName, destination.RegionID); + return false; } - public bool UpdateAgent(GridRegion destination, AgentPosition cAgentData) + public bool UpdateAgent(GridRegion destination, AgentPosition agentPosition) { if (destination == null) return false; @@ -239,38 +236,17 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation // note that we really don't need the GridRegion for this call foreach (Scene s in m_scenes.Values) { - //m_log.Debug("[LOCAL COMMS]: Found region to send ChildAgentUpdate"); - s.IncomingChildAgentDataUpdate(cAgentData); +// m_log.Debug("[LOCAL COMMS]: Found region to send ChildAgentUpdate"); + s.IncomingUpdateChildAgent(agentPosition); } //m_log.Debug("[LOCAL COMMS]: region not found for ChildAgentUpdate"); return true; } - public bool RetrieveAgent(GridRegion destination, UUID id, out IAgentData agent) - { - agent = null; - - if (destination == null) - return false; - - if (m_scenes.ContainsKey(destination.RegionID)) - { -// m_log.DebugFormat( -// "[LOCAL SIMULATION CONNECTOR]: Found region {0} {1} to send AgentUpdate", -// s.RegionInfo.RegionName, destination.RegionHandle); - - return m_scenes[destination.RegionID].IncomingRetrieveRootAgent(id, out agent); - } - - //m_log.Debug("[LOCAL COMMS]: region not found for ChildAgentUpdate"); - return false; - } - - public bool QueryAccess(GridRegion destination, UUID id, Vector3 position, out string version, out string reason) + public bool QueryAccess(GridRegion destination, UUID agentID, string agentHomeURI, bool viaTeleport, Vector3 position, List features, EntityTransferContext ctx, out string reason) { reason = "Communications failure"; - version = m_Version; if (destination == null) return false; @@ -279,8 +255,20 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation // m_log.DebugFormat( // "[LOCAL SIMULATION CONNECTOR]: Found region {0} {1} to send AgentUpdate", // s.RegionInfo.RegionName, destination.RegionHandle); + uint sizeX = m_scenes[destination.RegionID].RegionInfo.RegionSizeX; + uint sizeY = m_scenes[destination.RegionID].RegionInfo.RegionSizeY; - return m_scenes[destination.RegionID].QueryAccess(id, position, out reason); + // Var regions here, and the requesting simulator is in an older version. + // We will forbide this, because it crashes the viewers + if (ctx.OutboundVersion < 0.3f && (sizeX != 256 || sizeY != 256)) + { + reason = "Destination is a variable-sized region, and source is an old simulator. Consider upgrading."; + m_log.DebugFormat("[LOCAL SIMULATION CONNECTOR]: Request to access this variable-sized region from older simulator was denied"); + return false; + + } + + return m_scenes[destination.RegionID].QueryAccess(agentID, agentHomeURI, viaTeleport, position, features, out reason); } //m_log.Debug("[LOCAL COMMS]: region not found for QueryAccess"); @@ -303,7 +291,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; } - public bool CloseAgent(GridRegion destination, UUID id) + public bool CloseAgent(GridRegion destination, UUID id, string auth_token) { if (destination == null) return false; @@ -314,7 +302,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation // "[LOCAL SIMULATION CONNECTOR]: Found region {0} {1} to send AgentUpdate", // s.RegionInfo.RegionName, destination.RegionHandle); - Util.FireAndForget(delegate { m_scenes[destination.RegionID].IncomingCloseAgent(id, false); }); + m_scenes[destination.RegionID].CloseAgent(id, false, auth_token); return true; } @@ -356,7 +344,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; } - #endregion /* IInterregionComms */ + #endregion #region Misc diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs index b2a1b23..1e095ca 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.IO; using System.Net; using System.Reflection; @@ -37,7 +38,6 @@ using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; @@ -50,9 +50,9 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RemoteSimulationConnectorModule")] public class RemoteSimulationConnectorModule : ISharedRegionModule, ISimulationService { - private bool initialized = false; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private bool initialized = false; protected bool m_enabled = false; protected Scene m_aScene; // RemoteSimulationConnector does not care about local regions; it delegates that to the Local module @@ -60,31 +60,26 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation protected SimulationServiceConnector m_remoteConnector; protected bool m_safemode; - protected IPAddress m_thisIP; #region Region Module interface - public virtual void Initialise(IConfigSource config) + public virtual void Initialise(IConfigSource configSource) { - - IConfig moduleConfig = config.Configs["Modules"]; + IConfig moduleConfig = configSource.Configs["Modules"]; if (moduleConfig != null) { string name = moduleConfig.GetString("SimulationServices", ""); if (name == Name) { - //IConfig userConfig = config.Configs["SimulationService"]; - //if (userConfig == null) - //{ - // m_log.Error("[AVATAR CONNECTOR]: SimulationService missing from OpenSim.ini"); - // return; - //} + m_localBackend = new LocalSimulationConnectorModule(); + + m_localBackend.InitialiseService(configSource); m_remoteConnector = new SimulationServiceConnector(); m_enabled = true; - m_log.Info("[SIMULATION CONNECTOR]: Remote simulation enabled"); + m_log.Info("[REMOTE SIMULATION CONNECTOR]: Remote simulation enabled."); } } } @@ -142,16 +137,14 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation } protected virtual void InitOnce(Scene scene) - { - m_localBackend = new LocalSimulationConnectorModule(); + { m_aScene = scene; //m_regionClient = new RegionToRegionClient(m_aScene, m_hyperlinkService); - m_thisIP = Util.GetHostFromDNS(scene.RegionInfo.ExternalHostName); } #endregion - #region IInterregionComms + #region ISimulationService public IScene GetScene(UUID regionId) { @@ -167,7 +160,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation * Agent-related communications */ - public bool CreateAgent(GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) + public bool CreateAgent(GridRegion source, GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) { if (destination == null) { @@ -177,13 +170,13 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation } // Try local first - if (m_localBackend.CreateAgent(destination, aCircuit, teleportFlags, out reason)) + if (m_localBackend.CreateAgent(source, destination, aCircuit, teleportFlags, out reason)) return true; // else do the remote thing if (!m_localBackend.IsLocalRegion(destination.RegionID)) { - return m_remoteConnector.CreateAgent(destination, aCircuit, teleportFlags, out reason); + return m_remoteConnector.CreateAgent(source, destination, aCircuit, teleportFlags, out reason); } return false; } @@ -194,7 +187,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; // Try local first - if (m_localBackend.IsLocalRegion(destination.RegionHandle)) + if (m_localBackend.IsLocalRegion(destination.RegionID)) return m_localBackend.UpdateAgent(destination, cAgentData); return m_remoteConnector.UpdateAgent(destination, cAgentData); @@ -206,45 +199,26 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; // Try local first - if (m_localBackend.IsLocalRegion(destination.RegionHandle)) + if (m_localBackend.IsLocalRegion(destination.RegionID)) return m_localBackend.UpdateAgent(destination, cAgentData); return m_remoteConnector.UpdateAgent(destination, cAgentData); } - public bool RetrieveAgent(GridRegion destination, UUID id, out IAgentData agent) - { - agent = null; - - if (destination == null) - return false; - - // Try local first - if (m_localBackend.RetrieveAgent(destination, id, out agent)) - return true; - - // else do the remote thing - if (!m_localBackend.IsLocalRegion(destination.RegionHandle)) - return m_remoteConnector.RetrieveAgent(destination, id, out agent); - - return false; - } - - public bool QueryAccess(GridRegion destination, UUID id, Vector3 position, out string version, out string reason) + public bool QueryAccess(GridRegion destination, UUID agentID, string agentHomeURI, bool viaTeleport, Vector3 position, List features, EntityTransferContext ctx, out string reason) { reason = "Communications failure"; - version = "Unknown"; if (destination == null) return false; // Try local first - if (m_localBackend.QueryAccess(destination, id, position, out version, out reason)) + if (m_localBackend.QueryAccess(destination, agentID, agentHomeURI, viaTeleport, position, features, ctx, out reason)) return true; // else do the remote thing if (!m_localBackend.IsLocalRegion(destination.RegionID)) - return m_remoteConnector.QueryAccess(destination, id, position, out version, out reason); + return m_remoteConnector.QueryAccess(destination, agentID, agentHomeURI, viaTeleport, position, features, ctx, out reason); return false; } @@ -263,18 +237,18 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation } - public bool CloseAgent(GridRegion destination, UUID id) + public bool CloseAgent(GridRegion destination, UUID id, string auth_token) { if (destination == null) return false; // Try local first - if (m_localBackend.CloseAgent(destination, id)) + if (m_localBackend.CloseAgent(destination, id, auth_token)) return true; // else do the remote thing - if (!m_localBackend.IsLocalRegion(destination.RegionHandle)) - return m_remoteConnector.CloseAgent(destination, id); + if (!m_localBackend.IsLocalRegion(destination.RegionID)) + return m_remoteConnector.CloseAgent(destination, id, auth_token); return false; } @@ -296,12 +270,12 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation } // else do the remote thing - if (!m_localBackend.IsLocalRegion(destination.RegionHandle)) + if (!m_localBackend.IsLocalRegion(destination.RegionID)) return m_remoteConnector.CreateObject(destination, newPosition, sog, isLocalCall); return false; } - #endregion /* IInterregionComms */ + #endregion } } diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/LocalUserAccountServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/LocalUserAccountServiceConnector.cs index 529bfd7..6d4ac39 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/LocalUserAccountServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/LocalUserAccountServiceConnector.cs @@ -190,7 +190,15 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.UserAccounts // public bool StoreUserAccount(UserAccount data) { - return UserAccountService.StoreUserAccount(data); + bool ret = UserAccountService.StoreUserAccount(data); + if (ret) + m_Cache.Cache(data.PrincipalID, data); + return ret; + } + + public void InvalidateCache(UUID userID) + { + m_Cache.Invalidate(userID); } #endregion diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/UserAccountCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/UserAccountCache.cs index ddef75f..ed52e48 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/UserAccountCache.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/UserAccounts/UserAccountCache.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -61,6 +61,11 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.UserAccounts //m_log.DebugFormat("[USER CACHE]: cached user {0}", userID); } + public void Invalidate(UUID userID) + { + m_UUIDCache.Remove(userID); + } + public UserAccount Get(UUID userID, out bool inCache) { UserAccount account = null; diff --git a/OpenSim/Region/CoreModules/World/Access/AccessModule.cs b/OpenSim/Region/CoreModules/World/Access/AccessModule.cs index 1599f15..f567cab 100644 --- a/OpenSim/Region/CoreModules/World/Access/AccessModule.cs +++ b/OpenSim/Region/CoreModules/World/Access/AccessModule.cs @@ -91,13 +91,17 @@ namespace OpenSim.Region.CoreModules.World public void AddRegion(Scene scene) { - if (!m_SceneList.Contains(scene)) - m_SceneList.Add(scene); + lock (m_SceneList) + { + if (!m_SceneList.Contains(scene)) + m_SceneList.Add(scene); + } } public void RemoveRegion(Scene scene) { - m_SceneList.Remove(scene); + lock (m_SceneList) + m_SceneList.Remove(scene); } public void RegionLoaded(Scene scene) diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs index c810242..9c6706f 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs @@ -36,6 +36,7 @@ using System.Xml; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Serialization; using OpenSim.Framework.Serialization.External; using OpenSim.Region.CoreModules.World.Terrain; @@ -96,14 +97,42 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// /// Should the archive being loaded be merged with what is already on the region? + /// Merging usually suppresses terrain and parcel loading /// protected bool m_merge; /// + /// If true, force the loading of terrain from the oar file + /// + protected bool m_forceTerrain; + + /// + /// If true, force the loading of parcels from the oar file + /// + protected bool m_forceParcels; + + /// /// Should we ignore any assets when reloading the archive? /// protected bool m_skipAssets; + /// + /// Displacement added to each object as it is added to the world + /// + protected Vector3 m_displacement = Vector3.Zero; + + /// + /// Rotation (in radians) to apply to the objects as they are loaded. + /// + protected float m_rotation = 0f; + + /// + /// Center around which to apply the rotation relative to the origional oar position + /// + protected Vector3 m_rotationCenter = new Vector3(Constants.RegionSize / 2f, Constants.RegionSize / 2f, 0f); + + protected bool m_noObjects = false; + /// /// Used to cache lookups for valid uuids. /// @@ -132,10 +161,22 @@ namespace OpenSim.Region.CoreModules.World.Archiver private IAssetService m_assetService = null; - public ArchiveReadRequest(Scene scene, string loadPath, bool merge, bool skipAssets, Guid requestId) + private UUID m_defaultUser; + + public ArchiveReadRequest(Scene scene, string loadPath, Guid requestId, Dictionaryoptions) { m_rootScene = scene; + if (options.ContainsKey("default-user")) + { + m_defaultUser = (UUID)options["default-user"]; + m_log.InfoFormat("Using User {0} as default user", m_defaultUser.ToString()); + } + else + { + m_defaultUser = scene.RegionInfo.EstateSettings.EstateOwner; + } + m_loadPath = loadPath; try { @@ -150,26 +191,36 @@ namespace OpenSim.Region.CoreModules.World.Archiver } m_errorMessage = String.Empty; - m_merge = merge; - m_skipAssets = skipAssets; + m_merge = options.ContainsKey("merge"); + m_forceTerrain = options.ContainsKey("force-terrain"); + m_forceParcels = options.ContainsKey("force-parcels"); + m_noObjects = options.ContainsKey("no-objects"); + m_skipAssets = options.ContainsKey("skipAssets"); m_requestId = requestId; + m_displacement = options.ContainsKey("displacement") ? (Vector3)options["displacement"] : Vector3.Zero; + m_rotation = options.ContainsKey("rotation") ? (float)options["rotation"] : 0f; + m_rotationCenter = options.ContainsKey("rotation-center") ? (Vector3)options["rotation-center"] + : new Vector3(scene.RegionInfo.RegionSizeX / 2f, scene.RegionInfo.RegionSizeY / 2f, 0f); - // Zero can never be a valid user id + // Zero can never be a valid user or group id m_validUserUuids[UUID.Zero] = false; + m_validGroupUuids[UUID.Zero] = false; m_groupsModule = m_rootScene.RequestModuleInterface(); m_assetService = m_rootScene.AssetService; } - public ArchiveReadRequest(Scene scene, Stream loadStream, bool merge, bool skipAssets, Guid requestId) + public ArchiveReadRequest(Scene scene, Stream loadStream, Guid requestId, Dictionaryoptions) { m_rootScene = scene; m_loadPath = null; m_loadStream = loadStream; - m_merge = merge; - m_skipAssets = skipAssets; + m_skipAssets = options.ContainsKey("skipAssets"); + m_merge = options.ContainsKey("merge"); m_requestId = requestId; + m_defaultUser = scene.RegionInfo.EstateSettings.EstateOwner; + // Zero can never be a valid user id m_validUserUuids[UUID.Zero] = false; @@ -229,7 +280,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Process the file - if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH) && !m_noObjects) { sceneContext.SerialisedSceneObjects.Add(Encoding.UTF8.GetString(data)); } @@ -243,7 +294,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver if ((successfulAssetRestores + failedAssetRestores) % 250 == 0) m_log.Debug("[ARCHIVER]: Loaded " + successfulAssetRestores + " assets and failed to load " + failedAssetRestores + " assets..."); } - else if (!m_merge && filePath.StartsWith(ArchiveConstants.TERRAINS_PATH)) + else if (filePath.StartsWith(ArchiveConstants.TERRAINS_PATH) && (!m_merge || m_forceTerrain)) { LoadTerrain(scene, filePath, data); } @@ -251,7 +302,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver { LoadRegionSettings(scene, filePath, data, dearchivedScenes); } - else if (!m_merge && filePath.StartsWith(ArchiveConstants.LANDDATA_PATH)) + else if (filePath.StartsWith(ArchiveConstants.LANDDATA_PATH) && (!m_merge || m_forceParcels)) { sceneContext.SerialisedParcels.Add(Encoding.UTF8.GetString(data)); } @@ -321,7 +372,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Start the scripts. We delayed this because we want the OAR to finish loading ASAP, so // that users can enter the scene. If we allow the scripts to start in the loop above // then they significantly increase the time until the OAR finishes loading. - Util.FireAndForget(delegate(object o) + WorkManager.RunInThread(o => { Thread.Sleep(15000); m_log.Info("[ARCHIVER]: Starting scripts in scene objects"); @@ -336,7 +387,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver sceneContext.SceneObjects.Clear(); } - }); + }, null, string.Format("ReadArchiveStartScripts (request {0})", m_requestId)); m_log.InfoFormat("[ARCHIVER]: Successfully loaded archive"); @@ -422,6 +473,8 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Reload serialized prims m_log.InfoFormat("[ARCHIVER]: Loading {0} scene objects. Please wait.", serialisedSceneObjects.Count); + OpenMetaverse.Quaternion rot = OpenMetaverse.Quaternion.CreateFromAxisAngle(0, 0, 1, m_rotation); + UUID oldTelehubUUID = scene.RegionInfo.RegionSettings.TelehubObject; IRegionSerialiserModule serialiser = scene.RequestModuleInterface(); @@ -445,6 +498,32 @@ namespace OpenSim.Region.CoreModules.World.Archiver SceneObjectGroup sceneObject = serialiser.DeserializeGroupFromXml2(serialisedSceneObject); + // Happily this does not do much to the object since it hasn't been added to the scene yet + if (!sceneObject.IsAttachment) + { + if (m_displacement != Vector3.Zero || m_rotation != 0f) + { + Vector3 pos = sceneObject.AbsolutePosition; + if (m_rotation != 0f) + { + // Rotate the object + sceneObject.RootPart.RotationOffset = rot * sceneObject.GroupRotation; + // Get object position relative to rotation axis + Vector3 offset = pos - m_rotationCenter; + // Rotate the object position + offset *= rot; + // Restore the object position back to relative to the region + pos = m_rotationCenter + offset; + } + if (m_displacement != Vector3.Zero) + { + pos += m_displacement; + } + sceneObject.AbsolutePosition = pos; + } + } + + bool isTelehub = (sceneObject.UUID == oldTelehubUUID) && (oldTelehubUUID != UUID.Zero); // For now, give all incoming scene objects new uuids. This will allow scenes to be cloned @@ -460,58 +539,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver oldTelehubUUID = UUID.Zero; } - // Try to retain the original creator/owner/lastowner if their uuid is present on this grid - // or creator data is present. Otherwise, use the estate owner instead. - foreach (SceneObjectPart part in sceneObject.Parts) - { - if (part.CreatorData == null || part.CreatorData == string.Empty) - { - if (!ResolveUserUuid(scene, part.CreatorID)) - part.CreatorID = scene.RegionInfo.EstateSettings.EstateOwner; - } - if (UserManager != null) - UserManager.AddUser(part.CreatorID, part.CreatorData); - - if (!ResolveUserUuid(scene, part.OwnerID)) - part.OwnerID = scene.RegionInfo.EstateSettings.EstateOwner; - - if (!ResolveUserUuid(scene, part.LastOwnerID)) - part.LastOwnerID = scene.RegionInfo.EstateSettings.EstateOwner; - - if (!ResolveGroupUuid(part.GroupID)) - part.GroupID = UUID.Zero; - - // And zap any troublesome sit target information -// part.SitTargetOrientation = new Quaternion(0, 0, 0, 1); -// part.SitTargetPosition = new Vector3(0, 0, 0); - - // Fix ownership/creator of inventory items - // Not doing so results in inventory items - // being no copy/no mod for everyone - lock (part.TaskInventory) - { - TaskInventoryDictionary inv = part.TaskInventory; - foreach (KeyValuePair kvp in inv) - { - if (!ResolveUserUuid(scene, kvp.Value.OwnerID)) - { - kvp.Value.OwnerID = scene.RegionInfo.EstateSettings.EstateOwner; - } - - if (kvp.Value.CreatorData == null || kvp.Value.CreatorData == string.Empty) - { - if (!ResolveUserUuid(scene, kvp.Value.CreatorID)) - kvp.Value.CreatorID = scene.RegionInfo.EstateSettings.EstateOwner; - } - - if (UserManager != null) - UserManager.AddUser(kvp.Value.CreatorID, kvp.Value.CreatorData); - - if (!ResolveGroupUuid(kvp.Value.GroupID)) - kvp.Value.GroupID = UUID.Zero; - } - } - } + ModifySceneObject(scene, sceneObject); if (scene.AddRestoredSceneObject(sceneObject, true, false)) { @@ -535,6 +563,67 @@ namespace OpenSim.Region.CoreModules.World.Archiver scene.RegionInfo.RegionSettings.ClearSpawnPoints(); } } + + /// + /// Optionally modify a loaded SceneObjectGroup. Currently this just ensures that the + /// User IDs and Group IDs are valid, but other manipulations could be done as well. + /// + private void ModifySceneObject(Scene scene, SceneObjectGroup sceneObject) + { + // Try to retain the original creator/owner/lastowner if their uuid is present on this grid + // or creator data is present. Otherwise, use the estate owner instead. + foreach (SceneObjectPart part in sceneObject.Parts) + { + if (string.IsNullOrEmpty(part.CreatorData)) + { + if (!ResolveUserUuid(scene, part.CreatorID)) + part.CreatorID = m_defaultUser; + } + if (UserManager != null) + UserManager.AddUser(part.CreatorID, part.CreatorData); + + if (!(ResolveUserUuid(scene, part.OwnerID) || ResolveGroupUuid(part.OwnerID))) + part.OwnerID = m_defaultUser; + + if (!(ResolveUserUuid(scene, part.LastOwnerID) || ResolveGroupUuid(part.LastOwnerID))) + part.LastOwnerID = m_defaultUser; + + if (!ResolveGroupUuid(part.GroupID)) + part.GroupID = UUID.Zero; + + // And zap any troublesome sit target information + // part.SitTargetOrientation = new Quaternion(0, 0, 0, 1); + // part.SitTargetPosition = new Vector3(0, 0, 0); + + // Fix ownership/creator of inventory items + // Not doing so results in inventory items + // being no copy/no mod for everyone + lock (part.TaskInventory) + { + TaskInventoryDictionary inv = part.TaskInventory; + foreach (KeyValuePair kvp in inv) + { + if (!(ResolveUserUuid(scene, kvp.Value.OwnerID) || ResolveGroupUuid(kvp.Value.OwnerID))) + { + kvp.Value.OwnerID = m_defaultUser; + } + + if (string.IsNullOrEmpty(kvp.Value.CreatorData)) + { + if (!ResolveUserUuid(scene, kvp.Value.CreatorID)) + kvp.Value.CreatorID = m_defaultUser; + } + + if (UserManager != null) + UserManager.AddUser(kvp.Value.CreatorID, kvp.Value.CreatorData); + + if (!ResolveGroupUuid(kvp.Value.GroupID)) + kvp.Value.GroupID = UUID.Zero; + } + } + } + } + /// /// Load serialized parcels. @@ -549,15 +638,29 @@ namespace OpenSim.Region.CoreModules.World.Archiver foreach (string serialisedParcel in serialisedParcels) { LandData parcel = LandDataSerializer.Deserialize(serialisedParcel); + + if (m_displacement != Vector3.Zero) + { + Vector3 parcelDisp = new Vector3(m_displacement.X, m_displacement.Y, 0f); + parcel.AABBMin += parcelDisp; + parcel.AABBMax += parcelDisp; + } // Validate User and Group UUID's + if (!ResolveGroupUuid(parcel.GroupID)) + parcel.GroupID = UUID.Zero; + if (parcel.IsGroupOwned) { - if (!ResolveGroupUuid(parcel.GroupID)) + if (parcel.GroupID != UUID.Zero) + { + // In group-owned parcels, OwnerID=GroupID. This should already be the case, but let's make sure. + parcel.OwnerID = parcel.GroupID; + } + else { parcel.OwnerID = m_rootScene.RegionInfo.EstateSettings.EstateOwner; - parcel.GroupID = UUID.Zero; parcel.IsGroupOwned = false; } } @@ -565,9 +668,6 @@ namespace OpenSim.Region.CoreModules.World.Archiver { if (!ResolveUserUuid(scene, parcel.OwnerID)) parcel.OwnerID = m_rootScene.RegionInfo.EstateSettings.EstateOwner; - - if (!ResolveGroupUuid(parcel.GroupID)) - parcel.GroupID = UUID.Zero; } List accessList = new List(); @@ -604,13 +704,18 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// private bool ResolveUserUuid(Scene scene, UUID uuid) { - if (!m_validUserUuids.ContainsKey(uuid)) + lock (m_validUserUuids) { - UserAccount account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, uuid); - m_validUserUuids.Add(uuid, account != null); - } + if (!m_validUserUuids.ContainsKey(uuid)) + { + // Note: we call GetUserAccount() inside the lock because this UserID is likely + // to occur many times, and we only want to query the users service once. + UserAccount account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, uuid); + m_validUserUuids.Add(uuid, account != null); + } - return m_validUserUuids[uuid]; + return m_validUserUuids[uuid]; + } } /// @@ -620,22 +725,26 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// private bool ResolveGroupUuid(UUID uuid) { - if (uuid == UUID.Zero) - return true; // this means the object has no group - - if (!m_validGroupUuids.ContainsKey(uuid)) + lock (m_validGroupUuids) { - bool exists; - - if (m_groupsModule == null) - exists = false; - else - exists = (m_groupsModule.GetGroupRecord(uuid) != null); + if (!m_validGroupUuids.ContainsKey(uuid)) + { + bool exists; + if (m_groupsModule == null) + { + exists = false; + } + else + { + // Note: we call GetGroupRecord() inside the lock because this GroupID is likely + // to occur many times, and we only want to query the groups service once. + exists = (m_groupsModule.GetGroupRecord(uuid) != null); + } + m_validGroupUuids.Add(uuid, exists); + } - m_validGroupUuids.Add(uuid, exists); + return m_validGroupUuids[uuid]; } - - return m_validGroupUuids[uuid]; } /// Load an asset @@ -672,7 +781,21 @@ namespace OpenSim.Region.CoreModules.World.Archiver sbyte assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension]; if (assetType == (sbyte)AssetType.Unknown) + { m_log.WarnFormat("[ARCHIVER]: Importing {0} byte asset {1} with unknown type", data.Length, uuid); + } + else if (assetType == (sbyte)AssetType.Object) + { + data = SceneObjectSerializer.ModifySerializedObject(UUID.Parse(uuid), data, + sog => + { + ModifySceneObject(m_rootScene, sog); + return true; + }); + + if (data == null) + return false; + } //m_log.DebugFormat("[ARCHIVER]: Importing asset {0}, type {1}", uuid, assetType); @@ -796,9 +919,18 @@ namespace OpenSim.Region.CoreModules.World.Archiver { ITerrainModule terrainModule = scene.RequestModuleInterface(); - MemoryStream ms = new MemoryStream(data); - terrainModule.LoadFromStream(terrainPath, ms); - ms.Close(); + using (MemoryStream ms = new MemoryStream(data)) + { + if (m_displacement != Vector3.Zero || m_rotation != 0f) + { + Vector2 rotationCenter = new Vector2(m_rotationCenter.X, m_rotationCenter.Y); + terrainModule.LoadFromStream(terrainPath, m_displacement, m_rotation, rotationCenter, ms); + } + else + { + terrainModule.LoadFromStream(terrainPath, ms); + } + } m_log.DebugFormat("[ARCHIVER]: Restored terrain {0}", terrainPath); @@ -887,4 +1019,4 @@ namespace OpenSim.Region.CoreModules.World.Archiver return dearchivedScenes; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs index 7bdd65c..cb2c7f1 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs @@ -36,6 +36,7 @@ using System.Xml; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Serialization; using OpenSim.Region.CoreModules.World.Terrain; using OpenSim.Region.Framework.Interfaces; @@ -43,7 +44,9 @@ using OpenSim.Region.Framework.Scenes; using Ionic.Zlib; using GZipStream = Ionic.Zlib.GZipStream; using CompressionMode = Ionic.Zlib.CompressionMode; +using CompressionLevel = Ionic.Zlib.CompressionLevel; using OpenSim.Framework.Serialization.External; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.World.Archiver { @@ -78,7 +81,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// Determines which objects will be included in the archive, according to their permissions. /// Default is null, meaning no permission checks. /// - public string CheckPermissions { get; set; } + public string FilterContent { get; set; } protected Scene m_rootScene; protected Stream m_saveStream; @@ -129,7 +132,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver MultiRegionFormat = false; SaveAssets = true; - CheckPermissions = null; + FilterContent = null; } /// @@ -148,7 +151,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver Object temp; if (options.TryGetValue("checkPermissions", out temp)) - CheckPermissions = (string)temp; + FilterContent = (string)temp; // Find the regions to archive @@ -177,7 +180,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Archive the regions - Dictionary assetUuids = new Dictionary(); + Dictionary assetUuids = new Dictionary(); scenesGroup.ForEachScene(delegate(Scene scene) { @@ -198,7 +201,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver m_rootScene.AssetService, m_rootScene.UserAccountService, m_rootScene.RegionInfo.ScopeID, options, ReceivedAllAssets); - Util.FireAndForget(o => ar.Execute()); + WorkManager.RunInThread(o => ar.Execute(), null, "Archive Assets Request"); // CloseArchive() will be called from ReceivedAllAssets() } @@ -215,9 +218,9 @@ namespace OpenSim.Region.CoreModules.World.Archiver } } - private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary assetUuids) + private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary assetUuids) { - m_log.InfoFormat("[ARCHIVER]: Writing region {0}", scene.RegionInfo.RegionName); + m_log.InfoFormat("[ARCHIVER]: Writing region {0}", scene.Name); EntityBase[] entities = scene.GetEntities(); List sceneObjects = new List(); @@ -236,7 +239,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver if (!sceneObject.IsDeleted && !sceneObject.IsAttachment) { - if (!CanUserArchiveObject(scene.RegionInfo.EstateSettings.EstateOwner, sceneObject, CheckPermissions, permissionsModule)) + if (!CanUserArchiveObject(scene.RegionInfo.EstateSettings.EstateOwner, sceneObject, FilterContent, permissionsModule)) { // The user isn't allowed to copy/transfer this object, so it will not be included in the OAR. ++numObjectsSkippedPermissions; @@ -251,13 +254,13 @@ namespace OpenSim.Region.CoreModules.World.Archiver if (SaveAssets) { - UuidGatherer assetGatherer = new UuidGatherer(scene.AssetService); + UuidGatherer assetGatherer = new UuidGatherer(scene.AssetService, assetUuids); int prevAssets = assetUuids.Count; foreach (SceneObjectGroup sceneObject in sceneObjects) - { - assetGatherer.GatherAssetUuids(sceneObject, assetUuids); - } + assetGatherer.AddForInspection(sceneObject); + + assetGatherer.GatherAll(); m_log.DebugFormat( "[ARCHIVER]: {0} scene objects to serialize requiring save of {1} assets", @@ -275,16 +278,16 @@ namespace OpenSim.Region.CoreModules.World.Archiver RegionSettings regionSettings = scene.RegionInfo.RegionSettings; if (regionSettings.TerrainTexture1 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_1) - assetUuids[regionSettings.TerrainTexture1] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture1] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture2 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_2) - assetUuids[regionSettings.TerrainTexture2] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture2] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture3 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_3) - assetUuids[regionSettings.TerrainTexture3] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture3] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture4 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_4) - assetUuids[regionSettings.TerrainTexture4] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture4] = (sbyte)AssetType.Texture; Save(scene, sceneObjects, regionDir); } @@ -294,12 +297,12 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// /// The user /// The object group - /// Which permissions to check: "C" = Copy, "T" = Transfer + /// Which permissions to check: "C" = Copy, "T" = Transfer /// The scene's permissions module /// Whether the user is allowed to export the object to an OAR - private bool CanUserArchiveObject(UUID user, SceneObjectGroup objGroup, string checkPermissions, IPermissionsModule permissionsModule) + private bool CanUserArchiveObject(UUID user, SceneObjectGroup objGroup, string filterContent, IPermissionsModule permissionsModule) { - if (checkPermissions == null) + if (filterContent == null) return true; if (permissionsModule == null) @@ -341,9 +344,9 @@ namespace OpenSim.Region.CoreModules.World.Archiver canTransfer |= (obj.EveryoneMask & (uint)PermissionMask.Copy) != 0; bool partPermitted = true; - if (checkPermissions.Contains("C") && !canCopy) + if (filterContent.Contains("C") && !canCopy) partPermitted = false; - if (checkPermissions.Contains("T") && !canTransfer) + if (filterContent.Contains("T") && !canTransfer) partPermitted = false; // If the user is the Creator of the object then it can always be included in the OAR @@ -532,7 +535,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver if (isMegaregion) size = rcMod.GetSizeOfMegaregion(scene.RegionInfo.RegionID); else - size = new Vector2((float)Constants.RegionSize, (float)Constants.RegionSize); + size = new Vector2((float)scene.RegionInfo.RegionSizeX, (float)scene.RegionInfo.RegionSizeY); xtw.WriteElementString("is_megaregion", isMegaregion.ToString()); xtw.WriteElementString("size_in_meters", string.Format("{0},{1}", size.X, size.Y)); @@ -568,10 +571,11 @@ namespace OpenSim.Region.CoreModules.World.Archiver string terrainPath = String.Format("{0}{1}{2}.r32", regionDir, ArchiveConstants.TERRAINS_PATH, scene.RegionInfo.RegionName); - MemoryStream ms = new MemoryStream(); - scene.RequestModuleInterface().SaveToStream(terrainPath, ms); - m_archiveWriter.WriteFile(terrainPath, ms.ToArray()); - ms.Close(); + using (MemoryStream ms = new MemoryStream()) + { + scene.RequestModuleInterface().SaveToStream(terrainPath, ms); + m_archiveWriter.WriteFile(terrainPath, ms.ToArray()); + } m_log.InfoFormat("[ARCHIVER]: Adding scene objects to archive."); @@ -587,19 +591,29 @@ namespace OpenSim.Region.CoreModules.World.Archiver } } - protected void ReceivedAllAssets( - ICollection assetsFoundUuids, ICollection assetsNotFoundUuids) + protected void ReceivedAllAssets(ICollection assetsFoundUuids, ICollection assetsNotFoundUuids, bool timedOut) { - foreach (UUID uuid in assetsNotFoundUuids) + string errorMessage; + + if (timedOut) { - m_log.DebugFormat("[ARCHIVER]: Could not find asset {0}", uuid); + errorMessage = "Loading assets timed out"; } + else + { + foreach (UUID uuid in assetsNotFoundUuids) + { + m_log.DebugFormat("[ARCHIVER]: Could not find asset {0}", uuid); + } - // m_log.InfoFormat( - // "[ARCHIVER]: Received {0} of {1} assets requested", - // assetsFoundUuids.Count, assetsFoundUuids.Count + assetsNotFoundUuids.Count); + // m_log.InfoFormat( + // "[ARCHIVER]: Received {0} of {1} assets requested", + // assetsFoundUuids.Count, assetsFoundUuids.Count + assetsNotFoundUuids.Count); - CloseArchive(String.Empty); + errorMessage = String.Empty; + } + + CloseArchive(errorMessage); } /// @@ -626,4 +640,4 @@ namespace OpenSim.Region.CoreModules.World.Archiver m_rootScene.EventManager.TriggerOarFileSaved(m_requestId, errorMessage); } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs index 1be6386..6a09caf 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs @@ -33,11 +33,14 @@ using log4net; using NDesk.Options; using Nini.Config; using Mono.Addins; + using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenMetaverse; + namespace OpenSim.Region.CoreModules.World.Archiver { /// @@ -101,9 +104,62 @@ namespace OpenSim.Region.CoreModules.World.Archiver { bool mergeOar = false; bool skipAssets = false; + bool forceTerrain = false; + bool forceParcels = false; + bool noObjects = false; + Vector3 displacement = new Vector3(0f, 0f, 0f); + String defaultUser = ""; + float rotation = 0f; + Vector3 rotationCenter = new Vector3(Constants.RegionSize / 2f, Constants.RegionSize / 2f, 0); - OptionSet options = new OptionSet().Add("m|merge", delegate (string v) { mergeOar = v != null; }); - options.Add("s|skip-assets", delegate (string v) { skipAssets = v != null; }); + OptionSet options = new OptionSet(); + options.Add("m|merge", delegate (string v) { mergeOar = (v != null); }); + options.Add("s|skip-assets", delegate (string v) { skipAssets = (v != null); }); + options.Add("force-terrain", delegate (string v) { forceTerrain = (v != null); }); + options.Add("forceterrain", delegate (string v) { forceTerrain = (v != null); }); // downward compatibility + options.Add("force-parcels", delegate (string v) { forceParcels = (v != null); }); + options.Add("forceparcels", delegate (string v) { forceParcels = (v != null); }); // downward compatibility + options.Add("no-objects", delegate (string v) { noObjects = (v != null); }); + options.Add("default-user=", delegate(string v) { defaultUser = (v == null) ? "" : v; }); + options.Add("displacement=", delegate (string v) { + try + { + displacement = v == null ? Vector3.Zero : Vector3.Parse(v); + } + catch + { + m_log.ErrorFormat("[ARCHIVER MODULE] failure parsing displacement"); + m_log.ErrorFormat("[ARCHIVER MODULE] Must be represented as vector3: --displacement \"<128,128,0>\""); + return; + } + }); + options.Add("rotation=", delegate(string v) + { + try + { + rotation = v == null ? 0f : float.Parse(v); + } + catch + { + m_log.ErrorFormat("[ARCHIVER MODULE] failure parsing rotation"); + m_log.ErrorFormat("[ARCHIVER MODULE] Must be an angle in degrees between -360 and +360: --rotation 45"); + return; + } + // Convert to radians for internals + rotation = Util.Clamp(rotation, -359f, 359f) / 180f * (float)Math.PI; + }); + options.Add("rotation-center=", delegate (string v) { + try + { + rotationCenter = v == null ? Vector3.Zero : Vector3.Parse(v); + } + catch + { + m_log.ErrorFormat("[ARCHIVER MODULE] failure parsing rotation displacement"); + m_log.ErrorFormat("[ARCHIVER MODULE] Must be represented as vector3: --rotation-center \"<128,128,0>\""); + return; + } + }); // Send a message to the region ready module /* bluewall* Disable this for the time being @@ -122,13 +178,44 @@ namespace OpenSim.Region.CoreModules.World.Archiver // foreach (string param in mainParams) // m_log.DebugFormat("GOT PARAM [{0}]", param); + Dictionary archiveOptions = new Dictionary(); + if (mergeOar) archiveOptions.Add("merge", null); + if (skipAssets) archiveOptions.Add("skipAssets", null); + if (forceTerrain) archiveOptions.Add("force-terrain", null); + if (forceParcels) archiveOptions.Add("force-parcels", null); + if (noObjects) archiveOptions.Add("no-objects", null); + if (defaultUser != "") + { + UUID defaultUserUUID = UUID.Zero; + try + { + defaultUserUUID = Scene.UserManagementModule.GetUserIdByName(defaultUser); + } + catch + { + m_log.ErrorFormat("[ARCHIVER MODULE] default user must be in format \"First Last\"", defaultUser); + } + if (defaultUserUUID == UUID.Zero) + { + m_log.ErrorFormat("[ARCHIVER MODULE] cannot find specified default user {0}", defaultUser); + return; + } + else + { + archiveOptions.Add("default-user", defaultUserUUID); + } + } + archiveOptions.Add("displacement", displacement); + archiveOptions.Add("rotation", rotation); + archiveOptions.Add("rotation-center", rotationCenter); + if (mainParams.Count > 2) { - DearchiveRegion(mainParams[2], mergeOar, skipAssets, Guid.Empty); + DearchiveRegion(mainParams[2], Guid.Empty, archiveOptions); } else { - DearchiveRegion(DEFAULT_OAR_BACKUP_FILENAME, mergeOar, skipAssets, Guid.Empty); + DearchiveRegion(DEFAULT_OAR_BACKUP_FILENAME, Guid.Empty, archiveOptions); } } @@ -198,25 +285,27 @@ namespace OpenSim.Region.CoreModules.World.Archiver public void DearchiveRegion(string loadPath) { - DearchiveRegion(loadPath, false, false, Guid.Empty); + Dictionary archiveOptions = new Dictionary(); + DearchiveRegion(loadPath, Guid.Empty, archiveOptions); } - public void DearchiveRegion(string loadPath, bool merge, bool skipAssets, Guid requestId) + public void DearchiveRegion(string loadPath, Guid requestId, Dictionary options) { m_log.InfoFormat( "[ARCHIVER]: Loading archive to region {0} from {1}", Scene.RegionInfo.RegionName, loadPath); - new ArchiveReadRequest(Scene, loadPath, merge, skipAssets, requestId).DearchiveRegion(); + new ArchiveReadRequest(Scene, loadPath, requestId, options).DearchiveRegion(); } public void DearchiveRegion(Stream loadStream) { - DearchiveRegion(loadStream, false, false, Guid.Empty); + Dictionary archiveOptions = new Dictionary(); + DearchiveRegion(loadStream, Guid.Empty, archiveOptions); } - public void DearchiveRegion(Stream loadStream, bool merge, bool skipAssets, Guid requestId) + public void DearchiveRegion(Stream loadStream, Guid requestId, Dictionary options) { - new ArchiveReadRequest(Scene, loadStream, merge, skipAssets, requestId).DearchiveRegion(); + new ArchiveReadRequest(Scene, loadStream, requestId, options).DearchiveRegion(); } } } diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs index 95d109c..a8eae56 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs @@ -145,17 +145,10 @@ namespace OpenSim.Region.CoreModules.World.Archiver m_assetsWritten++; //m_log.DebugFormat("[ARCHIVER]: Added asset {0}", m_assetsWritten); - + if (m_assetsWritten % LOG_ASSET_LOAD_NOTIFICATION_INTERVAL == 0) m_log.InfoFormat("[ARCHIVER]: Added {0} assets to archive", m_assetsWritten); } - /// - /// Only call this if you need to force a close on the underlying writer. - /// - public void ForceClose() - { - m_archiveWriter.Close(); - } } } diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs index 103eb47..db66c83 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs @@ -33,6 +33,7 @@ using System.Timers; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Framework.Monitoring; using OpenSim.Framework.Serialization; using OpenSim.Framework.Serialization.External; using OpenSim.Services.Interfaces; @@ -50,7 +51,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// Method called when all the necessary assets for an archive request have been received. /// public delegate void AssetsRequestCallback( - ICollection assetsFoundUuids, ICollection assetsNotFoundUuids); + ICollection assetsFoundUuids, ICollection assetsNotFoundUuids, bool timedOut); enum RequestState { @@ -81,7 +82,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// /// uuids to request /// - protected IDictionary m_uuids; + protected IDictionary m_uuids; /// /// Callback used when all the assets requested have been received. @@ -115,7 +116,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver protected Dictionary m_options; protected internal AssetsRequest( - AssetsArchiver assetsArchiver, IDictionary uuids, + AssetsArchiver assetsArchiver, IDictionary uuids, IAssetService assetService, IUserAccountService userService, UUID scope, Dictionary options, AssetsRequestCallback assetsRequestCallback) @@ -143,19 +144,21 @@ namespace OpenSim.Region.CoreModules.World.Archiver m_requestState = RequestState.Running; m_log.DebugFormat("[ARCHIVER]: AssetsRequest executed looking for {0} possible assets", m_repliesRequired); - + // We can stop here if there are no assets to fetch if (m_repliesRequired == 0) { m_requestState = RequestState.Completed; - PerformAssetsRequestCallback(null); + PerformAssetsRequestCallback(false); return; } m_requestCallbackTimer.Enabled = true; - foreach (KeyValuePair kvp in m_uuids) + foreach (KeyValuePair kvp in m_uuids) { +// m_log.DebugFormat("[ARCHIVER]: Requesting asset {0}", kvp.Key); + // m_assetService.Get(kvp.Key.ToString(), kvp.Value, PreAssetRequestCallback); AssetBase asset = m_assetService.Get(kvp.Key.ToString()); PreAssetRequestCallback(kvp.Key.ToString(), kvp.Value, asset); @@ -164,7 +167,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver protected void OnRequestCallbackTimeout(object source, ElapsedEventArgs args) { - bool close = true; + bool timedOut = true; try { @@ -174,7 +177,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // the final request came in (assuming that such a thing is possible) if (m_requestState == RequestState.Completed) { - close = false; + timedOut = false; return; } @@ -223,8 +226,8 @@ namespace OpenSim.Region.CoreModules.World.Archiver } finally { - if (close) - m_assetsArchiver.ForceClose(); + if (timedOut) + WorkManager.RunInThread(PerformAssetsRequestCallback, true, "Archive Assets Request Callback"); } } @@ -233,9 +236,8 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Check for broken asset types and fix them with the AssetType gleaned by UuidGatherer if (fetchedAsset != null && fetchedAsset.Type == (sbyte)AssetType.Unknown) { - AssetType type = (AssetType)assetType; - m_log.InfoFormat("[ARCHIVER]: Rewriting broken asset type for {0} to {1}", fetchedAsset.ID, type); - fetchedAsset.Type = (sbyte)type; + m_log.InfoFormat("[ARCHIVER]: Rewriting broken asset type for {0} to {1}", fetchedAsset.ID, SLUtil.AssetTypeFromCode((sbyte)assetType)); + fetchedAsset.Type = (sbyte)assetType; } AssetRequestCallback(fetchedAssetID, this, fetchedAsset); @@ -294,7 +296,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // We want to stop using the asset cache thread asap // as we now need to do the work of producing the rest of the archive - Util.FireAndForget(PerformAssetsRequestCallback); + WorkManager.RunInThread(PerformAssetsRequestCallback, false, "Archive Assets Request Callback"); } else { @@ -315,9 +317,11 @@ namespace OpenSim.Region.CoreModules.World.Archiver { Culture.SetCurrentCulture(); + Boolean timedOut = (Boolean)o; + try { - m_assetsRequestCallback(m_foundAssetUuids, m_notFoundAssetUuids); + m_assetsRequestCallback(m_foundAssetUuids, m_notFoundAssetUuids, timedOut); } catch (Exception e) { @@ -331,7 +335,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver if (asset.Type == (sbyte)AssetType.Object && asset.Data != null && m_options.ContainsKey("home")) { //m_log.DebugFormat("[ARCHIVER]: Rewriting object data for {0}", asset.ID); - string xml = ExternalRepresentationUtils.RewriteSOP(Utils.BytesToString(asset.Data), m_options["home"].ToString(), m_userAccountService, m_scopeID); + string xml = ExternalRepresentationUtils.RewriteSOP(Utils.BytesToString(asset.Data), string.Empty, m_options["home"].ToString(), m_userAccountService, m_scopeID); asset.Data = Utils.StringToBytes(xml); } return asset; diff --git a/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs b/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs index eec1cec..9f197f5 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs @@ -45,7 +45,6 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; using ArchiveConstants = OpenSim.Framework.Serialization.ArchiveConstants; using TarArchiveReader = OpenSim.Framework.Serialization.TarArchiveReader; using TarArchiveWriter = OpenSim.Framework.Serialization.TarArchiveWriter; @@ -224,8 +223,9 @@ namespace OpenSim.Region.CoreModules.World.Archiver.Tests byte[] data = tar.ReadEntry(out filePath, out tarEntryType); Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); - - ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, false, false, Guid.Empty); + + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); Assert.That(arr.ControlFileLoaded, Is.True); @@ -308,8 +308,9 @@ namespace OpenSim.Region.CoreModules.World.Archiver.Tests byte[] data = tar.ReadEntry(out filePath, out tarEntryType); Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); - - ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, false, false, Guid.Empty); + + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); Assert.That(arr.ControlFileLoaded, Is.True); @@ -577,13 +578,14 @@ namespace OpenSim.Region.CoreModules.World.Archiver.Tests ArchiveConstants.CONTROL_FILE_PATH, new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty).CreateControlFile(new ArchiveScenesGroup())); - LandObject lo = new LandObject(groupID, true, null); + LandObject lo = new LandObject(groupID, true, m_scene); lo.SetLandBitmap(lo.BasicFullRegionLandBitmap()); LandData ld = lo.LandData; ld.GlobalID = landID; string ldPath = ArchiveConstants.CreateOarLandDataPath(ld); - tar.WriteFile(ldPath, LandDataSerializer.Serialize(ld, null)); + Dictionary options = new Dictionary(); + tar.WriteFile(ldPath, LandDataSerializer.Serialize(ld, options)); tar.Close(); oarStream = new MemoryStream(oarStream.ToArray()); @@ -752,7 +754,9 @@ namespace OpenSim.Region.CoreModules.World.Archiver.Tests byte[] archive = archiveWriteStream.ToArray(); MemoryStream archiveReadStream = new MemoryStream(archive); - m_archiverModule.DearchiveRegion(archiveReadStream, true, false, Guid.Empty); + Dictionary archiveOptions = new Dictionary(); + archiveOptions.Add("merge", null); + m_archiverModule.DearchiveRegion(archiveReadStream, Guid.Empty, archiveOptions); SceneObjectPart object1Existing = m_scene.GetSceneObjectPart(part1.Name); Assert.That(object1Existing, Is.Not.Null, "object1 was not present after merge"); @@ -860,7 +864,8 @@ namespace OpenSim.Region.CoreModules.World.Archiver.Tests byte[] data = tar.ReadEntry(out filePath, out tarEntryType); Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); - ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, false, false, Guid.Empty); + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); Assert.That(arr.ControlFileLoaded, Is.True); diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs index 4d49794..702b503 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs @@ -39,6 +39,7 @@ using OpenSim.Framework.Console; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; namespace OpenSim.Region.CoreModules.World.Estate { @@ -50,9 +51,7 @@ namespace OpenSim.Region.CoreModules.World.Estate private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected EstateManagementModule m_module; - - protected Commander m_commander = new Commander("estate"); - + public EstateManagementCommands(EstateManagementModule module) { m_module = module; @@ -60,7 +59,7 @@ namespace OpenSim.Region.CoreModules.World.Estate public void Initialise() { - m_log.DebugFormat("[ESTATE MODULE]: Setting up estate commands for region {0}", m_module.Scene.RegionInfo.RegionName); +// m_log.DebugFormat("[ESTATE MODULE]: Setting up estate commands for region {0}", m_module.Scene.RegionInfo.RegionName); m_module.Scene.AddCommand("Regions", m_module, "set terrain texture", "set terrain texture [] []", @@ -76,12 +75,19 @@ namespace OpenSim.Region.CoreModules.World.Estate " that coordinate. Corner # SW = 0, NW = 1, SE = 2, NE = 3, all corners = -1.", consoleSetTerrainHeights); + m_module.Scene.AddCommand("Regions", m_module, "set water height", + "set water height [] []", + "Sets the water height in meters. If and are specified, it will only set it on regions with a matching coordinate. " + + "Specify -1 in or to wildcard that coordinate.", + consoleSetWaterHeight); + m_module.Scene.AddCommand( "Estates", m_module, "estate show", "estate show", "Shows all estates on the simulator.", ShowEstatesCommand); - } + } public void Close() {} - + + #region CommandHandlers protected void consoleSetTerrainTexture(string module, string[] args) { string num = args[3]; @@ -121,7 +127,29 @@ namespace OpenSim.Region.CoreModules.World.Estate } } } - + protected void consoleSetWaterHeight(string module, string[] args) + { + string heightstring = args[3]; + + int x = (args.Length > 4 ? int.Parse(args[4]) : -1); + int y = (args.Length > 5 ? int.Parse(args[5]) : -1); + + if (x == -1 || m_module.Scene.RegionInfo.RegionLocX == x) + { + if (y == -1 || m_module.Scene.RegionInfo.RegionLocY == y) + { + double selectedheight = double.Parse(heightstring); + + m_log.Debug("[ESTATEMODULE]: Setting water height in " + m_module.Scene.RegionInfo.RegionName + " to " + + string.Format(" {0}", selectedheight)); + m_module.Scene.RegionInfo.RegionSettings.WaterHeight = selectedheight; + + m_module.Scene.RegionInfo.RegionSettings.Save(); + m_module.TriggerRegionInfoChange(); + m_module.sendRegionHandshakeToAll(); + } + } + } protected void consoleSetTerrainHeights(string module, string[] args) { string num = args[3]; @@ -196,6 +224,7 @@ namespace OpenSim.Region.CoreModules.World.Estate es.EstateName, es.EstateID, m_module.UserManager.GetUserName(es.EstateOwner)); MainConsole.Instance.Output(report.ToString()); - } + } + #endregion } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs index 311707b..80fa08a 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs @@ -32,6 +32,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security; +using System.Timers; using log4net; using Mono.Addins; using Nini.Config; @@ -39,6 +40,7 @@ using OpenMetaverse; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; using RegionFlags = OpenMetaverse.RegionFlags; namespace OpenSim.Region.CoreModules.World.Estate @@ -48,6 +50,7 @@ namespace OpenSim.Region.CoreModules.World.Estate { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Timer m_regionChangeTimer = new Timer(); public Scene Scene { get; private set; } public IUserManagement UserManager { get; private set; } @@ -112,13 +115,287 @@ namespace OpenSim.Region.CoreModules.World.Estate #endregion + #region IEstateModule Functions + public uint GetRegionFlags() + { + RegionFlags flags = RegionFlags.None; + + // Fully implemented + // + if (Scene.RegionInfo.RegionSettings.AllowDamage) + flags |= RegionFlags.AllowDamage; + if (Scene.RegionInfo.RegionSettings.BlockTerraform) + flags |= RegionFlags.BlockTerraform; + if (!Scene.RegionInfo.RegionSettings.AllowLandResell) + flags |= RegionFlags.BlockLandResell; + if (Scene.RegionInfo.RegionSettings.DisableCollisions) + flags |= RegionFlags.SkipCollisions; + if (Scene.RegionInfo.RegionSettings.DisableScripts) + flags |= RegionFlags.SkipScripts; + if (Scene.RegionInfo.RegionSettings.DisablePhysics) + flags |= RegionFlags.SkipPhysics; + if (Scene.RegionInfo.RegionSettings.BlockFly) + flags |= RegionFlags.NoFly; + if (Scene.RegionInfo.RegionSettings.RestrictPushing) + flags |= RegionFlags.RestrictPushObject; + if (Scene.RegionInfo.RegionSettings.AllowLandJoinDivide) + flags |= RegionFlags.AllowParcelChanges; + if (Scene.RegionInfo.RegionSettings.BlockShowInSearch) + flags |= RegionFlags.BlockParcelSearch; + + if (Scene.RegionInfo.RegionSettings.FixedSun) + flags |= RegionFlags.SunFixed; + if (Scene.RegionInfo.RegionSettings.Sandbox) + flags |= RegionFlags.Sandbox; + if (Scene.RegionInfo.EstateSettings.AllowVoice) + flags |= RegionFlags.AllowVoice; + if (Scene.RegionInfo.EstateSettings.AllowLandmark) + flags |= RegionFlags.AllowLandmark; + if (Scene.RegionInfo.EstateSettings.AllowSetHome) + flags |= RegionFlags.AllowSetHome; + if (Scene.RegionInfo.EstateSettings.BlockDwell) + flags |= RegionFlags.BlockDwell; + if (Scene.RegionInfo.EstateSettings.ResetHomeOnTeleport) + flags |= RegionFlags.ResetHomeOnTeleport; + + + // TODO: SkipUpdateInterestList + + // Omitted + // + // Omitted: NullLayer (what is that?) + // Omitted: SkipAgentAction (what does it do?) + + return (uint)flags; + } + + public bool IsManager(UUID avatarID) + { + if (avatarID == Scene.RegionInfo.EstateSettings.EstateOwner) + return true; + + List ems = new List(Scene.RegionInfo.EstateSettings.EstateManagers); + if (ems.Contains(avatarID)) + return true; + + return false; + } + + public void sendRegionHandshakeToAll() + { + Scene.ForEachClient(sendRegionHandshake); + } + + public void TriggerEstateInfoChange() + { + ChangeDelegate change = OnEstateInfoChange; + + if (change != null) + change(Scene.RegionInfo.RegionID); + } + + public void TriggerRegionInfoChange() + { + m_regionChangeTimer.Stop(); + m_regionChangeTimer.Start(); + + ChangeDelegate change = OnRegionInfoChange; + + if (change != null) + change(Scene.RegionInfo.RegionID); + } + + public void setEstateTerrainBaseTexture(int level, UUID texture) + { + setEstateTerrainBaseTexture(null, level, texture); + sendRegionHandshakeToAll(); + } + + public void setEstateTerrainTextureHeights(int corner, float lowValue, float highValue) + { + setEstateTerrainTextureHeights(null, corner, lowValue, highValue); + } + + public bool IsTerrainXfer(ulong xferID) + { + lock (this) + { + if (TerrainUploader == null) + return false; + else + return TerrainUploader.XferID == xferID; + } + } + + public string SetEstateOwner(int estateID, UserAccount account) + { + string response; + + // get the current settings from DB + EstateSettings dbSettings = Scene.EstateDataService.LoadEstateSettings(estateID); + if (dbSettings.EstateID == 0) + { + response = String.Format("No estate found with ID {0}", estateID); + } + else if (account.PrincipalID == dbSettings.EstateOwner) + { + response = String.Format("Estate already belongs to {0} ({1} {2})", account.PrincipalID, account.FirstName, account.LastName); + } + else + { + dbSettings.EstateOwner = account.PrincipalID; + Scene.EstateDataService.StoreEstateSettings(dbSettings); + response = String.Empty; + + // make sure there's a log entry to document the change + m_log.InfoFormat("[ESTATE]: Estate Owner for {0} changed to {1} ({2} {3})", dbSettings.EstateName, + account.PrincipalID, account.FirstName, account.LastName); + + // propagate the change + List regions = Scene.GetEstateRegions(estateID); + UUID regionId = (regions.Count() > 0) ? regions.ElementAt(0) : UUID.Zero; + if (regionId != UUID.Zero) + { + ChangeDelegate change = OnEstateInfoChange; + + if (change != null) + change(regionId); + } + + } + return response; + } + + public string SetEstateName(int estateID, string newName) + { + string response; + + // get the current settings from DB + EstateSettings dbSettings = Scene.EstateDataService.LoadEstateSettings(estateID); + + if (dbSettings.EstateID == 0) + { + response = String.Format("No estate found with ID {0}", estateID); + } + else if (newName == dbSettings.EstateName) + { + response = String.Format("Estate {0} is already named \"{1}\"", estateID, newName); + } + else + { + List estates = Scene.EstateDataService.GetEstates(newName); + if (estates.Count() > 0) + { + response = String.Format("An estate named \"{0}\" already exists.", newName); + } + else + { + string oldName = dbSettings.EstateName; + dbSettings.EstateName = newName; + Scene.EstateDataService.StoreEstateSettings(dbSettings); + response = String.Empty; + + // make sure there's a log entry to document the change + m_log.InfoFormat("[ESTATE]: Estate {0} renamed from \"{1}\" to \"{2}\"", estateID, oldName, newName); + + // propagate the change + List regions = Scene.GetEstateRegions(estateID); + UUID regionId = (regions.Count() > 0) ? regions.ElementAt(0) : UUID.Zero; + if (regionId != UUID.Zero) + { + ChangeDelegate change = OnEstateInfoChange; + + if (change != null) + change(regionId); + } + } + } + return response; + } + + public string SetRegionEstate(RegionInfo regionInfo, int estateID) + { + string response; + + if (regionInfo.EstateSettings.EstateID == estateID) + { + response = String.Format("\"{0}\" is already part of estate {1}", regionInfo.RegionName, estateID); + } + else + { + // get the current settings from DB + EstateSettings dbSettings = Scene.EstateDataService.LoadEstateSettings(estateID); + if (dbSettings.EstateID == 0) + { + response = String.Format("No estate found with ID {0}", estateID); + } + else if (Scene.EstateDataService.LinkRegion(regionInfo.RegionID, estateID)) + { + // make sure there's a log entry to document the change + m_log.InfoFormat("[ESTATE]: Region {0} ({1}) moved to Estate {2} ({3}).", regionInfo.RegionID, regionInfo.RegionName, estateID, dbSettings.EstateName); + + // propagate the change + ChangeDelegate change = OnEstateInfoChange; + + if (change != null) + change(regionInfo.RegionID); + + response = String.Empty; + } + else + { + response = String.Format("Could not move \"{0}\" to estate {1}", regionInfo.RegionName, estateID); + } + } + return response; + } + + public string CreateEstate(string estateName, UUID ownerID) + { + string response; + if (string.IsNullOrEmpty(estateName)) + { + response = "No estate name specified."; + } + else + { + List estates = Scene.EstateDataService.GetEstates(estateName); + if (estates.Count() > 0) + { + response = String.Format("An estate named \"{0}\" already exists.", estateName); + } + else + { + EstateSettings settings = Scene.EstateDataService.CreateNewEstate(); + if (settings == null) + response = String.Format("Unable to create estate \"{0}\" at this simulator", estateName); + else + { + settings.EstateOwner = ownerID; + settings.EstateName = estateName; + Scene.EstateDataService.StoreEstateSettings(settings); + response = String.Empty; + } + } + } + return response; + } + + #endregion + #region Packet Data Responders + private void clientSendDetailedEstateData(IClientAPI remote_client, UUID invoice) + { + sendDetailedEstateData(remote_client, invoice); + sendEstateLists(remote_client, invoice); + } + private void sendDetailedEstateData(IClientAPI remote_client, UUID invoice) { uint sun = 0; - if (!Scene.RegionInfo.EstateSettings.UseGlobalTime) + if (Scene.RegionInfo.EstateSettings.FixedSun) sun = (uint)(Scene.RegionInfo.EstateSettings.SunPosition * 1024.0) + 0x1800; UUID estateOwner; estateOwner = Scene.RegionInfo.EstateSettings.EstateOwner; @@ -136,7 +413,10 @@ namespace OpenSim.Region.CoreModules.World.Estate (uint) Scene.RegionInfo.RegionSettings.CovenantChangedDateTime, Scene.RegionInfo.EstateSettings.AbuseEmail, estateOwner); + } + private void sendEstateLists(IClientAPI remote_client, UUID invoice) + { remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.EstateManagers, Scene.RegionInfo.EstateSettings.EstateManagers, @@ -210,12 +490,6 @@ namespace OpenSim.Region.CoreModules.World.Estate sendRegionInfoPacketToAll(); } - public void setEstateTerrainBaseTexture(int level, UUID texture) - { - setEstateTerrainBaseTexture(null, level, texture); - sendRegionHandshakeToAll(); - } - public void setEstateTerrainBaseTexture(IClientAPI remoteClient, int level, UUID texture) { if (texture == UUID.Zero) @@ -242,11 +516,6 @@ namespace OpenSim.Region.CoreModules.World.Estate sendRegionInfoPacketToAll(); } - public void setEstateTerrainTextureHeights(int corner, float lowValue, float highValue) - { - setEstateTerrainTextureHeights(null, corner, lowValue, highValue); - } - public void setEstateTerrainTextureHeights(IClientAPI client, int corner, float lowValue, float highValue) { switch (corner) @@ -330,7 +599,7 @@ namespace OpenSim.Region.CoreModules.World.Estate timeInSeconds -= 15; } - restartModule.ScheduleRestart(UUID.Zero, "Region will restart in {0}", times.ToArray(), true); + restartModule.ScheduleRestart(UUID.Zero, "Region will restart in {0}", times.ToArray(), false); m_log.InfoFormat( "User {0} requested restart of region {1} in {2} seconds", @@ -372,13 +641,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.AddEstateUser(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.AddEstateUser(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.AccessOptions, Scene.RegionInfo.EstateSettings.EstateAccess, Scene.RegionInfo.EstateSettings.EstateID); @@ -405,13 +674,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.RemoveEstateUser(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.RemoveEstateUser(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.AccessOptions, Scene.RegionInfo.EstateSettings.EstateAccess, Scene.RegionInfo.EstateSettings.EstateID); @@ -437,13 +706,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.AddEstateGroup(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.AddEstateGroup(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.AllowedGroups, Scene.RegionInfo.EstateSettings.EstateGroups, Scene.RegionInfo.EstateSettings.EstateID); @@ -469,13 +738,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.RemoveEstateGroup(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.RemoveEstateGroup(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.AllowedGroups, Scene.RegionInfo.EstateSettings.EstateGroups, Scene.RegionInfo.EstateSettings.EstateID); @@ -524,7 +793,7 @@ namespace OpenSim.Region.CoreModules.World.Estate estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.AddBan(bitem); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } @@ -537,7 +806,7 @@ namespace OpenSim.Region.CoreModules.World.Estate item.BannedHostIPMask = "0.0.0.0"; Scene.RegionInfo.EstateSettings.AddBan(item); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); @@ -546,7 +815,11 @@ namespace OpenSim.Region.CoreModules.World.Estate { if (!s.IsChildAgent) { - Scene.TeleportClientHome(user, s.ControllingClient); + if (!Scene.TeleportClientHome(user, s.ControllingClient)) + { + s.ControllingClient.Kick("Your access to the region was revoked and TP home failed - you have been logged out."); + Scene.CloseAgent(s.UUID, false); + } } } @@ -555,7 +828,7 @@ namespace OpenSim.Region.CoreModules.World.Estate { remote_client.SendAlertMessage("User is already on the region ban list"); } - //m_scene.RegionInfo.regionBanlist.Add(Manager(user); + //Scene.RegionInfo.regionBanlist.Add(Manager(user); remote_client.SendBannedUserList(invoice, Scene.RegionInfo.EstateSettings.EstateBans, Scene.RegionInfo.EstateSettings.EstateID); } else @@ -596,13 +869,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.RemoveBan(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.RemoveBan(listitem.BannedUserID); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); } @@ -611,7 +884,7 @@ namespace OpenSim.Region.CoreModules.World.Estate remote_client.SendAlertMessage("User is not on the region ban list"); } - //m_scene.RegionInfo.regionBanlist.Add(Manager(user); + //Scene.RegionInfo.regionBanlist.Add(Manager(user); remote_client.SendBannedUserList(invoice, Scene.RegionInfo.EstateSettings.EstateBans, Scene.RegionInfo.EstateSettings.EstateID); } else @@ -635,13 +908,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.AddEstateManager(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.AddEstateManager(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.EstateManagers, Scene.RegionInfo.EstateSettings.EstateManagers, Scene.RegionInfo.EstateSettings.EstateID); @@ -667,13 +940,13 @@ namespace OpenSim.Region.CoreModules.World.Estate { estateSettings = Scene.EstateDataService.LoadEstateSettings(estateID); estateSettings.RemoveEstateManager(user); - estateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(estateSettings); } } } Scene.RegionInfo.EstateSettings.RemoveEstateManager(user); - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); remote_client.SendEstateList(invoice, (int)Constants.EstateAccessCodex.EstateManagers, Scene.RegionInfo.EstateSettings.EstateManagers, Scene.RegionInfo.EstateSettings.EstateID); @@ -685,7 +958,7 @@ namespace OpenSim.Region.CoreModules.World.Estate } } - public void handleOnEstateManageTelehub(IClientAPI client, UUID invoice, UUID senderID, string cmd, uint param1) + public void HandleOnEstateManageTelehub(IClientAPI client, UUID invoice, UUID senderID, string cmd, uint param1) { SceneObjectPart part; @@ -725,7 +998,9 @@ namespace OpenSim.Region.CoreModules.World.Estate default: break; } - SendTelehubInfo(client); + + if (client != null) + SendTelehubInfo(client); } private void SendSimulatorBlueBoxMessage( @@ -777,7 +1052,11 @@ namespace OpenSim.Region.CoreModules.World.Estate ScenePresence s = Scene.GetScenePresence(prey); if (s != null) { - Scene.TeleportClientHome(prey, s.ControllingClient); + if (!Scene.TeleportClientHome(prey, s.ControllingClient)) + { + s.ControllingClient.Kick("You were teleported home by the region owner, but the TP failed - you have been logged out."); + Scene.CloseAgent(s.UUID, false); + } } } } @@ -795,33 +1074,36 @@ namespace OpenSim.Region.CoreModules.World.Estate // Also make sure they are actually in the region ScenePresence p; if(Scene.TryGetScenePresence(client.AgentId, out p)) - Scene.TeleportClientHome(p.UUID, p.ControllingClient); + { + if (!Scene.TeleportClientHome(p.UUID, p.ControllingClient)) + { + p.ControllingClient.Kick("You were teleported home by the region owner, but the TP failed - you have been logged out."); + Scene.CloseAgent(p.UUID, false); + } + } } }); } private void AbortTerrainXferHandler(IClientAPI remoteClient, ulong XferID) { - if (TerrainUploader != null) + lock (this) { - lock (TerrainUploader) + if ((TerrainUploader != null) && (XferID == TerrainUploader.XferID)) { - if (XferID == TerrainUploader.XferID) - { - remoteClient.OnXferReceive -= TerrainUploader.XferReceive; - remoteClient.OnAbortXfer -= AbortTerrainXferHandler; - TerrainUploader.TerrainUploadDone -= HandleTerrainApplication; + remoteClient.OnXferReceive -= TerrainUploader.XferReceive; + remoteClient.OnAbortXfer -= AbortTerrainXferHandler; + TerrainUploader.TerrainUploadDone -= HandleTerrainApplication; - TerrainUploader = null; - remoteClient.SendAlertMessage("Terrain Upload aborted by the client"); - } + TerrainUploader = null; + remoteClient.SendAlertMessage("Terrain Upload aborted by the client"); } } - } + private void HandleTerrainApplication(string filename, byte[] terrainData, IClientAPI remoteClient) { - lock (TerrainUploader) + lock (this) { remoteClient.OnXferReceive -= TerrainUploader.XferReceive; remoteClient.OnAbortXfer -= AbortTerrainXferHandler; @@ -829,18 +1111,18 @@ namespace OpenSim.Region.CoreModules.World.Estate TerrainUploader = null; } + + m_log.DebugFormat("[CLIENT]: Terrain upload from {0} to {1} complete.", remoteClient.Name, Scene.Name); remoteClient.SendAlertMessage("Terrain Upload Complete. Loading...."); + ITerrainModule terr = Scene.RequestModuleInterface(); if (terr != null) { - m_log.Warn("[CLIENT]: Got Request to Send Terrain in region " + Scene.RegionInfo.RegionName); - try { - MemoryStream terrainStream = new MemoryStream(terrainData); - terr.LoadFromStream(filename, terrainStream); - terrainStream.Close(); + using (MemoryStream terrainStream = new MemoryStream(terrainData)) + terr.LoadFromStream(filename, terrainStream); FileInfo x = new FileInfo(filename); remoteClient.SendAlertMessage("Your terrain was loaded as a " + x.Extension + " file. It may take a few moments to appear."); @@ -880,25 +1162,27 @@ namespace OpenSim.Region.CoreModules.World.Estate private void handleUploadTerrain(IClientAPI remote_client, string clientFileName) { - if (TerrainUploader == null) + lock (this) { - - TerrainUploader = new EstateTerrainXferHandler(remote_client, clientFileName); - lock (TerrainUploader) + if (TerrainUploader == null) { + m_log.DebugFormat( + "[TERRAIN]: Started receiving terrain upload for region {0} from {1}", + Scene.Name, remote_client.Name); + + TerrainUploader = new EstateTerrainXferHandler(remote_client, clientFileName); remote_client.OnXferReceive += TerrainUploader.XferReceive; remote_client.OnAbortXfer += AbortTerrainXferHandler; TerrainUploader.TerrainUploadDone += HandleTerrainApplication; + TerrainUploader.RequestStartXfer(remote_client); + } + else + { + remote_client.SendAlertMessage("Another Terrain Upload is in progress. Please wait your turn!"); } - TerrainUploader.RequestStartXfer(remote_client); - - } - else - { - remote_client.SendAlertMessage("Another Terrain Upload is in progress. Please wait your turn!"); } } - + private void handleTerrainRequest(IClientAPI remote_client, string clientFileName) { // Save terrain here @@ -906,7 +1190,7 @@ namespace OpenSim.Region.CoreModules.World.Estate if (terr != null) { - m_log.Warn("[CLIENT]: Got Request to Send Terrain in region " + Scene.RegionInfo.RegionName); +// m_log.Warn("[CLIENT]: Got Request to Send Terrain in region " + Scene.RegionInfo.RegionName); if (File.Exists(Util.dataDir() + "/terrain.raw")) { File.Delete(Util.dataDir() + "/terrain.raw"); @@ -918,8 +1202,9 @@ namespace OpenSim.Region.CoreModules.World.Estate input.Read(bdata, 0, (int)input.Length); remote_client.SendAlertMessage("Terrain file written, starting download..."); Scene.XferManager.AddNewFile("terrain.raw", bdata); - // Tell client about it - m_log.Warn("[CLIENT]: Sending Terrain to " + remote_client.Name); + + m_log.DebugFormat("[CLIENT]: Sending terrain for region {0} to {1}", Scene.Name, remote_client.Name); + remote_client.SendInitiateDownload("terrain.raw", clientFileName); } } @@ -1080,11 +1365,6 @@ namespace OpenSim.Region.CoreModules.World.Estate remoteClient.SendRegionHandshake(Scene.RegionInfo,args); } - public void sendRegionHandshakeToAll() - { - Scene.ForEachClient(sendRegionHandshake); - } - public void handleEstateChangeInfo(IClientAPI remoteClient, UUID invoice, UUID senderID, UInt32 parms1, UInt32 parms2) { if (parms2 == 0) @@ -1096,6 +1376,7 @@ namespace OpenSim.Region.CoreModules.World.Estate { Scene.RegionInfo.EstateSettings.UseGlobalTime = false; Scene.RegionInfo.EstateSettings.SunPosition = (parms2 - 0x1800)/1024.0; + // Warning: FixedSun should be set to True, otherwise this sun position won't be used. } if ((parms1 & 0x00000010) != 0) @@ -1138,7 +1419,7 @@ namespace OpenSim.Region.CoreModules.World.Estate else Scene.RegionInfo.EstateSettings.DenyMinors = false; - Scene.RegionInfo.EstateSettings.Save(); + Scene.EstateDataService.StoreEstateSettings(Scene.RegionInfo.EstateSettings); TriggerEstateInfoChange(); Scene.TriggerEstateSunUpdate(); @@ -1165,11 +1446,12 @@ namespace OpenSim.Region.CoreModules.World.Estate sendRegionInfoPacketToAll(); } - #endregion + + #endregion private void EventManager_OnNewClient(IClientAPI client) { - client.OnDetailedEstateDataRequest += sendDetailedEstateData; + client.OnDetailedEstateDataRequest += clientSendDetailedEstateData; client.OnSetEstateFlagsRequest += estateSetRegionInfoHandler; // client.OnSetEstateTerrainBaseTexture += setEstateTerrainBaseTexture; client.OnSetEstateTerrainDetailTexture += setEstateTerrainBaseTexture; @@ -1179,7 +1461,7 @@ namespace OpenSim.Region.CoreModules.World.Estate client.OnEstateRestartSimRequest += handleEstateRestartSimRequest; client.OnEstateChangeCovenantRequest += handleChangeEstateCovenantRequest; client.OnEstateChangeInfo += handleEstateChangeInfo; - client.OnEstateManageTelehub += handleOnEstateManageTelehub; + client.OnEstateManageTelehub += HandleOnEstateManageTelehub; client.OnUpdateEstateAccessDeltaRequest += handleEstateAccessDeltaRequest; client.OnSimulatorBlueBoxMessageRequest += SendSimulatorBlueBoxMessage; client.OnEstateBlueBoxMessageRequest += SendEstateBlueBoxMessage; @@ -1195,56 +1477,7 @@ namespace OpenSim.Region.CoreModules.World.Estate sendRegionHandshake(client); } - public uint GetRegionFlags() - { - RegionFlags flags = RegionFlags.None; - - // Fully implemented - // - if (Scene.RegionInfo.RegionSettings.AllowDamage) - flags |= RegionFlags.AllowDamage; - if (Scene.RegionInfo.RegionSettings.BlockTerraform) - flags |= RegionFlags.BlockTerraform; - if (!Scene.RegionInfo.RegionSettings.AllowLandResell) - flags |= RegionFlags.BlockLandResell; - if (Scene.RegionInfo.RegionSettings.DisableCollisions) - flags |= RegionFlags.SkipCollisions; - if (Scene.RegionInfo.RegionSettings.DisableScripts) - flags |= RegionFlags.SkipScripts; - if (Scene.RegionInfo.RegionSettings.DisablePhysics) - flags |= RegionFlags.SkipPhysics; - if (Scene.RegionInfo.RegionSettings.BlockFly) - flags |= RegionFlags.NoFly; - if (Scene.RegionInfo.RegionSettings.RestrictPushing) - flags |= RegionFlags.RestrictPushObject; - if (Scene.RegionInfo.RegionSettings.AllowLandJoinDivide) - flags |= RegionFlags.AllowParcelChanges; - if (Scene.RegionInfo.RegionSettings.BlockShowInSearch) - flags |= RegionFlags.BlockParcelSearch; - - if (Scene.RegionInfo.RegionSettings.FixedSun) - flags |= RegionFlags.SunFixed; - if (Scene.RegionInfo.RegionSettings.Sandbox) - flags |= RegionFlags.Sandbox; - if (Scene.RegionInfo.EstateSettings.AllowVoice) - flags |= RegionFlags.AllowVoice; - - // Fudge these to always on, so the menu options activate - // - flags |= RegionFlags.AllowLandmark; - flags |= RegionFlags.AllowSetHome; - - // TODO: SkipUpdateInterestList - - // Omitted - // - // Omitted: NullLayer (what is that?) - // Omitted: SkipAgentAction (what does it do?) - - return (uint)flags; - } - - public uint GetEstateFlags() + private uint GetEstateFlags() { RegionFlags flags = RegionFlags.None; @@ -1273,40 +1506,18 @@ namespace OpenSim.Region.CoreModules.World.Estate flags |= RegionFlags.ResetHomeOnTeleport; if (Scene.RegionInfo.EstateSettings.TaxFree) flags |= RegionFlags.TaxFree; + if (Scene.RegionInfo.EstateSettings.AllowLandmark) + flags |= RegionFlags.AllowLandmark; + if (Scene.RegionInfo.EstateSettings.AllowParcelChanges) + flags |= RegionFlags.AllowParcelChanges; + if (Scene.RegionInfo.EstateSettings.AllowSetHome) + flags |= RegionFlags.AllowSetHome; if (Scene.RegionInfo.EstateSettings.DenyMinors) flags |= (RegionFlags)(1 << 30); return (uint)flags; } - public bool IsManager(UUID avatarID) - { - if (avatarID == Scene.RegionInfo.EstateSettings.EstateOwner) - return true; - - List ems = new List(Scene.RegionInfo.EstateSettings.EstateManagers); - if (ems.Contains(avatarID)) - return true; - - return false; - } - - public void TriggerRegionInfoChange() - { - ChangeDelegate change = OnRegionInfoChange; - - if (change != null) - change(Scene.RegionInfo.RegionID); - } - - public void TriggerEstateInfoChange() - { - ChangeDelegate change = OnEstateInfoChange; - - if (change != null) - change(Scene.RegionInfo.RegionID); - } - public void TriggerEstateMessage(UUID fromID, string fromName, string message) { MessageDelegate onmessage = OnEstateMessage; diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs b/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs index b8d8b10..2d74eaf 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs @@ -78,7 +78,10 @@ namespace OpenSim.Region.CoreModules.World.Estate /// public void XferReceive(IClientAPI remoteClient, ulong xferID, uint packetID, byte[] data) { - if (mXferID == xferID) + if (mXferID != xferID) + return; + + lock (this) { if (m_asset.Data.Length > 1) { @@ -99,7 +102,6 @@ namespace OpenSim.Region.CoreModules.World.Estate if ((packetID & 0x80000000) != 0) { SendCompleteMessage(remoteClient); - } } } diff --git a/OpenSim/Region/CoreModules/World/Estate/XEstateConnector.cs b/OpenSim/Region/CoreModules/World/Estate/XEstateConnector.cs new file mode 100644 index 0000000..73e706c --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Estate/XEstateConnector.cs @@ -0,0 +1,218 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; + +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Server.Base; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; + +using OpenMetaverse; +using log4net; + +namespace OpenSim.Region.CoreModules.World.Estate +{ + public class EstateConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected XEstateModule m_EstateModule; + + public EstateConnector(XEstateModule module) + { + m_EstateModule = module; + } + + public void SendTeleportHomeOneUser(uint EstateID, UUID PreyID) + { + Dictionary sendData = new Dictionary(); + sendData["METHOD"] = "teleport_home_one_user"; + + sendData["EstateID"] = EstateID.ToString(); + sendData["PreyID"] = PreyID.ToString(); + + SendToEstate(EstateID, sendData); + } + + public void SendTeleportHomeAllUsers(uint EstateID) + { + Dictionary sendData = new Dictionary(); + sendData["METHOD"] = "teleport_home_all_users"; + + sendData["EstateID"] = EstateID.ToString(); + + SendToEstate(EstateID, sendData); + } + + public bool SendUpdateCovenant(uint EstateID, UUID CovenantID) + { + Dictionary sendData = new Dictionary(); + sendData["METHOD"] = "update_covenant"; + + sendData["CovenantID"] = CovenantID.ToString(); + sendData["EstateID"] = EstateID.ToString(); + + // Handle local regions locally + // + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == EstateID) + s.RegionInfo.RegionSettings.Covenant = CovenantID; +// s.ReloadEstateData(); + } + + SendToEstate(EstateID, sendData); + + return true; + } + + public bool SendUpdateEstate(uint EstateID) + { + Dictionary sendData = new Dictionary(); + sendData["METHOD"] = "update_estate"; + + sendData["EstateID"] = EstateID.ToString(); + + // Handle local regions locally + // + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == EstateID) + s.ReloadEstateData(); + } + + SendToEstate(EstateID, sendData); + + return true; + } + + public void SendEstateMessage(uint EstateID, UUID FromID, string FromName, string Message) + { + Dictionary sendData = new Dictionary(); + sendData["METHOD"] = "estate_message"; + + sendData["EstateID"] = EstateID.ToString(); + sendData["FromID"] = FromID.ToString(); + sendData["FromName"] = FromName; + sendData["Message"] = Message; + + SendToEstate(EstateID, sendData); + } + + private void SendToEstate(uint EstateID, Dictionary sendData) + { + List regions = m_EstateModule.Scenes[0].GetEstateRegions((int)EstateID); + + UUID ScopeID = UUID.Zero; + + // Handle local regions locally + // + lock (m_EstateModule.Scenes) + { + foreach (Scene s in m_EstateModule.Scenes) + { + if (regions.Contains(s.RegionInfo.RegionID)) + { + // All regions in one estate are in the same scope. + // Use that scope. + // + ScopeID = s.RegionInfo.ScopeID; + regions.Remove(s.RegionInfo.RegionID); + } + } + } + + // Our own region should always be in the above list. + // In a standalone this would not be true. But then, + // Scope ID is not relevat there. Use first scope. + // + if (ScopeID == UUID.Zero) + ScopeID = m_EstateModule.Scenes[0].RegionInfo.ScopeID; + + // Don't send to the same instance twice + // + List done = new List(); + + // Send to remote regions + // + foreach (UUID regionID in regions) + { + GridRegion region = m_EstateModule.Scenes[0].GridService.GetRegionByUUID(ScopeID, regionID); + if (region != null) + { + string url = "http://" + region.ExternalHostName + ":" + region.HttpPort; + if (done.Contains(url)) + continue; + + Call(region, sendData); + done.Add(url); + } + } + } + + private bool Call(GridRegion region, Dictionary sendData) + { + string reqString = ServerUtils.BuildQueryString(sendData); + // m_log.DebugFormat("[XESTATE CONNECTOR]: queryString = {0}", reqString); + try + { + string url = "http://" + region.ExternalHostName + ":" + region.HttpPort; + string reply = SynchronousRestFormsRequester.MakeRequest("POST", + url + "/estate", + reqString); + if (reply != string.Empty) + { + Dictionary replyData = ServerUtils.ParseXmlResponse(reply); + + if (replyData.ContainsKey("RESULT")) + { + if (replyData["RESULT"].ToString().ToLower() == "true") + return true; + else + return false; + } + else + m_log.DebugFormat("[XESTATE CONNECTOR]: reply data does not contain result field"); + + } + else + m_log.DebugFormat("[XESTATE CONNECTOR]: received empty reply"); + } + catch (Exception e) + { + m_log.DebugFormat("[XESTATE CONNECTOR]: Exception when contacting remote sim: {0}", e.Message); + } + + return false; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Estate/XEstateModule.cs b/OpenSim/Region/CoreModules/World/Estate/XEstateModule.cs new file mode 100644 index 0000000..4bb3799 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Estate/XEstateModule.cs @@ -0,0 +1,255 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Server.Base; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.World.Estate +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XEstate")] + public class XEstateModule : ISharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected List m_Scenes = new List(); + protected bool m_InInfoUpdate = false; + + public bool InInfoUpdate + { + get { return m_InInfoUpdate; } + set { m_InInfoUpdate = value; } + } + + public List Scenes + { + get { return m_Scenes; } + } + + protected EstateConnector m_EstateConnector; + + public void Initialise(IConfigSource config) + { + int port = 0; + + IConfig estateConfig = config.Configs["Estate"]; + if (estateConfig != null) + { + port = estateConfig.GetInt("Port", 0); + } + + m_EstateConnector = new EstateConnector(this); + + // Instantiate the request handler + IHttpServer server = MainServer.GetHttpServer((uint)port); + server.AddStreamHandler(new EstateRequestHandler(this)); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + lock (m_Scenes) + m_Scenes.Add(scene); + + scene.EventManager.OnNewClient += OnNewClient; + } + + public void RegionLoaded(Scene scene) + { + IEstateModule em = scene.RequestModuleInterface(); + + em.OnRegionInfoChange += OnRegionInfoChange; + em.OnEstateInfoChange += OnEstateInfoChange; + em.OnEstateMessage += OnEstateMessage; + } + + public void RemoveRegion(Scene scene) + { + scene.EventManager.OnNewClient -= OnNewClient; + + lock (m_Scenes) + m_Scenes.Remove(scene); + } + + public string Name + { + get { return "EstateModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + private Scene FindScene(UUID RegionID) + { + foreach (Scene s in Scenes) + { + if (s.RegionInfo.RegionID == RegionID) + return s; + } + + return null; + } + + private void OnRegionInfoChange(UUID RegionID) + { + Scene s = FindScene(RegionID); + if (s == null) + return; + + if (!m_InInfoUpdate) + m_EstateConnector.SendUpdateCovenant(s.RegionInfo.EstateSettings.EstateID, s.RegionInfo.RegionSettings.Covenant); + } + + private void OnEstateInfoChange(UUID RegionID) + { + Scene s = FindScene(RegionID); + if (s == null) + return; + + if (!m_InInfoUpdate) + m_EstateConnector.SendUpdateEstate(s.RegionInfo.EstateSettings.EstateID); + } + + private void OnEstateMessage(UUID RegionID, UUID FromID, string FromName, string Message) + { + Scene senderScenes = FindScene(RegionID); + if (senderScenes == null) + return; + + uint estateID = senderScenes.RegionInfo.EstateSettings.EstateID; + + foreach (Scene s in Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == estateID) + { + IDialogModule dm = s.RequestModuleInterface(); + + if (dm != null) + { + dm.SendNotificationToUsersInRegion(FromID, FromName, + Message); + } + } + } + if (!m_InInfoUpdate) + m_EstateConnector.SendEstateMessage(estateID, FromID, FromName, Message); + } + + private void OnNewClient(IClientAPI client) + { + client.OnEstateTeleportOneUserHomeRequest += OnEstateTeleportOneUserHomeRequest; + client.OnEstateTeleportAllUsersHomeRequest += OnEstateTeleportAllUsersHomeRequest; + + } + + private void OnEstateTeleportOneUserHomeRequest(IClientAPI client, UUID invoice, UUID senderID, UUID prey) + { + if (prey == UUID.Zero) + return; + + if (!(client.Scene is Scene)) + return; + + Scene scene = (Scene)client.Scene; + + uint estateID = scene.RegionInfo.EstateSettings.EstateID; + + if (!scene.Permissions.CanIssueEstateCommand(client.AgentId, false)) + return; + + foreach (Scene s in Scenes) + { + if (s == scene) + continue; // Already handles by estate module + if (s.RegionInfo.EstateSettings.EstateID != estateID) + continue; + + ScenePresence p = scene.GetScenePresence(prey); + if (p != null && !p.IsChildAgent) + { + p.ControllingClient.SendTeleportStart(16); + scene.TeleportClientHome(prey, p.ControllingClient); + } + } + + m_EstateConnector.SendTeleportHomeOneUser(estateID, prey); + } + + private void OnEstateTeleportAllUsersHomeRequest(IClientAPI client, UUID invoice, UUID senderID) + { + if (!(client.Scene is Scene)) + return; + + Scene scene = (Scene)client.Scene; + + uint estateID = scene.RegionInfo.EstateSettings.EstateID; + + if (!scene.Permissions.CanIssueEstateCommand(client.AgentId, false)) + return; + + foreach (Scene s in Scenes) + { + if (s == scene) + continue; // Already handles by estate module + if (s.RegionInfo.EstateSettings.EstateID != estateID) + continue; + + scene.ForEachScenePresence(delegate(ScenePresence p) { + if (p != null && !p.IsChildAgent) + { + p.ControllingClient.SendTeleportStart(16); + scene.TeleportClientHome(p.ControllingClient.AgentId, p.ControllingClient); + } + }); + } + + m_EstateConnector.SendTeleportHomeAllUsers(estateID); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Estate/XEstateRequestHandler.cs b/OpenSim/Region/CoreModules/World/Estate/XEstateRequestHandler.cs new file mode 100644 index 0000000..ec5af2b --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Estate/XEstateRequestHandler.cs @@ -0,0 +1,288 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; + +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; +using log4net; + +namespace OpenSim.Region.CoreModules.World.Estate +{ + public class EstateRequestHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected XEstateModule m_EstateModule; + protected Object m_RequestLock = new Object(); + + public EstateRequestHandler(XEstateModule fmodule) + : base("POST", "/estate") + { + m_EstateModule = fmodule; + } + + protected override byte[] ProcessRequest(string path, Stream requestData, + IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + StreamReader sr = new StreamReader(requestData); + string body = sr.ReadToEnd(); + sr.Close(); + body = body.Trim(); + + m_log.DebugFormat("[XESTATE HANDLER]: query String: {0}", body); + + try + { + lock (m_RequestLock) + { + Dictionary request = + ServerUtils.ParseQueryString(body); + + if (!request.ContainsKey("METHOD")) + return FailureResult(); + + string method = request["METHOD"].ToString(); + request.Remove("METHOD"); + + try + { + m_EstateModule.InInfoUpdate = false; + + switch (method) + { + case "update_covenant": + return UpdateCovenant(request); + case "update_estate": + return UpdateEstate(request); + case "estate_message": + return EstateMessage(request); + case "teleport_home_one_user": + return TeleportHomeOneUser(request); + case "teleport_home_all_users": + return TeleportHomeAllUsers(request); + } + } + finally + { + m_EstateModule.InInfoUpdate = false; + } + } + } + catch (Exception e) + { + m_log.Debug("[XESTATE]: Exception {0}" + e.ToString()); + } + + return FailureResult(); + } + + byte[] TeleportHomeAllUsers(Dictionary request) + { + UUID PreyID = UUID.Zero; + int EstateID = 0; + + if (!request.ContainsKey("EstateID")) + return FailureResult(); + + if (!Int32.TryParse(request["EstateID"].ToString(), out EstateID)) + return FailureResult(); + + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == EstateID) + { + s.ForEachScenePresence(delegate(ScenePresence p) { + if (p != null && !p.IsChildAgent) + { + p.ControllingClient.SendTeleportStart(16); + s.TeleportClientHome(p.ControllingClient.AgentId, p.ControllingClient); + } + }); + } + } + + return SuccessResult(); + } + + byte[] TeleportHomeOneUser(Dictionary request) + { + UUID PreyID = UUID.Zero; + int EstateID = 0; + + if (!request.ContainsKey("PreyID") || + !request.ContainsKey("EstateID")) + { + return FailureResult(); + } + + if (!UUID.TryParse(request["PreyID"].ToString(), out PreyID)) + return FailureResult(); + + if (!Int32.TryParse(request["EstateID"].ToString(), out EstateID)) + return FailureResult(); + + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == EstateID) + { + ScenePresence p = s.GetScenePresence(PreyID); + if (p != null && !p.IsChildAgent) + { + p.ControllingClient.SendTeleportStart(16); + s.TeleportClientHome(PreyID, p.ControllingClient); + } + } + } + + return SuccessResult(); + } + + byte[] EstateMessage(Dictionary request) + { + UUID FromID = UUID.Zero; + string FromName = String.Empty; + string Message = String.Empty; + int EstateID = 0; + + if (!request.ContainsKey("FromID") || + !request.ContainsKey("FromName") || + !request.ContainsKey("Message") || + !request.ContainsKey("EstateID")) + { + return FailureResult(); + } + + if (!UUID.TryParse(request["FromID"].ToString(), out FromID)) + return FailureResult(); + + if (!Int32.TryParse(request["EstateID"].ToString(), out EstateID)) + return FailureResult(); + + FromName = request["FromName"].ToString(); + Message = request["Message"].ToString(); + + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == EstateID) + { + IDialogModule dm = s.RequestModuleInterface(); + + if (dm != null) + { + dm.SendNotificationToUsersInRegion(FromID, FromName, + Message); + } + } + } + + return SuccessResult(); + } + + byte[] UpdateCovenant(Dictionary request) + { + UUID CovenantID = UUID.Zero; + int EstateID = 0; + + if (!request.ContainsKey("CovenantID") || !request.ContainsKey("EstateID")) + return FailureResult(); + + if (!UUID.TryParse(request["CovenantID"].ToString(), out CovenantID)) + return FailureResult(); + + if (!Int32.TryParse(request["EstateID"].ToString(), out EstateID)) + return FailureResult(); + + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == (uint)EstateID) + s.RegionInfo.RegionSettings.Covenant = CovenantID; + } + + return SuccessResult(); + } + + byte[] UpdateEstate(Dictionary request) + { + int EstateID = 0; + + if (!request.ContainsKey("EstateID")) + return FailureResult(); + if (!Int32.TryParse(request["EstateID"].ToString(), out EstateID)) + return FailureResult(); + + foreach (Scene s in m_EstateModule.Scenes) + { + if (s.RegionInfo.EstateSettings.EstateID == (uint)EstateID) + s.ReloadEstateData(); + } + return SuccessResult(); + } + + private byte[] FailureResult() + { + return BoolResult(false); + } + + private byte[] SuccessResult() + { + return BoolResult(true); + } + + private byte[] BoolResult(bool value) + { + XmlDocument doc = new XmlDocument(); + + XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration, + "", ""); + + doc.AppendChild(xmlnode); + + XmlElement rootElement = doc.CreateElement("", "ServerResponse", + ""); + + doc.AppendChild(rootElement); + + XmlElement result = doc.CreateElement("", "RESULT", ""); + result.AppendChild(doc.CreateTextNode(value.ToString())); + + rootElement.AppendChild(result); + + return Util.DocToBytes(doc); + } + + } +} diff --git a/OpenSim/Region/CoreModules/World/Land/DwellModule.cs b/OpenSim/Region/CoreModules/World/Land/DwellModule.cs index bd22155..70c6028 100644 --- a/OpenSim/Region/CoreModules/World/Land/DwellModule.cs +++ b/OpenSim/Region/CoreModules/World/Land/DwellModule.cs @@ -45,17 +45,19 @@ using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using GridRegion = OpenSim.Services.Interfaces.GridRegion; namespace OpenSim.Region.CoreModules.World.Land { - [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DwellModule")] - public class DwellModule : IDwellModule, INonSharedRegionModule + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DefaultDwellModule")] + public class DefaultDwellModule : IDwellModule, INonSharedRegionModule { private Scene m_scene; + private IConfigSource m_Config; + private bool m_Enabled = false; public Type ReplaceableInterface { @@ -64,15 +66,27 @@ namespace OpenSim.Region.CoreModules.World.Land public string Name { - get { return "DwellModule"; } + get { return "DefaultDwellModule"; } } public void Initialise(IConfigSource source) { + m_Config = source; + + IConfig DwellConfig = m_Config.Configs ["Dwell"]; + + if (DwellConfig == null) { + m_Enabled = false; + return; + } + m_Enabled = (DwellConfig.GetString ("DwellModule", "DefaultDwellModule") == "DefaultDwellModule"); } public void AddRegion(Scene scene) { + if (!m_Enabled) + return; + m_scene = scene; m_scene.EventManager.OnNewClient += OnNewClient; diff --git a/OpenSim/Region/CoreModules/World/Land/LandChannel.cs b/OpenSim/Region/CoreModules/World/Land/LandChannel.cs index 7fc358d..73c592d 100644 --- a/OpenSim/Region/CoreModules/World/Land/LandChannel.cs +++ b/OpenSim/Region/CoreModules/World/Land/LandChannel.cs @@ -95,6 +95,11 @@ namespace OpenSim.Region.CoreModules.World.Land return null; } + public ILandObject GetLandObject(Vector3 position) + { + return GetLandObject(position.X, position.Y); + } + public ILandObject GetLandObject(int x, int y) { if (m_landManagementModule != null) diff --git a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs index bad7205..92f6c1b 100644 --- a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs @@ -42,10 +42,9 @@ using OpenSim.Framework.Capabilities; using OpenSim.Framework.Console; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using GridRegion = OpenSim.Services.Interfaces.GridRegion; @@ -65,25 +64,27 @@ namespace OpenSim.Region.CoreModules.World.Land public class LandManagementModule : INonSharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[LAND MANAGEMENT MODULE]"; + + /// + /// Minimum land unit size in region co-ordinates. + /// + public const int LandUnit = 4; private static readonly string remoteParcelRequestPath = "0009/"; private LandChannel landChannel; private Scene m_scene; - protected Commander m_commander = new Commander("land"); - + + protected IGroupsModule m_groupManager; protected IUserManagement m_userManager; protected IPrimCountModule m_primCountModule; - - // Minimum for parcels to work is 64m even if we don't actually use them. - #pragma warning disable 0429 - private const int landArrayMax = ((int)((int)Constants.RegionSize / 4) >= 64) ? (int)((int)Constants.RegionSize / 4) : 64; - #pragma warning restore 0429 + protected IDialogModule m_Dialog; /// /// Local land ids at specified region co-ordinates (region size / 4) /// - private readonly int[,] m_landIDList = new int[landArrayMax, landArrayMax]; + private int[,] m_landIDList; /// /// Land objects keyed by local id @@ -97,11 +98,17 @@ namespace OpenSim.Region.CoreModules.World.Land // caches ExtendedLandData private Cache parcelInfoCache; + /// /// Record positions that avatar's are currently being forced to move to due to parcel entry restrictions. /// private Dictionary forcedPosition = new Dictionary(); + // Enables limiting parcel layer info transmission when doing simple updates + private bool shouldLimitParcelLayerInfoToViewDistance { get; set; } + // "View distance" for sending parcel layer info if asked for from a view point in the region + private int parcelLayerViewDistance { get; set; } + #region INonSharedRegionModule Members public Type ReplaceableInterface @@ -111,12 +118,20 @@ namespace OpenSim.Region.CoreModules.World.Land public void Initialise(IConfigSource source) { + shouldLimitParcelLayerInfoToViewDistance = true; + parcelLayerViewDistance = 128; + IConfig landManagementConfig = source.Configs["LandManagement"]; + if (landManagementConfig != null) + { + shouldLimitParcelLayerInfoToViewDistance = landManagementConfig.GetBoolean("LimitParcelLayerUpdateDistance", shouldLimitParcelLayerInfoToViewDistance); + parcelLayerViewDistance = landManagementConfig.GetInt("ParcelLayerViewDistance", parcelLayerViewDistance); + } } public void AddRegion(Scene scene) { m_scene = scene; - m_landIDList.Initialize(); + m_landIDList = new int[m_scene.RegionInfo.RegionSizeX / LandUnit, m_scene.RegionInfo.RegionSizeY / LandUnit]; landChannel = new LandChannel(scene, this); parcelInfoCache = new Cache(); @@ -139,28 +154,26 @@ namespace OpenSim.Region.CoreModules.World.Land m_scene.EventManager.OnIncomingLandDataFromStorage += EventManagerOnIncomingLandDataFromStorage; m_scene.EventManager.OnSetAllowForcefulBan += EventManagerOnSetAllowedForcefulBan; m_scene.EventManager.OnRegisterCaps += EventManagerOnRegisterCaps; - m_scene.EventManager.OnPluginConsole += EventManagerOnPluginConsole; lock (m_scene) { m_scene.LandChannel = (ILandChannel)landChannel; } - InstallInterfaces(); + RegisterCommands(); } public void RegionLoaded(Scene scene) { - m_userManager = m_scene.RequestModuleInterface(); - m_primCountModule = m_scene.RequestModuleInterface(); + m_userManager = m_scene.RequestModuleInterface(); + m_groupManager = m_scene.RequestModuleInterface(); + m_primCountModule = m_scene.RequestModuleInterface(); + m_Dialog = m_scene.RequestModuleInterface(); } public void RemoveRegion(Scene scene) { - // TODO: Also release other event manager listeners here - - m_scene.EventManager.OnPluginConsole -= EventManagerOnPluginConsole; - m_scene.UnregisterModuleCommander(m_commander.Name); + // TODO: Release event manager listeners here } // private bool OnVerifyUserConnection(ScenePresence scenePresence, out string reason) @@ -168,30 +181,7 @@ namespace OpenSim.Region.CoreModules.World.Land // ILandObject nearestParcel = m_scene.GetNearestAllowedParcel(scenePresence.UUID, scenePresence.AbsolutePosition.X, scenePresence.AbsolutePosition.Y); // reason = "You are not allowed to enter this sim."; // return nearestParcel != null; -// } - - /// - /// Processes commandline input. Do not call directly. - /// - /// Commandline arguments - protected void EventManagerOnPluginConsole(string[] args) - { - if (args[0] == "land") - { - if (args.Length == 1) - { - m_commander.ProcessConsoleCommand("help", new string[0]); - return; - } - - string[] tmpArgs = new string[args.Length - 2]; - int i; - for (i = 2; i < args.Length; i++) - tmpArgs[i - 2] = args[i]; - - m_commander.ProcessConsoleCommand(args[1], tmpArgs); - } - } +// } void EventManagerOnNewClient(IClientAPI client) { @@ -210,6 +200,10 @@ namespace OpenSim.Region.CoreModules.World.Land client.OnParcelInfoRequest += ClientOnParcelInfoRequest; client.OnParcelDeedToGroup += ClientOnParcelDeedToGroup; client.OnPreAgentUpdate += ClientOnPreAgentUpdate; + client.OnParcelEjectUser += ClientOnParcelEjectUser; + client.OnParcelFreezeUser += ClientOnParcelFreezeUser; + client.OnSetStartLocationRequest += ClientOnSetHome; + EntityBase presenceEntity; if (m_scene.Entities.TryGetValue(client.AgentId, out presenceEntity) && presenceEntity is ScenePresence) @@ -293,14 +287,15 @@ namespace OpenSim.Region.CoreModules.World.Land LandData newData = data.Copy(); newData.LocalID = local_id; + ILandObject land; lock (m_landList) { - if (m_landList.ContainsKey(local_id)) - { - m_landList[local_id].LandData = newData; - m_scene.EventManager.TriggerLandObjectUpdated((uint)local_id, m_landList[local_id]); - } + if (m_landList.TryGetValue(local_id, out land)) + land.LandData = newData; } + + if (land != null) + m_scene.EventManager.TriggerLandObjectUpdated((uint)local_id, land); } public bool AllowedForcefulBans @@ -319,7 +314,7 @@ namespace OpenSim.Region.CoreModules.World.Land { m_landList.Clear(); m_lastLandLocalID = LandChannel.START_LAND_LOCAL_ID - 1; - m_landIDList.Initialize(); + m_landIDList = new int[m_scene.RegionInfo.RegionSizeX / LandUnit, m_scene.RegionInfo.RegionSizeY / LandUnit]; } } @@ -333,7 +328,8 @@ namespace OpenSim.Region.CoreModules.World.Land "[LAND MANAGEMENT MODULE]: Creating default parcel for region {0}", m_scene.RegionInfo.RegionName); ILandObject fullSimParcel = new LandObject(UUID.Zero, false, m_scene); - fullSimParcel.SetLandBitmap(fullSimParcel.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + fullSimParcel.SetLandBitmap(fullSimParcel.GetSquareLandBitmap(0, 0, + (int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY)); fullSimParcel.LandData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner; fullSimParcel.LandData.ClaimDate = Util.UnixTimeSinceEpoch(); @@ -460,8 +456,8 @@ namespace OpenSim.Region.CoreModules.World.Land public void SendLandUpdate(ScenePresence avatar, bool force) { - ILandObject over = GetLandObject((int)Math.Min(((int)Constants.RegionSize - 1), Math.Max(0, Math.Round(avatar.AbsolutePosition.X))), - (int)Math.Min(((int)Constants.RegionSize - 1), Math.Max(0, Math.Round(avatar.AbsolutePosition.Y)))); + ILandObject over = GetLandObject((int)Math.Min(((int)m_scene.RegionInfo.RegionSizeX - 1), Math.Max(0, Math.Round(avatar.AbsolutePosition.X))), + (int)Math.Min(((int)m_scene.RegionInfo.RegionSizeY - 1), Math.Max(0, Math.Round(avatar.AbsolutePosition.Y)))); if (over != null) { @@ -543,16 +539,13 @@ namespace OpenSim.Region.CoreModules.World.Land /// /// public void EventManagerOnClientMovement(ScenePresence avatar) - // { - ILandObject over = GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + Vector3 pos = avatar.AbsolutePosition; + ILandObject over = GetLandObject(pos.X, pos.Y); if (over != null) { - if (!over.IsRestrictedFromLand(avatar.UUID) && (!over.IsBannedFromLand(avatar.UUID) || avatar.AbsolutePosition.Z >= LandChannel.BAN_LINE_SAFETY_HIEGHT)) - { - avatar.lastKnownAllowedPosition = - new Vector3(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z); - } + if (!over.IsRestrictedFromLand(avatar.UUID) && (!over.IsBannedFromLand(avatar.UUID) || pos.Z >= LandChannel.BAN_LINE_SAFETY_HIEGHT)) + avatar.lastKnownAllowedPosition = pos; } } @@ -611,7 +604,10 @@ namespace OpenSim.Region.CoreModules.World.Land /// /// Adds a land object to the stored list and adds them to the landIDList to what they own /// - /// The land object being added + /// + /// The land object being added. + /// Will return null if this overlaps with an existing parcel that has not had its bitmap adjusted. + /// public ILandObject AddLandObject(ILandObject land) { ILandObject new_land = land.Copy(); @@ -619,34 +615,76 @@ namespace OpenSim.Region.CoreModules.World.Land // Only now can we add the prim counts to the land object - we rely on the global ID which is generated // as a random UUID inside LandData initialization if (m_primCountModule != null) - new_land.PrimCounts = m_primCountModule.GetPrimCounts(new_land.LandData.GlobalID); + new_land.PrimCounts = m_primCountModule.GetPrimCounts(new_land.LandData.GlobalID); lock (m_landList) { - int newLandLocalID = ++m_lastLandLocalID; + int newLandLocalID = m_lastLandLocalID + 1; new_land.LandData.LocalID = newLandLocalID; bool[,] landBitmap = new_land.GetLandBitmap(); - for (int x = 0; x < landArrayMax; x++) + // m_log.DebugFormat("{0} AddLandObject. new_land.bitmapSize=({1},{2}). newLocalID={3}", + // LogHeader, landBitmap.GetLength(0), landBitmap.GetLength(1), newLandLocalID); + + if (landBitmap.GetLength(0) != m_landIDList.GetLength(0) || landBitmap.GetLength(1) != m_landIDList.GetLength(1)) + { + // Going to variable sized regions can cause mismatches + m_log.ErrorFormat("{0} AddLandObject. Added land bitmap different size than region ID map. bitmapSize=({1},{2}), landIDSize=({3},{4})", + LogHeader, landBitmap.GetLength(0), landBitmap.GetLength(1), m_landIDList.GetLength(0), m_landIDList.GetLength(1) ); + } + else { - for (int y = 0; y < landArrayMax; y++) + // If other land objects still believe that they occupy any parts of the same space, + // then do not allow the add to proceed. + for (int x = 0; x < landBitmap.GetLength(0); x++) { - if (landBitmap[x, y]) + for (int y = 0; y < landBitmap.GetLength(1); y++) { -// m_log.DebugFormat( -// "[LAND MANAGEMENT MODULE]: Registering parcel {0} for land co-ord ({1}, {2}) on {3}", -// new_land.LandData.Name, x, y, m_scene.RegionInfo.RegionName); - - m_landIDList[x, y] = newLandLocalID; + if (landBitmap[x, y]) + { + int lastRecordedLandId = m_landIDList[x, y]; + + if (lastRecordedLandId > 0) + { + ILandObject lastRecordedLo = m_landList[lastRecordedLandId]; + + if (lastRecordedLo.LandBitmap[x, y]) + { + m_log.ErrorFormat( + "{0}: Cannot add parcel \"{1}\", local ID {2} at tile {3},{4} because this is still occupied by parcel \"{5}\", local ID {6} in {7}", + LogHeader, new_land.LandData.Name, new_land.LandData.LocalID, x, y, + lastRecordedLo.LandData.Name, lastRecordedLo.LandData.LocalID, m_scene.Name); + + return null; + } + } + } + } + } + + for (int x = 0; x < landBitmap.GetLength(0); x++) + { + for (int y = 0; y < landBitmap.GetLength(1); y++) + { + if (landBitmap[x, y]) + { + // m_log.DebugFormat( + // "[LAND MANAGEMENT MODULE]: Registering parcel {0} for land co-ord ({1}, {2}) on {3}", + // new_land.LandData.Name, x, y, m_scene.RegionInfo.RegionName); + + m_landIDList[x, y] = newLandLocalID; + } } } } m_landList.Add(newLandLocalID, new_land); + m_lastLandLocalID++; } new_land.ForceUpdateLandInfo(); m_scene.EventManager.TriggerLandObjectAdded(new_land); + return new_land; } @@ -656,11 +694,12 @@ namespace OpenSim.Region.CoreModules.World.Land /// Land.localID of the peice of land to remove. public void removeLandObject(int local_id) { + ILandObject land; lock (m_landList) { - for (int x = 0; x < 64; x++) + for (int x = 0; x < m_landIDList.GetLength(0); x++) { - for (int y = 0; y < 64; y++) + for (int y = 0; y < m_landIDList.GetLength(1); y++) { if (m_landIDList[x, y] == local_id) { @@ -672,9 +711,11 @@ namespace OpenSim.Region.CoreModules.World.Land } } - m_scene.EventManager.TriggerLandObjectRemoved(m_landList[local_id].LandData.GlobalID); + land = m_landList[local_id]; m_landList.Remove(local_id); } + + m_scene.EventManager.TriggerLandObjectRemoved(land.LandData.GlobalID); } /// @@ -682,21 +723,27 @@ namespace OpenSim.Region.CoreModules.World.Land /// public void Clear(bool setupDefaultParcel) { + List parcels; lock (m_landList) { - foreach (ILandObject lo in m_landList.Values) - { - //m_scene.SimulationDataService.RemoveLandObject(lo.LandData.GlobalID); - m_scene.EventManager.TriggerLandObjectRemoved(lo.LandData.GlobalID); - } + parcels = new List(m_landList.Values); + } + + foreach (ILandObject lo in parcels) + { + //m_scene.SimulationDataService.RemoveLandObject(lo.LandData.GlobalID); + m_scene.EventManager.TriggerLandObjectRemoved(lo.LandData.GlobalID); + } + lock (m_landList) + { m_landList.Clear(); ResetSimLandObjects(); - - if (setupDefaultParcel) - CreateDefaultParcel(); } + + if (setupDefaultParcel) + CreateDefaultParcel(); } private void performFinalLandJoin(ILandObject master, ILandObject slave) @@ -704,9 +751,9 @@ namespace OpenSim.Region.CoreModules.World.Land bool[,] landBitmapSlave = slave.GetLandBitmap(); lock (m_landList) { - for (int x = 0; x < 64; x++) + for (int x = 0; x < landBitmapSlave.GetLength(0); x++) { - for (int y = 0; y < 64; y++) + for (int y = 0; y < landBitmapSlave.GetLength(1); y++) { if (landBitmapSlave[x, y]) { @@ -740,23 +787,28 @@ namespace OpenSim.Region.CoreModules.World.Land /// Land object at the point supplied public ILandObject GetLandObject(float x_float, float y_float) { + return GetLandObject((int)x_float, (int)y_float, true /* returnNullIfLandObjectNotFound */); + /* int x; int y; - if (x_float >= Constants.RegionSize || x_float < 0 || y_float >= Constants.RegionSize || y_float < 0) + if (x_float >= m_scene.RegionInfo.RegionSizeX || x_float < 0 || y_float >= m_scene.RegionInfo.RegionSizeX || y_float < 0) return null; try { - x = Convert.ToInt32(Math.Floor(Convert.ToDouble(x_float) / 4.0)); - y = Convert.ToInt32(Math.Floor(Convert.ToDouble(y_float) / 4.0)); + x = Convert.ToInt32(Math.Floor(Convert.ToDouble(x_float) / (float)landUnit)); + y = Convert.ToInt32(Math.Floor(Convert.ToDouble(y_float) / (float)landUnit)); } catch (OverflowException) { return null; } - if (x >= 64 || y >= 64 || x < 0 || y < 0) + if (x >= (m_scene.RegionInfo.RegionSizeX / landUnit) + || y >= (m_scene.RegionInfo.RegionSizeY / landUnit) + || x < 0 + || y < 0) { return null; } @@ -772,38 +824,70 @@ namespace OpenSim.Region.CoreModules.World.Land // m_log.DebugFormat( // "[LAND MANAGEMENT MODULE]: No land object found at ({0}, {1}) on {2}", // x, y, m_scene.RegionInfo.RegionName); - - if (m_landList.ContainsKey(m_landIDList[x, y])) - return m_landList[m_landIDList[x, y]]; + + try + { + if (m_landList.ContainsKey(m_landIDList[x, y])) + return m_landList[m_landIDList[x, y]]; + } + catch (Exception e) + { + m_log.DebugFormat("{0} GetLandObject exception. x={1}, y={2}, m_landIDList.len=({3},{4})", + LogHeader, x, y, m_landIDList.GetLength(0), m_landIDList.GetLength(1)); + } return null; } + */ } + // Public entry. + // Throws exception if land object is not found public ILandObject GetLandObject(int x, int y) { - if (x >= Convert.ToInt32(Constants.RegionSize) || y >= Convert.ToInt32(Constants.RegionSize) || x < 0 || y < 0) + return GetLandObject(x, y, false /* returnNullIfLandObjectNotFound */); + } + + /// + /// Given a region position, return the parcel land object for that location + /// + /// + /// The land object. + /// + /// + /// + /// + /// Return null if the land object requested is not within the region's bounds. + /// + private ILandObject GetLandObject(int x, int y, bool returnNullIfLandObjectOutsideBounds) + { + if (x >= m_scene.RegionInfo.RegionSizeX || y >= m_scene.RegionInfo.RegionSizeY || x < 0 || y < 0) { // These exceptions here will cause a lot of complaints from the users specifically because // they happen every time at border crossings - throw new Exception("Error: Parcel not found at point " + x + ", " + y); - } - - lock (m_landIDList) - { - try - { - return m_landList[m_landIDList[x / 4, y / 4]]; - } - catch (IndexOutOfRangeException) - { -// m_log.WarnFormat( -// "[LAND MANAGEMENT MODULE]: Tried to retrieve land object from out of bounds co-ordinate ({0},{1}) in {2}", -// x, y, m_scene.RegionInfo.RegionName); - + if (returnNullIfLandObjectOutsideBounds) return null; - } + else + throw new Exception( + String.Format("{0} GetLandObject for non-existent position. Region={1}, pos=<{2},{3}", + LogHeader, m_scene.RegionInfo.RegionName, x, y) + ); } + + return m_landList[m_landIDList[x / 4, y / 4]]; + } + + // Create a 'parcel is here' bitmap for the parcel identified by the passed landID + private bool[,] CreateBitmapForID(int landID) + { + bool[,] ret = new bool[m_landIDList.GetLength(0), m_landIDList.GetLength(1)]; + + for (int xx = 0; xx < m_landIDList.GetLength(0); xx++) + for (int yy = 0; yy < m_landIDList.GetLength(0); yy++) + if (m_landIDList[xx, yy] == landID) + ret[xx, yy] = true; + + return ret; } #endregion @@ -973,8 +1057,12 @@ namespace OpenSim.Region.CoreModules.World.Land //Now add the new land object ILandObject result = AddLandObject(newLand); - UpdateLandObject(startLandObject.LandData.LocalID, startLandObject.LandData); - result.SendLandUpdateToAvatarsOverMe(); + + if (result != null) + { + UpdateLandObject(startLandObject.LandData.LocalID, startLandObject.LandData); + result.SendLandUpdateToAvatarsOverMe(); + } } /// @@ -1055,96 +1143,164 @@ namespace OpenSim.Region.CoreModules.World.Land #region Parcel Updating + // Send parcel layer info for the whole region + public void SendParcelOverlay(IClientAPI remote_client) + { + SendParcelOverlay(remote_client, 0, 0, (int)Constants.MaximumRegionSize); + } + /// - /// Where we send the ParcelOverlay packet to the client + /// Send the parcel overlay blocks to the client. We send the overlay packets + /// around a location and limited by the 'parcelLayerViewDistance'. This number + /// is usually 128 and the code is arranged so it sends all the parcel overlay + /// information for a whole region if the region is legacy sized (256x256). If + /// the region is larger, only the parcel layer information is sent around + /// the point specified. This reduces the problem of parcel layer information + /// blocks increasing exponentially as region size increases. /// /// The object representing the client - public void SendParcelOverlay(IClientAPI remote_client) + /// X position in the region to send surrounding parcel layer info + /// y position in the region to send surrounding parcel layer info + /// Distance from x,y position to send parcel layer info + private void SendParcelOverlay(IClientAPI remote_client, int xPlace, int yPlace, int layerViewDistance) { const int LAND_BLOCKS_PER_PACKET = 1024; byte[] byteArray = new byte[LAND_BLOCKS_PER_PACKET]; int byteArrayCount = 0; int sequenceID = 0; - int blockmeters = 4 * (int) Constants.RegionSize/(int)Constants.TerrainPatchSize; + int xLow = 0; + int xHigh = (int)m_scene.RegionInfo.RegionSizeX; + int yLow = 0; + int yHigh = (int)m_scene.RegionInfo.RegionSizeY; - for (int y = 0; y < blockmeters; y++) + if (shouldLimitParcelLayerInfoToViewDistance) { - for (int x = 0; x < blockmeters; x++) + // Compute view distance around the given point + int txLow = xPlace - layerViewDistance; + int txHigh = xPlace + layerViewDistance; + // If the distance is outside the region area, move the view distance to ba all in the region + if (txLow < xLow) + { + txLow = xLow; + txHigh = Math.Min(yLow + (layerViewDistance * 2), xHigh); + } + if (txHigh > xHigh) { - byte tempByte = 0; //This represents the byte for the current 4x4 + txLow = Math.Max(xLow, xHigh - (layerViewDistance * 2)); + txHigh = xHigh; + } + xLow = txLow; + xHigh = txHigh; - ILandObject currentParcelBlock = GetLandObject(x * 4, y * 4); + int tyLow = yPlace - layerViewDistance; + int tyHigh = yPlace + layerViewDistance; + if (tyLow < yLow) + { + tyLow = yLow; + tyHigh = Math.Min(yLow + (layerViewDistance * 2), yHigh); + } + if (tyHigh > yHigh) + { + tyLow = Math.Max(yLow, yHigh - (layerViewDistance * 2)); + tyHigh = yHigh; + } + yLow = tyLow; + yHigh = tyHigh; + } + // m_log.DebugFormat("{0} SendParcelOverlay: place=<{1},{2}>, vDist={3}, xLH=<{4},{5}, yLH=<{6},{7}>", + // LogHeader, xPlace, yPlace, layerViewDistance, xLow, xHigh, yLow, yHigh); - if (currentParcelBlock != null) + // Layer data is in landUnit (4m) chunks + for (int y = yLow; y < yHigh / Constants.TerrainPatchSize * (Constants.TerrainPatchSize / LandUnit); y++) + { + for (int x = xLow; x < xHigh / Constants.TerrainPatchSize * (Constants.TerrainPatchSize / LandUnit); x++) + { + byteArray[byteArrayCount] = BuildLayerByte(GetLandObject(x * LandUnit, y * LandUnit), x, y, remote_client); + byteArrayCount++; + if (byteArrayCount >= LAND_BLOCKS_PER_PACKET) { - if (currentParcelBlock.LandData.OwnerID == remote_client.AgentId) - { - //Owner Flag - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_REQUESTER); - } - else if (currentParcelBlock.LandData.SalePrice > 0 && - (currentParcelBlock.LandData.AuthBuyerID == UUID.Zero || - currentParcelBlock.LandData.AuthBuyerID == remote_client.AgentId)) - { - //Sale Flag - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_IS_FOR_SALE); - } - else if (currentParcelBlock.LandData.OwnerID == UUID.Zero) - { - //Public Flag - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_PUBLIC); - } - else - { - //Other Flag - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_OTHER); - } + // m_log.DebugFormat("{0} SendParcelOverlay, sending packet, bytes={1}", LogHeader, byteArray.Length); + remote_client.SendLandParcelOverlay(byteArray, sequenceID); + byteArrayCount = 0; + sequenceID++; + byteArray = new byte[LAND_BLOCKS_PER_PACKET]; + } - //Now for border control + } + } - ILandObject westParcel = null; - ILandObject southParcel = null; - if (x > 0) - { - westParcel = GetLandObject((x - 1) * 4, y * 4); - } - if (y > 0) - { - southParcel = GetLandObject(x * 4, (y - 1) * 4); - } + if (byteArrayCount != 0) + { + remote_client.SendLandParcelOverlay(byteArray, sequenceID); + // m_log.DebugFormat("{0} SendParcelOverlay, complete sending packet, bytes={1}", LogHeader, byteArray.Length); + } + } - if (x == 0) - { - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); - } - else if (westParcel != null && westParcel != currentParcelBlock) - { - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); - } + private byte BuildLayerByte(ILandObject currentParcelBlock, int x, int y, IClientAPI remote_client) + { + byte tempByte = 0; //This represents the byte for the current 4x4 - if (y == 0) - { - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); - } - else if (southParcel != null && southParcel != currentParcelBlock) - { - tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); - } + if (currentParcelBlock != null) + { + if (currentParcelBlock.LandData.OwnerID == remote_client.AgentId) + { + //Owner Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_REQUESTER); + } + else if (currentParcelBlock.LandData.SalePrice > 0 && + (currentParcelBlock.LandData.AuthBuyerID == UUID.Zero || + currentParcelBlock.LandData.AuthBuyerID == remote_client.AgentId)) + { + //Sale Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_IS_FOR_SALE); + } + else if (currentParcelBlock.LandData.OwnerID == UUID.Zero) + { + //Public Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_PUBLIC); + } + else + { + //Other Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_OTHER); + } - byteArray[byteArrayCount] = tempByte; - byteArrayCount++; - if (byteArrayCount >= LAND_BLOCKS_PER_PACKET) - { - remote_client.SendLandParcelOverlay(byteArray, sequenceID); - byteArrayCount = 0; - sequenceID++; - byteArray = new byte[LAND_BLOCKS_PER_PACKET]; - } - } + //Now for border control + + ILandObject westParcel = null; + ILandObject southParcel = null; + if (x > 0) + { + westParcel = GetLandObject((x - 1) * LandUnit, y * LandUnit); + } + if (y > 0) + { + southParcel = GetLandObject(x * LandUnit, (y - 1) * LandUnit); + } + + if (x == 0) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); + } + else if (westParcel != null && westParcel != currentParcelBlock) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); } + + if (y == 0) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); + } + else if (southParcel != null && southParcel != currentParcelBlock) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); + } + } + + return tempByte; } public void ClientOnParcelPropertiesRequest(int start_x, int start_y, int end_x, int end_y, int sequence_id, @@ -1182,7 +1338,8 @@ namespace OpenSim.Region.CoreModules.World.Land temp[i].SendLandProperties(sequence_id, snap_selection, requestResult, remote_client); } - SendParcelOverlay(remote_client); + // Also send the layer data around the point of interest + SendParcelOverlay(remote_client, (start_x + end_x) / 2, (start_y + end_y) / 2, parcelLayerViewDistance); } public void ClientOnParcelPropertiesUpdateRequest(LandUpdateArgs args, int localID, IClientAPI remote_client) @@ -1254,6 +1411,7 @@ namespace OpenSim.Region.CoreModules.World.Land m_scene.ForEachClient(SendParcelOverlay); land.SendLandUpdateToClient(true, remote_client); + UpdateLandObject(land.LandData.LocalID, land.LandData); } } } @@ -1274,8 +1432,10 @@ namespace OpenSim.Region.CoreModules.World.Land land.LandData.GroupID = UUID.Zero; land.LandData.IsGroupOwned = false; land.LandData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory); + m_scene.ForEachClient(SendParcelOverlay); land.SendLandUpdateToClient(true, remote_client); + UpdateLandObject(land.LandData.LocalID, land.LandData); } } } @@ -1302,6 +1462,7 @@ namespace OpenSim.Region.CoreModules.World.Land m_scene.ForEachClient(SendParcelOverlay); land.SendLandUpdateToClient(true, remote_client); + UpdateLandObject(land.LandData.LocalID, land.LandData); } } } @@ -1382,19 +1543,78 @@ namespace OpenSim.Region.CoreModules.World.Land #region Land Object From Storage Functions - public void EventManagerOnIncomingLandDataFromStorage(List data) + private void EventManagerOnIncomingLandDataFromStorage(List data) { // m_log.DebugFormat( // "[LAND MANAGMENT MODULE]: Processing {0} incoming parcels on {1}", data.Count, m_scene.Name); - for (int i = 0; i < data.Count; i++) - IncomingLandObjectFromStorage(data[i]); + // Prevent race conditions from any auto-creation of new parcels for varregions whilst we are still loading + // the existing parcels. + lock (m_landList) + { + for (int i = 0; i < data.Count; i++) + IncomingLandObjectFromStorage(data[i]); + + // Layer data is in landUnit (4m) chunks + for (int y = 0; y < m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize * (Constants.TerrainPatchSize / LandUnit); y++) + { + for (int x = 0; x < m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize * (Constants.TerrainPatchSize / LandUnit); x++) + { + if (m_landIDList[x, y] == 0) + { + if (m_landList.Count == 1) + { + m_log.DebugFormat( + "[{0}]: Auto-extending land parcel as landID at {1},{2} is 0 and only one land parcel is present in {3}", + LogHeader, x, y, m_scene.Name); + + int onlyParcelID = 0; + ILandObject onlyLandObject = null; + foreach (KeyValuePair kvp in m_landList) + { + onlyParcelID = kvp.Key; + onlyLandObject = kvp.Value; + break; + } + + // There is only one parcel. Grow it to fill all the unallocated spaces. + for (int xx = 0; xx < m_landIDList.GetLength(0); xx++) + for (int yy = 0; yy < m_landIDList.GetLength(1); yy++) + if (m_landIDList[xx, yy] == 0) + m_landIDList[xx, yy] = onlyParcelID; + + onlyLandObject.LandBitmap = CreateBitmapForID(onlyParcelID); + } + else if (m_landList.Count > 1) + { + m_log.DebugFormat( + "{0}: Auto-creating land parcel as landID at {1},{2} is 0 and more than one land parcel is present in {3}", + LogHeader, x, y, m_scene.Name); + + // There are several other parcels so we must create a new one for the unassigned space + ILandObject newLand = new LandObject(UUID.Zero, false, m_scene); + // Claim all the unclaimed "0" ids + newLand.SetLandBitmap(CreateBitmapForID(0)); + newLand.LandData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner; + newLand.LandData.ClaimDate = Util.UnixTimeSinceEpoch(); + newLand = AddLandObject(newLand); + } + else + { + // We should never reach this point as the separate code path when no land data exists should have fired instead. + m_log.WarnFormat( + "{0}: Ignoring request to auto-create parcel in {1} as there are no other parcels present", + LogHeader, m_scene.Name); + } + } + } + } + } } - public void IncomingLandObjectFromStorage(LandData data) + private void IncomingLandObjectFromStorage(LandData data) { - ILandObject new_land = new LandObject(data.OwnerID, data.IsGroupOwned, m_scene); - new_land.LandData = data.Copy(); + ILandObject new_land = new LandObject(data, m_scene); new_land.SetLandBitmapFromByteArray(); AddLandObject(new_land); } @@ -1409,7 +1629,8 @@ namespace OpenSim.Region.CoreModules.World.Land m_landList.TryGetValue(localID, out selectedParcel); } - if (selectedParcel == null) return; + if (selectedParcel == null) + return; selectedParcel.ReturnLandObjects(returnType, agentIDs, taskIDs, remoteClient); } @@ -1417,7 +1638,7 @@ namespace OpenSim.Region.CoreModules.World.Land { if (returnType != 1) { - m_log.WarnFormat("[LAND MANAGEMENT MODULE] ReturnObjectsInParcel: unknown return type {0}", returnType); + m_log.WarnFormat("[LAND MANAGEMENT MODULE]: ReturnObjectsInParcel: unknown return type {0}", returnType); return; } @@ -1437,14 +1658,14 @@ namespace OpenSim.Region.CoreModules.World.Land } else { - m_log.WarnFormat("[LAND MANAGEMENT MODULE] ReturnObjectsInParcel: unknown object {0}", groupID); + m_log.WarnFormat("[LAND MANAGEMENT MODULE]: ReturnObjectsInParcel: unknown object {0}", groupID); } } int num = 0; foreach (HashSet objs in returns.Values) num += objs.Count; - m_log.DebugFormat("[LAND MANAGEMENT MODULE] Returning {0} specific object(s)", num); + m_log.DebugFormat("[LAND MANAGEMENT MODULE]: Returning {0} specific object(s)", num); foreach (HashSet objs in returns.Values) { @@ -1455,7 +1676,7 @@ namespace OpenSim.Region.CoreModules.World.Land } else { - m_log.WarnFormat("[LAND MANAGEMENT MODULE] ReturnObjectsInParcel: not permitted to return {0} object(s) belonging to user {1}", + m_log.WarnFormat("[LAND MANAGEMENT MODULE]: ReturnObjectsInParcel: not permitted to return {0} object(s) belonging to user {1}", objs2.Count, objs2[0].OwnerID); } } @@ -1464,11 +1685,8 @@ namespace OpenSim.Region.CoreModules.World.Land public void EventManagerOnNoLandDataFromStorage() { - lock (m_landList) - { - ResetSimLandObjects(); - CreateDefaultParcel(); - } + ResetSimLandObjects(); + CreateDefaultParcel(); } #endregion @@ -1694,7 +1912,7 @@ namespace OpenSim.Region.CoreModules.World.Land { // most likely still cached from building the extLandData entry uint x = 0, y = 0; - Utils.LongToUInts(data.RegionHandle, out x, out y); + Util.RegionHandleToWorldLoc(data.RegionHandle, out x, out y); info = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, (int)x, (int)y); } // we need to transfer the fake parcelID, not the one in landData, so the viewer can match it to the landmark. @@ -1730,66 +1948,332 @@ namespace OpenSim.Region.CoreModules.World.Land UpdateLandObject(localID, land.LandData); } - protected void InstallInterfaces() + Dictionary Timers = new Dictionary(); + + public void ClientOnParcelFreezeUser(IClientAPI client, UUID parcelowner, uint flags, UUID target) + { + ScenePresence targetAvatar = null; + ((Scene)client.Scene).TryGetScenePresence(target, out targetAvatar); + ScenePresence parcelManager = null; + ((Scene)client.Scene).TryGetScenePresence(client.AgentId, out parcelManager); + System.Threading.Timer Timer; + + if (targetAvatar.UserLevel == 0) + { + ILandObject land = ((Scene)client.Scene).LandChannel.GetLandObject(targetAvatar.AbsolutePosition.X, targetAvatar.AbsolutePosition.Y); + if (!((Scene)client.Scene).Permissions.CanEditParcelProperties(client.AgentId, land, GroupPowers.LandEjectAndFreeze)) + return; + if (flags == 0) + { + targetAvatar.AllowMovement = false; + targetAvatar.ControllingClient.SendAlertMessage(parcelManager.Firstname + " " + parcelManager.Lastname + " has frozen you for 30 seconds. You cannot move or interact with the world."); + parcelManager.ControllingClient.SendAlertMessage("Avatar Frozen."); + System.Threading.TimerCallback timeCB = new System.Threading.TimerCallback(OnEndParcelFrozen); + Timer = new System.Threading.Timer(timeCB, targetAvatar, 30000, 0); + Timers.Add(targetAvatar.UUID, Timer); + } + else + { + targetAvatar.AllowMovement = true; + targetAvatar.ControllingClient.SendAlertMessage(parcelManager.Firstname + " " + parcelManager.Lastname + " has unfrozen you."); + parcelManager.ControllingClient.SendAlertMessage("Avatar Unfrozen."); + Timers.TryGetValue(targetAvatar.UUID, out Timer); + Timers.Remove(targetAvatar.UUID); + Timer.Dispose(); + } + } + } + + private void OnEndParcelFrozen(object avatar) { - Command clearCommand - = new Command("clear", CommandIntentions.COMMAND_HAZARDOUS, ClearCommand, "Clears all the parcels from the region."); - Command showCommand - = new Command("show", CommandIntentions.COMMAND_STATISTICAL, ShowParcelsCommand, "Shows all parcels on the region."); + ScenePresence targetAvatar = (ScenePresence)avatar; + targetAvatar.AllowMovement = true; + System.Threading.Timer Timer; + Timers.TryGetValue(targetAvatar.UUID, out Timer); + Timers.Remove(targetAvatar.UUID); + targetAvatar.ControllingClient.SendAgentAlertMessage("The freeze has worn off; you may go about your business.", false); + } + + public void ClientOnParcelEjectUser(IClientAPI client, UUID parcelowner, uint flags, UUID target) + { + ScenePresence targetAvatar = null; + ScenePresence parcelManager = null; + + // Must have presences + if (!m_scene.TryGetScenePresence(target, out targetAvatar) || + !m_scene.TryGetScenePresence(client.AgentId, out parcelManager)) + return; + + // Cannot eject estate managers or gods + if (m_scene.Permissions.IsAdministrator(target)) + return; + + // Check if you even have permission to do this + ILandObject land = m_scene.LandChannel.GetLandObject(targetAvatar.AbsolutePosition.X, targetAvatar.AbsolutePosition.Y); + if (!m_scene.Permissions.CanEditParcelProperties(client.AgentId, land, GroupPowers.LandEjectAndFreeze) && + !m_scene.Permissions.IsAdministrator(client.AgentId)) + return; + Vector3 pos = m_scene.GetNearestAllowedPosition(targetAvatar, land); + + targetAvatar.TeleportWithMomentum(pos, null); + targetAvatar.ControllingClient.SendAlertMessage("You have been ejected by " + parcelManager.Firstname + " " + parcelManager.Lastname); + parcelManager.ControllingClient.SendAlertMessage("Avatar Ejected."); - m_commander.RegisterCommand("clear", clearCommand); - m_commander.RegisterCommand("show", showCommand); + if ((flags & 1) != 0) // Ban TODO: Remove magic number + { + LandAccessEntry entry = new LandAccessEntry(); + entry.AgentID = targetAvatar.UUID; + entry.Flags = AccessList.Ban; + entry.Expires = 0; // Perm - // Add this to our scene so scripts can call these functions - m_scene.RegisterModuleCommander(m_commander); + land.LandData.ParcelAccessList.Add(entry); + } + } + + /// + /// Sets the Home Point. The LoginService uses this to know where to put a user when they log-in + /// + /// + /// + /// + /// + /// + public virtual void ClientOnSetHome(IClientAPI remoteClient, ulong regionHandle, Vector3 position, Vector3 lookAt, uint flags) + { + // Let's find the parcel in question + ILandObject land = landChannel.GetLandObject(position); + if (land == null || m_scene.GridUserService == null) + { + m_Dialog.SendAlertToUser(remoteClient, "Set Home request failed."); + return; + } + + // Gather some data + ulong gpowers = remoteClient.GetGroupPowers(land.LandData.GroupID); + SceneObjectGroup telehub = null; + if (m_scene.RegionInfo.RegionSettings.TelehubObject != UUID.Zero) + // Does the telehub exist in the scene? + telehub = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject); + + // Can the user set home here? + if (// Required: local user; foreign users cannot set home + m_scene.UserManagementModule.IsLocalGridUser(remoteClient.AgentId) && + (// (a) gods and land managers can set home + m_scene.Permissions.IsAdministrator(remoteClient.AgentId) || + m_scene.Permissions.IsGod(remoteClient.AgentId) || + // (b) land owners can set home + remoteClient.AgentId == land.LandData.OwnerID || + // (c) members of the land-associated group in roles that can set home + ((gpowers & (ulong)GroupPowers.AllowSetHome) == (ulong)GroupPowers.AllowSetHome) || + // (d) parcels with telehubs can be the home of anyone + (telehub != null && land.ContainsPoint((int)telehub.AbsolutePosition.X, (int)telehub.AbsolutePosition.Y)))) + { + string userId; + UUID test; + if (!m_scene.UserManagementModule.GetUserUUI(remoteClient.AgentId, out userId)) + { + /* Do not set a home position in this grid for a HG visitor */ + m_Dialog.SendAlertToUser(remoteClient, "Set Home request failed. (User Lookup)"); + } + else if (!UUID.TryParse(userId, out test)) + { + m_Dialog.SendAlertToUser(remoteClient, "Set Home request failed. (HG visitor)"); + } + else if (m_scene.GridUserService.SetHome(userId, land.RegionUUID, position, lookAt)) + { + // FUBAR ALERT: this needs to be "Home position set." so the viewer saves a home-screenshot. + m_Dialog.SendAlertToUser(remoteClient, "Home position set."); + } + else + { + m_Dialog.SendAlertToUser(remoteClient, "Set Home request failed."); + } + } + else + m_Dialog.SendAlertToUser(remoteClient, "You are not allowed to set your home location in this parcel."); + } + + protected void RegisterCommands() + { + ICommands commands = MainConsole.Instance.Commands; + + commands.AddCommand( + "Land", false, "land clear", + "land clear", + "Clear all the parcels from the region.", + "Command will ask for confirmation before proceeding.", + HandleClearCommand); + + commands.AddCommand( + "Land", false, "land show", + "land show []", + "Show information about the parcels on the region.", + "If no local land ID is given, then summary information about all the parcels is shown.\n" + + "If a local land ID is given then full information about that parcel is shown.", + HandleShowCommand); } - protected void ClearCommand(Object[] args) + protected void HandleClearCommand(string module, string[] args) { + if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) + return; + string response = MainConsole.Instance.CmdPrompt( string.Format( - "Are you sure that you want to clear all land parcels from {0} (y or n)", - m_scene.RegionInfo.RegionName), + "Are you sure that you want to clear all land parcels from {0} (y or n)", m_scene.Name), "n"); if (response.ToLower() == "y") { Clear(true); - MainConsole.Instance.OutputFormat("Cleared all parcels from {0}", m_scene.RegionInfo.RegionName); + MainConsole.Instance.OutputFormat("Cleared all parcels from {0}", m_scene.Name); } else { - MainConsole.Instance.OutputFormat("Aborting clear of all parcels from {0}", m_scene.RegionInfo.RegionName); + MainConsole.Instance.OutputFormat("Aborting clear of all parcels from {0}", m_scene.Name); } } - protected void ShowParcelsCommand(Object[] args) + protected void HandleShowCommand(string module, string[] args) { - StringBuilder report = new StringBuilder(); - - report.AppendFormat("Land information for {0}\n", m_scene.RegionInfo.RegionName); - report.AppendFormat( - "{0,-20} {1,-10} {2,-9} {3,-18} {4,-18} {5,-20}\n", - "Parcel Name", - "Local ID", - "Area", - "Starts", - "Ends", - "Owner"); + if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) + return; + + StringBuilder report = new StringBuilder(); + + if (args.Length <= 2) + { + AppendParcelsSummaryReport(report); + } + else + { + int landLocalId; + + if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[2], out landLocalId)) + return; + + ILandObject lo; + + lock (m_landList) + { + if (!m_landList.TryGetValue(landLocalId, out lo)) + { + MainConsole.Instance.OutputFormat("No parcel found with local ID {0}", landLocalId); + return; + } + } + + AppendParcelReport(report, lo); + } + + MainConsole.Instance.Output(report.ToString()); + } + + private void AppendParcelsSummaryReport(StringBuilder report) + { + report.AppendFormat("Land information for {0}\n", m_scene.Name); + + ConsoleDisplayTable cdt = new ConsoleDisplayTable(); + cdt.AddColumn("Parcel Name", ConsoleDisplayUtil.ParcelNameSize); + cdt.AddColumn("ID", 3); + cdt.AddColumn("Area", 6); + cdt.AddColumn("Starts", ConsoleDisplayUtil.VectorSize); + cdt.AddColumn("Ends", ConsoleDisplayUtil.VectorSize); + cdt.AddColumn("Owner", ConsoleDisplayUtil.UserNameSize); lock (m_landList) { foreach (ILandObject lo in m_landList.Values) { LandData ld = lo.LandData; - - report.AppendFormat( - "{0,-20} {1,-10} {2,-9} {3,-18} {4,-18} {5,-20}\n", - ld.Name, ld.LocalID, ld.Area, lo.StartPoint, lo.EndPoint, m_userManager.GetUserName(ld.OwnerID)); + string ownerName; + if (ld.IsGroupOwned) + { + GroupRecord rec = m_groupManager.GetGroupRecord(ld.GroupID); + ownerName = (rec != null) ? rec.GroupName : "Unknown Group"; + } + else + { + ownerName = m_userManager.GetUserName(ld.OwnerID); + } + cdt.AddRow( + ld.Name, ld.LocalID, ld.Area, lo.StartPoint, lo.EndPoint, ownerName); } } - - MainConsole.Instance.Output(report.ToString()); - } + + report.Append(cdt.ToString()); + } + + private void AppendParcelReport(StringBuilder report, ILandObject lo) + { + LandData ld = lo.LandData; + + ConsoleDisplayList cdl = new ConsoleDisplayList(); + cdl.AddRow("Parcel name", ld.Name); + cdl.AddRow("Local ID", ld.LocalID); + cdl.AddRow("Description", ld.Description); + cdl.AddRow("Snapshot ID", ld.SnapshotID); + cdl.AddRow("Area", ld.Area); + cdl.AddRow("Starts", lo.StartPoint); + cdl.AddRow("Ends", lo.EndPoint); + cdl.AddRow("AABB Min", ld.AABBMin); + cdl.AddRow("AABB Max", ld.AABBMax); + string ownerName; + if (ld.IsGroupOwned) + { + GroupRecord rec = m_groupManager.GetGroupRecord(ld.GroupID); + ownerName = (rec != null) ? rec.GroupName : "Unknown Group"; + } + else + { + ownerName = m_userManager.GetUserName(ld.OwnerID); + } + cdl.AddRow("Owner", ownerName); + cdl.AddRow("Is group owned?", ld.IsGroupOwned); + cdl.AddRow("GroupID", ld.GroupID); + + cdl.AddRow("Status", ld.Status); + cdl.AddRow("Flags", (ParcelFlags)ld.Flags); + + cdl.AddRow("Landing Type", (LandingType)ld.LandingType); + cdl.AddRow("User Location", ld.UserLocation); + cdl.AddRow("User look at", ld.UserLookAt); + + cdl.AddRow("Other clean time", ld.OtherCleanTime); + + cdl.AddRow("Max Prims", lo.GetParcelMaxPrimCount()); + IPrimCounts pc = lo.PrimCounts; + cdl.AddRow("Owner Prims", pc.Owner); + cdl.AddRow("Group Prims", pc.Group); + cdl.AddRow("Other Prims", pc.Others); + cdl.AddRow("Selected Prims", pc.Selected); + cdl.AddRow("Total Prims", pc.Total); + + cdl.AddRow("Music URL", ld.MusicURL); + cdl.AddRow("Obscure Music", ld.ObscureMusic); + + cdl.AddRow("Media ID", ld.MediaID); + cdl.AddRow("Media Autoscale", Convert.ToBoolean(ld.MediaAutoScale)); + cdl.AddRow("Media URL", ld.MediaURL); + cdl.AddRow("Media Type", ld.MediaType); + cdl.AddRow("Media Description", ld.MediaDescription); + cdl.AddRow("Media Width", ld.MediaWidth); + cdl.AddRow("Media Height", ld.MediaHeight); + cdl.AddRow("Media Loop", ld.MediaLoop); + cdl.AddRow("Obscure Media", ld.ObscureMedia); + + cdl.AddRow("Parcel Category", ld.Category); + + cdl.AddRow("Claim Date", ld.ClaimDate); + cdl.AddRow("Claim Price", ld.ClaimPrice); + cdl.AddRow("Pass Hours", ld.PassHours); + cdl.AddRow("Pass Price", ld.PassPrice); + + cdl.AddRow("Auction ID", ld.AuctionID); + cdl.AddRow("Authorized Buyer ID", ld.AuthBuyerID); + cdl.AddRow("Sale Price", ld.SalePrice); + + cdl.AddToStringBuilder(report); + } } } diff --git a/OpenSim/Region/CoreModules/World/Land/LandObject.cs b/OpenSim/Region/CoreModules/World/Land/LandObject.cs index 5969d45..a0c1b9d 100644 --- a/OpenSim/Region/CoreModules/World/Land/LandObject.cs +++ b/OpenSim/Region/CoreModules/World/Land/LandObject.cs @@ -45,14 +45,12 @@ namespace OpenSim.Region.CoreModules.World.Land #region Member Variables private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - #pragma warning disable 0429 - private const int landArrayMax = ((int)((int)Constants.RegionSize / 4) >= 64) ? (int)((int)Constants.RegionSize / 4) : 64; - #pragma warning restore 0429 - private bool[,] m_landBitmap = new bool[landArrayMax,landArrayMax]; + private static readonly string LogHeader = "[LAND OBJECT]"; - private int m_lastSeqId = 0; + private readonly int landUnit = 4; - protected LandData m_landData = new LandData(); + private int m_lastSeqId = 0; + protected Scene m_scene; protected List primsOverMe = new List(); protected Dictionary m_listTransactions = new Dictionary(); @@ -60,76 +58,83 @@ namespace OpenSim.Region.CoreModules.World.Land protected ExpiringCache m_groupMemberCache = new ExpiringCache(); protected TimeSpan m_groupMemberCacheTimeout = TimeSpan.FromSeconds(30); // cache invalidation after 30 seconds - public bool[,] LandBitmap - { - get { return m_landBitmap; } - set { m_landBitmap = value; } - } + public bool[,] LandBitmap { get; set; } #endregion public int GetPrimsFree() { m_scene.EventManager.TriggerParcelPrimCountUpdate(); - int free = GetSimulatorMaxPrimCount() - m_landData.SimwidePrims; + int free = GetSimulatorMaxPrimCount() - LandData.SimwidePrims; return free; } - public LandData LandData - { - get { return m_landData; } + public LandData LandData { get; set; } - set { m_landData = value; } - } - public IPrimCounts PrimCounts { get; set; } public UUID RegionUUID { get { return m_scene.RegionInfo.RegionID; } } - + public Vector3 StartPoint { get { - for (int y = 0; y < landArrayMax; y++) + for (int y = 0; y < LandBitmap.GetLength(1); y++) { - for (int x = 0; x < landArrayMax; x++) + for (int x = 0; x < LandBitmap.GetLength(0); x++) { if (LandBitmap[x, y]) - return new Vector3(x * 4, y * 4, 0); + return new Vector3(x * landUnit, y * landUnit, 0); } } - + + m_log.ErrorFormat("{0} StartPoint. No start point found. bitmapSize=<{1},{2}>", + LogHeader, LandBitmap.GetLength(0), LandBitmap.GetLength(1)); return new Vector3(-1, -1, -1); } - } - + } + public Vector3 EndPoint { get { - for (int y = landArrayMax - 1; y >= 0; y--) + for (int y = LandBitmap.GetLength(1) - 1; y >= 0; y--) { - for (int x = landArrayMax - 1; x >= 0; x--) + for (int x = LandBitmap.GetLength(0) - 1; x >= 0; x--) { if (LandBitmap[x, y]) { - return new Vector3(x * 4, y * 4, 0); - } + return new Vector3(x * landUnit + landUnit, y * landUnit + landUnit, 0); + } } - } - + } + + m_log.ErrorFormat("{0} EndPoint. No end point found. bitmapSize=<{1},{2}>", + LogHeader, LandBitmap.GetLength(0), LandBitmap.GetLength(1)); return new Vector3(-1, -1, -1); } } - + #region Constructors + public LandObject(LandData landData, Scene scene) + { + LandData = landData.Copy(); + m_scene = scene; + } + public LandObject(UUID owner_id, bool is_group_owned, Scene scene) { m_scene = scene; + if (m_scene == null) + LandBitmap = new bool[Constants.RegionSize / landUnit, Constants.RegionSize / landUnit]; + else + LandBitmap = new bool[m_scene.RegionInfo.RegionSizeX / landUnit, m_scene.RegionInfo.RegionSizeY / landUnit]; + + LandData = new LandData(); LandData.OwnerID = owner_id; if (is_group_owned) LandData.GroupID = owner_id; @@ -152,9 +157,9 @@ namespace OpenSim.Region.CoreModules.World.Land /// Returns true if the piece of land contains the specified point public bool ContainsPoint(int x, int y) { - if (x >= 0 && y >= 0 && x < Constants.RegionSize && y < Constants.RegionSize) + if (x >= 0 && y >= 0 && x < m_scene.RegionInfo.RegionSizeX && y < m_scene.RegionInfo.RegionSizeY) { - return (LandBitmap[x / 4, y / 4] == true); + return LandBitmap[x / landUnit, y / landUnit]; } else { @@ -164,12 +169,8 @@ namespace OpenSim.Region.CoreModules.World.Land public ILandObject Copy() { - ILandObject newLand = new LandObject(LandData.OwnerID, LandData.IsGroupOwned, m_scene); - - //Place all new variables here! + ILandObject newLand = new LandObject(LandData, m_scene); newLand.LandBitmap = (bool[,]) (LandBitmap.Clone()); - newLand.LandData = LandData.Copy(); - return newLand; } @@ -194,7 +195,7 @@ namespace OpenSim.Region.CoreModules.World.Land else { // Normal Calculations - int parcelMax = (int)(((float)LandData.Area / 65536.0f) + int parcelMax = (int)(((float)LandData.Area / (m_scene.RegionInfo.RegionSizeX * m_scene.RegionInfo.RegionSizeY)) * (float)m_scene.RegionInfo.ObjectCapacity * (float)m_scene.RegionInfo.RegionSettings.ObjectBonus); // TODO: The calculation of ObjectBonus should be refactored. It does still not work in the same manner as SL! @@ -211,7 +212,7 @@ namespace OpenSim.Region.CoreModules.World.Land else { //Normal Calculations - int simMax = (int)(((float)LandData.SimwideArea / 65536.0f) + int simMax = (int)(((float)LandData.SimwideArea / (m_scene.RegionInfo.RegionSizeX * m_scene.RegionInfo.RegionSizeY)) * (float)m_scene.RegionInfo.ObjectCapacity); return simMax; } @@ -224,17 +225,15 @@ namespace OpenSim.Region.CoreModules.World.Land public void SendLandProperties(int sequence_id, bool snap_selection, int request_result, IClientAPI remote_client) { IEstateModule estateModule = m_scene.RequestModuleInterface(); - uint regionFlags = 336723974 & ~((uint)(RegionFlags.AllowLandmark | RegionFlags.AllowSetHome)); + // uint regionFlags = 336723974 & ~((uint)(RegionFlags.AllowLandmark | RegionFlags.AllowSetHome)); + uint regionFlags = (uint)(RegionFlags.PublicAllowed + | RegionFlags.AllowDirectTeleport + | RegionFlags.AllowParcelChanges + | RegionFlags.AllowVoice ); + if (estateModule != null) regionFlags = estateModule.GetRegionFlags(); - // In a perfect world, this would have worked. - // -// if ((landData.Flags & (uint)ParcelFlags.AllowLandmark) != 0) -// regionFlags |= (uint)RegionFlags.AllowLandmark; -// if (landData.OwnerID == remote_client.AgentId) -// regionFlags |= (uint)RegionFlags.AllowSetHome; - int seq_id; if (snap_selection && (sequence_id == 0)) { @@ -368,12 +367,14 @@ namespace OpenSim.Region.CoreModules.World.Land ParcelFlags.DenyAgeUnverified); } - uint preserve = LandData.Flags & ~allowedDelta; - newData.Flags = preserve | (args.ParcelFlags & allowedDelta); - - m_scene.LandChannel.UpdateLandObject(LandData.LocalID, newData); + if (allowedDelta != (uint)ParcelFlags.None) + { + uint preserve = LandData.Flags & ~allowedDelta; + newData.Flags = preserve | (args.ParcelFlags & allowedDelta); - SendLandUpdateToAvatarsOverMe(snap_selection); + m_scene.LandChannel.UpdateLandObject(LandData.LocalID, newData); + SendLandUpdateToAvatarsOverMe(snap_selection); + } } public void UpdateLandSold(UUID avatarID, UUID groupID, bool groupOwned, uint AuctionID, int claimprice, int area) @@ -388,9 +389,16 @@ namespace OpenSim.Region.CoreModules.World.Land newData.SalePrice = 0; newData.AuthBuyerID = UUID.Zero; newData.Flags &= ~(uint) (ParcelFlags.ForSale | ParcelFlags.ForSaleObjects | ParcelFlags.SellParcelObjects | ParcelFlags.ShowDirectory); + + bool sellObjects = (LandData.Flags & (uint)(ParcelFlags.SellParcelObjects)) != 0 + && !LandData.IsGroupOwned && !groupOwned; + UUID previousOwner = LandData.OwnerID; + m_scene.LandChannel.UpdateLandObject(LandData.LocalID, newData); m_scene.EventManager.TriggerParcelPrimCountUpdate(); SendLandUpdateToAvatarsOverMe(true); + + if (sellObjects) SellLandObjects(previousOwner); } public void DeedToGroup(UUID groupID) @@ -421,6 +429,19 @@ namespace OpenSim.Region.CoreModules.World.Land return false; } + public bool CanBeOnThisLand(UUID avatar, float posHeight) + { + if (posHeight < LandChannel.BAN_LINE_SAFETY_HIEGHT && IsBannedFromLand(avatar)) + { + return false; + } + else if (IsRestrictedFromLand(avatar)) + { + return false; + } + return true; + } + public bool HasGroupAccess(UUID avatar) { if (LandData.GroupID != UUID.Zero && (LandData.Flags & (uint)ParcelFlags.UseAccessGroup) == (uint)ParcelFlags.UseAccessGroup) @@ -553,8 +574,8 @@ namespace OpenSim.Region.CoreModules.World.Land try { over = - m_scene.LandChannel.GetLandObject(Util.Clamp((int)Math.Round(avatar.AbsolutePosition.X), 0, ((int)Constants.RegionSize - 1)), - Util.Clamp((int)Math.Round(avatar.AbsolutePosition.Y), 0, ((int)Constants.RegionSize - 1))); + m_scene.LandChannel.GetLandObject(Util.Clamp((int)Math.Round(avatar.AbsolutePosition.X), 0, ((int)m_scene.RegionInfo.RegionSizeX - 1)), + Util.Clamp((int)Math.Round(avatar.AbsolutePosition.Y), 0, ((int)m_scene.RegionInfo.RegionSizeY - 1))); } catch (Exception) { @@ -701,15 +722,15 @@ namespace OpenSim.Region.CoreModules.World.Land /// private void UpdateAABBAndAreaValues() { - int min_x = 64; - int min_y = 64; + int min_x = 10000; + int min_y = 10000; int max_x = 0; int max_y = 0; int tempArea = 0; int x, y; - for (x = 0; x < 64; x++) + for (x = 0; x < LandBitmap.GetLength(0); x++) { - for (y = 0; y < 64; y++) + for (y = 0; y < LandBitmap.GetLength(1); y++) { if (LandBitmap[x, y] == true) { @@ -717,31 +738,31 @@ namespace OpenSim.Region.CoreModules.World.Land if (min_y > y) min_y = y; if (max_x < x) max_x = x; if (max_y < y) max_y = y; - tempArea += 16; //16sqm peice of land + tempArea += landUnit * landUnit; //16sqm peice of land } } } - int tx = min_x * 4; - if (tx > ((int)Constants.RegionSize - 1)) - tx = ((int)Constants.RegionSize - 1); - int ty = min_y * 4; - if (ty > ((int)Constants.RegionSize - 1)) - ty = ((int)Constants.RegionSize - 1); + int tx = min_x * landUnit; + if (tx > ((int)m_scene.RegionInfo.RegionSizeX - 1)) + tx = ((int)m_scene.RegionInfo.RegionSizeX - 1); + int ty = min_y * landUnit; + if (ty > ((int)m_scene.RegionInfo.RegionSizeY - 1)) + ty = ((int)m_scene.RegionInfo.RegionSizeY - 1); LandData.AABBMin = new Vector3( - (float)(min_x * 4), (float)(min_y * 4), m_scene != null ? (float)m_scene.Heightmap[tx, ty] : 0); + (float)(min_x * landUnit), (float)(min_y * landUnit), m_scene != null ? (float)m_scene.Heightmap[tx, ty] : 0); - tx = max_x * 4; - if (tx > ((int)Constants.RegionSize - 1)) - tx = ((int)Constants.RegionSize - 1); - ty = max_y * 4; - if (ty > ((int)Constants.RegionSize - 1)) - ty = ((int)Constants.RegionSize - 1); + tx = max_x * landUnit; + if (tx > ((int)m_scene.RegionInfo.RegionSizeX - 1)) + tx = ((int)m_scene.RegionInfo.RegionSizeX - 1); + ty = max_y * landUnit; + if (ty > ((int)m_scene.RegionInfo.RegionSizeY - 1)) + ty = ((int)m_scene.RegionInfo.RegionSizeY - 1); LandData.AABBMax = new Vector3( - (float)(max_x * 4), (float)(max_y * 4), m_scene != null ? (float)m_scene.Heightmap[tx, ty] : 0); + (float)(max_x * landUnit), (float)(max_y * landUnit), m_scene != null ? (float)m_scene.Heightmap[tx, ty] : 0); LandData.Area = tempArea; } @@ -753,20 +774,12 @@ namespace OpenSim.Region.CoreModules.World.Land /// /// Sets the land's bitmap manually /// - /// 64x64 block representing where this land is on a map + /// block representing where this land is on a map mapped in a 4x4 meter grid public void SetLandBitmap(bool[,] bitmap) { - if (bitmap.GetLength(0) != 64 || bitmap.GetLength(1) != 64 || bitmap.Rank != 2) - { - //Throw an exception - The bitmap is not 64x64 - //throw new Exception("Error: Invalid Parcel Bitmap"); - } - else - { - //Valid: Lets set it - LandBitmap = bitmap; - ForceUpdateLandInfo(); - } + LandBitmap = bitmap; + // m_log.DebugFormat("{0} SetLandBitmap. BitmapSize=<{1},{2}>", LogHeader, LandBitmap.GetLength(0), LandBitmap.GetLength(1)); + ForceUpdateLandInfo(); } /// @@ -780,15 +793,19 @@ namespace OpenSim.Region.CoreModules.World.Land public bool[,] BasicFullRegionLandBitmap() { - return GetSquareLandBitmap(0, 0, (int) Constants.RegionSize, (int) Constants.RegionSize); + return GetSquareLandBitmap(0, 0, (int)m_scene.RegionInfo.RegionSizeX, (int) m_scene.RegionInfo.RegionSizeY); } public bool[,] GetSquareLandBitmap(int start_x, int start_y, int end_x, int end_y) { - bool[,] tempBitmap = new bool[64,64]; + // Empty bitmap for the whole region + bool[,] tempBitmap = new bool[m_scene.RegionInfo.RegionSizeX / landUnit, m_scene.RegionInfo.RegionSizeY / landUnit]; tempBitmap.Initialize(); + // Fill the bitmap square area specified by state and end tempBitmap = ModifyLandBitmapSquare(tempBitmap, start_x, start_y, end_x, end_y, true); + // m_log.DebugFormat("{0} GetSquareLandBitmap. tempBitmapSize=<{1},{2}>", + // LogHeader, tempBitmap.GetLength(0), tempBitmap.GetLength(1)); return tempBitmap; } @@ -805,24 +822,20 @@ namespace OpenSim.Region.CoreModules.World.Land public bool[,] ModifyLandBitmapSquare(bool[,] land_bitmap, int start_x, int start_y, int end_x, int end_y, bool set_value) { - if (land_bitmap.GetLength(0) != 64 || land_bitmap.GetLength(1) != 64 || land_bitmap.Rank != 2) - { - //Throw an exception - The bitmap is not 64x64 - //throw new Exception("Error: Invalid Parcel Bitmap in modifyLandBitmapSquare()"); - } - int x, y; - for (y = 0; y < 64; y++) + for (y = 0; y < land_bitmap.GetLength(1); y++) { - for (x = 0; x < 64; x++) + for (x = 0; x < land_bitmap.GetLength(0); x++) { - if (x >= start_x / 4 && x < end_x / 4 - && y >= start_y / 4 && y < end_y / 4) + if (x >= start_x / landUnit && x < end_x / landUnit + && y >= start_y / landUnit && y < end_y / landUnit) { land_bitmap[x, y] = set_value; } } } + // m_log.DebugFormat("{0} ModifyLandBitmapSquare. startXY=<{1},{2}>, endXY=<{3},{4}>, val={5}, landBitmapSize=<{6},{7}>", + // LogHeader, start_x, start_y, end_x, end_y, set_value, land_bitmap.GetLength(0), land_bitmap.GetLength(1)); return land_bitmap; } @@ -834,21 +847,21 @@ namespace OpenSim.Region.CoreModules.World.Land /// public bool[,] MergeLandBitmaps(bool[,] bitmap_base, bool[,] bitmap_add) { - if (bitmap_base.GetLength(0) != 64 || bitmap_base.GetLength(1) != 64 || bitmap_base.Rank != 2) + if (bitmap_base.GetLength(0) != bitmap_add.GetLength(0) + || bitmap_base.GetLength(1) != bitmap_add.GetLength(1) + || bitmap_add.Rank != 2 + || bitmap_base.Rank != 2) { - //Throw an exception - The bitmap is not 64x64 - throw new Exception("Error: Invalid Parcel Bitmap - Bitmap_base in mergeLandBitmaps"); - } - if (bitmap_add.GetLength(0) != 64 || bitmap_add.GetLength(1) != 64 || bitmap_add.Rank != 2) - { - //Throw an exception - The bitmap is not 64x64 - throw new Exception("Error: Invalid Parcel Bitmap - Bitmap_add in mergeLandBitmaps"); + throw new Exception( + String.Format("{0} MergeLandBitmaps. merging maps not same size. baseSizeXY=<{1},{2}>, addSizeXY=<{3},{4}>", + LogHeader, bitmap_base.GetLength(0), bitmap_base.GetLength(1), bitmap_add.GetLength(0), bitmap_add.GetLength(1)) + ); } int x, y; - for (y = 0; y < 64; y++) + for (y = 0; y < bitmap_base.GetLength(1); y++) { - for (x = 0; x < 64; x++) + for (x = 0; x < bitmap_add.GetLength(0); x++) { if (bitmap_add[x, y]) { @@ -865,13 +878,13 @@ namespace OpenSim.Region.CoreModules.World.Land /// private byte[] ConvertLandBitmapToBytes() { - byte[] tempConvertArr = new byte[512]; + byte[] tempConvertArr = new byte[LandBitmap.GetLength(0) * LandBitmap.GetLength(1) / 8]; byte tempByte = 0; - int x, y, i, byteNum = 0; - i = 0; - for (y = 0; y < 64; y++) + int byteNum = 0; + int i = 0; + for (int y = 0; y < LandBitmap.GetLength(1); y++) { - for (x = 0; x < 64; x++) + for (int x = 0; x < LandBitmap.GetLength(0); x++) { tempByte = Convert.ToByte(tempByte | Convert.ToByte(LandBitmap[x, y]) << (i++ % 8)); if (i % 8 == 0) @@ -883,30 +896,52 @@ namespace OpenSim.Region.CoreModules.World.Land } } } + // m_log.DebugFormat("{0} ConvertLandBitmapToBytes. BitmapSize=<{1},{2}>", + // LogHeader, LandBitmap.GetLength(0), LandBitmap.GetLength(1)); return tempConvertArr; } private bool[,] ConvertBytesToLandBitmap() { - bool[,] tempConvertMap = new bool[landArrayMax, landArrayMax]; + bool[,] tempConvertMap = new bool[m_scene.RegionInfo.RegionSizeX / landUnit, m_scene.RegionInfo.RegionSizeY / landUnit]; tempConvertMap.Initialize(); byte tempByte = 0; - int x = 0, y = 0, i = 0, bitNum = 0; - for (i = 0; i < 512; i++) + // Math.Min overcomes an old bug that might have made it into the database. Only use the bytes that fit into convertMap. + int bitmapLen = Math.Min(LandData.Bitmap.Length, tempConvertMap.GetLength(0) * tempConvertMap.GetLength(1) / 8); + int xLen = (int)(m_scene.RegionInfo.RegionSizeX / landUnit); + + if (bitmapLen == 512) + { + // Legacy bitmap being passed in. Use the legacy region size + // and only set the lower area of the larger region. + xLen = (int)(Constants.RegionSize / landUnit); + } + // m_log.DebugFormat("{0} ConvertBytesToLandBitmap: bitmapLen={1}, xLen={2}", LogHeader, bitmapLen, xLen); + + int x = 0, y = 0; + for (int i = 0; i < bitmapLen; i++) { tempByte = LandData.Bitmap[i]; - for (bitNum = 0; bitNum < 8; bitNum++) + for (int bitNum = 0; bitNum < 8; bitNum++) { bool bit = Convert.ToBoolean(Convert.ToByte(tempByte >> bitNum) & (byte) 1); - tempConvertMap[x, y] = bit; + try + { + tempConvertMap[x, y] = bit; + } + catch (Exception) + { + m_log.DebugFormat("{0} ConvertBytestoLandBitmap: i={1}, x={2}, y={3}", LogHeader, i, x, y); + } x++; - if (x > 63) + if (x >= xLen) { x = 0; y++; } } } + return tempConvertMap; } @@ -1043,6 +1078,43 @@ namespace OpenSim.Region.CoreModules.World.Land #endregion + #region Object Sales + + public void SellLandObjects(UUID previousOwner) + { + // m_log.DebugFormat( + // "[LAND OBJECT]: Request to sell objects in {0} from {1}", LandData.Name, previousOwner); + + if (LandData.IsGroupOwned) + return; + + IBuySellModule m_BuySellModule = m_scene.RequestModuleInterface(); + if (m_BuySellModule == null) + { + m_log.Error("[LAND OBJECT]: BuySellModule not found"); + return; + } + + ScenePresence sp; + if (!m_scene.TryGetScenePresence(LandData.OwnerID, out sp)) + { + m_log.Error("[LAND OBJECT]: New owner is not present in scene"); + return; + } + + lock (primsOverMe) + { + foreach (SceneObjectGroup obj in primsOverMe) + { + if (obj.OwnerID == previousOwner && obj.GroupID == UUID.Zero && + (obj.GetEffectivePermissions() & (uint)(OpenSim.Framework.PermissionMask.Transfer)) != 0) + m_BuySellModule.BuyObject(sp.ControllingClient, UUID.Zero, obj.LocalId, 1, 0); + } + } + } + + #endregion + #region Object Returning public void ReturnObject(SceneObjectGroup obj) @@ -1065,7 +1137,7 @@ namespace OpenSim.Region.CoreModules.World.Land { foreach (SceneObjectGroup obj in primsOverMe) { - if (obj.OwnerID == m_landData.OwnerID) + if (obj.OwnerID == LandData.OwnerID) { if (!returns.ContainsKey(obj.OwnerID)) returns[obj.OwnerID] = @@ -1074,11 +1146,11 @@ namespace OpenSim.Region.CoreModules.World.Land } } } - else if (type == (uint)ObjectReturnType.Group && m_landData.GroupID != UUID.Zero) + else if (type == (uint)ObjectReturnType.Group && LandData.GroupID != UUID.Zero) { foreach (SceneObjectGroup obj in primsOverMe) { - if (obj.GroupID == m_landData.GroupID) + if (obj.GroupID == LandData.GroupID) { if (!returns.ContainsKey(obj.OwnerID)) returns[obj.OwnerID] = @@ -1091,9 +1163,9 @@ namespace OpenSim.Region.CoreModules.World.Land { foreach (SceneObjectGroup obj in primsOverMe) { - if (obj.OwnerID != m_landData.OwnerID && - (obj.GroupID != m_landData.GroupID || - m_landData.GroupID == UUID.Zero)) + if (obj.OwnerID != LandData.OwnerID && + (obj.GroupID != LandData.GroupID || + LandData.GroupID == UUID.Zero)) { if (!returns.ContainsKey(obj.OwnerID)) returns[obj.OwnerID] = diff --git a/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs b/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs index f9cc0cf..9b51cc8 100644 --- a/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs +++ b/OpenSim/Region/CoreModules/World/Land/PrimCountModule.cs @@ -490,11 +490,14 @@ namespace OpenSim.Region.CoreModules.World.Land m_Scene.ForEachSOG(AddObject); - List primcountKeys = new List(m_PrimCounts.Keys); - foreach (UUID k in primcountKeys) + lock (m_PrimCounts) { - if (!m_OwnerMap.ContainsKey(k)) - m_PrimCounts.Remove(k); + List primcountKeys = new List(m_PrimCounts.Keys); + foreach (UUID k in primcountKeys) + { + if (!m_OwnerMap.ContainsKey(k)) + m_PrimCounts.Remove(k); + } } m_Tainted = false; diff --git a/OpenSim/Region/CoreModules/World/Land/Tests/LandManagementModuleTests.cs b/OpenSim/Region/CoreModules/World/Land/Tests/LandManagementModuleTests.cs new file mode 100644 index 0000000..4ed67f3 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Land/Tests/LandManagementModuleTests.cs @@ -0,0 +1,266 @@ +/* + * 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 NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Land.Tests +{ + public class LandManagementModuleTests : OpenSimTestCase + { + [Test] + public void TestAddLandObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + LandManagementModule lmm = new LandManagementModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject lo = new LandObject(userId, false, scene); + lo.LandData.Name = "lo1"; + lo.SetLandBitmap( + lo.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo = lmm.AddLandObject(lo); + + // TODO: Should add asserts to check that land object was added properly. + + // At the moment, this test just makes sure that we can't add a land object that overlaps the areas that + // the first still holds. + ILandObject lo2 = new LandObject(userId, false, scene); + lo2.SetLandBitmap( + lo2.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo2.LandData.Name = "lo2"; + lo2 = lmm.AddLandObject(lo2); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + } + + /// + /// Test parcels on region when no land data exists to be loaded. + /// + [Test] + public void TestLoadWithNoParcels() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.LocalID, Is.Not.EqualTo(0)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.Not.EqualTo(UUID.Zero)); + + ILandObject loAtCoord2 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord2.LandData.LocalID, Is.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(loAtCoord1.LandData.GlobalID)); + } + + /// + /// Test parcels on region when a single parcel already exists but it does not cover the whole region. + /// + [Test] + public void TestLoadWithSinglePartialCoveringParcel() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + + ILandObject loAtCoord2 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord2.LandData.LocalID, Is.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(loAtCoord1.LandData.GlobalID)); + } + + /// + /// Test parcels on region when a single parcel already exists but it does not cover the whole region. + /// + [Test] + public void TestLoadWithMultiplePartialCoveringParcels() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + ILandObject originalLo2 = new LandObject(userId, false, scene); + originalLo2.LandData.Name = "lo2"; + originalLo2.SetLandBitmap( + originalLo2.GetSquareLandBitmap( + 0, (int)Constants.RegionSize / 2, (int)Constants.RegionSize, ((int)Constants.RegionSize / 4) * 3)); + + sh.SimDataService.StoreLandObject(originalLo2); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + + ILandObject loAtCoord2 + = lmm.GetLandObject((int)Constants.RegionSize - 1, (((int)Constants.RegionSize / 4) * 3) - 1); + Assert.That(loAtCoord2.LandData.Name, Is.EqualTo(originalLo2.LandData.Name)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(originalLo2.LandData.GlobalID)); + + ILandObject loAtCoord3 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord3.LandData.LocalID, Is.Not.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord3.LandData.LocalID, Is.Not.EqualTo(loAtCoord2.LandData.LocalID)); + Assert.That(loAtCoord3.LandData.GlobalID, Is.Not.EqualTo(loAtCoord1.LandData.GlobalID)); + Assert.That(loAtCoord3.LandData.GlobalID, Is.Not.EqualTo(loAtCoord2.LandData.GlobalID)); + } + + /// + /// Test parcels on region when whole region is parcelled (which should normally always be the case). + /// + [Test] + public void TestLoad() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + ILandObject originalLo2 = new LandObject(userId, false, scene); + originalLo2.LandData.Name = "lo2"; + originalLo2.SetLandBitmap( + originalLo2.GetSquareLandBitmap(0, (int)Constants.RegionSize / 2, (int)Constants.RegionSize, (int)Constants.RegionSize)); + + sh.SimDataService.StoreLandObject(originalLo2); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord.LandData.Name, Is.EqualTo(originalLo2.LandData.Name)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(originalLo2.LandData.GlobalID)); + } + } + + [Test] + public void TestSubdivide() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + LandManagementModule lmm = new LandManagementModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject lo = new LandObject(userId, false, scene); + lo.LandData.Name = "lo1"; + lo.SetLandBitmap( + lo.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo = lmm.AddLandObject(lo); + + lmm.Subdivide(0, 0, LandManagementModule.LandUnit, LandManagementModule.LandUnit, userId); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.LocalID, Is.Not.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.Not.EqualTo(lo.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject(LandManagementModule.LandUnit, LandManagementModule.LandUnit); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Land/Tests/PrimCountModuleTests.cs b/OpenSim/Region/CoreModules/World/Land/Tests/PrimCountModuleTests.cs index 0945b43..949acb6 100644 --- a/OpenSim/Region/CoreModules/World/Land/Tests/PrimCountModuleTests.cs +++ b/OpenSim/Region/CoreModules/World/Land/Tests/PrimCountModuleTests.cs @@ -36,7 +36,6 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.World.Land.Tests { diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs index 8a422b0..796a15f 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public struct DrawStruct { public DrawRoutine dr; - public Rectangle rect; +// public Rectangle rect; public SolidBrush brush; public face[] trns; } @@ -77,40 +77,71 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { bool drawPrimVolume = true; bool textureTerrain = false; + bool generateMaptiles = true; + Bitmap mapbmp; - try - { - IConfig startupConfig = m_config.Configs["Startup"]; - drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", drawPrimVolume); - textureTerrain = startupConfig.GetBoolean("TextureOnMapTile", textureTerrain); - } - catch - { - m_log.Warn("[MAPTILE]: Failed to load StartupConfig"); - } + string[] configSections = new string[] { "Map", "Startup" }; - if (textureTerrain) - { - terrainRenderer = new TexturedMapTileRenderer(); - } - else + drawPrimVolume + = Util.GetConfigVarFromSections(m_config, "DrawPrimOnMapTile", configSections, drawPrimVolume); + textureTerrain + = Util.GetConfigVarFromSections(m_config, "TextureOnMapTile", configSections, textureTerrain); + generateMaptiles + = Util.GetConfigVarFromSections(m_config, "GenerateMaptiles", configSections, generateMaptiles); + + if (generateMaptiles) { - terrainRenderer = new ShadedMapTileRenderer(); - } - terrainRenderer.Initialise(m_scene, m_config); + if (String.IsNullOrEmpty(m_scene.RegionInfo.MaptileStaticFile)) + { + if (textureTerrain) + { + terrainRenderer = new TexturedMapTileRenderer(); + } + else + { + terrainRenderer = new ShadedMapTileRenderer(); + } + + terrainRenderer.Initialise(m_scene, m_config); - Bitmap mapbmp = new Bitmap((int)Constants.RegionSize, (int)Constants.RegionSize, System.Drawing.Imaging.PixelFormat.Format24bppRgb); - //long t = System.Environment.TickCount; - //for (int i = 0; i < 10; ++i) { - terrainRenderer.TerrainToBitmap(mapbmp); - //} - //t = System.Environment.TickCount - t; - //m_log.InfoFormat("[MAPTILE] generation of 10 maptiles needed {0} ms", t); + mapbmp = new Bitmap((int)m_scene.Heightmap.Width, (int)m_scene.Heightmap.Height, + System.Drawing.Imaging.PixelFormat.Format24bppRgb); + //long t = System.Environment.TickCount; + //for (int i = 0; i < 10; ++i) { + terrainRenderer.TerrainToBitmap(mapbmp); + //} + //t = System.Environment.TickCount - t; + //m_log.InfoFormat("[MAPTILE] generation of 10 maptiles needed {0} ms", t); + if (drawPrimVolume) + { + DrawObjectVolume(m_scene, mapbmp); + } + } + else + { + try + { + mapbmp = new Bitmap(m_scene.RegionInfo.MaptileStaticFile); + } + catch (Exception) + { + m_log.ErrorFormat( + "[MAPTILE]: Failed to load Static map image texture file: {0} for {1}", + m_scene.RegionInfo.MaptileStaticFile, m_scene.Name); + //mapbmp = new Bitmap((int)m_scene.Heightmap.Width, (int)m_scene.Heightmap.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + mapbmp = null; + } - if (drawPrimVolume) + if (mapbmp != null) + m_log.DebugFormat( + "[MAPTILE]: Static map image texture file {0} found for {1}", + m_scene.RegionInfo.MaptileStaticFile, m_scene.Name); + } + } + else { - DrawObjectVolume(m_scene, mapbmp); + mapbmp = FetchTexture(m_scene.RegionInfo.RegionSettings.TerrainImageID); } return mapbmp; @@ -121,7 +152,10 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap try { using (Bitmap mapbmp = CreateMapTile()) - return OpenJPEG.EncodeFromImage(mapbmp, true); + { + if (mapbmp != null) + return OpenJPEG.EncodeFromImage(mapbmp, true); + } } catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke { @@ -139,9 +173,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { m_config = source; - IConfig startupConfig = m_config.Configs["Startup"]; - if (startupConfig.GetString("MapImageModule", "MapImageModule") != - "MapImageModule") + if (Util.GetConfigVarFromSections( + m_config, "MapImageModule", new string[] { "Startup", "Map" }, "MapImageModule") != "MapImageModule") return; m_Enabled = true; @@ -222,342 +255,395 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap // } // } + private Bitmap FetchTexture(UUID id) + { + AssetBase asset = m_scene.AssetService.Get(id.ToString()); + + if (asset != null) + { + m_log.DebugFormat("[MAPTILE]: Static map image texture {0} found for {1}", id, m_scene.Name); + } + else + { + m_log.WarnFormat("[MAPTILE]: Static map image texture {0} not found for {1}", id, m_scene.Name); + return null; + } + + ManagedImage managedImage; + Image image; + + try + { + if (OpenJPEG.DecodeToImage(asset.Data, out managedImage, out image)) + return new Bitmap(image); + else + return null; + } + catch (DllNotFoundException) + { + m_log.ErrorFormat("[MAPTILE]: OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", id); + + } + catch (IndexOutOfRangeException) + { + m_log.ErrorFormat("[MAPTILE]: OpenJpeg was unable to decode this. Asset Data is empty for {0}", id); + + } + catch (Exception) + { + m_log.ErrorFormat("[MAPTILE]: OpenJpeg was unable to decode this. Asset Data is empty for {0}", id); + + } + return null; + + } + private Bitmap DrawObjectVolume(Scene whichScene, Bitmap mapbmp) { int tc = 0; - double[,] hm = whichScene.Heightmap.GetDoubles(); + ITerrainChannel hm = whichScene.Heightmap; tc = Environment.TickCount; m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); EntityBase[] objs = whichScene.GetEntities(); - Dictionary z_sort = new Dictionary(); - //SortedList z_sort = new SortedList(); List z_sortheights = new List(); List z_localIDs = new List(); + Dictionary z_sort = new Dictionary(); - lock (objs) + try { - foreach (EntityBase obj in objs) + lock (objs) { - // Only draw the contents of SceneObjectGroup - if (obj is SceneObjectGroup) + foreach (EntityBase obj in objs) { - SceneObjectGroup mapdot = (SceneObjectGroup)obj; - Color mapdotspot = Color.Gray; // Default color when prim color is white - - // Loop over prim in group - foreach (SceneObjectPart part in mapdot.Parts) + // Only draw the contents of SceneObjectGroup + if (obj is SceneObjectGroup) { - if (part == null) - continue; - - // Draw if the object is at least 1 meter wide in any direction - if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) + SceneObjectGroup mapdot = (SceneObjectGroup)obj; + Color mapdotspot = Color.Gray; // Default color when prim color is white + // Loop over prim in group + foreach (SceneObjectPart part in mapdot.Parts) { - // Try to get the RGBA of the default texture entry.. - // - try + if (part == null) + continue; + + // Draw if the object is at least 1 meter wide in any direction + if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) { - // get the null checks out of the way - // skip the ones that break - if (part == null) - continue; + // Try to get the RGBA of the default texture entry.. + // + try + { + // get the null checks out of the way + // skip the ones that break + if (part == null) + continue; - if (part.Shape == null) - continue; + if (part.Shape == null) + continue; - if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass) - continue; // eliminates trees from this since we don't really have a good tree representation - // if you want tree blocks on the map comment the above line and uncomment the below line - //mapdotspot = Color.PaleGreen; + if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass) + continue; // eliminates trees from this since we don't really have a good tree representation + // if you want tree blocks on the map comment the above line and uncomment the below line + //mapdotspot = Color.PaleGreen; - Primitive.TextureEntry textureEntry = part.Shape.Textures; + Primitive.TextureEntry textureEntry = part.Shape.Textures; - if (textureEntry == null || textureEntry.DefaultTexture == null) - continue; + if (textureEntry == null || textureEntry.DefaultTexture == null) + continue; - Color4 texcolor = textureEntry.DefaultTexture.RGBA; + Color4 texcolor = textureEntry.DefaultTexture.RGBA; - // Not sure why some of these are null, oh well. + // Not sure why some of these are null, oh well. - int colorr = 255 - (int)(texcolor.R * 255f); - int colorg = 255 - (int)(texcolor.G * 255f); - int colorb = 255 - (int)(texcolor.B * 255f); + int colorr = 255 - (int)(texcolor.R * 255f); + int colorg = 255 - (int)(texcolor.G * 255f); + int colorb = 255 - (int)(texcolor.B * 255f); - if (!(colorr == 255 && colorg == 255 && colorb == 255)) - { - //Try to set the map spot color - try - { - // If the color gets goofy somehow, skip it *shakes fist at Color4 - mapdotspot = Color.FromArgb(colorr, colorg, colorb); - } - catch (ArgumentException) + if (!(colorr == 255 && colorg == 255 && colorb == 255)) { + //Try to set the map spot color + try + { + // If the color gets goofy somehow, skip it *shakes fist at Color4 + mapdotspot = Color.FromArgb(colorr, colorg, colorb); + } + catch (ArgumentException) + { + } } } - } - catch (IndexOutOfRangeException) - { - // Windows Array - } - catch (ArgumentOutOfRangeException) - { - // Mono Array - } - - Vector3 pos = part.GetWorldPosition(); - - // skip prim outside of retion - if (pos.X < 0f || pos.X > 256f || pos.Y < 0f || pos.Y > 256f) - continue; - - // skip prim in non-finite position - if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || - Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y)) - continue; - - // Figure out if object is under 256m above the height of the terrain - bool isBelow256AboveTerrain = false; + catch (IndexOutOfRangeException) + { + // Windows Array + } + catch (ArgumentOutOfRangeException) + { + // Mono Array + } - try - { - isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); - } - catch (Exception) - { - } + Vector3 pos = part.GetWorldPosition(); - if (isBelow256AboveTerrain) - { - // Translate scale by rotation so scale is represented properly when object is rotated - Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); - Vector3 scale = new Vector3(); - Vector3 tScale = new Vector3(); - Vector3 axPos = new Vector3(pos.X,pos.Y,pos.Z); - - Quaternion llrot = part.GetWorldRotation(); - Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); - scale = lscale * rot; - - // negative scales don't work in this situation - scale.X = Math.Abs(scale.X); - scale.Y = Math.Abs(scale.Y); - scale.Z = Math.Abs(scale.Z); - - // This scaling isn't very accurate and doesn't take into account the face rotation :P - int mapdrawstartX = (int)(pos.X - scale.X); - int mapdrawstartY = (int)(pos.Y - scale.Y); - int mapdrawendX = (int)(pos.X + scale.X); - int mapdrawendY = (int)(pos.Y + scale.Y); - - // If object is beyond the edge of the map, don't draw it to avoid errors - if (mapdrawstartX < 0 || mapdrawstartX > ((int)Constants.RegionSize - 1) || mapdrawendX < 0 || mapdrawendX > ((int)Constants.RegionSize - 1) - || mapdrawstartY < 0 || mapdrawstartY > ((int)Constants.RegionSize - 1) || mapdrawendY < 0 - || mapdrawendY > ((int)Constants.RegionSize - 1)) + // skip prim outside of retion + if (!m_scene.PositionIsInCurrentRegion(pos)) continue; -#region obb face reconstruction part duex - Vector3[] vertexes = new Vector3[8]; - - // float[] distance = new float[6]; - Vector3[] FaceA = new Vector3[6]; // vertex A for Facei - Vector3[] FaceB = new Vector3[6]; // vertex B for Facei - Vector3[] FaceC = new Vector3[6]; // vertex C for Facei - Vector3[] FaceD = new Vector3[6]; // vertex D for Facei - - tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - // vertexes[0].x = pos.X + vertexes[0].x; - //vertexes[0].y = pos.Y + vertexes[0].y; - //vertexes[0].z = pos.Z + vertexes[0].z; - - FaceA[0] = vertexes[0]; - FaceB[3] = vertexes[0]; - FaceA[4] = vertexes[0]; - - tScale = lscale; - scale = ((tScale * rot)); - vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[1].x = pos.X + vertexes[1].x; - // vertexes[1].y = pos.Y + vertexes[1].y; - //vertexes[1].z = pos.Z + vertexes[1].z; - - FaceB[0] = vertexes[1]; - FaceA[1] = vertexes[1]; - FaceC[4] = vertexes[1]; - - tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - - vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - //vertexes[2].x = pos.X + vertexes[2].x; - //vertexes[2].y = pos.Y + vertexes[2].y; - //vertexes[2].z = pos.Z + vertexes[2].z; - - FaceC[0] = vertexes[2]; - FaceD[3] = vertexes[2]; - FaceC[5] = vertexes[2]; - - tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - //vertexes[3].x = pos.X + vertexes[3].x; - // vertexes[3].y = pos.Y + vertexes[3].y; - // vertexes[3].z = pos.Z + vertexes[3].z; - - FaceD[0] = vertexes[3]; - FaceC[1] = vertexes[3]; - FaceA[5] = vertexes[3]; - - tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[4].x = pos.X + vertexes[4].x; - // vertexes[4].y = pos.Y + vertexes[4].y; - // vertexes[4].z = pos.Z + vertexes[4].z; - - FaceB[1] = vertexes[4]; - FaceA[2] = vertexes[4]; - FaceD[4] = vertexes[4]; - - tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[5].x = pos.X + vertexes[5].x; - // vertexes[5].y = pos.Y + vertexes[5].y; - // vertexes[5].z = pos.Z + vertexes[5].z; - - FaceD[1] = vertexes[5]; - FaceC[2] = vertexes[5]; - FaceB[5] = vertexes[5]; - - tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + // skip prim in non-finite position + if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || + Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y)) + continue; - // vertexes[6].x = pos.X + vertexes[6].x; - // vertexes[6].y = pos.Y + vertexes[6].y; - // vertexes[6].z = pos.Z + vertexes[6].z; + // Figure out if object is under 256m above the height of the terrain + bool isBelow256AboveTerrain = false; - FaceB[2] = vertexes[6]; - FaceA[3] = vertexes[6]; - FaceB[4] = vertexes[6]; + try + { + isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); + } + catch (Exception) + { + } - tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + if (isBelow256AboveTerrain) + { + // Translate scale by rotation so scale is represented properly when object is rotated + Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); + Vector3 scale = new Vector3(); + Vector3 tScale = new Vector3(); + Vector3 axPos = new Vector3(pos.X, pos.Y, pos.Z); + + Quaternion llrot = part.GetWorldRotation(); + Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); + scale = lscale * rot; + + // negative scales don't work in this situation + scale.X = Math.Abs(scale.X); + scale.Y = Math.Abs(scale.Y); + scale.Z = Math.Abs(scale.Z); + + // This scaling isn't very accurate and doesn't take into account the face rotation :P + int mapdrawstartX = (int)(pos.X - scale.X); + int mapdrawstartY = (int)(pos.Y - scale.Y); + int mapdrawendX = (int)(pos.X + scale.X); + int mapdrawendY = (int)(pos.Y + scale.Y); + + // If object is beyond the edge of the map, don't draw it to avoid errors + if (mapdrawstartX < 0 + || mapdrawstartX > (hm.Width - 1) + || mapdrawendX < 0 + || mapdrawendX > (hm.Width - 1) + || mapdrawstartY < 0 + || mapdrawstartY > (hm.Height - 1) + || mapdrawendY < 0 + || mapdrawendY > (hm.Height - 1)) + continue; + + #region obb face reconstruction part duex + Vector3[] vertexes = new Vector3[8]; + + // float[] distance = new float[6]; + Vector3[] FaceA = new Vector3[6]; // vertex A for Facei + Vector3[] FaceB = new Vector3[6]; // vertex B for Facei + Vector3[] FaceC = new Vector3[6]; // vertex C for Facei + Vector3[] FaceD = new Vector3[6]; // vertex D for Facei + + tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + // vertexes[0].x = pos.X + vertexes[0].x; + //vertexes[0].y = pos.Y + vertexes[0].y; + //vertexes[0].z = pos.Z + vertexes[0].z; + + FaceA[0] = vertexes[0]; + FaceB[3] = vertexes[0]; + FaceA[4] = vertexes[0]; + + tScale = lscale; + scale = ((tScale * rot)); + vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[1].x = pos.X + vertexes[1].x; + // vertexes[1].y = pos.Y + vertexes[1].y; + //vertexes[1].z = pos.Z + vertexes[1].z; + + FaceB[0] = vertexes[1]; + FaceA[1] = vertexes[1]; + FaceC[4] = vertexes[1]; + + tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + + vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[2].x = pos.X + vertexes[2].x; + //vertexes[2].y = pos.Y + vertexes[2].y; + //vertexes[2].z = pos.Z + vertexes[2].z; + + FaceC[0] = vertexes[2]; + FaceD[3] = vertexes[2]; + FaceC[5] = vertexes[2]; + + tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[3].x = pos.X + vertexes[3].x; + // vertexes[3].y = pos.Y + vertexes[3].y; + // vertexes[3].z = pos.Z + vertexes[3].z; + + FaceD[0] = vertexes[3]; + FaceC[1] = vertexes[3]; + FaceA[5] = vertexes[3]; + + tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[4].x = pos.X + vertexes[4].x; + // vertexes[4].y = pos.Y + vertexes[4].y; + // vertexes[4].z = pos.Z + vertexes[4].z; + + FaceB[1] = vertexes[4]; + FaceA[2] = vertexes[4]; + FaceD[4] = vertexes[4]; + + tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[5].x = pos.X + vertexes[5].x; + // vertexes[5].y = pos.Y + vertexes[5].y; + // vertexes[5].z = pos.Z + vertexes[5].z; + + FaceD[1] = vertexes[5]; + FaceC[2] = vertexes[5]; + FaceB[5] = vertexes[5]; + + tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[6].x = pos.X + vertexes[6].x; + // vertexes[6].y = pos.Y + vertexes[6].y; + // vertexes[6].z = pos.Z + vertexes[6].z; + + FaceB[2] = vertexes[6]; + FaceA[3] = vertexes[6]; + FaceB[4] = vertexes[6]; + + tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - // vertexes[7].x = pos.X + vertexes[7].x; - // vertexes[7].y = pos.Y + vertexes[7].y; - // vertexes[7].z = pos.Z + vertexes[7].z; + // vertexes[7].x = pos.X + vertexes[7].x; + // vertexes[7].y = pos.Y + vertexes[7].y; + // vertexes[7].z = pos.Z + vertexes[7].z; - FaceD[2] = vertexes[7]; - FaceC[3] = vertexes[7]; - FaceD[5] = vertexes[7]; -#endregion + FaceD[2] = vertexes[7]; + FaceC[3] = vertexes[7]; + FaceD[5] = vertexes[7]; + #endregion - //int wy = 0; + //int wy = 0; - //bool breakYN = false; // If we run into an error drawing, break out of the - // loop so we don't lag to death on error handling - DrawStruct ds = new DrawStruct(); - ds.brush = new SolidBrush(mapdotspot); - //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY); + //bool breakYN = false; // If we run into an error drawing, break out of the + // loop so we don't lag to death on error handling + DrawStruct ds = new DrawStruct(); + ds.brush = new SolidBrush(mapdotspot); + //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY); - ds.trns = new face[FaceA.Length]; + ds.trns = new face[FaceA.Length]; - for (int i = 0; i < FaceA.Length; i++) - { - Point[] working = new Point[5]; - working[0] = project(FaceA[i], axPos); - working[1] = project(FaceB[i], axPos); - working[2] = project(FaceD[i], axPos); - working[3] = project(FaceC[i], axPos); - working[4] = project(FaceA[i], axPos); + for (int i = 0; i < FaceA.Length; i++) + { + Point[] working = new Point[5]; + working[0] = project(hm, FaceA[i], axPos); + working[1] = project(hm, FaceB[i], axPos); + working[2] = project(hm, FaceD[i], axPos); + working[3] = project(hm, FaceC[i], axPos); + working[4] = project(hm, FaceA[i], axPos); - face workingface = new face(); - workingface.pts = working; + face workingface = new face(); + workingface.pts = working; - ds.trns[i] = workingface; - } + ds.trns[i] = workingface; + } - z_sort.Add(part.LocalId, ds); - z_localIDs.Add(part.LocalId); - z_sortheights.Add(pos.Z); - - //for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) - //{ - //for (wy = mapdrawstartY; wy < mapdrawendY; wy++) - //{ - //m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); - //try - //{ - // Remember, flip the y! - // mapbmp.SetPixel(wx, (255 - wy), mapdotspot); - //} - //catch (ArgumentException) - //{ - // breakYN = true; - //} - - //if (breakYN) - // break; + z_sort.Add(part.LocalId, ds); + z_localIDs.Add(part.LocalId); + z_sortheights.Add(pos.Z); + + // for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) + // { + // for (wy = mapdrawstartY; wy < mapdrawendY; wy++) + // { + // m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); + // try + // { + // // Remember, flip the y! + // mapbmp.SetPixel(wx, (255 - wy), mapdotspot); + // } + // catch (ArgumentException) + // { + // breakYN = true; + // } + // } + // if (breakYN) + // break; + // } + // } //} + } // Object is within 256m Z of terrain + } // object is at least a meter wide + } // loop over group children + } // entitybase is sceneobject group + } // foreach loop over entities - //if (breakYN) - // break; - //} - } // Object is within 256m Z of terrain - } // object is at least a meter wide - } // loop over group children - } // entitybase is sceneobject group - } // foreach loop over entities + float[] sortedZHeights = z_sortheights.ToArray(); + uint[] sortedlocalIds = z_localIDs.ToArray(); - float[] sortedZHeights = z_sortheights.ToArray(); - uint[] sortedlocalIds = z_localIDs.ToArray(); + // Sort prim by Z position + Array.Sort(sortedZHeights, sortedlocalIds); - // Sort prim by Z position - Array.Sort(sortedZHeights, sortedlocalIds); - - Graphics g = Graphics.FromImage(mapbmp); - - for (int s = 0; s < sortedZHeights.Length; s++) - { - if (z_sort.ContainsKey(sortedlocalIds[s])) + using (Graphics g = Graphics.FromImage(mapbmp)) { - DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]]; - for (int r = 0; r < rectDrawStruct.trns.Length; r++) + for (int s = 0; s < sortedZHeights.Length; s++) { - g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts); + if (z_sort.ContainsKey(sortedlocalIds[s])) + { + DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]]; + for (int r = 0; r < rectDrawStruct.trns.Length; r++) + { + g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts); + } + //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect); + } } - //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect); } - } + } // lock entities objs - g.Dispose(); - } // lock entities objs + } + finally + { + foreach (DrawStruct ds in z_sort.Values) + ds.brush.Dispose(); + } m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Done in " + (Environment.TickCount - tc) + " ms"); + return mapbmp; } - private Point project(Vector3 point3d, Vector3 originpos) + private Point project(ITerrainChannel hm, Vector3 point3d, Vector3 originpos) { Point returnpt = new Point(); //originpos = point3d; //int d = (int)(256f / 1.5f); //Vector3 topos = new Vector3(0, 0, 0); - // float z = -point3d.z - topos.z; + // float z = -point3d.z - topos.z; returnpt.X = (int)point3d.X;//(int)((topos.x - point3d.x) / z * d); - returnpt.Y = (int)(((int)Constants.RegionSize - 1) - point3d.Y);//(int)(255 - (((topos.y - point3d.y) / z * d))); + returnpt.Y = (int)((hm.Width - 1) - point3d.Y);//(int)(255 - (((topos.y - point3d.y) / z * d))); return returnpt; } diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs index 992bff3..708286c 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs @@ -31,6 +31,7 @@ using System.Reflection; using log4net; using Nini.Config; using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; namespace OpenSim.Region.CoreModules.World.LegacyMap @@ -39,8 +40,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { private static readonly Color WATER_COLOR = Color.FromArgb(29, 71, 95); - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[SHADED MAPTILE RENDERER]"; private Scene m_scene; //private IConfigSource m_config; // not used currently @@ -53,19 +54,26 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { + m_log.DebugFormat("{0} Generating Maptile Step 1: Terrain", LogHeader); int tc = Environment.TickCount; - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); - double[,] hm = m_scene.Heightmap.GetDoubles(); + ITerrainChannel hm = m_scene.Heightmap; + + if (mapbmp.Width != hm.Width || mapbmp.Height != hm.Height) + { + m_log.ErrorFormat("{0} TerrainToBitmap. Passed bitmap wrong dimensions. passed=<{1},{2}>, size=<{3},{4}>", + LogHeader, mapbmp.Width, mapbmp.Height, hm.Width, hm.Height); + } + bool ShadowDebugContinue = true; bool terraincorruptedwarningsaid = false; float low = 255; float high = 0; - for (int x = 0; x < (int)Constants.RegionSize; x++) + for (int x = 0; x < hm.Width; x++) { - for (int y = 0; y < (int)Constants.RegionSize; y++) + for (int y = 0; y < hm.Height; y++) { float hmval = (float)hm[x, y]; if (hmval < low) @@ -77,12 +85,12 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap float waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; - for (int x = 0; x < (int)Constants.RegionSize; x++) + for (int x = 0; x < hm.Width; x++) { - for (int y = 0; y < (int)Constants.RegionSize; y++) + for (int y = 0; y < hm.Height; y++) { // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left - int yr = ((int)Constants.RegionSize - 1) - y; + int yr = ((int)hm.Height - 1) - y; float heightvalue = (float)hm[x, y]; @@ -109,12 +117,12 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap // . // // Shade the terrain for shadows - if (x < ((int)Constants.RegionSize - 1) && yr < ((int)Constants.RegionSize - 1)) + if (x < (hm.Width - 1) && yr < (hm.Height - 1)) { float hfvalue = (float)hm[x, y]; float hfvaluecompare = 0f; - if ((x + 1 < (int)Constants.RegionSize) && (y + 1 < (int)Constants.RegionSize)) + if ((x + 1 < hm.Width) && (y + 1 < hm.Height)) { hfvaluecompare = (float)hm[x + 1, y + 1]; // light from north-east => look at land height there } @@ -179,7 +187,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap if (ShadowDebugContinue) { - if ((x - 1 > 0) && (yr + 1 < (int)Constants.RegionSize)) + if ((x - 1 > 0) && (yr + 1 < hm.Height)) { color = mapbmp.GetPixel(x - 1, yr + 1); int r = color.R; @@ -199,7 +207,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { if (!terraincorruptedwarningsaid) { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + m_log.WarnFormat("[SHADED MAP TILE RENDERER]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); terraincorruptedwarningsaid = true; } color = Color.Black; @@ -229,16 +237,17 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { if (!terraincorruptedwarningsaid) { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + m_log.WarnFormat("[SHADED MAP TILE RENDERER]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); terraincorruptedwarningsaid = true; } Color black = Color.Black; - mapbmp.SetPixel(x, ((int)Constants.RegionSize - y) - 1, black); + mapbmp.SetPixel(x, (hm.Width - y) - 1, black); } } } } - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + + m_log.Debug("[SHADED MAP TILE RENDERER]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } } diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs index d13c2ef..9f23141 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs @@ -34,6 +34,8 @@ using Nini.Config; using OpenMetaverse; using OpenMetaverse.Imaging; using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; namespace OpenSim.Region.CoreModules.World.LegacyMap @@ -122,8 +124,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { #region Constants - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[TEXTURED MAPTILE RENDERER]"; // some hardcoded terrain UUIDs that work with SL 1.20 (the four default textures and "Blank"). // The color-values were choosen because they "look right" (at least to me) ;-) @@ -173,7 +175,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap private Bitmap fetchTexture(UUID id) { AssetBase asset = m_scene.AssetService.Get(id.ToString()); - m_log.DebugFormat("[TexturedMapTileRenderer]: Fetched texture {0}, found: {1}", id, asset != null); + m_log.DebugFormat("{0} Fetched texture {1}, found: {2}", LogHeader, id, asset != null); if (asset == null) return null; ManagedImage managedImage; @@ -188,18 +190,15 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } catch (DllNotFoundException) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", id); - + m_log.ErrorFormat("{0} OpenJpeg is not installed correctly on this system. Asset Data is empty for {1}", LogHeader, id); } catch (IndexOutOfRangeException) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); - + m_log.ErrorFormat("{0} OpenJpeg was unable to encode this. Asset Data is empty for {1}", LogHeader, id); } catch (Exception) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); - + m_log.ErrorFormat("{0} OpenJpeg was unable to encode this. Asset Data is empty for {1}", LogHeader, id); } return null; @@ -233,10 +232,14 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap if (textureID == UUID.Zero) return defaultColor; // not set if (m_mapping.ContainsKey(textureID)) return m_mapping[textureID]; // one of the predefined textures - Bitmap bmp = fetchTexture(textureID); - Color color = bmp == null ? defaultColor : computeAverageColor(bmp); - // store it for future reference - m_mapping[textureID] = color; + Color color; + + using (Bitmap bmp = fetchTexture(textureID)) + { + color = bmp == null ? defaultColor : computeAverageColor(bmp); + // store it for future reference + m_mapping[textureID] = color; + } return color; } @@ -267,8 +270,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap // the heigthfield might have some jumps in values. Rendered land is smooth, though, // as a slope is rendered at that place. So average 4 neighbour values to emulate that. - private float getHeight(double[,] hm, int x, int y) { - if (x < ((int)Constants.RegionSize - 1) && y < ((int)Constants.RegionSize - 1)) + private float getHeight(ITerrainChannel hm, int x, int y) { + if (x < (hm.Width - 1) && y < (hm.Height - 1)) return (float)(hm[x, y] * .444 + (hm[x + 1, y] + hm[x, y + 1]) * .222 + hm[x + 1, y +1] * .112); else return (float)hm[x, y]; @@ -278,7 +281,15 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { int tc = Environment.TickCount; - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); + m_log.DebugFormat("{0} Generating Maptile Step 1: Terrain", LogHeader); + + ITerrainChannel hm = m_scene.Heightmap; + + if (mapbmp.Width != hm.Width || mapbmp.Height != hm.Height) + { + m_log.ErrorFormat("{0} TerrainToBitmap. Passed bitmap wrong dimensions. passed=<{1},{2}>, size=<{3},{4}>", + LogHeader, mapbmp.Width, mapbmp.Height, hm.Width, hm.Height); + } // These textures should be in the AssetCache anyway, as every client conneting to this // region needs them. Except on start, when the map is recreated (before anyone connected), @@ -306,19 +317,17 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap float waterHeight = (float)settings.WaterHeight; - double[,] hm = m_scene.Heightmap.GetDoubles(); - - for (int x = 0; x < (int)Constants.RegionSize; x++) + for (int x = 0; x < hm.Width; x++) { - float columnRatio = x / ((float)Constants.RegionSize - 1); // 0 - 1, for interpolation - for (int y = 0; y < (int)Constants.RegionSize; y++) + float columnRatio = x / (hm.Width - 1); // 0 - 1, for interpolation + for (int y = 0; y < hm.Height; y++) { - float rowRatio = y / ((float)Constants.RegionSize - 1); // 0 - 1, for interpolation + float rowRatio = y / (hm.Height - 1); // 0 - 1, for interpolation // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left - int yr = ((int)Constants.RegionSize - 1) - y; + int yr = (hm.Height - 1) - y; - float heightvalue = getHeight(hm, x, y); + float heightvalue = getHeight(m_scene.Heightmap, x, y); if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) heightvalue = 0; @@ -368,9 +377,9 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } // Shade the terrain for shadows - if (x < ((int)Constants.RegionSize - 1) && y < ((int)Constants.RegionSize - 1)) + if (x < (hm.Width - 1) && y < (hm.Height - 1)) { - float hfvaluecompare = getHeight(hm, x + 1, y + 1); // light from north-east => look at land height there + float hfvaluecompare = getHeight(m_scene.Heightmap, x + 1, y + 1); // light from north-east => look at land height there if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) hfvaluecompare = 0f; @@ -412,7 +421,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } } } - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + + m_log.Debug("[TEXTURED MAP TILE RENDERER]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } } diff --git a/OpenSim/Region/CoreModules/World/LightShare/LightShareModule.cs b/OpenSim/Region/CoreModules/World/LightShare/LightShareModule.cs index 4e20196..0a4e83e 100644 --- a/OpenSim/Region/CoreModules/World/LightShare/LightShareModule.cs +++ b/OpenSim/Region/CoreModules/World/LightShare/LightShareModule.cs @@ -195,19 +195,20 @@ namespace OpenSim.Region.CoreModules.World.LightShare if (m_scene.RegionInfo.WindlightSettings.valid) { List param = compileWindlightSettings(wl); - client.SendGenericMessage("Windlight", param); + client.SendGenericMessage("Windlight", UUID.Random(), param); } else { List param = new List(); - client.SendGenericMessage("WindlightReset", param); + client.SendGenericMessage("WindlightReset", UUID.Random(), param); } } } private void EventManager_OnMakeRootAgent(ScenePresence presence) { - m_log.Debug("[WINDLIGHT]: Sending windlight scene to new client"); +// m_log.Debug("[WINDLIGHT]: Sending windlight scene to new client {0}", presence.Name); + SendProfileToClient(presence.ControllingClient); } diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs index 601e81e..46b0470 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/MoapModule.cs @@ -595,6 +595,9 @@ namespace OpenSim.Region.CoreModules.World.Media.Moap /// true if the url matches an entry on the whitelist, false otherwise protected bool CheckUrlAgainstWhitelist(string rawUrl, string[] whitelist) { + if (whitelist == null) + return false; + Uri url = new Uri(rawUrl); foreach (string origWlUrl in whitelist) diff --git a/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs b/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs index 03a96a4..ee57aed 100644 --- a/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs +++ b/OpenSim/Region/CoreModules/World/Media/Moap/Tests/MoapTests.cs @@ -39,7 +39,6 @@ using OpenSim.Region.CoreModules.World.Media.Moap; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.CoreModules.World.Media.Moap.Tests { diff --git a/OpenSim/Region/CoreModules/World/Objects/BuySell/BuySellModule.cs b/OpenSim/Region/CoreModules/World/Objects/BuySell/BuySellModule.cs index 1e4f0a4..2abc910 100644 --- a/OpenSim/Region/CoreModules/World/Objects/BuySell/BuySellModule.cs +++ b/OpenSim/Region/CoreModules/World/Objects/BuySell/BuySellModule.cs @@ -38,6 +38,7 @@ using OpenSim.Region.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.World.Objects.BuySell { @@ -140,6 +141,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.BuySell part.ObjectSaleType = 0; part.SalePrice = 10; + part.ClickAction = Convert.ToByte(0); group.HasGroupChanged = true; part.SendPropertiesToClient(remoteClient); @@ -150,14 +152,9 @@ namespace OpenSim.Region.CoreModules.World.Objects.BuySell break; case 2: // Sell a copy - Vector3 inventoryStoredPosition = new Vector3 - (((group.AbsolutePosition.X > (int)Constants.RegionSize) - ? 250 - : group.AbsolutePosition.X) - , - (group.AbsolutePosition.X > (int)Constants.RegionSize) - ? 250 - : group.AbsolutePosition.X, + Vector3 inventoryStoredPosition = new Vector3( + Math.Min(group.AbsolutePosition.X, m_scene.RegionInfo.RegionSizeX - 6), + Math.Min(group.AbsolutePosition.Y, m_scene.RegionInfo.RegionSizeY - 6), group.AbsolutePosition.Z); Vector3 originalPosition = group.AbsolutePosition; @@ -197,13 +194,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.BuySell item.InvType = (int)InventoryType.Object; item.Folder = categoryID; - 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; + PermissionsUtil.ApplyFoldedPermissions(perms, ref perms); item.BasePermissions = perms & part.NextOwnerMask; item.CurrentPermissions = perms & part.NextOwnerMask; diff --git a/OpenSim/Region/CoreModules/World/Objects/Commands/ObjectCommandsModule.cs b/OpenSim/Region/CoreModules/World/Objects/Commands/ObjectCommandsModule.cs index 9fc2daf..e77f0aa 100644 --- a/OpenSim/Region/CoreModules/World/Objects/Commands/ObjectCommandsModule.cs +++ b/OpenSim/Region/CoreModules/World/Objects/Commands/ObjectCommandsModule.cs @@ -416,7 +416,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands if (!ConsoleUtil.TryParseConsoleMinVector(rawConsoleStartVector, out startVector)) { - m_console.OutputFormat("Error: Start vector {0} does not have a valid format", rawConsoleStartVector); + m_console.OutputFormat("Error: Start vector '{0}' does not have a valid format", rawConsoleStartVector); return; } @@ -425,7 +425,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands if (!ConsoleUtil.TryParseConsoleMaxVector(rawConsoleEndVector, out endVector)) { - m_console.OutputFormat("Error: End vector {0} does not have a valid format", rawConsoleEndVector); + m_console.OutputFormat("Error: End vector '{0}' does not have a valid format", rawConsoleEndVector); return; } @@ -546,7 +546,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands { ConsoleDisplayList cdl = new ConsoleDisplayList(); cdl.AddRow("Name", so.Name); - cdl.AddRow("Descrition", so.Description); + cdl.AddRow("Description", so.Description); cdl.AddRow("Local ID", so.LocalId); cdl.AddRow("UUID", so.UUID); cdl.AddRow("Location", string.Format("{0} @ {1}", so.AbsolutePosition, so.Scene.Name)); @@ -597,6 +597,7 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands cdl.AddRow("LightFalloff", s.LightFalloff); cdl.AddRow("LightIntensity", s.LightIntensity); cdl.AddRow("LightRadius", s.LightRadius); + cdl.AddRow("Location (relative)", sop.RelativePosition); cdl.AddRow("Media", string.Format("{0} entries", s.Media != null ? s.Media.Count.ToString() : "n/a")); cdl.AddRow("PathBegin", s.PathBegin); cdl.AddRow("PathEnd", s.PathEnd); @@ -619,6 +620,8 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands cdl.AddRow("ProjectionFocus", s.ProjectionFocus); cdl.AddRow("ProjectionFOV", s.ProjectionFOV); cdl.AddRow("ProjectionTextureUUID", s.ProjectionTextureUUID); + cdl.AddRow("Rotation (Relative)", sop.RotationOffset); + cdl.AddRow("Rotation (World)", sop.GetWorldRotation()); cdl.AddRow("Scale", s.Scale); cdl.AddRow( "SculptData", @@ -628,7 +631,22 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands cdl.AddRow("SculptType", s.SculptType); cdl.AddRow("State", s.State); - // TODO, unpack and display texture entries + // TODO, need to display more information about textures but in a compact format + // to stop output becoming huge. + for (int i = 0; i < sop.GetNumberOfSides(); i++) + { + Primitive.TextureEntryFace teFace = s.Textures.FaceTextures[i]; + + UUID textureID; + + if (teFace != null) + textureID = teFace.TextureID; + else + textureID = s.Textures.DefaultTexture.TextureID; + + cdl.AddRow(string.Format("Face {0} texture ID", i), textureID); + } + //cdl.AddRow("Textures", string.Format("{0} entries", s.Textures. } @@ -896,17 +914,17 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands if (!ConsoleUtil.TryParseConsoleMinVector(rawConsoleStartVector, out startVector)) { - m_console.OutputFormat("Error: Start vector {0} does not have a valid format", rawConsoleStartVector); + m_console.OutputFormat("Error: Start vector '{0}' does not have a valid format", rawConsoleStartVector); endVector = Vector3.Zero; return false; } - string rawConsoleEndVector = rawComponents.Skip(1).Take(1).Single(); + string rawConsoleEndVector = rawComponents.Skip(2).Take(1).Single(); if (!ConsoleUtil.TryParseConsoleMaxVector(rawConsoleEndVector, out endVector)) { - m_console.OutputFormat("Error: End vector {0} does not have a valid format", rawConsoleEndVector); + m_console.OutputFormat("Error: End vector '{0}' does not have a valid format", rawConsoleEndVector); return false; } diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs index ddaa227..780ec69 100644 --- a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using log4net; using Nini.Config; @@ -38,6 +39,7 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Mono.Addins; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.CoreModules.World.Permissions { @@ -156,37 +158,44 @@ namespace OpenSim.Region.CoreModules.World.Permissions public void Initialise(IConfigSource config) { - IConfig myConfig = config.Configs["Startup"]; + string permissionModules = Util.GetConfigVarFromSections(config, "permissionmodules", + new string[] { "Startup", "Permissions" }, "DefaultPermissionsModule"); - string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); - - List modules = new List(permissionModules.Split(',')); + List modules = new List(permissionModules.Split(',').Select(m => m.Trim())); if (!modules.Contains("DefaultPermissionsModule")) return; m_Enabled = true; - m_allowGridGods = myConfig.GetBoolean("allow_grid_gods", false); - m_bypassPermissions = !myConfig.GetBoolean("serverside_object_permissions", true); - m_propagatePermissions = myConfig.GetBoolean("propagate_permissions", true); - m_RegionOwnerIsGod = myConfig.GetBoolean("region_owner_is_god", true); - m_RegionManagerIsGod = myConfig.GetBoolean("region_manager_is_god", false); - m_ParcelOwnerIsGod = myConfig.GetBoolean("parcel_owner_is_god", true); - - m_SimpleBuildPermissions = myConfig.GetBoolean("simple_build_permissions", false); + m_allowGridGods = Util.GetConfigVarFromSections(config, "allow_grid_gods", + new string[] { "Startup", "Permissions" }, false); + m_bypassPermissions = !Util.GetConfigVarFromSections(config, "serverside_object_permissions", + new string[] { "Startup", "Permissions" }, true); + m_propagatePermissions = Util.GetConfigVarFromSections(config, "propagate_permissions", + new string[] { "Startup", "Permissions" }, true); + m_RegionOwnerIsGod = Util.GetConfigVarFromSections(config, "region_owner_is_god", + new string[] { "Startup", "Permissions" }, true); + m_RegionManagerIsGod = Util.GetConfigVarFromSections(config, "region_manager_is_god", + new string[] { "Startup", "Permissions" }, false); + m_ParcelOwnerIsGod = Util.GetConfigVarFromSections(config, "parcel_owner_is_god", + new string[] { "Startup", "Permissions" }, true); + + m_SimpleBuildPermissions = Util.GetConfigVarFromSections(config, "simple_build_permissions", + new string[] { "Startup", "Permissions" }, false); m_allowedScriptCreators - = ParseUserSetConfigSetting(myConfig, "allowed_script_creators", m_allowedScriptCreators); + = ParseUserSetConfigSetting(config, "allowed_script_creators", m_allowedScriptCreators); m_allowedScriptEditors - = ParseUserSetConfigSetting(myConfig, "allowed_script_editors", m_allowedScriptEditors); + = ParseUserSetConfigSetting(config, "allowed_script_editors", m_allowedScriptEditors); if (m_bypassPermissions) m_log.Info("[PERMISSIONS]: serverside_object_permissions = false in ini file so disabling all region service permission checks"); else m_log.Debug("[PERMISSIONS]: Enabling all region service permission checks"); - string grant = myConfig.GetString("GrantLSL", ""); + string grant = Util.GetConfigVarFromSections(config, "GrantLSL", + new string[] { "Startup", "Permissions" }, string.Empty); if (grant.Length > 0) { foreach (string uuidl in grant.Split(',')) @@ -196,7 +205,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions } } - grant = myConfig.GetString("GrantCS", ""); + grant = Util.GetConfigVarFromSections(config, "GrantCS", + new string[] { "Startup", "Permissions" }, string.Empty); if (grant.Length > 0) { foreach (string uuidl in grant.Split(',')) @@ -206,7 +216,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions } } - grant = myConfig.GetString("GrantVB", ""); + grant = Util.GetConfigVarFromSections(config, "GrantVB", + new string[] { "Startup", "Permissions" }, string.Empty); if (grant.Length > 0) { foreach (string uuidl in grant.Split(',')) @@ -216,7 +227,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions } } - grant = myConfig.GetString("GrantJS", ""); + grant = Util.GetConfigVarFromSections(config, "GrantJS", + new string[] { "Startup", "Permissions" }, string.Empty); if (grant.Length > 0) { foreach (string uuidl in grant.Split(',')) @@ -226,7 +238,8 @@ namespace OpenSim.Region.CoreModules.World.Permissions } } - grant = myConfig.GetString("GrantYP", ""); + grant = Util.GetConfigVarFromSections(config, "GrantYP", + new string[] { "Startup", "Permissions" }, string.Empty); if (grant.Length > 0) { foreach (string uuidl in grant.Split(',')) @@ -456,7 +469,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions return false; } - + /// /// Parse a user set configuration setting /// @@ -464,11 +477,12 @@ namespace OpenSim.Region.CoreModules.World.Permissions /// /// The default value for this attribute /// The parsed value - private static UserSet ParseUserSetConfigSetting(IConfig config, string settingName, UserSet defaultValue) + private static UserSet ParseUserSetConfigSetting(IConfigSource config, string settingName, UserSet defaultValue) { UserSet userSet = defaultValue; - - string rawSetting = config.GetString(settingName, defaultValue.ToString()); + + string rawSetting = Util.GetConfigVarFromSections(config, settingName, + new string[] {"Startup", "Permissions"}, defaultValue.ToString()); // Temporary measure to allow 'gods' to be specified in config for consistency's sake. In the long term // this should disappear. @@ -787,8 +801,10 @@ namespace OpenSim.Region.CoreModules.World.Permissions // Friends with benefits should be able to edit the objects too if (IsFriendWithPerms(currentUser, objectOwner)) + { // Return immediately, so that the administrator can share objects with friends return true; + } // Users should be able to edit what is over their land. ILandObject parcel = m_scene.LandChannel.GetLandObject(group.AbsolutePosition.X, group.AbsolutePosition.Y); @@ -887,7 +903,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions return permission; } - protected bool GenericParcelOwnerPermission(UUID user, ILandObject parcel, ulong groupPowers) + protected bool GenericParcelOwnerPermission(UUID user, ILandObject parcel, ulong groupPowers, bool allowEstateManager) { if (parcel.LandData.OwnerID == user) { @@ -902,7 +918,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions return true; } - if (IsEstateManager(user)) + if (allowEstateManager && IsEstateManager(user)) { return true; } @@ -929,7 +945,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); if (m_bypassPermissions) return m_bypassPermissionsValue; - return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandRelease); + return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandRelease, false); } private bool CanReclaimParcel(UUID user, ILandObject parcel, Scene scene) @@ -937,7 +953,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); if (m_bypassPermissions) return m_bypassPermissionsValue; - return GenericParcelOwnerPermission(user, parcel, 0); + return GenericParcelOwnerPermission(user, parcel, 0,true); } private bool CanDeedParcel(UUID user, ILandObject parcel, Scene scene) @@ -954,7 +970,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions if ((client.GetGroupPowers(parcel.LandData.GroupID) & (ulong)GroupPowers.LandDeed) == 0) return false; - return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandDeed); + return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandDeed, false); } private bool CanDeedObject(UUID user, UUID group, Scene scene) @@ -995,9 +1011,11 @@ namespace OpenSim.Region.CoreModules.World.Permissions return false; if (part.OwnerID == owner) - return ((part.OwnerMask & PERM_COPY) != 0); - - if (part.GroupID != UUID.Zero) + { + if ((part.OwnerMask & PERM_COPY) == 0) + return false; + } + else if (part.GroupID != UUID.Zero) { if ((part.OwnerID == part.GroupID) && ((owner != part.LastOwnerID) || ((part.GroupMask & PERM_TRANS) == 0))) return false; @@ -1039,7 +1057,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); if (m_bypassPermissions) return m_bypassPermissionsValue; - return GenericParcelOwnerPermission(user, parcel, (ulong)p); + return GenericParcelOwnerPermission(user, parcel, (ulong)p, false); } /// @@ -1438,27 +1456,33 @@ namespace OpenSim.Region.CoreModules.World.Permissions DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); if (m_bypassPermissions) return m_bypassPermissionsValue; - bool permission = false; - - ILandObject land = m_scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); - if (land == null) return false; +// m_log.DebugFormat("[PERMISSIONS MODULE]: Checking rez object at {0} in {1}", objectPosition, m_scene.Name); - if ((land.LandData.Flags & ((int)ParcelFlags.CreateObjects)) == - (int)ParcelFlags.CreateObjects) - permission = true; + ILandObject parcel = m_scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); + if (parcel == null) + return false; - if (IsAdministrator(owner)) + if ((parcel.LandData.Flags & (uint)ParcelFlags.CreateObjects) != 0) { - permission = true; + return true; } - - // Powers are zero, because GroupPowers.AllowRez is not a precondition for rezzing objects - if (GenericParcelPermission(owner, objectPosition, 0)) + else if ((owner == parcel.LandData.OwnerID) || IsAdministrator(owner)) { - permission = true; + return true; + } + else if (((parcel.LandData.Flags & (uint)ParcelFlags.CreateGroupObjects) != 0) + && (parcel.LandData.GroupID != UUID.Zero) && IsGroupMember(parcel.LandData.GroupID, owner, 0)) + { + return true; + } + else if (parcel.LandData.GroupID != UUID.Zero && IsGroupMember(parcel.LandData.GroupID, owner, (ulong)GroupPowers.AllowRez)) + { + return true; + } + else + { + return false; } - - return permission; } private bool CanRunConsoleCommand(UUID user, Scene requestFromScene) @@ -1483,7 +1507,7 @@ namespace OpenSim.Region.CoreModules.World.Permissions DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); if (m_bypassPermissions) return m_bypassPermissionsValue; - return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandSetSale); + return GenericParcelOwnerPermission(user, parcel, (ulong)GroupPowers.LandSetSale, false); } private bool CanTakeObject(UUID objectID, UUID stealer, Scene scene) @@ -1500,6 +1524,9 @@ namespace OpenSim.Region.CoreModules.World.Permissions if (m_bypassPermissions) return m_bypassPermissionsValue; bool permission = GenericObjectPermission(userID, objectID, false); + + SceneObjectGroup so = (SceneObjectGroup)m_scene.Entities[objectID]; + if (!permission) { if (!m_scene.Entities.ContainsKey(objectID)) @@ -1513,31 +1540,23 @@ namespace OpenSim.Region.CoreModules.World.Permissions return false; } - SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; // UUID taskOwner = null; // Added this because at this point in time it wouldn't be wise for // the administrator object permissions to take effect. // UUID objectOwner = task.OwnerID; - if ((task.RootPart.EveryoneMask & PERM_COPY) != 0) + if ((so.RootPart.EveryoneMask & PERM_COPY) != 0) permission = true; + } - if (task.OwnerID != userID) - { - if ((task.GetEffectivePermissions() & (PERM_COPY | PERM_TRANS)) != (PERM_COPY | PERM_TRANS)) - permission = false; - } - else - { - if ((task.GetEffectivePermissions() & PERM_COPY) != PERM_COPY) - permission = false; - } + if (so.OwnerID != userID) + { + if ((so.GetEffectivePermissions() & (PERM_COPY | PERM_TRANS)) != (PERM_COPY | PERM_TRANS)) + permission = false; } else { - SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; - - if ((task.GetEffectivePermissions() & (PERM_COPY | PERM_TRANS)) != (PERM_COPY | PERM_TRANS)) + if ((so.GetEffectivePermissions() & PERM_COPY) != PERM_COPY) permission = false; } @@ -1556,10 +1575,10 @@ namespace OpenSim.Region.CoreModules.World.Permissions float X = position.X; float Y = position.Y; - if (X > ((int)Constants.RegionSize - 1)) - X = ((int)Constants.RegionSize - 1); - if (Y > ((int)Constants.RegionSize - 1)) - Y = ((int)Constants.RegionSize - 1); + if (X > ((int)m_scene.RegionInfo.RegionSizeX - 1)) + X = ((int)m_scene.RegionInfo.RegionSizeX - 1); + if (Y > ((int)m_scene.RegionInfo.RegionSizeY - 1)) + Y = ((int)m_scene.RegionInfo.RegionSizeY - 1); if (X < 0) X = 0; if (Y < 0) diff --git a/OpenSim/Region/CoreModules/World/Region/RegionCommandsModule.cs b/OpenSim/Region/CoreModules/World/Region/RegionCommandsModule.cs index 7d35473..bb4dcce 100644 --- a/OpenSim/Region/CoreModules/World/Region/RegionCommandsModule.cs +++ b/OpenSim/Region/CoreModules/World/Region/RegionCommandsModule.cs @@ -81,7 +81,32 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands m_console.Commands.AddCommand( "Regions", false, "show scene", "show scene", - "Show live scene information for the currently selected region.", HandleShowScene); + "Show live information for the currently selected scene (fps, prims, etc.).", HandleShowScene); + + m_console.Commands.AddCommand( + "Regions", false, "show region", + "show region", + "Show control information for the currently selected region (host name, max physical prim size, etc).", + "A synonym for \"region get\"", + HandleShowRegion); + + m_console.Commands.AddCommand( + "Regions", false, "region get", + "region get", + "Show control information for the currently selected region (host name, max physical prim size, etc).", + "Some parameters can be set with the \"region set\" command.\n" + + "Others must be changed via a viewer (usually via the region/estate dialog box).", + HandleShowRegion); + + m_console.Commands.AddCommand( + "Regions", false, "region set", + "region set", + "Set control information for the currently selected region.", + "Currently, the following parameters can be set:\n" + + "agent-limit - Current root agent limit. This is persisted over restart.\n" + + "max-agent-limit - Maximum root agent limit. agent-limit cannot exceed this." + + " This is not persisted over restart - to set it every time you must add a MaxAgents entry to your regions file.", + HandleRegionSet); } public void RemoveRegion(Scene scene) @@ -94,6 +119,139 @@ namespace OpenSim.Region.CoreModules.World.Objects.Commands // m_log.DebugFormat("[REGION COMMANDS MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); } + private void HandleShowRegion(string module, string[] cmd) + { + if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) + return; + + RegionInfo ri = m_scene.RegionInfo; + RegionSettings rs = ri.RegionSettings; + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Region information for {0}\n", m_scene.Name); + + ConsoleDisplayList dispList = new ConsoleDisplayList(); + dispList.AddRow("Region ID", ri.RegionID); + dispList.AddRow("Region handle", ri.RegionHandle); + dispList.AddRow("Region location", string.Format("{0},{1}", ri.RegionLocX, ri.RegionLocY)); + dispList.AddRow("Region size", string.Format("{0}x{1}", ri.RegionSizeX, ri.RegionSizeY)); + //dispList.AddRow("Region type", ri.RegionType); + dispList.AddRow("Maturity", rs.Maturity); + dispList.AddRow("Region address", ri.ServerURI); + dispList.AddRow("From region file", ri.RegionFile); + dispList.AddRow("External endpoint", ri.ExternalEndPoint); + dispList.AddRow("Internal endpoint", ri.InternalEndPoint); + dispList.AddRow("Access level", ri.AccessLevel); + dispList.AddRow("Agent limit", rs.AgentLimit); + dispList.AddRow("Max agent limit", ri.AgentCapacity); + dispList.AddRow("Linkset capacity", ri.LinksetCapacity <= 0 ? "not set" : ri.LinksetCapacity.ToString()); + dispList.AddRow("Prim capacity", ri.ObjectCapacity); + dispList.AddRow("Prim bonus", rs.ObjectBonus); + dispList.AddRow("Max prims per user", ri.MaxPrimsPerUser < 0 ? "n/a" : ri.MaxPrimsPerUser.ToString()); + dispList.AddRow("Clamp prim size", ri.ClampPrimSize); + dispList.AddRow("Non physical prim min size", ri.NonphysPrimMin <= 0 ? "not set" : string.Format("{0} m", ri.NonphysPrimMin)); + dispList.AddRow("Non physical prim max size", ri.NonphysPrimMax <= 0 ? "not set" : string.Format("{0} m", ri.NonphysPrimMax)); + dispList.AddRow("Physical prim min size", ri.PhysPrimMin <= 0 ? "not set" : string.Format("{0} m", ri.PhysPrimMin)); + dispList.AddRow("Physical prim max size", ri.PhysPrimMax <= 0 ? "not set" : string.Format("{0} m", ri.PhysPrimMax)); + + dispList.AddRow("Allow Damage", rs.AllowDamage); + dispList.AddRow("Allow Land join/divide", rs.AllowLandJoinDivide); + dispList.AddRow("Allow land resell", rs.AllowLandResell); + dispList.AddRow("Block fly", rs.BlockFly); + dispList.AddRow("Block show in search", rs.BlockShowInSearch); + dispList.AddRow("Block terraform", rs.BlockTerraform); + dispList.AddRow("Covenant UUID", rs.Covenant); + dispList.AddRow("Convenant change Unix time", rs.CovenantChangedDateTime); + dispList.AddRow("Disable collisions", rs.DisableCollisions); + dispList.AddRow("Disable physics", rs.DisablePhysics); + dispList.AddRow("Disable scripts", rs.DisableScripts); + dispList.AddRow("Restrict pushing", rs.RestrictPushing); + dispList.AddRow("Fixed sun", rs.FixedSun); + dispList.AddRow("Sun position", rs.SunPosition); + dispList.AddRow("Sun vector", rs.SunVector); + dispList.AddRow("Use estate sun", rs.UseEstateSun); + dispList.AddRow("Telehub UUID", rs.TelehubObject); + dispList.AddRow("Terrain lower limit", string.Format("{0} m", rs.TerrainLowerLimit)); + dispList.AddRow("Terrain raise limit", string.Format("{0} m", rs.TerrainRaiseLimit)); + dispList.AddRow("Water height", string.Format("{0} m", rs.WaterHeight)); + + dispList.AddRow("Maptile static file", ri.MaptileStaticFile); + dispList.AddRow("Maptile static UUID", ri.MaptileStaticUUID); + dispList.AddRow("Last map refresh", ri.lastMapRefresh); + dispList.AddRow("Last map UUID", ri.lastMapUUID); + + dispList.AddToStringBuilder(sb); + + MainConsole.Instance.Output(sb.ToString()); + } + + private void HandleRegionSet(string module, string[] args) + { + if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) + return; + + if (args.Length != 4) + { + MainConsole.Instance.OutputFormat("Usage: region set "); + return; + } + + string param = args[2]; + string rawValue = args[3]; + + if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) + return; + + RegionInfo ri = m_scene.RegionInfo; + RegionSettings rs = ri.RegionSettings; + + if (param == "agent-limit") + { + int newValue; + + if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, rawValue, out newValue)) + return; + + if (newValue > ri.AgentCapacity) + { + MainConsole.Instance.OutputFormat( + "Cannot set {0} to {1} in {2} as max-agent-limit is {3}", "agent-limit", + newValue, m_scene.Name, ri.AgentCapacity); + } + else + { + rs.AgentLimit = newValue; + + MainConsole.Instance.OutputFormat( + "{0} set to {1} in {2}", "agent-limit", newValue, m_scene.Name); + } + + rs.Save(); + } + else if (param == "max-agent-limit") + { + int newValue; + + if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, rawValue, out newValue)) + return; + + ri.AgentCapacity = newValue; + + MainConsole.Instance.OutputFormat( + "{0} set to {1} in {2}", "max-agent-limit", newValue, m_scene.Name); + + if (ri.AgentCapacity < rs.AgentLimit) + { + rs.AgentLimit = ri.AgentCapacity; + + MainConsole.Instance.OutputFormat( + "Reducing {0} to {1} in {2}", "agent-limit", rs.AgentLimit, m_scene.Name); + } + + rs.Save(); + } + } + private void HandleShowScene(string module, string[] cmd) { if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_scene)) diff --git a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs index 249a40d..75a8295 100644 --- a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs +++ b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs @@ -46,8 +46,8 @@ namespace OpenSim.Region.CoreModules.World.Region [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RestartModule")] public class RestartModule : INonSharedRegionModule, IRestartModule { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected Scene m_Scene; protected Timer m_CountdownTimer = null; @@ -203,18 +203,30 @@ namespace OpenSim.Region.CoreModules.World.Region public void SetTimer(int intervalSeconds) { - m_CountdownTimer = new Timer(); - m_CountdownTimer.AutoReset = false; - m_CountdownTimer.Interval = intervalSeconds * 1000; - m_CountdownTimer.Elapsed += OnTimer; - m_CountdownTimer.Start(); + if (intervalSeconds > 0) + { + m_CountdownTimer = new Timer(); + m_CountdownTimer.AutoReset = false; + m_CountdownTimer.Interval = intervalSeconds * 1000; + m_CountdownTimer.Elapsed += OnTimer; + m_CountdownTimer.Start(); + } + else if (m_CountdownTimer != null) + { + m_CountdownTimer.Stop(); + m_CountdownTimer = null; + } + else + { + m_log.WarnFormat( + "[RESTART MODULE]: Tried to set restart timer to {0} in {1}, which is not a valid interval", + intervalSeconds, m_Scene.Name); + } } private void OnTimer(object source, ElapsedEventArgs e) { - int nextInterval = DoOneNotice(); - - SetTimer(nextInterval); + SetTimer(DoOneNotice()); } public void AbortRestart(string message) diff --git a/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs index 328fbf0..65f464a 100644 --- a/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs +++ b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs @@ -54,13 +54,14 @@ namespace OpenSim.Region.CoreModules.World.Serialiser { string xmlstream = GetObjectXml(scene); - MemoryStream stream = ReformatXmlString(xmlstream); - - stream.Seek(0, SeekOrigin.Begin); - CreateXmlFile(stream, fileName); + using (MemoryStream stream = ReformatXmlString(xmlstream)) + { + stream.Seek(0, SeekOrigin.Begin); + CreateXmlFile(stream, fileName); - stream.Seek(0, SeekOrigin.Begin); - CreateCompressedXmlFile(stream, fileName); + stream.Seek(0, SeekOrigin.Begin); + CreateCompressedXmlFile(stream, fileName); + } } private static MemoryStream ReformatXmlString(string xmlstream) @@ -112,13 +113,16 @@ namespace OpenSim.Region.CoreModules.World.Serialiser { #region GZip Compressed Version - FileStream objectsFileCompressed = new FileStream(fileName + ".gzs", FileMode.Create); - MemoryStream gzipMSStream = new MemoryStream(); - GZipStream gzipStream = new GZipStream(gzipMSStream, CompressionMode.Compress); - xmlStream.WriteTo(gzipStream); - gzipMSStream.WriteTo(objectsFileCompressed); - objectsFileCompressed.Flush(); - objectsFileCompressed.Close(); + using (FileStream objectsFileCompressed = new FileStream(fileName + ".gzs", FileMode.Create)) + using (MemoryStream gzipMSStream = new MemoryStream()) + { + using (GZipStream gzipStream = new GZipStream(gzipMSStream, CompressionMode.Compress, true)) + { + xmlStream.WriteTo(gzipStream); + } + + gzipMSStream.WriteTo(objectsFileCompressed); + } #endregion } diff --git a/OpenSim/Region/CoreModules/World/Serialiser/Tests/SerialiserTests.cs b/OpenSim/Region/CoreModules/World/Serialiser/Tests/SerialiserTests.cs index bcb8e2f..a5bb1a7 100644 --- a/OpenSim/Region/CoreModules/World/Serialiser/Tests/SerialiserTests.cs +++ b/OpenSim/Region/CoreModules/World/Serialiser/Tests/SerialiserTests.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.IO; +using System.Text; using System.Xml; using log4net.Config; using NUnit.Framework; @@ -35,120 +36,358 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Tests.Common; +using OpenMetaverse.StructuredData; namespace OpenSim.Region.CoreModules.World.Serialiser.Tests { [TestFixture] public class SerialiserTests : OpenSimTestCase { - private string xml = @" - - - - false - a6dacf01-4636-4bb9-8a97-30609438af9d - e6a5a05e-e8cc-4816-8701-04165e335790 - 1 - - 0 - e6a5a05e-e8cc-4816-8701-04165e335790 - 2698615125 - PrimMyRide - 0 - false - 1099511628032000 - 0 - 147.2392.69822.78084 - 000 - -4.371139E-08-1-4.371139E-080 - 000 - 000 - 000 - 000 - - - - - - 0 - 0 - - 1 - AAAAAAAAERGZmQAAAAAABQCVlZUAAAAAQEAAAABAQAAAAAAAAAAAAAAAAAAAAA== - AA== - 0 - 16 - 0 - 0 - 0 - 100 - 100 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 9 - 0 - 0 - 0 - 10100.5 - 0 - Square - Same - 00000000-0000-0000-0000-000000000000 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 1 - 0 - 0 - 0 - 1 - false - false - false - - 10100.5 - 0 - 0001 - 000 - 000 - 0001 - 0 - 1211330445 - 0 - 0 - 0 - 0 - 00000000-0000-0000-0000-000000000000 - a6dacf01-4636-4bb9-8a97-30609438af9d - a6dacf01-4636-4bb9-8a97-30609438af9d - 2147483647 - 2147483647 - 0 - 0 - 2147483647 - None - 00000000-0000-0000-0000-000000000000 - 0 - - - - "; - - private string badFloatsXml = @" + private const string ObjectRootPartStubXml = +@" + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + e6a5a05e-e8cc-4816-8701-04165e335790 + 1 + + 0 + e6a5a05e-e8cc-4816-8701-04165e335790 + 2698615125 + PrimMyRide + 0 + false + 1099511628032000 + 0 + 147.2392.69822.78084 + 000 + -4.371139E-08-1-4.371139E-080 + 000 + 000 + 000 + 000 + + + + + + 0 + 0 + + 1 + AAAAAAAAERGZmQAAAAAABQCVlZUAAAAAQEAAAABAQAAAAAAAAAAAAAAAAAAAAA== + AA== + 0 + 16 + 0 + 0 + 0 + 100 + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 10100.5 + 0 + Square + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 10100.5 + 0 + 0001 + 000 + 000 + 0001 + 0 + 1211330445 + 0 + 0 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + a6dacf01-4636-4bb9-8a97-30609438af9d + a6dacf01-4636-4bb9-8a97-30609438af9d + 2147483647 + 2147483647 + 0 + 0 + 2147483647 + None + 00000000-0000-0000-0000-000000000000 + 0 + + + + MyNamespace + + MyStore + + the answer + 42 + + + + + + + "; + + private const string ObjectWithNoOtherPartsXml = ObjectRootPartStubXml + +@" + +"; + + private const string ObjectWithOtherPartsXml = ObjectRootPartStubXml + +@" + + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + 9958feb1-02a6-49e4-a4ce-eba6f578ee13 + 3 + 9958feb1-02a6-49e4-a4ce-eba6f578ee13 + 1154704500 + Alien Head 1 + 3 + false + false + 21990232560640000 + 0 + 125.5655127.34622.48036 + -0.21719360.10839840.0009994507 + -0.51221060.4851225-0.49574540.5064908 + 000 + 000 + 000 + (No Description) + 000255 + + + + 253 + 0 + + 5 + Vw3dpvgTRUOiIUOGsnpWlAB/f38AAAAAgL8AAACAPwAAAAAAAAAF4ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AA== + 0 + 32 + 0 + 0 + 0 + 100 + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 9 + 0 + HalfCircle + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 0.11481950.01438910.02768878 + 0001 + 000 + 000 + 0001 + 1154704499 + 1256611042 + 0 + 10 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 647168 + 647168 + 0 + 0 + 581632 + None + 00000000-0000-0000-0000-000000000000 + 0 + 000 + + + -2 + -2 + -2 + -2 + -2 + + + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + 674b6b86-f5aa-439a-8e00-0d75bc08c80a + 3 + 674b6b86-f5aa-439a-8e00-0d75bc08c80a + 1154704501 + Alien Head 2 + 3 + false + false + 21990232560640000 + 0 + 125.5655127.34622.48036 + -0.24909970.085201260.0009002686 + -0.47653680.5194498-0.53013720.4712104 + 000 + 000 + 000 + (No Description) + 000255 + + + + 252 + 0 + + 0 + Vw3dpvgTRUOiIUOGsnpWlAB/f38AAAAAgL8AAACAPwAAAAAAAAAF4ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AA== + 0 + 32 + 0 + 0 + 0 + 100 + 150 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 9 + 0 + Circle + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 0.035743850.059580320.04764182 + 0001 + 000 + 000 + 0001 + 1154704499 + 1256611042 + 0 + 10 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 647168 + 647168 + 0 + 0 + 581632 + None + 00000000-0000-0000-0000-000000000000 + 0 + 000 + + + -2 + -2 + -2 + -2 + -2 + + + +"; + + private const string ObjectWithBadFloatsXml = @" @@ -255,7 +494,7 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests "; - private string xml2 = @" + private const string ObjectWithNoPartsXml2 = @" b46ef588-411e-4a8b-a284-d7dcfe8e74ef @@ -331,6 +570,20 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests 0 2147483647 None + + + + MyNamespace + + MyStore + + last words + Rosebud + + + + + 00000000-0000-0000-0000-000000000000 @@ -348,17 +601,58 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests } [Test] - public void TestDeserializeXml() + public void TestDeserializeXmlObjectWithNoOtherParts() { TestHelpers.InMethod(); - //log4net.Config.XmlConfigurator.Configure(); + TestHelpers.EnableLogging(); - SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(xml); + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithNoOtherPartsXml); SceneObjectPart rootPart = so.RootPart; Assert.That(rootPart.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); Assert.That(rootPart.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); Assert.That(rootPart.Name, Is.EqualTo("PrimMyRide")); + OSDMap store = rootPart.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual(42, store["the answer"].AsInteger()); + + // TODO: Check other properties + } + + [Test] + public void TestDeserializeXmlObjectWithOtherParts() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithOtherPartsXml); + SceneObjectPart[] parts = so.Parts; + Assert.AreEqual(3, so.Parts.Length); + + { + SceneObjectPart part = parts[0]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("PrimMyRide")); + OSDMap store = part.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual(42, store["the answer"].AsInteger()); + } + + { + SceneObjectPart part = parts[1]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("9958feb1-02a6-49e4-a4ce-eba6f578ee13"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("Alien Head 1")); + } + + { + SceneObjectPart part = parts[2]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("674b6b86-f5aa-439a-8e00-0d75bc08c80a"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("Alien Head 2")); + } // TODO: Check other properties } @@ -369,7 +663,7 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); - SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(badFloatsXml); + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithBadFloatsXml); SceneObjectPart rootPart = so.RootPart; Assert.That(rootPart.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); @@ -409,6 +703,15 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests rp.CreatorID = rpCreatorId; rp.Shape = shape; + string daNamespace = "MyNamespace"; + string daStoreName = "MyStore"; + string daKey = "foo"; + string daValue = "bar"; + OSDMap myStore = new OSDMap(); + myStore.Add(daKey, daValue); + rp.DynAttrs = new DAMap(); + rp.DynAttrs.SetStore(daNamespace, daStoreName, myStore); + SceneObjectGroup so = new SceneObjectGroup(rp); // Need to add the object to the scene so that the request to get script state succeeds @@ -424,6 +727,7 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests UUID uuid = UUID.Zero; string name = null; UUID creatorId = UUID.Zero; + DAMap daMap = null; while (xtr.Read() && xtr.Name != "SceneObjectPart") { @@ -449,6 +753,10 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests creatorId = UUID.Parse(xtr.ReadElementString("UUID")); xtr.ReadEndElement(); break; + case "DynAttrs": + daMap = new DAMap(); + daMap.ReadXml(xtr); + break; } } @@ -462,6 +770,8 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests Assert.That(uuid, Is.EqualTo(rpUuid)); Assert.That(name, Is.EqualTo(rpName)); Assert.That(creatorId, Is.EqualTo(rpCreatorId)); + Assert.NotNull(daMap); + Assert.AreEqual(daValue, daMap.GetStore(daNamespace, daStoreName)[daKey].AsString()); } [Test] @@ -470,12 +780,14 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests TestHelpers.InMethod(); //log4net.Config.XmlConfigurator.Configure(); - SceneObjectGroup so = m_serialiserModule.DeserializeGroupFromXml2(xml2); + SceneObjectGroup so = m_serialiserModule.DeserializeGroupFromXml2(ObjectWithNoPartsXml2); SceneObjectPart rootPart = so.RootPart; Assert.That(rootPart.UUID, Is.EqualTo(new UUID("9be68fdd-f740-4a0f-9675-dfbbb536b946"))); Assert.That(rootPart.CreatorID, Is.EqualTo(new UUID("b46ef588-411e-4a8b-a284-d7dcfe8e74ef"))); Assert.That(rootPart.Name, Is.EqualTo("PrimFun")); + OSDMap store = rootPart.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual("Rosebud", store["last words"].AsString()); // TODO: Check other properties } @@ -500,6 +812,15 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests rp.CreatorID = rpCreatorId; rp.Shape = shape; + string daNamespace = "MyNamespace"; + string daStoreName = "MyStore"; + string daKey = "foo"; + string daValue = "bar"; + OSDMap myStore = new OSDMap(); + myStore.Add(daKey, daValue); + rp.DynAttrs = new DAMap(); + rp.DynAttrs.SetStore(daNamespace, daStoreName, myStore); + SceneObjectGroup so = new SceneObjectGroup(rp); // Need to add the object to the scene so that the request to get script state succeeds @@ -516,6 +837,7 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests UUID uuid = UUID.Zero; string name = null; UUID creatorId = UUID.Zero; + DAMap daMap = null; while (xtr.Read() && xtr.Name != "SceneObjectPart") { @@ -537,6 +859,10 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests creatorId = UUID.Parse(xtr.ReadElementString("Guid")); xtr.ReadEndElement(); break; + case "DynAttrs": + daMap = new DAMap(); + daMap.ReadXml(xtr); + break; } } @@ -549,6 +875,8 @@ namespace OpenSim.Region.CoreModules.World.Serialiser.Tests Assert.That(uuid, Is.EqualTo(rpUuid)); Assert.That(name, Is.EqualTo(rpName)); Assert.That(creatorId, Is.EqualTo(rpCreatorId)); + Assert.NotNull(daMap); + Assert.AreEqual(daValue, daMap.GetStore(daNamespace, daStoreName)[daKey].AsString()); } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs b/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs index 883045a..d093224 100644 --- a/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs +++ b/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs @@ -369,6 +369,15 @@ namespace OpenSim.Region.CoreModules.World.Sound }); } + public void SetSoundQueueing(UUID objectID, bool shouldQueue) + { + SceneObjectPart part; + if (!m_scene.TryGetSceneObjectPart(objectID, out part)) + return; + + part.SoundQueueing = shouldQueue; + } + #endregion } } diff --git a/OpenSim/Region/CoreModules/World/Sun/SunModule.cs b/OpenSim/Region/CoreModules/World/Sun/SunModule.cs index a321c09..d0318eb 100644 --- a/OpenSim/Region/CoreModules/World/Sun/SunModule.cs +++ b/OpenSim/Region/CoreModules/World/Sun/SunModule.cs @@ -68,9 +68,6 @@ namespace OpenSim.Region.CoreModules // updating those region settings in GenSunPos() private bool receivedEstateToolsSunUpdate = false; - // Configurable values - private string m_RegionMode = "SL"; - // Sun's position information is updated and sent to clients every m_UpdateInterval frames private int m_UpdateInterval = 0; @@ -90,7 +87,6 @@ namespace OpenSim.Region.CoreModules // private double m_longitude = 0; // private double m_latitude = 0; // Configurable defaults Defaults close to SL - private string d_mode = "SL"; private int d_frame_mod = 100; // Every 10 seconds (actually less) private double d_day_length = 4; // A VW day is 4 RW hours long private int d_year_length = 60; // There are 60 VW days in a VW year @@ -134,12 +130,15 @@ namespace OpenSim.Region.CoreModules private const int TICKS_PER_SECOND = 10000000; + private ulong m_CurrentTimeOffset = 0; + // Current time in elapsed seconds since Jan 1st 1970 private ulong CurrentTime { get { - return (ulong)(((DateTime.Now.Ticks) - TicksToEpoch + TicksUTCOffset) / TICKS_PER_SECOND); + ulong ctime = (ulong)(((DateTime.Now.Ticks) - TicksToEpoch + TicksUTCOffset) / TICKS_PER_SECOND); + return ctime + m_CurrentTimeOffset; } } @@ -252,21 +251,18 @@ namespace OpenSim.Region.CoreModules } // TODO: Decouple this, so we can get rid of Linden Hour info - // Update Region infor with new Sun Position and Hour + // Update Region with new Sun Vector // set estate settings for region access to sun position if (receivedEstateToolsSunUpdate) { m_scene.RegionInfo.RegionSettings.SunVector = Position; - m_scene.RegionInfo.RegionSettings.SunPosition = GetCurrentTimeAsLindenSunHour(); } } private float GetCurrentTimeAsLindenSunHour() { - if (m_SunFixed) - return m_SunFixedHour + 6; - - return GetCurrentSunHour() + 6.0f; + float curtime = m_SunFixed ? m_SunFixedHour : GetCurrentSunHour(); + return (curtime + 6.0f) % 24.0f; } #region INonSharedRegion Methods @@ -292,8 +288,6 @@ namespace OpenSim.Region.CoreModules try { // Mode: determines how the sun is handled - m_RegionMode = config.Configs["Sun"].GetString("mode", d_mode); - // Mode: determines how the sun is handled // m_latitude = config.Configs["Sun"].GetDouble("latitude", d_latitude); // Mode: determines how the sun is handled // m_longitude = config.Configs["Sun"].GetDouble("longitude", d_longitude); @@ -315,7 +309,6 @@ namespace OpenSim.Region.CoreModules catch (Exception e) { m_log.Debug("[SUN]: Configuration access failed, using defaults. Reason: " + e.Message); - m_RegionMode = d_mode; m_YearLengthDays = d_year_length; m_DayLengthHours = d_day_length; m_HorizonShift = d_day_night; @@ -326,40 +319,28 @@ namespace OpenSim.Region.CoreModules // m_longitude = d_longitude; } - switch (m_RegionMode) - { - case "T1": - default: - case "SL": - // Time taken to complete a cycle (day and season) - - SecondsPerSunCycle = (uint) (m_DayLengthHours * 60 * 60); - SecondsPerYear = (uint) (SecondsPerSunCycle*m_YearLengthDays); - - // Ration of real-to-virtual time + SecondsPerSunCycle = (uint) (m_DayLengthHours * 60 * 60); + SecondsPerYear = (uint) (SecondsPerSunCycle*m_YearLengthDays); - // VWTimeRatio = 24/m_day_length; + // Ration of real-to-virtual time - // Speed of rotation needed to complete a cycle in the - // designated period (day and season) + // VWTimeRatio = 24/m_day_length; - SunSpeed = m_SunCycle/SecondsPerSunCycle; - SeasonSpeed = m_SeasonalCycle/SecondsPerYear; + // Speed of rotation needed to complete a cycle in the + // designated period (day and season) - // Horizon translation + SunSpeed = m_SunCycle/SecondsPerSunCycle; + SeasonSpeed = m_SeasonalCycle/SecondsPerYear; - HorizonShift = m_HorizonShift; // Z axis translation - // HoursToRadians = (SunCycle/24)*VWTimeRatio; + // Horizon translation - m_log.Debug("[SUN]: Mode is " + m_RegionMode); - m_log.Debug("[SUN]: Initialization completed. Day is " + SecondsPerSunCycle + " seconds, and year is " + m_YearLengthDays + " days"); - m_log.Debug("[SUN]: Axis offset is " + m_HorizonShift); - m_log.Debug("[SUN]: Percentage of time for daylight " + m_DayTimeSunHourScale); - m_log.Debug("[SUN]: Positional data updated every " + m_UpdateInterval + " frames"); - - break; - } + HorizonShift = m_HorizonShift; // Z axis translation + // HoursToRadians = (SunCycle/24)*VWTimeRatio; + m_log.Debug("[SUN]: Initialization completed. Day is " + SecondsPerSunCycle + " seconds, and year is " + m_YearLengthDays + " days"); + m_log.Debug("[SUN]: Axis offset is " + m_HorizonShift); + m_log.Debug("[SUN]: Percentage of time for daylight " + m_DayTimeSunHourScale); + m_log.Debug("[SUN]: Positional data updated every " + m_UpdateInterval + " frames"); } public Type ReplaceableInterface @@ -386,7 +367,8 @@ namespace OpenSim.Region.CoreModules string sunCommand = string.Format("sun {0}", kvp.Key); m_scene.AddCommand("Regions", this, sunCommand, string.Format("{0} []", sunCommand), kvp.Value, "", HandleSunConsoleCommand); } - + m_scene.AddCommand("Regions", this, "sun help", "sun help", "list parameters that can be changed", "", HandleSunConsoleCommand); + m_scene.AddCommand("Regions", this, "sun list", "sun list", "list parameters that can be changed", "", HandleSunConsoleCommand); ready = true; } @@ -395,7 +377,7 @@ namespace OpenSim.Region.CoreModules ready = false; // Remove our hooks - m_scene.EventManager.OnFrame -= SunUpdate; + m_scene.EventManager.OnFrame -= SunUpdate; m_scene.EventManager.OnAvatarEnteringNewParcel -= AvatarEnteringParcel; m_scene.EventManager.OnEstateToolsSunUpdate -= EstateToolsSunUpdate; m_scene.EventManager.OnGetCurrentTimeAsLindenSunHour -= GetCurrentTimeAsLindenSunHour; @@ -420,23 +402,22 @@ namespace OpenSim.Region.CoreModules public void SunToClient(IClientAPI client) { - if (m_RegionMode != "T1") + if (ready) { - if (ready) + if (m_SunFixed) { - if (m_SunFixed) - { - // m_log.DebugFormat("[SUN]: SunHour {0}, Position {1}, PosTime {2}, OrbitalPosition : {3} ", m_SunFixedHour, Position.ToString(), PosTime.ToString(), OrbitalPosition.ToString()); - client.SendSunPos(Position, Velocity, PosTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); - } - else - { - // m_log.DebugFormat("[SUN]: SunHour {0}, Position {1}, PosTime {2}, OrbitalPosition : {3} ", m_SunFixedHour, Position.ToString(), PosTime.ToString(), OrbitalPosition.ToString()); - client.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); - } + // m_log.DebugFormat("[SUN]: Fixed SunHour {0}, Position {1}, PosTime {2}, OrbitalPosition : {3} ", + // m_SunFixedHour, Position.ToString(), PosTime.ToString(), OrbitalPosition.ToString()); + client.SendSunPos(Position, Velocity, PosTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); + } + else + { + // m_log.DebugFormat("[SUN]: SunHour {0}, Position {1}, PosTime {2}, OrbitalPosition : {3} ", + // m_SunFixedHour, Position.ToString(), PosTime.ToString(), OrbitalPosition.ToString()); + client.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); } } - } + } public void SunUpdate() { @@ -459,26 +440,33 @@ namespace OpenSim.Region.CoreModules SunToClient(avatar.ControllingClient); } - /// - /// - /// - /// - /// Is the sun's position fixed? - /// Use the Region or Estate Sun hour? - /// What hour of the day is the Sun Fixed at? - public void EstateToolsSunUpdate(ulong regionHandle, bool FixedSun, bool useEstateTime, float FixedSunHour) + public void EstateToolsSunUpdate(ulong regionHandle) { if (m_scene.RegionInfo.RegionHandle == regionHandle) { - // Must limit the Sun Hour to 0 ... 24 - while (FixedSunHour > 24.0f) - FixedSunHour -= 24; + float sunFixedHour; + bool fixedSun; - while (FixedSunHour < 0) - FixedSunHour += 24; + if (m_scene.RegionInfo.RegionSettings.UseEstateSun) + { + sunFixedHour = (float)m_scene.RegionInfo.EstateSettings.SunPosition; + fixedSun = m_scene.RegionInfo.EstateSettings.FixedSun; + } + else + { + sunFixedHour = (float)m_scene.RegionInfo.RegionSettings.SunPosition - 6.0f; + fixedSun = m_scene.RegionInfo.RegionSettings.FixedSun; + } + + // Must limit the Sun Hour to 0 ... 24 + while (sunFixedHour > 24.0f) + sunFixedHour -= 24; - m_SunFixedHour = FixedSunHour; - m_SunFixed = FixedSun; + while (sunFixedHour < 0) + sunFixedHour += 24; + + m_SunFixedHour = sunFixedHour; + m_SunFixed = fixedSun; // m_log.DebugFormat("[SUN]: Sun Settings Update: Fixed Sun? : {0}", m_SunFixed.ToString()); // m_log.DebugFormat("[SUN]: Sun Settings Update: Sun Hour : {0}", m_SunFixedHour.ToString()); @@ -501,7 +489,7 @@ namespace OpenSim.Region.CoreModules { m_scene.ForEachRootClient(delegate(IClientAPI client) { - SunToClient(client); + SunToClient(client); }); } @@ -526,6 +514,9 @@ namespace OpenSim.Region.CoreModules case "update_interval": return m_UpdateInterval; + case "current_time": + return GetCurrentTimeAsLindenSunHour(); + default: throw new Exception("Unknown sun parameter."); } @@ -533,7 +524,51 @@ namespace OpenSim.Region.CoreModules public void SetSunParameter(string param, double value) { - HandleSunConsoleCommand("sun", new string[] {param, value.ToString() }); + switch (param) + { + case "year_length": + m_YearLengthDays = (int)value; + SecondsPerYear = (uint) (SecondsPerSunCycle*m_YearLengthDays); + SeasonSpeed = m_SeasonalCycle/SecondsPerYear; + break; + + case "day_length": + m_DayLengthHours = value; + SecondsPerSunCycle = (uint) (m_DayLengthHours * 60 * 60); + SecondsPerYear = (uint) (SecondsPerSunCycle*m_YearLengthDays); + SunSpeed = m_SunCycle/SecondsPerSunCycle; + SeasonSpeed = m_SeasonalCycle/SecondsPerYear; + break; + + case "day_night_offset": + m_HorizonShift = value; + HorizonShift = m_HorizonShift; + break; + + case "day_time_sun_hour_scale": + m_DayTimeSunHourScale = value; + break; + + case "update_interval": + m_UpdateInterval = (int)value; + break; + + case "current_time": + value = (value + 18.0) % 24.0; + // set the current offset so that the effective sun time is the parameter + m_CurrentTimeOffset = 0; // clear this first so we use raw time + m_CurrentTimeOffset = (ulong)(SecondsPerSunCycle * value/ 24.0) - (CurrentTime % SecondsPerSunCycle); + break; + + default: + throw new Exception("Unknown sun parameter."); + } + + // Generate shared values + GenSunPos(); + + // When sun settings are updated, we should update all clients with new settings. + SunUpdateToAllClients(); } public float GetCurrentSunHour() @@ -566,7 +601,7 @@ namespace OpenSim.Region.CoreModules foreach (string output in ParseCmdParams(cmdparams)) { - m_log.Info("[SUN] " + output); + MainConsole.Instance.Output(output); } } @@ -575,10 +610,11 @@ namespace OpenSim.Region.CoreModules Dictionary Params = new Dictionary(); Params.Add("year_length", "number of days to a year"); - Params.Add("day_length", "number of seconds to a day"); + Params.Add("day_length", "number of hours to a day"); Params.Add("day_night_offset", "induces a horizon shift"); Params.Add("update_interval", "how often to update the sun's position in frames"); Params.Add("day_time_sun_hour_scale", "scales day light vs nite hours to change day/night ratio"); + Params.Add("current_time", "time in seconds of the simulator"); return Params; } @@ -612,46 +648,15 @@ namespace OpenSim.Region.CoreModules } else if (args.Length == 3) { - float value = 0.0f; - if (!float.TryParse(args[2], out value)) + double value = 0.0; + if (! double.TryParse(args[2], out value)) { Output.Add(String.Format("The parameter value {0} is not a valid number.", args[2])); + return Output; } - switch (args[1].ToLower()) - { - case "year_length": - m_YearLengthDays = (int)value; - break; - - case "day_length": - m_DayLengthHours = value; - break; - - case "day_night_offset": - m_HorizonShift = value; - break; - - case "day_time_sun_hour_scale": - m_DayTimeSunHourScale = value; - break; - - case "update_interval": - m_UpdateInterval = (int)value; - break; - - default: - Output.Add(String.Format("Unknown parameter {0}.", args[1])); - return Output; - } - + SetSunParameter(args[1].ToLower(), value); Output.Add(String.Format("Parameter {0} set to {1}.", args[1], value.ToString())); - - // Generate shared values - GenSunPos(); - - // When sun settings are updated, we should update all clients with new settings. - SunUpdateToAllClients(); } return Output; diff --git a/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs b/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs index 7186dd7..89087b1 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs @@ -42,7 +42,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.Effects for (y = 0; y < map.Height; y++) { map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 3, 0.25) * 10; - double spherFac = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2, Constants.RegionSize / 2, 50) * 0.01; + double spherFac = TerrainUtil.SphericalFactor(x, y, map.Width / 2, map.Height / 2, 50) * 0.01; if (map[x, y] < spherFac) { map[x, y] = spherFac; diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs index d78ade5..d5c77ec 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs @@ -67,7 +67,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { using (Bitmap bitmap = new Bitmap(filename)) { - ITerrainChannel retval = new TerrainChannel(true); + ITerrainChannel retval = new TerrainChannel(w, h); for (int x = 0; x < retval.Width; x++) { diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs index 62d232e..be1fb24 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs @@ -27,6 +27,7 @@ using System; using System.IO; +using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -73,12 +74,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders public ITerrainChannel LoadFile(string filename) { FileInfo file = new FileInfo(filename); - FileStream s = file.Open(FileMode.Open, FileAccess.Read); - ITerrainChannel retval = LoadStream(s); - s.Close(); + ITerrainChannel channel; - return retval; + using (FileStream s = file.Open(FileMode.Open, FileAccess.Read)) + channel = LoadStream(s); + + return channel; } public ITerrainChannel LoadFile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight) @@ -86,153 +88,159 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders TerrainChannel retval = new TerrainChannel(sectionWidth, sectionHeight); FileInfo file = new FileInfo(filename); - FileStream s = file.Open(FileMode.Open, FileAccess.Read); - BinaryReader bs = new BinaryReader(s); - int currFileYOffset = fileHeight - 1; - - // if our region isn't on the first Y section of the areas to be landscaped, then - // advance to our section of the file - while (currFileYOffset > offsetY) + using (FileStream s = file.Open(FileMode.Open, FileAccess.Read)) + using (BinaryReader bs = new BinaryReader(s)) { - // read a whole strip of regions - int heightsToRead = sectionHeight * (fileWidth * sectionWidth); - bs.ReadBytes(heightsToRead * 13); // because there are 13 fun channels - currFileYOffset--; - } + int currFileYOffset = fileHeight - 1; - // got to the Y start offset within the file of our region - // so read the file bits associated with our region - int y; - // for each Y within our Y offset - for (y = sectionHeight - 1; y >= 0; y--) - { - int currFileXOffset = 0; - - // if our region isn't the first X section of the areas to be landscaped, then - // advance the stream to the X start pos of our section in the file - // i.e. eat X upto where we start - while (currFileXOffset < offsetX) + // if our region isn't on the first Y section of the areas to be landscaped, then + // advance to our section of the file + while (currFileYOffset > offsetY) { - bs.ReadBytes(sectionWidth * 13); - currFileXOffset++; + // read a whole strip of regions + int heightsToRead = sectionHeight * (fileWidth * sectionWidth); + bs.ReadBytes(heightsToRead * 13); // because there are 13 fun channels + currFileYOffset--; } - // got to our X offset, so write our regions X line - int x; - for (x = 0; x < sectionWidth; x++) - { - // Read a strip and continue - retval[x, y] = bs.ReadByte() * (bs.ReadByte() / 128.0); - bs.ReadBytes(11); - } - // record that we wrote it - currFileXOffset++; + // got to the Y start offset within the file of our region + // so read the file bits associated with our region + int y; - // if our region isn't the last X section of the areas to be landscaped, then - // advance the stream to the end of this Y column - while (currFileXOffset < fileWidth) + // for each Y within our Y offset + for (y = sectionHeight - 1; y >= 0; y--) { - // eat the next regions x line - bs.ReadBytes(sectionWidth * 13); //The 13 channels again + int currFileXOffset = 0; + + // if our region isn't the first X section of the areas to be landscaped, then + // advance the stream to the X start pos of our section in the file + // i.e. eat X upto where we start + while (currFileXOffset < offsetX) + { + bs.ReadBytes(sectionWidth * 13); + currFileXOffset++; + } + + // got to our X offset, so write our regions X line + int x; + for (x = 0; x < sectionWidth; x++) + { + // Read a strip and continue + retval[x, y] = bs.ReadByte() * (bs.ReadByte() / 128.0); + bs.ReadBytes(11); + } + // record that we wrote it currFileXOffset++; + + // if our region isn't the last X section of the areas to be landscaped, then + // advance the stream to the end of this Y column + while (currFileXOffset < fileWidth) + { + // eat the next regions x line + bs.ReadBytes(sectionWidth * 13); //The 13 channels again + currFileXOffset++; + } } } - bs.Close(); - s.Close(); - return retval; } public ITerrainChannel LoadStream(Stream s) { - TerrainChannel retval = new TerrainChannel(); + // The raw format doesn't contain any dimension information. + // Guess the square dimensions by using the length of the raw file. + double dimension = Math.Sqrt((double)(s.Length / 13)); + // Regions are always multiples of 256. + int trimmedDimension = (int)dimension - ((int)dimension % (int)Constants.RegionSize); + if (trimmedDimension < Constants.RegionSize) + trimmedDimension = (int)Constants.RegionSize; + + TerrainChannel retval = new TerrainChannel(trimmedDimension, trimmedDimension); - BinaryReader bs = new BinaryReader(s); - int y; - for (y = 0; y < retval.Height; y++) + using (BinaryReader bs = new BinaryReader(s)) { - int x; - for (x = 0; x < retval.Width; x++) + int y; + for (y = 0; y < retval.Height; y++) { - retval[x, (retval.Height - 1) - y] = bs.ReadByte() * (bs.ReadByte() / 128.0); - bs.ReadBytes(11); // Advance the stream to next bytes. + int x; + for (x = 0; x < retval.Width; x++) + { + retval[x, (retval.Height - 1) - y] = bs.ReadByte() * (bs.ReadByte() / 128.0); + bs.ReadBytes(11); // Advance the stream to next bytes. + } } } - bs.Close(); - return retval; } public void SaveFile(string filename, ITerrainChannel map) { FileInfo file = new FileInfo(filename); - FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write); - SaveStream(s, map); - s.Close(); + using (FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write)) + SaveStream(s, map); } public void SaveStream(Stream s, ITerrainChannel map) { - BinaryWriter binStream = new BinaryWriter(s); - - // Output the calculated raw - for (int y = 0; y < map.Height; y++) + using (BinaryWriter binStream = new BinaryWriter(s)) { - for (int x = 0; x < map.Width; x++) + // Output the calculated raw + for (int y = 0; y < map.Height; y++) { - double t = map[x, (map.Height - 1) - y]; - //if height is less than 0, set it to 0 as - //can't save -ve values in a LLRAW file - if (t < 0d) + for (int x = 0; x < map.Width; x++) { - t = 0d; + double t = map[x, (map.Height - 1) - y]; + //if height is less than 0, set it to 0 as + //can't save -ve values in a LLRAW file + if (t < 0d) + { + t = 0d; + } + + int index = 0; + + // The lookup table is pre-sorted, so we either find an exact match or + // the next closest (smaller) match with a binary search + index = Array.BinarySearch(LookupHeightTable, new HeightmapLookupValue(0, (float)t)); + if (index < 0) + index = ~index - 1; + + index = LookupHeightTable[index].Index; + + byte red = (byte) (index & 0xFF); + byte green = (byte) ((index >> 8) & 0xFF); + const byte blue = 20; + const byte alpha1 = 0; + const byte alpha2 = 0; + const byte alpha3 = 0; + const byte alpha4 = 0; + const byte alpha5 = 255; + const byte alpha6 = 255; + const byte alpha7 = 255; + const byte alpha8 = 255; + byte alpha9 = red; + byte alpha10 = green; + + binStream.Write(red); + binStream.Write(green); + binStream.Write(blue); + binStream.Write(alpha1); + binStream.Write(alpha2); + binStream.Write(alpha3); + binStream.Write(alpha4); + binStream.Write(alpha5); + binStream.Write(alpha6); + binStream.Write(alpha7); + binStream.Write(alpha8); + binStream.Write(alpha9); + binStream.Write(alpha10); } - - int index = 0; - - // The lookup table is pre-sorted, so we either find an exact match or - // the next closest (smaller) match with a binary search - index = Array.BinarySearch(LookupHeightTable, new HeightmapLookupValue(0, (float)t)); - if (index < 0) - index = ~index - 1; - - index = LookupHeightTable[index].Index; - - byte red = (byte) (index & 0xFF); - byte green = (byte) ((index >> 8) & 0xFF); - const byte blue = 20; - const byte alpha1 = 0; - const byte alpha2 = 0; - const byte alpha3 = 0; - const byte alpha4 = 0; - const byte alpha5 = 255; - const byte alpha6 = 255; - const byte alpha7 = 255; - const byte alpha8 = 255; - byte alpha9 = red; - byte alpha10 = green; - - binStream.Write(red); - binStream.Write(green); - binStream.Write(blue); - binStream.Write(alpha1); - binStream.Write(alpha2); - binStream.Write(alpha3); - binStream.Write(alpha4); - binStream.Write(alpha5); - binStream.Write(alpha6); - binStream.Write(alpha7); - binStream.Write(alpha8); - binStream.Write(alpha9); - binStream.Write(alpha10); } } - - binStream.Close(); } public string FileExtension @@ -259,7 +267,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders public bool SupportsTileSave() { return false; - } - + } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs index 9fb7ef7..d467abb 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs @@ -25,7 +25,10 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; using System.IO; + +using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; @@ -116,7 +119,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders public ITerrainChannel LoadStream(Stream s) { - TerrainChannel retval = new TerrainChannel(); + // The raw format doesn't contain any dimension information. + // Guess the square dimensions by using the length of the raw file. + double dimension = Math.Sqrt((double)(s.Length / 4)); + // Regions are always multiples of 256. + int trimmedDimension = (int)dimension - ((int)dimension % (int)Constants.RegionSize); + if (trimmedDimension < Constants.RegionSize) + trimmedDimension = (int)Constants.RegionSize; + + TerrainChannel retval = new TerrainChannel(trimmedDimension, trimmedDimension); BinaryReader bs = new BinaryReader(s); int y; diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs index b5c7d33..219011e 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs @@ -65,7 +65,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bool eof = false; int fileXPoints = 0; -// int fileYPoints = 0; + int fileYPoints = 0; // Terragen file while (eof == false) @@ -75,7 +75,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { case "SIZE": fileXPoints = bs.ReadInt16() + 1; -// fileYPoints = fileXPoints; + fileYPoints = fileXPoints; bs.ReadInt16(); break; case "XPTS": @@ -83,8 +83,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bs.ReadInt16(); break; case "YPTS": -// fileYPoints = bs.ReadInt16(); - bs.ReadInt16(); + fileYPoints = bs.ReadInt16(); bs.ReadInt16(); break; case "ALTW": @@ -138,7 +137,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bs.ReadInt16(); } - break; default: bs.ReadInt32(); @@ -154,10 +152,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders public ITerrainChannel LoadStream(Stream s) { - + // Set to default size int w = (int)Constants.RegionSize; int h = (int)Constants.RegionSize; + // create a dummy channel (in case data is bad) TerrainChannel retval = new TerrainChannel(w, h); BinaryReader bs = new BinaryReader(s); @@ -165,8 +164,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bool eof = false; if (Encoding.ASCII.GetString(bs.ReadBytes(16)) == "TERRAGENTERRAIN ") { -// int fileWidth = w; -// int fileHeight = h; // Terragen file while (eof == false) @@ -175,31 +172,29 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders switch (tmp) { case "SIZE": -// int sztmp = bs.ReadInt16() + 1; -// fileWidth = sztmp; -// fileHeight = sztmp; - bs.ReadInt16(); + w = bs.ReadInt16() + 1; + h = w; bs.ReadInt16(); break; case "XPTS": -// fileWidth = bs.ReadInt16(); - bs.ReadInt16(); + w = bs.ReadInt16(); bs.ReadInt16(); break; case "YPTS": -// fileHeight = bs.ReadInt16(); - bs.ReadInt16(); + h = bs.ReadInt16(); bs.ReadInt16(); break; case "ALTW": eof = true; - Int16 heightScale = bs.ReadInt16(); - Int16 baseHeight = bs.ReadInt16(); + // create new channel of proper size (now that we know it) + retval = new TerrainChannel(w, h); + double heightScale = (double)bs.ReadInt16() / 65536.0; + double baseHeight = (double)bs.ReadInt16(); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - retval[x, y] = baseHeight + bs.ReadInt16() * (double)heightScale / 65536.0; + retval[x, y] = baseHeight + (double)bs.ReadInt16() * heightScale; } } break; @@ -209,9 +204,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders } } } - bs.Close(); - return retval; } @@ -257,17 +250,17 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bs.Write(enc.GetBytes("TERRAGENTERRAIN ")); bs.Write(enc.GetBytes("SIZE")); - bs.Write(Convert.ToInt16(Constants.RegionSize)); + bs.Write(Convert.ToInt16(map.Width)); bs.Write(Convert.ToInt16(0)); // necessary padding //The XPTS and YPTS chunks are not needed for square regions //but L3DT won't load the terrain file properly without them. bs.Write(enc.GetBytes("XPTS")); - bs.Write(Convert.ToInt16(Constants.RegionSize)); + bs.Write(Convert.ToInt16(map.Width)); bs.Write(Convert.ToInt16(0)); // necessary padding bs.Write(enc.GetBytes("YPTS")); - bs.Write(Convert.ToInt16(Constants.RegionSize)); + bs.Write(Convert.ToInt16(map.Height)); bs.Write(Convert.ToInt16(0)); // necessary padding bs.Write(enc.GetBytes("SCAL")); @@ -283,11 +276,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders bs.Write(Convert.ToInt16(horizontalScale)); // range between max and min bs.Write(Convert.ToInt16(baseHeight)); // base height or mid point + double factor = 65536.0 / horizontalScale; // avoid computing this on each iteration + for (int y = 0; y < map.Height; y++) { for (int x = 0; x < map.Width; x++) { - float elevation = (float)((map[x,y] - baseHeight) * 65536 ) / (float)horizontalScale; // see LoadStream for inverse + float elevation = (float)((map[x,y] - baseHeight) * factor); // see LoadStream for inverse // clamp rounding issues if (elevation > Int16.MaxValue) @@ -299,7 +294,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders } } - //This is only necessary for older versions of Terragen. + //This is necessary for older versions of Terragen. bs.Write(enc.GetBytes("EOF ")); bs.Close(); @@ -343,7 +338,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders if (BitConverter.IsLittleEndian == false) { byte[] tmp = new byte[4]; - for (int i = 0; i < 4; i++) + for (int i = 3; i >= 0; i--) { tmp[i] = retVal[3 - i]; } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs index 630473e..b6c635c 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs @@ -45,7 +45,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes { if (fillArea[x, y]) { - double noise = TerrainUtil.PerlinNoise2D((double) x / Constants.RegionSize, (double) y / Constants.RegionSize, 8, 1.0); + double noise = TerrainUtil.PerlinNoise2D((double) x / map.Width, (double) y / map.Height, 8, 1.0); map[x, y] += noise * strength; } diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainFeature.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainFeature.cs new file mode 100644 index 0000000..78a43db --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainFeature.cs @@ -0,0 +1,60 @@ +/* + * 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 OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainFeature + { + /// + /// Creates the feature. + /// + /// + /// Empty string if successful, otherwise error message. + /// + /// + /// ITerrainChannel holding terrain data. + /// + /// + /// command-line arguments from console. + /// + string CreateFeature(ITerrainChannel map, string[] args); + + /// + /// Gets a string describing the usage. + /// + /// + /// A string describing parameters for creating the feature. + /// Format is "feature-name ..." + /// + string GetUsage(); + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainModifier.cs new file mode 100644 index 0000000..0e0a0e4 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainModifier.cs @@ -0,0 +1,77 @@ +/* + * 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 OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainModifier + { + /// + /// Creates the feature. + /// + /// + /// Empty string if successful, otherwise error message. + /// + /// + /// ITerrainChannel holding terrain data. + /// + /// + /// command-line arguments from console. + /// + string ModifyTerrain(ITerrainChannel map, string[] args); + + /// + /// Gets a string describing the usage. + /// + /// + /// A string describing parameters for creating the feature. + /// Format is "feature-name ..." + /// + string GetUsage(); + + /// + /// Apply the appropriate operation on the specified map, at (x, y). + /// + /// + /// Map. + /// + /// + /// Data. + /// + /// + /// X. + /// + /// + /// Y. + /// + double operate(double[,] map, TerrainModifierData data, int x, int y); + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/FillModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/FillModifier.cs new file mode 100644 index 0000000..32f1de9 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/FillModifier.cs @@ -0,0 +1,93 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class FillModifier : TerrainModifier + { + + public FillModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "fill [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nSets all points within the specified range to the specified value."; + return val; + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double result = data.elevation - (data.elevation - data.bevelevation) * factor; + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/LowerModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/LowerModifier.cs new file mode 100644 index 0000000..2ab4bcc --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/LowerModifier.cs @@ -0,0 +1,92 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class LowerModifier : TerrainModifier + { + public LowerModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "lower [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nLowers all points within the specified range by the specified amount."; + return val; + + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double result = map[x, y] - (data.elevation - (data.elevation - data.bevelevation) * factor); + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MaxModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MaxModifier.cs new file mode 100644 index 0000000..0939c0a --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MaxModifier.cs @@ -0,0 +1,92 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class MaxModifier : TerrainModifier + { + public MaxModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "max [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nEnsures that all points within the specified range are no higher than the specified value."; + return val; + + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double result = Math.Min(data.elevation - (data.elevation - data.bevelevation) * factor, map[x, y]); + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MinModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MinModifier.cs new file mode 100644 index 0000000..cbbccc0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/MinModifier.cs @@ -0,0 +1,92 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class MinModifier : TerrainModifier + { + public MinModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "min [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nEnsures that all points within the specified range are no lower than the specified value."; + return val; + + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double result = Math.Max(data.elevation - (data.elevation - data.bevelevation) * factor, map[x, y]); + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/NoiseModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/NoiseModifier.cs new file mode 100644 index 0000000..d6b95d0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/NoiseModifier.cs @@ -0,0 +1,108 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class NoiseModifier : TerrainModifier + { + public NoiseModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.bevel == "taper") + { + if (data.bevelevation < 0.0 || data.bevelevation > 1.0) + { + result = String.Format("Taper must be 0.0 to 1.0: {0}", data.bevelevation); + } + } + else + { + data.bevelevation = 1.0f; + } + + if (data.elevation < 0.0 || data.elevation > 1.0) + { + result = String.Format("Noise strength must be 0.0 to 1.0: {0}", data.elevation); + } + + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "noise [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nAdds noise to all points within the specified range."; + return val; + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double noise = TerrainUtil.PerlinNoise2D((double)x / map.GetLength(0), (double)y / map.GetLength(1), 8, 1.0); + return map[x, y] + (data.elevation - (data.elevation - data.bevelevation) * factor) * (noise - .5); + } + + } + +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/RaiseModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/RaiseModifier.cs new file mode 100644 index 0000000..35fb9d6 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/RaiseModifier.cs @@ -0,0 +1,92 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class RaiseModifier : TerrainModifier + { + public RaiseModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "raise [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nRaises all points within the specified range by the specified amount."; + return val; + + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double factor = this.computeBevel(data, x, y); + double result = map[x, y] + (data.elevation - (data.elevation - data.bevelevation) * factor); + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/Modifiers/SmoothModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/SmoothModifier.cs new file mode 100644 index 0000000..9f8d5b2 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Modifiers/SmoothModifier.cs @@ -0,0 +1,131 @@ +/* + * 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 OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.Modifiers +{ + public class SmoothModifier : TerrainModifier + { + public SmoothModifier(ITerrainModule module) : base(module) + { + } + + public override string ModifyTerrain(ITerrainChannel map, string[] args) + { + string result; + if (args.Length < 3) + { + result = "Usage: " + GetUsage(); + } + else + { + TerrainModifierData data; + result = this.parseParameters(args, out data); + + // Context-specific validation + if (result == String.Empty) + { + if (data.bevel == "taper") + { + if (data.bevelevation < 0.01 || data.bevelevation > 0.99) + { + result = String.Format("Taper must be 0.01 to 0.99: {0}", data.bevelevation); + } + } + else + { + data.bevelevation = 2.0f / 3.0f; + } + + if (data.elevation < 0.0 || data.elevation > 1.0) + { + result = String.Format("Smoothing strength must be 0.0 to 1.0: {0}", data.elevation); + } + + if (data.shape == String.Empty) + { + data.shape = "rectangle"; + data.x0 = 0; + data.y0 = 0; + data.dx = map.Width; + data.dy = map.Height; + } + } + + // if it's all good, then do the work + if (result == String.Empty) + { + this.applyModification(map, data); + } + } + + return result; + } + + public override string GetUsage() + { + string val = "smooth [ -rec=x1,y1,dx[,dy] | -ell=x0,y0,rx[,ry] ] [-taper=]" + + "\nSmooths all points within the specified range using a simple averaging algorithm."; + return val; + } + + public override double operate(double[,] map, TerrainModifierData data, int x, int y) + { + double[] scale = new double[3]; + scale[0] = data.elevation; + scale[1] = ((1.0 - scale[0]) * data.bevelevation) / 8.0; + scale[2] = ((1.0 - scale[0]) * (1.0 - data.bevelevation)) / 16.0; + int xMax = map.GetLength(0); + int yMax = map.GetLength(1); + double result; + if ((x == 0) || (y == 0) || (x == (xMax - 1)) || (y == (yMax - 1))) + { + result = map[x, y]; + } + else + { + result = 0.0; + for(int yPos = (y - 2); yPos < (y + 3); yPos++) + { + int yVal = (yPos <= 0) ? 0 : ((yPos < yMax) ? yPos : yMax - 1); + for(int xPos = (x - 2); xPos < (x + 3); xPos++) + { + int xVal = (xPos <= 0) ? 0 : ((xPos < xMax) ? xPos : xMax - 1); + int dist = Math.Max(Math.Abs(x - xVal), Math.Abs(y - yVal)); + result += map[xVal, yVal] * scale[dist]; + } + } + } + return result; + } + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs index 989b7d8..e7df3f8 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs @@ -53,7 +53,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes z *= z; z -= ((x - rx) * (x - rx)) + ((y - ry) * (y - ry)); - double noise = TerrainUtil.PerlinNoise2D(x / (double) Constants.RegionSize, y / (double) Constants.RegionSize, 8, 1.0); + double noise = TerrainUtil.PerlinNoise2D(x / (double) map.Width, y / (double) map.Height, 8, 1.0); if (z > 0.0) map[x, y] += noise * z * duration; diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModifier.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModifier.cs new file mode 100644 index 0000000..7ebd08e --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModifier.cs @@ -0,0 +1,378 @@ +/* + * 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.Reflection; +using log4net; + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public abstract class TerrainModifier : ITerrainModifier + { + protected ITerrainModule m_module; + protected static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected TerrainModifier(ITerrainModule module) + { + m_module = module; + } + + public abstract string ModifyTerrain(ITerrainChannel map, string[] args); + + public abstract string GetUsage(); + + public abstract double operate(double[,] map, TerrainModifierData data, int x, int y); + + protected String parseParameters(string[] args, out TerrainModifierData data) + { + string val; + string arg; + string result; + data = new TerrainModifierData(); + data.shape = String.Empty; + data.bevel = String.Empty; + data.dx = 0; + data.dy = 0; + if (args.Length < 4) + { + result = "Usage: " + GetUsage(); + } + else + { + result = this.parseFloat(args[3], out data.elevation); + } + if (result == String.Empty) + { + int index = 3; + while(++index < args.Length && result == String.Empty) + { + arg = args[index]; + // check for shape + if (arg.StartsWith("-rec=") || arg.StartsWith("-ell=")) + { + if (data.shape != String.Empty) + { + result = "Only 1 '-rec' or '-ell' parameter is permitted."; + } + else + { + data.shape = arg.StartsWith("-ell=") ? "ellipse" : "rectangle"; + val = arg.Substring(arg.IndexOf("=") + 1); + string[] coords = val.Split(new char[] {','}); + if ((coords.Length < 3) || (coords.Length > 4)) + { + result = String.Format("Bad format for shape parameter {0}", arg); + } + else + { + result = this.parseInt(coords[0], out data.x0); + if (result == String.Empty) + { + result = this.parseInt(coords[1], out data.y0); + } + if (result == String.Empty) + { + result = this.parseInt(coords[2], out data.dx); + } + if (result == String.Empty) + { + if (coords.Length == 4) + { + result = this.parseInt(coords[3], out data.dy); + } + else + { + data.dy = data.dx; + } + } + if (result == String.Empty) + { + if ((data.dx <= 0) || (data.dy <= 0)) + { + result = "Shape sizes must be positive integers"; + } + } + else + { + result = String.Format("Bad value in shape parameters {0}", arg); + } + } + } + } + else if (arg.StartsWith("-taper=")) + { + if (data.bevel != String.Empty) + { + result = "Only 1 '-taper' parameter is permitted."; + } + else + { + data.bevel = "taper"; + val = arg.Substring(arg.IndexOf("=") + 1); + result = this.parseFloat(val, out data.bevelevation); + if (result != String.Empty) + { + result = String.Format("Bad format for taper parameter {0}", arg); + } + } + } + else + { + result = String.Format("Unrecognized parameter {0}", arg); + } + } + } + return result; + } + + protected string parseFloat(String s, out float f) + { + string result; + double d; + if (Double.TryParse(s, out d)) + { + try + { + f = (float)d; + result = String.Empty; + } + catch(InvalidCastException) + { + result = String.Format("{0} is invalid", s); + f = -1.0f; + } + } + else + { + f = -1.0f; + result = String.Format("{0} is invalid", s); + } + return result; + } + + protected string parseInt(String s, out int i) + { + string result; + if (Int32.TryParse(s, out i)) + { + result = String.Empty; + } + else + { + result = String.Format("{0} is invalid", s); + } + return result; + } + + protected void applyModification(ITerrainChannel map, TerrainModifierData data) + { + bool[,] mask; + int xMax; + int yMax; + int xMid; + int yMid; + if (data.shape == "ellipse") + { + mask = this.ellipticalMask(data.dx, data.dy); + xMax = mask.GetLength(0); + yMax = mask.GetLength(1); + xMid = xMax / 2 + xMax % 2; + yMid = yMax / 2 + yMax % 2; + } + else + { + mask = this.rectangularMask(data.dx, data.dy); + xMax = mask.GetLength(0); + yMax = mask.GetLength(1); + xMid = 0; + yMid = 0; + } +// m_log.DebugFormat("Apply {0} mask {1}x{2} @ {3},{4}", data.shape, xMax, yMax, xMid, yMid); + double[,] buffer = map.GetDoubles(); + int yDim = yMax; + while(--yDim >= 0) + { + int yPos = data.y0 + yDim - yMid; + if ((yPos >= 0) && (yPos < map.Height)) + { + int xDim = xMax; + while(--xDim >= 0) + { + int xPos = data.x0 + xDim - xMid; + if ((xPos >= 0) && (xPos < map.Width) && (mask[xDim, yDim])) + { + double endElevation = this.operate(buffer, data, xPos, yPos); + map[xPos, yPos] = endElevation; + } + } + } + } + } + + protected double computeBevel(TerrainModifierData data, int x, int y) + { + int deltaX; + int deltaY; + int xMax; + int yMax; + double factor; + if (data.bevel == "taper") + { + if (data.shape == "ellipse") + { + deltaX = x - data.x0; + deltaY = y - data.y0; + xMax = data.dx; + yMax = data.dy; + factor = (double)((deltaX * deltaX) + (deltaY * deltaY)); + factor /= ((xMax * xMax) + (yMax * yMax)); + } + else + { + // pyramid + xMax = data.dx / 2 + data.dx % 2; + yMax = data.dy / 2 + data.dy % 2; + deltaX = Math.Abs(data.x0 + xMax - x); + deltaY = Math.Abs(data.y0 + yMax - y); + factor = Math.Max(((double)(deltaY) / yMax), ((double)(deltaX) / xMax)); + } + } + else + { + factor = 0.0; + } + return factor; + } + + private bool[,] rectangularMask(int xSize, int ySize) + { + bool[,] mask = new bool[xSize, ySize]; + int yPos = ySize; + while(--yPos >= 0) + { + int xPos = xSize; + while(--xPos >= 0) + { + mask[xPos, yPos] = true; + } + } + return mask; + } + + /* + * Fast ellipse-based derivative of Bresenham algorithm. + * https://web.archive.org/web/20120225095359/http://homepage.smc.edu/kennedy_john/belipse.pdf + */ + private bool[,] ellipticalMask(int xRadius, int yRadius) + { + long twoASquared = 2L * xRadius * xRadius; + long twoBSquared = 2L * yRadius * yRadius; + + bool[,] mask = new bool[2 * xRadius + 1, 2 * yRadius + 1]; + + long ellipseError = 0L; + long stoppingX = twoBSquared * xRadius; + long stoppingY = 0L; + long xChange = yRadius * yRadius * (1L - 2L * xRadius); + long yChange = xRadius * xRadius; + + int xPos = xRadius; + int yPos = 0; + + // first set of points + while(stoppingX >= stoppingY) + { + int yUpper = yRadius + yPos; + int yLower = yRadius - yPos; + // fill in the mask + int xNow = xPos; + while(xNow >= 0) + { + mask[xRadius + xNow, yUpper] = true; + mask[xRadius - xNow, yUpper] = true; + mask[xRadius + xNow, yLower] = true; + mask[xRadius - xNow, yLower] = true; + --xNow; + } + yPos++; + stoppingY += twoASquared; + ellipseError += yChange; + yChange += twoASquared; + if ((2L * ellipseError + xChange) > 0L) + { + xPos--; + stoppingX -= twoBSquared; + ellipseError += xChange; + xChange += twoBSquared; + } + } + + // second set of points + xPos = 0; + yPos = yRadius; + xChange = yRadius * yRadius; + yChange = xRadius * xRadius * (1L - 2L * yRadius); + + ellipseError = 0L; + stoppingX = 0L; + stoppingY = twoASquared * yRadius; + + while(stoppingX <= stoppingY) + { + int xUpper = xRadius + xPos; + int xLower = xRadius - xPos; + // fill in the mask + int yNow = yPos; + while(yNow >= 0) + { + mask[xUpper, yRadius + yNow] = true; + mask[xUpper, yRadius - yNow] = true; + mask[xLower, yRadius + yNow] = true; + mask[xLower, yRadius - yNow] = true; + --yNow; + } + xPos++; + stoppingX += twoBSquared; + ellipseError += xChange; + xChange += twoBSquared; + if ((2L * ellipseError + yChange) > 0L) + { + yPos--; + stoppingY -= twoASquared; + ellipseError += yChange; + yChange += twoASquared; + } + } + return mask; + } + + + } + +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModifierData.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModifierData.cs new file mode 100644 index 0000000..4e0f8d7 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModifierData.cs @@ -0,0 +1,17 @@ +using System; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public struct TerrainModifierData + { + public float elevation; + public string shape; + public int x0; + public int y0; + public int dx; + public int dy; + public string bevel; + public float bevelevation; + } +} + diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index fd30c46..932652c 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -24,19 +24,24 @@ * (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.Net; + using log4net; using Nini.Config; + using OpenMetaverse; using Mono.Addins; + +using OpenSim.Data; using OpenSim.Framework; +using OpenSim.Framework.Console; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.CoreModules.World.Terrain.FileLoaders; +using OpenSim.Region.CoreModules.World.Terrain.Modifiers; using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes; using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; using OpenSim.Region.Framework.Interfaces; @@ -70,26 +75,112 @@ namespace OpenSim.Region.CoreModules.World.Terrain #endregion private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private readonly Commander m_commander = new Commander("terrain"); +#pragma warning disable 414 + private static readonly string LogHeader = "[TERRAIN MODULE]"; +#pragma warning restore 414 + + private readonly Commander m_commander = new Commander("terrain"); private readonly Dictionary m_floodeffects = new Dictionary(); - private readonly Dictionary m_loaders = new Dictionary(); - private readonly Dictionary m_painteffects = new Dictionary(); - - private ITerrainChannel m_channel; private Dictionary m_plugineffects; + private Dictionary m_modifyOperations = + new Dictionary(); + private ITerrainChannel m_channel; private ITerrainChannel m_revert; private Scene m_scene; private volatile bool m_tainted; private readonly Stack m_undo = new Stack(5); - private String m_InitialTerrain = "pinhead-island"; + // If true, send terrain patch updates to clients based on their view distance + private bool m_sendTerrainUpdatesByViewDistance = true; + + // Class to keep the per client collection of terrain patches that must be sent. + // A patch is set to 'true' meaning it should be sent to the client. Once the + // patch packet is queued to the client, the bit for that patch is set to 'false'. + private class PatchUpdates + { + private bool[,] updated; // for each patch, whether it needs to be sent to this client + private int updateCount; // number of patches that need to be sent + public ScenePresence Presence; // a reference to the client to send to + public TerrainData Terrain; // reference to the underlying terrain + public PatchUpdates(TerrainData terrData, ScenePresence pPresence) + { + updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize]; + updateCount = 0; + Presence = pPresence; + Terrain = terrData; + // Initially, send all patches to the client + SetAll(true); + } + // Returns 'true' if there are any patches marked for sending + public bool HasUpdates() + { + return (updateCount > 0); + } + + public void SetByXY(int x, int y, bool state) + { + this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state); + } + + public bool GetByPatch(int patchX, int patchY) + { + return updated[patchX, patchY]; + } + + public void SetByPatch(int patchX, int patchY, bool state) + { + bool prevState = updated[patchX, patchY]; + if (!prevState && state) + updateCount++; + if (prevState && !state) + updateCount--; + updated[patchX, patchY] = state; + } + + public void SetAll(bool state) + { + updateCount = 0; + for(int xx = 0; xx < updated.GetLength(0); xx++) + for(int yy = 0; yy < updated.GetLength(1); yy++) + updated[xx, yy] = state; + if (state) + updateCount = updated.GetLength(0) * updated.GetLength(1); + } + // Logically OR's the terrain data's patch taint map into this client's update map. + public void SetAll(TerrainData terrData) + { + if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize) + || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize)) + { + throw new Exception( + String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>", + LogHeader, updated.GetLength(0), updated.GetLength(1), + terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize) + ); + } + for(int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize) + { + for(int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize) + { + // Only set tainted. The patch bit may be set if the patch was to be sent later. + if (terrData.IsTaintedAt(xx, yy, false)) + { + this.SetByXY(xx, yy, true); + } + } + } + } + } + + // The flags of which terrain patches to send for each of the ScenePresence's + private Dictionary m_perClientPatchUpdates = new Dictionary(); + /// /// Human readable list of terrain file extensions that are supported. /// @@ -100,8 +191,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain #region ICommandableModule Members - public ICommander CommandInterface - { + public ICommander CommandInterface { get { return m_commander; } } @@ -118,7 +208,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain { IConfig terrainConfig = config.Configs["Terrain"]; if (terrainConfig != null) + { m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); + m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance); + } } public void AddRegion(Scene scene) @@ -126,26 +219,28 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_scene = scene; // Install terrain module in the simulator - lock (m_scene) + lock(m_scene) { if (m_scene.Heightmap == null) { - m_channel = new TerrainChannel(m_InitialTerrain); + m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX, + (int)m_scene.RegionInfo.RegionSizeY, + (int)m_scene.RegionInfo.RegionSizeZ); m_scene.Heightmap = m_channel; - m_revert = new TerrainChannel(); UpdateRevertMap(); } else { m_channel = m_scene.Heightmap; - m_revert = new TerrainChannel(); UpdateRevertMap(); } m_scene.RegisterModuleInterface(this); m_scene.EventManager.OnNewClient += EventManager_OnNewClient; + m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed; m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick; + m_scene.EventManager.OnFrame += EventManager_OnFrame; } InstallDefaultEffects(); @@ -156,7 +251,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain string supportedFilesSeparatorForTileSave = ""; m_supportFileExtensionsForTileSave = ""; - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")"; supportedFilesSeparator = ", "; @@ -179,13 +274,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain public void RemoveRegion(Scene scene) { - lock (m_scene) + lock(m_scene) { // remove the commands m_scene.UnregisterModuleCommander(m_commander.Name); // remove the event-handlers + m_scene.EventManager.OnFrame -= EventManager_OnFrame; m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick; m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole; + m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed; m_scene.EventManager.OnNewClient -= EventManager_OnNewClient; // remove the interface m_scene.UnregisterModuleInterface(this); @@ -196,13 +293,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain { } - public Type ReplaceableInterface - { + public Type ReplaceableInterface { get { return null; } } - public string Name - { + public string Name { get { return "TerrainModule"; } } @@ -221,47 +316,46 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// Filename to terrain file. Type is determined by extension. public void LoadFromFile(string filename) { - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key)) { - lock (m_scene) + lock(m_scene) { try { ITerrainChannel channel = loader.Value.LoadFile(filename); - if (channel.Width != Constants.RegionSize || channel.Height != Constants.RegionSize) + if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY) { // TerrainChannel expects a RegionSize x RegionSize map, currently throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}", - Constants.RegionSize, Constants.RegionSize)); + m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY)); } m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height); m_scene.Heightmap = channel; m_channel = channel; UpdateRevertMap(); } - catch (NotImplementedException) + catch(NotImplementedException) { m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + " parser does not support file loading. (May be save only)"); throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); } - catch (FileNotFoundException) + catch(FileNotFoundException) { m_log.Error( "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)"); throw new TerrainException( String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename)); } - catch (ArgumentException e) + catch(ArgumentException e) { m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message); throw new TerrainException( String.Format("Unable to load heightmap: {0}", e.Message)); } } - CheckForTerrainUpdates(); m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); return; } @@ -279,7 +373,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { try { - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key)) { @@ -289,7 +383,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } } - catch (IOException ioe) + catch(IOException ioe) { m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message)); } @@ -309,27 +403,32 @@ namespace OpenSim.Region.CoreModules.World.Terrain LoadFromStream(filename, URIFetch(pathToTerrainHeightmap)); } + public void LoadFromStream(string filename, Stream stream) + { + LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream); + } + /// /// Loads a terrain file from a stream and installs it in the scene. /// /// Filename to terrain file. Type is determined by extension. /// - public void LoadFromStream(string filename, Stream stream) + public void LoadFromStream(string filename, Vector3 displacement, + float radianRotation, Vector2 rotationDisplacement, Stream stream) { - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key)) { - lock (m_scene) + lock(m_scene) { try { ITerrainChannel channel = loader.Value.LoadStream(stream); - m_scene.Heightmap = channel; - m_channel = channel; + m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement); UpdateRevertMap(); } - catch (NotImplementedException) + catch(NotImplementedException) { m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + " parser does not support file loading. (May be save only)"); @@ -337,7 +436,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } - CheckForTerrainUpdates(); m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); return; } @@ -390,7 +488,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { try { - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key)) { @@ -399,18 +497,56 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } } - catch (NotImplementedException) + catch(NotImplementedException) { m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented."); throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented")); } } - public void TaintTerrain () + // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients. + // ITerrainModule.TaintTerrain() + public void TaintTerrain() { - CheckForTerrainUpdates(); + lock(m_perClientPatchUpdates) + { + // Set the flags for all clients so the tainted patches will be sent out + foreach(PatchUpdates pups in m_perClientPatchUpdates.Values) + { + pups.SetAll(m_scene.Heightmap.GetTerrainData()); + } + } } + // ITerrainModule.PushTerrain() + public void PushTerrain(IClientAPI pClient) + { + // If view distance based, set the modified patch bits and the frame event will send the updates + if (m_sendTerrainUpdatesByViewDistance) + { + ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId); + if (presence != null) + { + lock(m_perClientPatchUpdates) + { + PatchUpdates pups; + if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups)) + { + // There is a ScenePresence without a send patch map. Create one. + pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence); + m_perClientPatchUpdates.Add(presence.UUID, pups); + } + // By setting all to modified, the next update tick will send the patches + pups.SetAll(true); + } + } + } + else + { + // The traditional way is to call into the protocol stack to send them all. + pClient.SendLayerData(new float[10]); + } + } #region Plugin Loading Methods private void LoadPlugins() @@ -418,13 +554,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_plugineffects = new Dictionary(); LoadPlugins(Assembly.GetCallingAssembly()); string plugineffectsPath = "Terrain"; - + // Load the files in the Terrain/ dir if (!Directory.Exists(plugineffectsPath)) return; - + string[] files = Directory.GetFiles(plugineffectsPath); - foreach (string file in files) + foreach(string file in files) { m_log.Info("Loading effects in " + file); try @@ -432,7 +568,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain Assembly library = Assembly.LoadFrom(file); LoadPlugins(library); } - catch (BadImageFormatException) + catch(BadImageFormatException) { } } @@ -440,7 +576,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void LoadPlugins(Assembly library) { - foreach (Type pluginType in library.GetTypes()) + foreach(Type pluginType in library.GetTypes()) { try { @@ -462,7 +598,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_log.Info("L ... " + typeName); } } - catch (AmbiguousMatchException) + catch(AmbiguousMatchException) { } } @@ -470,7 +606,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain public void InstallPlugin(string pluginName, ITerrainEffect effect) { - lock (m_plugineffects) + lock(m_plugineffects) { if (!m_plugineffects.ContainsKey(pluginName)) { @@ -513,6 +649,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea(); m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert); + // Terrain Modifier operations + m_modifyOperations["min"] = new MinModifier(this); + m_modifyOperations["max"] = new MaxModifier(this); + m_modifyOperations["raise"] = new RaiseModifier(this); + m_modifyOperations["lower"] = new LowerModifier(this); + m_modifyOperations["fill"] = new FillModifier(this); + m_modifyOperations["smooth"] = new SmoothModifier(this); + m_modifyOperations["noise"] = new NoiseModifier(this); + // Filesystem load/save loaders m_loaders[".r32"] = new RAW32(); m_loaders[".f32"] = m_loaders[".r32"]; @@ -532,6 +677,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// public void UpdateRevertMap() { + /* int x; for (x = 0; x < m_channel.Width; x++) { @@ -541,6 +687,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_revert[x, y] = m_channel[x, y]; } } + */ + m_revert = m_channel.MakeCopy(); } /// @@ -553,22 +701,22 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// Where to begin our slice public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY) { - int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX; - int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY; + int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX; + int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY; if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight) { // this region is included in the tile request - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key)) { - lock (m_scene) + lock(m_scene) { ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY, fileWidth, fileHeight, - (int) Constants.RegionSize, - (int) Constants.RegionSize); + (int)m_scene.RegionInfo.RegionSizeX, + (int)m_scene.RegionInfo.RegionSizeY); m_scene.Heightmap = channel; m_channel = channel; UpdateRevertMap(); @@ -607,23 +755,23 @@ namespace OpenSim.Region.CoreModules.World.Terrain } // this region is included in the tile request - foreach (KeyValuePair loader in m_loaders) + foreach(KeyValuePair loader in m_loaders) { if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave()) { - lock (m_scene) + lock(m_scene) { loader.Value.SaveFile(m_channel, filename, offsetX, offsetY, fileWidth, fileHeight, - (int)Constants.RegionSize, - (int)Constants.RegionSize); + (int)m_scene.RegionInfo.RegionSizeX, + (int)m_scene.RegionInfo.RegionSizeY); MainConsole.Instance.OutputFormat( "Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}", fileStartX, fileStartY, fileStartX + fileWidth - 1, fileStartY + fileHeight - 1, m_scene.RegionInfo.RegionName, filename); } - + return; } } @@ -634,7 +782,44 @@ namespace OpenSim.Region.CoreModules.World.Terrain } /// + /// Called before processing of every simulation frame. + /// This is used to check to see of any of the terrain is tainted and, if so, schedule + /// updates for all the presences. + /// This also checks to see if there are updates that need to be sent for each presence. + /// This is where the logic is to send terrain updates to clients. + /// + private void EventManager_OnFrame() + { + TerrainData terrData = m_channel.GetTerrainData(); + + bool shouldTaint = false; + for(int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize) + { + for(int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize) + { + if (terrData.IsTaintedAt(x, y)) + { + // Found a patch that was modified. Push this flag into the clients. + SendToClients(terrData, x, y); + shouldTaint = true; + } + } + } + + // This event also causes changes to be sent to the clients + CheckSendingPatchesToClients(); + + // If things changes, generate some events + if (shouldTaint) + { + m_scene.EventManager.TriggerTerrainTainted(); + m_tainted = true; + } + } + + /// /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections + /// Called infrequently (like every 5 seconds or so). Best used for storing terrain. /// private void EventManager_OnTerrainTick() { @@ -665,7 +850,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain string[] tmpArgs = new string[args.Length - 2]; int i; - for (i = 2; i < args.Length; i++) + for(i = 2; i < args.Length; i++) tmpArgs[i - 2] = args[i]; m_commander.ProcessConsoleCommand(args[1], tmpArgs); @@ -683,56 +868,50 @@ namespace OpenSim.Region.CoreModules.World.Terrain client.OnLandUndo += client_OnLandUndo; client.OnUnackedTerrain += client_OnUnackedTerrain; } - + /// - /// Checks to see if the terrain has been modified since last check - /// but won't attempt to limit those changes to the limits specified in the estate settings - /// currently invoked by the command line operations in the region server only + /// Installs terrain brush hook to IClientAPI /// - private void CheckForTerrainUpdates() + /// + private void EventManager_OnClientClosed(UUID client, Scene scene) { - CheckForTerrainUpdates(false); + ScenePresence presence = scene.GetScenePresence(client); + if (presence != null) + { + presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain; + presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain; + presence.ControllingClient.OnLandUndo -= client_OnLandUndo; + presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain; + } + + lock(m_perClientPatchUpdates) + m_perClientPatchUpdates.Remove(client); } /// - /// Checks to see if the terrain has been modified since last check. - /// If it has been modified, every all the terrain patches are sent to the client. - /// If the call is asked to respect the estate settings for terrain_raise_limit and - /// terrain_lower_limit, it will clamp terrain updates between these values - /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces - /// should height map deltas be limited to the estate settings limits + /// Scan over changes in the terrain and limit height changes. This enforces the + /// non-estate owner limits on rate of terrain editting. + /// Returns 'true' if any heights were limited. /// - private void CheckForTerrainUpdates(bool respectEstateSettings) + private bool EnforceEstateLimits() { - bool shouldTaint = false; - float[] serialised = m_channel.GetFloatsSerialised(); - int x; - for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize) + TerrainData terrData = m_channel.GetTerrainData(); + + bool wasLimited = false; + for(int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize) { - int y; - for (y = 0; y < m_channel.Height; y += Constants.TerrainPatchSize) + for(int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize) { - if (m_channel.Tainted(x, y)) + if (terrData.IsTaintedAt(x, y, false /* clearOnTest */)) { - // if we should respect the estate settings then - // fixup and height deltas that don't respect them - if (respectEstateSettings && LimitChannelChanges(x, y)) - { - // this has been vetoed, so update - // what we are going to send to the client - serialised = m_channel.GetFloatsSerialised(); - } - - SendToClients(serialised, x, y); - shouldTaint = true; + // If we should respect the estate settings then + // fixup and height deltas that don't respect them. + // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values. + wasLimited |= LimitChannelChanges(terrData, x, y); } } } - if (shouldTaint) - { - m_scene.EventManager.TriggerTerrainTainted(); - m_tainted = true; - } + return wasLimited; } /// @@ -740,31 +919,30 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// are all within the current estate limits /// true if changes were limited, false otherwise /// - private bool LimitChannelChanges(int xStart, int yStart) + private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart) { bool changesLimited = false; - double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; - double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; + float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; + float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; // loop through the height map for this patch and compare it against // the revert map - for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++) + for(int x = xStart; x < xStart + Constants.TerrainPatchSize; x++) { - for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++) + for(int y = yStart; y < yStart + Constants.TerrainPatchSize; y++) { - - double requestedHeight = m_channel[x, y]; - double bakedHeight = m_revert[x, y]; - double requestedDelta = requestedHeight - bakedHeight; + float requestedHeight = terrData[x, y]; + float bakedHeight = (float)m_revert[x, y]; + float requestedDelta = requestedHeight - bakedHeight; if (requestedDelta > maxDelta) { - m_channel[x, y] = bakedHeight + maxDelta; + terrData[x, y] = bakedHeight + maxDelta; changesLimited = true; } else if (requestedDelta < minDelta) { - m_channel[x, y] = bakedHeight + minDelta; //as lower is a -ve delta + terrData[x, y] = bakedHeight + minDelta; //as lower is a -ve delta changesLimited = true; } } @@ -775,7 +953,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void client_OnLandUndo(IClientAPI client) { - lock (m_undo) + lock(m_undo) { if (m_undo.Count > 0) { @@ -792,14 +970,177 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// A copy of the terrain as a 1D float array of size w*h /// The patch corner to send /// The patch corner to send - private void SendToClients(float[] serialised, int x, int y) + private void SendToClients(TerrainData terrData, int x, int y) { - m_scene.ForEachClient( - delegate(IClientAPI controller) - { controller.SendLayerData( - x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised); + if (m_sendTerrainUpdatesByViewDistance) + { + // Add that this patch needs to be sent to the accounting for each client. + lock(m_perClientPatchUpdates) + { + m_scene.ForEachScenePresence(presence => + { + PatchUpdates thisClientUpdates; + if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates)) + { + // There is a ScenePresence without a send patch map. Create one. + thisClientUpdates = new PatchUpdates(terrData, presence); + m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates); + } + thisClientUpdates.SetByXY(x, y, true); } - ); + ); + } + } + else + { + // Legacy update sending where the update is sent out as soon as noticed + // We know the actual terrain data that is passed is ignored so this passes a dummy heightmap. + //float[] heightMap = terrData.GetFloatsSerialized(); + float[] heightMap = new float[10]; + m_scene.ForEachClient( + delegate(IClientAPI controller) + { + controller.SendLayerData(x / Constants.TerrainPatchSize, + y / Constants.TerrainPatchSize, + heightMap); + } + ); + } + } + + private class PatchesToSend : IComparable + { + public int PatchX; + public int PatchY; + public float Dist; + + public PatchesToSend(int pX, int pY, float pDist) + { + PatchX = pX; + PatchY = pY; + Dist = pDist; + } + + public int CompareTo(PatchesToSend other) + { + return Dist.CompareTo(other.Dist); + } + } + + // Called each frame time to see if there are any patches to send to any of the + // ScenePresences. + // We know this is only called if we are doing view distance patch sending so some + // tests are not made. + // Loop through all the per-client info and send any patches necessary. + private void CheckSendingPatchesToClients() + { + lock(m_perClientPatchUpdates) + { + foreach(PatchUpdates pups in m_perClientPatchUpdates.Values) + { + if (pups.HasUpdates()) + { + // There is something that could be sent to this client. + List toSend = GetModifiedPatchesInViewDistance(pups); + if (toSend.Count > 0) + { + // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}", + // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName); + // Sort the patches to send by the distance from the presence + toSend.Sort(); + /* old way that sent individual patches + foreach (PatchesToSend pts in toSend) + { + pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null); + // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land); + } + */ + + // new way that sends all patches to the protocol so they can be sent in one block + int[] xPieces = new int[toSend.Count]; + int[] yPieces = new int[toSend.Count]; + float[] patchPieces = new float[toSend.Count * 2]; + int pieceIndex = 0; + foreach(PatchesToSend pts in toSend) + { + patchPieces[pieceIndex++] = pts.PatchX; + patchPieces[pieceIndex++] = pts.PatchY; + } + pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces); + } + } + } + } + } + + // Compute a list of modified patches that are within our view distance. + private List GetModifiedPatchesInViewDistance(PatchUpdates pups) + { + List ret = new List(); + + ScenePresence presence = pups.Presence; + if (presence == null) + return ret; + + Vector3 presencePos = presence.AbsolutePosition; + + // Before this distance check, the whole region just showed up. Adding the distance + // check causes different things to happen for the current and adjacent regions. + // So, to keep legacy views, if the region is legacy sized, don't do distance check. + bool isLegacySizedRegion = pups.Terrain.SizeX == Constants.RegionSize && pups.Terrain.SizeY == Constants.RegionSize; + bool shouldCheckViewDistance = m_sendTerrainUpdatesByViewDistance && !isLegacySizedRegion; + + int startX = 0; + int endX = (int)m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize; + int startY = 0; + int endY = (int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize; + + // The following only reduces the size of area scanned for updates. Only significant for very large varregions. + if (shouldCheckViewDistance) + { + // Compute the area of patches within our draw distance + startX = (((int)(presencePos.X - presence.DrawDistance)) / Constants.TerrainPatchSize) - 2; + startX = Math.Max(startX, 0); + startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize); + startY = (((int)(presencePos.Y - presence.DrawDistance)) / Constants.TerrainPatchSize) - 2; + startY = Math.Max(startY, 0); + startY = Math.Min(startY, (int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize); + endX = (((int)(presencePos.X + presence.DrawDistance)) / Constants.TerrainPatchSize) + 2; + endX = Math.Max(endX, 0); + endX = Math.Min(endX, (int)m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize); + endY = (((int)(presencePos.Y + presence.DrawDistance)) / Constants.TerrainPatchSize) + 2; + endY = Math.Max(endY, 0); + endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize); + } + + // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. rName={1}, ddist={2}, apos={3}, cpos={4}, isChild={5}, start=<{6},{7}>, end=<{8},{9}>", + // LogHeader, m_scene.RegionInfo.RegionName, + // presence.DrawDistance, presencePos, presence.CameraPosition, + // isLegacySizeChildRegion, + // startX, startY, endX, endY); + for(int x = startX; x < endX; x++) + { + for(int y = startY; y < endY; y++) + { + //Need to make sure we don't send the same ones over and over + Vector3 patchPos = new Vector3(x * Constants.TerrainPatchSize, y * Constants.TerrainPatchSize, presencePos.Z); + if (pups.GetByPatch(x, y)) + { + //Check which has less distance, camera or avatar position, both have to be done. + //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off + if (!shouldCheckViewDistance + || Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50) + || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50)) + { + //They can see it, send it to them + pups.SetByPatch(x, y, false); + float dist = Vector3.DistanceSquared(presencePos, patchPos); + ret.Add(new PatchesToSend(x, y, dist)); + } + } + } + } + return ret; } private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action, @@ -809,28 +1150,28 @@ namespace OpenSim.Region.CoreModules.World.Terrain bool allowed = false; if (north == south && east == west) { - if (m_painteffects.ContainsKey((StandardTerrainEffects) action)) + if (m_painteffects.ContainsKey((StandardTerrainEffects)action)) { - bool[,] allowMask = new bool[m_channel.Width,m_channel.Height]; + bool[,] allowMask = new bool[m_channel.Width, m_channel.Height]; allowMask.Initialize(); int n = size + 1; if (n > 2) n = 4; - int zx = (int) (west + 0.5); - int zy = (int) (north + 0.5); + int zx = (int)(west + 0.5); + int zy = (int)(north + 0.5); int dx; - for (dx=-n; dx<=n; dx++) + for(dx=-n; dx<=n; dx++) { int dy; - for (dy=-n; dy<=n; dy++) + for(dy=-n; dy<=n; dy++) { int x = zx + dx; int y = zy + dy; - if (x>=0 && y>=0 && x= 0 && y >= 0 && x < m_channel.Width && y < m_channel.Height) { - if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0))) + if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x, y, 0))) { allowMask[x, y] = true; allowed = true; @@ -841,10 +1182,12 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (allowed) { StoreUndoState(); - m_painteffects[(StandardTerrainEffects) action].PaintEffect( + m_painteffects[(StandardTerrainEffects)action].PaintEffect( m_channel, allowMask, west, south, height, size, seconds); - CheckForTerrainUpdates(!god); //revert changes outside estate limits + //revert changes outside estate limits + if (!god) + EnforceEstateLimits(); } } else @@ -854,22 +1197,22 @@ namespace OpenSim.Region.CoreModules.World.Terrain } else { - if (m_floodeffects.ContainsKey((StandardTerrainEffects) action)) + if (m_floodeffects.ContainsKey((StandardTerrainEffects)action)) { - bool[,] fillArea = new bool[m_channel.Width,m_channel.Height]; + bool[,] fillArea = new bool[m_channel.Width, m_channel.Height]; fillArea.Initialize(); int x; - for (x = 0; x < m_channel.Width; x++) + for(x = 0; x < m_channel.Width; x++) { int y; - for (y = 0; y < m_channel.Height; y++) + for(y = 0; y < m_channel.Height; y++) { if (x < east && x > west) { if (y < north && y > south) { - if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0))) + if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x, y, 0))) { fillArea[x, y] = true; allowed = true; @@ -882,10 +1225,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (allowed) { StoreUndoState(); - m_floodeffects[(StandardTerrainEffects) action].FloodEffect( - m_channel, fillArea, size); + m_floodeffects[(StandardTerrainEffects)action].FloodEffect(m_channel, fillArea, size); - CheckForTerrainUpdates(!god); //revert changes outside estate limits + //revert changes outside estate limits + if (!god) + EnforceEstateLimits(); } } else @@ -905,16 +1249,18 @@ namespace OpenSim.Region.CoreModules.World.Terrain InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter } } - + protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY) { //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY); - client.SendLayerData(patchX, patchY, m_scene.Heightmap.GetFloatsSerialised()); + // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI. + float[] heightMap = new float[10]; + client.SendLayerData(patchX, patchY, heightMap); } private void StoreUndoState() { - lock (m_undo) + lock(m_undo) { if (m_undo.Count > 0) { @@ -935,23 +1281,21 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InterfaceLoadFile(Object[] args) { - LoadFromFile((string) args[0]); - CheckForTerrainUpdates(); + LoadFromFile((string)args[0]); } private void InterfaceLoadTileFile(Object[] args) { - LoadFromFile((string) args[0], - (int) args[1], - (int) args[2], - (int) args[3], - (int) args[4]); - CheckForTerrainUpdates(); + LoadFromFile((string)args[0], + (int)args[1], + (int)args[2], + (int)args[3], + (int)args[4]); } private void InterfaceSaveFile(Object[] args) { - SaveToFile((string) args[0]); + SaveToFile((string)args[0]); } private void InterfaceSaveTileFile(Object[] args) @@ -971,11 +1315,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InterfaceRevertTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) - for (y = 0; y < m_channel.Height; y++) + for(x = 0; x < m_channel.Width; x++) + for(y = 0; y < m_channel.Height; y++) m_channel[x, y] = m_revert[x, y]; - CheckForTerrainUpdates(); } private void InterfaceFlipTerrain(Object[] args) @@ -984,39 +1327,36 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (direction.ToLower().StartsWith("y")) { - for (int x = 0; x < Constants.RegionSize; x++) + for(int x = 0; x < m_channel.Width; x++) { - for (int y = 0; y < Constants.RegionSize / 2; y++) + for(int y = 0; y < m_channel.Height / 2; y++) { double height = m_channel[x, y]; - double flippedHeight = m_channel[x, (int)Constants.RegionSize - 1 - y]; + double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y]; m_channel[x, y] = flippedHeight; - m_channel[x, (int)Constants.RegionSize - 1 - y] = height; + m_channel[x, (int)m_channel.Height - 1 - y] = height; } } } else if (direction.ToLower().StartsWith("x")) { - for (int y = 0; y < Constants.RegionSize; y++) + for(int y = 0; y < m_channel.Height; y++) { - for (int x = 0; x < Constants.RegionSize / 2; x++) + for(int x = 0; x < m_channel.Width / 2; x++) { double height = m_channel[x, y]; - double flippedHeight = m_channel[(int)Constants.RegionSize - 1 - x, y]; + double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y]; m_channel[x, y] = flippedHeight; - m_channel[(int)Constants.RegionSize - 1 - x, y] = height; + m_channel[(int)m_channel.Width - 1 - x, y] = height; } } } else { - m_log.Error("Unrecognised direction - need x or y"); + MainConsole.Instance.OutputFormat("ERROR: Unrecognised direction {0} - need x or y", direction); } - - - CheckForTerrainUpdates(); } private void InterfaceRescaleTerrain(Object[] args) @@ -1042,9 +1382,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain int width = m_channel.Width; int height = m_channel.Height; - for (int x = 0; x < width; x++) + for(int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) + for(int y = 0; y < height; y++) { double currHeight = m_channel[x, y]; if (currHeight < currMin) @@ -1065,16 +1405,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain //m_log.InfoFormat("Scale = {0}", scale); // scale the heightmap accordingly - for (int x = 0; x < width; x++) + for(int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) + for(int y = 0; y < height; y++) { - double currHeight = m_channel[x, y] - currMin; - m_channel[x, y] = desiredMin + (currHeight * scale); + double currHeight = m_channel[x, y] - currMin; + m_channel[x, y] = desiredMin + (currHeight * scale); } } - CheckForTerrainUpdates(); } } @@ -1082,64 +1421,73 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InterfaceElevateTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) - for (y = 0; y < m_channel.Height; y++) - m_channel[x, y] += (double) args[0]; - CheckForTerrainUpdates(); + for(x = 0; x < m_channel.Width; x++) + for(y = 0; y < m_channel.Height; y++) + m_channel[x, y] += (double)args[0]; } private void InterfaceMultiplyTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) - for (y = 0; y < m_channel.Height; y++) - m_channel[x, y] *= (double) args[0]; - CheckForTerrainUpdates(); + for(x = 0; x < m_channel.Width; x++) + for(y = 0; y < m_channel.Height; y++) + m_channel[x, y] *= (double)args[0]; } private void InterfaceLowerTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) - for (y = 0; y < m_channel.Height; y++) - m_channel[x, y] -= (double) args[0]; - CheckForTerrainUpdates(); + for(x = 0; x < m_channel.Width; x++) + for(y = 0; y < m_channel.Height; y++) + m_channel[x, y] -= (double)args[0]; } - private void InterfaceFillTerrain(Object[] args) + public void InterfaceFillTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) - for (y = 0; y < m_channel.Height; y++) - m_channel[x, y] = (double) args[0]; - CheckForTerrainUpdates(); + for(x = 0; x < m_channel.Width; x++) + for(y = 0; y < m_channel.Height; y++) + m_channel[x, y] = (double)args[0]; } private void InterfaceMinTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) + for(x = 0; x < m_channel.Width; x++) { - for (y = 0; y < m_channel.Height; y++) + for(y = 0; y < m_channel.Height; y++) { m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]); } } - CheckForTerrainUpdates(); } private void InterfaceMaxTerrain(Object[] args) { int x, y; - for (x = 0; x < m_channel.Width; x++) + for(x = 0; x < m_channel.Width; x++) { - for (y = 0; y < m_channel.Height; y++) + for(y = 0; y < m_channel.Height; y++) { m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]); } } - CheckForTerrainUpdates(); + } + + private void InterfaceShow(Object[] args) + { + Vector2 point; + + if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point)) + { + Console.WriteLine("ERROR: {0} is not a valid vector", args[0]); + return; + } + + double height = m_channel[(int)point.X, (int)point.Y]; + + Console.WriteLine("Terrain height at {0} is {1}", point, height); } private void InterfaceShowDebugStats(Object[] args) @@ -1149,10 +1497,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain double sum = 0; int x; - for (x = 0; x < m_channel.Width; x++) + for(x = 0; x < m_channel.Width; x++) { int y; - for (y = 0; y < m_channel.Height; y++) + for(y = 0; y < m_channel.Height; y++) { sum += m_channel[x, y]; if (max < m_channel[x, y]) @@ -1164,13 +1512,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain double avg = sum / (m_channel.Height * m_channel.Width); - m_log.Info("Channel " + m_channel.Width + "x" + m_channel.Height); - m_log.Info("max/min/avg/sum: " + max + "/" + min + "/" + avg + "/" + sum); + MainConsole.Instance.OutputFormat("Channel {0}x{1}", m_channel.Width, m_channel.Height); + MainConsole.Instance.OutputFormat("max/min/avg/sum: {0}/{1}/{2}/{3}", max, min, avg, sum); } private void InterfaceEnableExperimentalBrushes(Object[] args) { - if ((bool) args[0]) + if ((bool)args[0]) { m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere(); m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere(); @@ -1185,28 +1533,30 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InterfaceRunPluginEffect(Object[] args) { string firstArg = (string)args[0]; + if (firstArg == "list") { - m_log.Info("List of loaded plugins"); - foreach (KeyValuePair kvp in m_plugineffects) + MainConsole.Instance.Output("List of loaded plugins"); + foreach(KeyValuePair kvp in m_plugineffects) { - m_log.Info(kvp.Key); + MainConsole.Instance.Output(kvp.Key); } return; } + if (firstArg == "reload") { LoadPlugins(); return; } + if (m_plugineffects.ContainsKey(firstArg)) { m_plugineffects[firstArg].RunEffect(m_channel); - CheckForTerrainUpdates(); } else { - m_log.Warn("No such plugin effect loaded."); + MainConsole.Instance.Output("WARNING: No such plugin effect {0} loaded.", firstArg); } } @@ -1295,12 +1645,17 @@ namespace OpenSim.Region.CoreModules.World.Terrain new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats, "Shows some information about the regions heightmap for debugging purposes."); + Command showCommand = + new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow, + "Shows terrain height at a given co-ordinate."); + showCommand.AddArgument("point", "point in , format with no spaces (e.g. 45,45)", "String"); + Command experimentalBrushesCommand = new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes, "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time."); experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean"); - //Plugins + // Plugins Command pluginRunCommand = new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect"); pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String"); @@ -1316,6 +1671,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_commander.RegisterCommand("bake", bakeRegionCommand); m_commander.RegisterCommand("revert", revertRegionCommand); m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand); + m_commander.RegisterCommand("show", showCommand); m_commander.RegisterCommand("stats", showDebugStatsCommand); m_commander.RegisterCommand("effect", pluginRunCommand); m_commander.RegisterCommand("flip", flipCommand); @@ -1325,10 +1681,66 @@ namespace OpenSim.Region.CoreModules.World.Terrain // Add this to our scene so scripts can call these functions m_scene.RegisterModuleCommander(m_commander); + + // Add Modify command to Scene, since Command object requires fixed-length arglists + m_scene.AddCommand("Terrain", this, "terrain modify", + "terrain modify [] []", + "Modifies the terrain as instructed." + + "\nEach operation can be limited to an area of effect:" + + "\n * -ell=x,y,rx[,ry] constrains the operation to an ellipse centred at x,y" + + "\n * -rec=x,y,dx[,dy] constrains the operation to a rectangle based at x,y" + + "\nEach operation can have its effect tapered based on distance from centre:" + + "\n * elliptical operations taper as cones" + + "\n * rectangular operations taper as pyramids" + , + ModifyCommand); + } + public void ModifyCommand(string module, string[] cmd) + { + string result; + Scene scene = SceneManager.Instance.CurrentScene; + if ((scene != null) && (scene != m_scene)) + { + result = String.Empty; + } + else if (cmd.Length > 2) + { + string operationType = cmd[2]; - #endregion + + ITerrainModifier operation; + if (!m_modifyOperations.TryGetValue(operationType, out operation)) + { + result = String.Format("Terrain Modify \"{0}\" not found.", operationType); + } + else if ((cmd.Length > 3) && (cmd[3] == "usage")) + { + result = "Usage: " + operation.GetUsage(); + } + else + { + result = operation.ModifyTerrain(m_channel, cmd); + } + + if (result == String.Empty) + { + result = "Modified terrain"; + m_log.DebugFormat("Performed terrain operation {0}", operationType); + } + } + else + { + result = "Usage: ..."; + } + if (result != String.Empty) + { + MainConsole.Instance.Output(result); + } + } + +#endregion } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainModuleTests.cs b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainModuleTests.cs new file mode 100644 index 0000000..0563ad0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainModuleTests.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 NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Terrain.Tests +{ + public class TerrainModuleTests : OpenSimTestCase + { + [Test] + public void TestTerrainFill() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + //UUID userId = TestHelpers.ParseTail(0x1); + + TerrainModule tm = new TerrainModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, tm); + + // Fillheight of 30 + { + double fillHeight = 30; + + tm.InterfaceFillTerrain(new object[] { fillHeight }); + + double height = scene.Heightmap[128, 128]; + + Assert.AreEqual(fillHeight, height); + } + + // Max fillheight of 30 + // According to http://wiki.secondlife.com/wiki/Tips_for_Creating_Heightfields_and_Details_on_Terrain_RAW_Files#Notes_for_Creating_Height_Field_Maps_for_Second_Life + { + double fillHeight = 508; + + tm.InterfaceFillTerrain(new object[] { fillHeight }); + + double height = scene.Heightmap[128, 128]; + + Assert.AreEqual(fillHeight, height); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs index be719ea..29e80ef 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs @@ -40,10 +40,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain.Tests [Test] public void BrushTest() { + int midRegion = (int)Constants.RegionSize / 2; + + // Create a mask that covers only the left half of the region bool[,] allowMask = new bool[(int)Constants.RegionSize, 256]; int x; int y; - for (x = 0; x < (int)((int)Constants.RegionSize * 0.5f); x++) + for (x = 0; x < midRegion; x++) { for (y = 0; y < (int)Constants.RegionSize; y++) { @@ -57,13 +60,12 @@ namespace OpenSim.Region.CoreModules.World.Terrain.Tests TerrainChannel map = new TerrainChannel((int)Constants.RegionSize, (int)Constants.RegionSize); ITerrainPaintableEffect effect = new RaiseSphere(); - effect.PaintEffect(map, allowMask, (int)Constants.RegionSize * 0.5f, (int)Constants.RegionSize * 0.5f, -1.0, 2, 0.1); - Assert.That(map[127, (int)((int)Constants.RegionSize * 0.5f)] > 0.0, "Raise brush should raising value at this point (127,128)."); - Assert.That(map[124, (int)((int)Constants.RegionSize * 0.5f)] > 0.0, "Raise brush should raising value at this point (124,128)."); - Assert.That(map[123, (int)((int)Constants.RegionSize * 0.5f)] == 0.0, "Raise brush should not change value at this point (123,128)."); - Assert.That(map[128, (int)((int)Constants.RegionSize * 0.5f)] == 0.0, "Raise brush should not change value at this point (128,128)."); - Assert.That(map[0, (int)((int)Constants.RegionSize * 0.5f)] == 0.0, "Raise brush should not change value at this point (0,128)."); - + effect.PaintEffect(map, allowMask, midRegion, midRegion, -1.0, 2, 6.0); + Assert.That(map[127, midRegion] > 0.0, "Raise brush should raising value at this point (127,128)."); + Assert.That(map[125, midRegion] > 0.0, "Raise brush should raising value at this point (124,128)."); + Assert.That(map[120, midRegion] == 0.0, "Raise brush should not change value at this point (120,128)."); + Assert.That(map[128, midRegion] == 0.0, "Raise brush should not change value at this point (128,128)."); + Assert.That(map[0, midRegion] == 0.0, "Raise brush should not change value at this point (0,128)."); // // Test LowerSphere // @@ -77,13 +79,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain.Tests } effect = new LowerSphere(); - effect.PaintEffect(map, allowMask, ((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), -1.0, 2, 6.0); - Assert.That(map[127, (int)((int)Constants.RegionSize * 0.5f)] >= 0.0, "Lower should not lowering value below 0.0 at this point (127,128)."); - Assert.That(map[127, (int)((int)Constants.RegionSize * 0.5f)] == 0.0, "Lower brush should lowering value to 0.0 at this point (127,128)."); - Assert.That(map[124, (int)((int)Constants.RegionSize * 0.5f)] < 1.0, "Lower brush should lowering value at this point (124,128)."); - Assert.That(map[123, (int)((int)Constants.RegionSize * 0.5f)] == 1.0, "Lower brush should not change value at this point (123,128)."); - Assert.That(map[128, (int)((int)Constants.RegionSize * 0.5f)] == 1.0, "Lower brush should not change value at this point (128,128)."); - Assert.That(map[0, (int)((int)Constants.RegionSize * 0.5f)] == 1.0, "Lower brush should not change value at this point (0,128)."); + effect.PaintEffect(map, allowMask, midRegion, midRegion, -1.0, 2, 6.0); + Assert.That(map[127, midRegion] >= 0.0, "Lower should not lowering value below 0.0 at this point (127,128)."); + Assert.That(map[127, midRegion] == 0.0, "Lower brush should lowering value to 0.0 at this point (127,128)."); + Assert.That(map[125, midRegion] < 1.0, "Lower brush should lowering value at this point (124,128)."); + Assert.That(map[120, midRegion] == 1.0, "Lower brush should not change value at this point (120,128)."); + Assert.That(map[128, midRegion] == 1.0, "Lower brush should not change value at this point (128,128)."); + Assert.That(map[0, midRegion] == 1.0, "Lower brush should not change value at this point (0,128)."); } [Test] @@ -100,10 +102,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain.Tests x[0, 0] -= 1.0; Assert.That(x[0, 0] == 4.0, "Terrain addition/subtraction error."); - x[0, 0] = Math.PI; - double[,] doublesExport = x.GetDoubles(); - Assert.That(doublesExport[0, 0] == Math.PI, "Export to double[,] array not working correctly."); - x[0, 0] = 1.0; float[] floatsExport = x.GetFloatsSerialised(); Assert.That(floatsExport[0] == 1.0f, "Export to float[] not working correctly."); diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs index df5ac92..9534ad3 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -32,6 +32,7 @@ using System.Drawing.Imaging; using log4net; using OpenMetaverse; using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; using OpenSim.Services.Interfaces; namespace OpenSim.Region.CoreModules.World.Warp3DMap @@ -66,261 +67,271 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap #endregion Constants private static readonly ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + private static string LogHeader = "[WARP3D TERRAIN SPLAT]"; /// /// Builds a composited terrain texture given the region texture /// and heightmap settings /// - /// Terrain heightmap + /// Terrain heightmap /// Region information including terrain texture parameters - /// A composited 256x256 RGB texture ready for rendering + /// A 256x256 square RGB texture ready for rendering /// Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting + /// Note we create a 256x256 dimension texture even if the actual terrain is larger. /// - public static Bitmap Splat(float[] heightmap, UUID[] textureIDs, float[] startHeights, float[] heightRanges, Vector3d regionPosition, IAssetService assetService, bool textureTerrain) + public static Bitmap Splat(ITerrainChannel terrain, + UUID[] textureIDs, float[] startHeights, float[] heightRanges, + Vector3d regionPosition, IAssetService assetService, bool textureTerrain) { - Debug.Assert(heightmap.Length == 256 * 256); Debug.Assert(textureIDs.Length == 4); Debug.Assert(startHeights.Length == 4); Debug.Assert(heightRanges.Length == 4); Bitmap[] detailTexture = new Bitmap[4]; - Bitmap output = null; - BitmapData outputData = null; - try + if (textureTerrain) { - if (textureTerrain) + // Swap empty terrain textureIDs with default IDs + for (int i = 0; i < textureIDs.Length; i++) { - // Swap empty terrain textureIDs with default IDs - for (int i = 0; i < textureIDs.Length; i++) - { - if (textureIDs[i] == UUID.Zero) - textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i]; - } - - #region Texture Fetching - - if (assetService != null) + if (textureIDs[i] == UUID.Zero) + textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i]; + } + + #region Texture Fetching + + if (assetService != null) + { + for (int i = 0; i < 4; i++) { - for (int i = 0; i < 4; i++) + AssetBase asset; + UUID cacheID = UUID.Combine(TERRAIN_CACHE_MAGIC, textureIDs[i]); + + // Try to fetch a cached copy of the decoded/resized version of this texture + asset = assetService.GetCached(cacheID.ToString()); + if (asset != null) + { + try + { + using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) + detailTexture[i] = (Bitmap)Image.FromStream(stream); + } + catch (Exception ex) + { + m_log.Warn("Failed to decode cached terrain texture " + cacheID + + " (textureID: " + textureIDs[i] + "): " + ex.Message); + } + } + + if (detailTexture[i] == null) { - AssetBase asset; - UUID cacheID = UUID.Combine(TERRAIN_CACHE_MAGIC, textureIDs[i]); - - // Try to fetch a cached copy of the decoded/resized version of this texture - asset = assetService.GetCached(cacheID.ToString()); + // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG + asset = assetService.Get(textureIDs[i].ToString()); if (asset != null) { -// m_log.DebugFormat( -// "[TERRAIN SPLAT]: Got asset service cached terrain texture {0} {1}", i, asset.ID); +// m_log.DebugFormat( +// "[TERRAIN SPLAT]: Got cached original JPEG2000 terrain texture {0} {1}", i, asset.ID); - try - { - using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) - detailTexture[i] = (Bitmap)Image.FromStream(stream); - } + try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); } catch (Exception ex) { - m_log.Warn("Failed to decode cached terrain texture " + cacheID + - " (textureID: " + textureIDs[i] + "): " + ex.Message); + m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message); } } - - if (detailTexture[i] == null) - { - // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG - asset = assetService.Get(textureIDs[i].ToString()); - if (asset != null) - { -// m_log.DebugFormat( -// "[TERRAIN SPLAT]: Got cached original JPEG2000 terrain texture {0} {1}", i, asset.ID); - try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); } - catch (Exception ex) + if (detailTexture[i] != null) + { + // Make sure this texture is the correct size, otherwise resize + if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) + { + using (Bitmap origBitmap = detailTexture[i]) { - m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message); + detailTexture[i] = ImageUtils.ResizeImage(origBitmap, 256, 256); } } - - if (detailTexture[i] != null) - { - // Make sure this texture is the correct size, otherwise resize - if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) - { - using (Bitmap origBitmap = detailTexture[i]) - { - detailTexture[i] = ImageUtils.ResizeImage(origBitmap, 256, 256); - } - } - - // Save the decoded and resized texture to the cache - byte[] data; - using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) - { - detailTexture[i].Save(stream, ImageFormat.Png); - data = stream.ToArray(); - } - - // Cache a PNG copy of this terrain texture - AssetBase newAsset = new AssetBase - { - Data = data, - Description = "PNG", - Flags = AssetFlags.Collectable, - FullID = cacheID, - ID = cacheID.ToString(), - Local = true, - Name = String.Empty, - Temporary = true, - Type = (sbyte)AssetType.Unknown - }; - newAsset.Metadata.ContentType = "image/png"; - assetService.Store(newAsset); + + // Save the decoded and resized texture to the cache + byte[] data; + using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) + { + detailTexture[i].Save(stream, ImageFormat.Png); + data = stream.ToArray(); } + + // Cache a PNG copy of this terrain texture + AssetBase newAsset = new AssetBase + { + Data = data, + Description = "PNG", + Flags = AssetFlags.Collectable, + FullID = cacheID, + ID = cacheID.ToString(), + Local = true, + Name = String.Empty, + Temporary = true, + Type = (sbyte)AssetType.Unknown + }; + newAsset.Metadata.ContentType = "image/png"; + assetService.Store(newAsset); } } } - - #endregion Texture Fetching } - - // Fill in any missing textures with a solid color - for (int i = 0; i < 4; i++) + + #endregion Texture Fetching + } + + // Fill in any missing textures with a solid color + for (int i = 0; i < 4; i++) + { + if (detailTexture[i] == null) { - if (detailTexture[i] == null) + m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color", + LogHeader, i); + // Create a solid color texture for this layer + detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); + using (Graphics gfx = Graphics.FromImage(detailTexture[i])) { -// m_log.DebugFormat( -// "[TERRAIN SPLAT]: Generating solid colour for missing texture {0}", i); - - // Create a solid color texture for this layer - detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); - using (Graphics gfx = Graphics.FromImage(detailTexture[i])) - { - using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i])) - gfx.FillRectangle(brush, 0, 0, 256, 256); - } + using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i])) + gfx.FillRectangle(brush, 0, 0, 256, 256); } } - - #region Layer Map - - float[] layermap = new float[256 * 256]; - - for (int y = 0; y < 256; y++) + else { - for (int x = 0; x < 256; x++) + if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) { - float height = heightmap[y * 256 + x]; - - float pctX = (float)x / 255f; - float pctY = (float)y / 255f; - - // Use bilinear interpolation between the four corners of start height and - // height range to select the current values at this position - float startHeight = ImageUtils.Bilinear( - startHeights[0], - startHeights[2], - startHeights[1], - startHeights[3], - pctX, pctY); - startHeight = Utils.Clamp(startHeight, 0f, 255f); - - float heightRange = ImageUtils.Bilinear( - heightRanges[0], - heightRanges[2], - heightRanges[1], - heightRanges[3], - pctX, pctY); - heightRange = Utils.Clamp(heightRange, 0f, 255f); - - // Generate two frequencies of perlin noise based on our global position - // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting - Vector3 vec = new Vector3 - ( - ((float)regionPosition.X + x) * 0.20319f, - ((float)regionPosition.Y + y) * 0.20319f, - height * 0.25f - ); - - float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f; - float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f; - float noise = (lowFreq + highFreq) * 2f; - - // Combine the current height, generated noise, start height, and height range parameters, then scale all of it - float layer = ((height + noise - startHeight) / heightRange) * 4f; - if (Single.IsNaN(layer)) layer = 0f; - layermap[y * 256 + x] = Utils.Clamp(layer, 0f, 3f); + detailTexture[i] = ResizeBitmap(detailTexture[i], 256, 256); } } - - #endregion Layer Map - - #region Texture Compositing - - output = new Bitmap(256, 256, PixelFormat.Format24bppRgb); - outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); - - unsafe + } + + #region Layer Map + + float[,] layermap = new float[256, 256]; + + // Scale difference between actual region size and the 256 texture being created + int xFactor = terrain.Width / 256; + int yFactor = terrain.Height / 256; + + // Create 'layermap' where each value is the fractional layer number to place + // at that point. For instance, a value of 1.345 gives the blending of + // layer 1 and layer 2 for that point. + for (int y = 0; y < 256; y++) + { + for (int x = 0; x < 256; x++) { - // Get handles to all of the texture data arrays - BitmapData[] datas = new BitmapData[] - { - detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat), - detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat), - detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat), - detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) - }; - - int[] comps = new int[] - { - (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3 - }; - - for (int y = 0; y < 256; y++) - { - for (int x = 0; x < 256; x++) - { - float layer = layermap[y * 256 + x]; - - // Select two textures - int l0 = (int)Math.Floor(layer); - int l1 = Math.Min(l0 + 1, 3); - - byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0]; - byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1]; - byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3; - - float aB = *(ptrA + 0); - float aG = *(ptrA + 1); - float aR = *(ptrA + 2); - - float bB = *(ptrB + 0); - float bG = *(ptrB + 1); - float bR = *(ptrB + 2); - - float layerDiff = layer - l0; - - // Interpolate between the two selected textures - *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB)); - *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG)); - *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR)); - } - } - - for (int i = 0; i < 4; i++) - detailTexture[i].UnlockBits(datas[i]); + float height = (float)terrain[x * xFactor, y * yFactor]; + + float pctX = (float)x / 255f; + float pctY = (float)y / 255f; + + // Use bilinear interpolation between the four corners of start height and + // height range to select the current values at this position + float startHeight = ImageUtils.Bilinear( + startHeights[0], + startHeights[2], + startHeights[1], + startHeights[3], + pctX, pctY); + startHeight = Utils.Clamp(startHeight, 0f, 255f); + + float heightRange = ImageUtils.Bilinear( + heightRanges[0], + heightRanges[2], + heightRanges[1], + heightRanges[3], + pctX, pctY); + heightRange = Utils.Clamp(heightRange, 0f, 255f); + + // Generate two frequencies of perlin noise based on our global position + // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting + Vector3 vec = new Vector3 + ( + ((float)regionPosition.X + (x * xFactor)) * 0.20319f, + ((float)regionPosition.Y + (y * yFactor)) * 0.20319f, + height * 0.25f + ); + + float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f; + float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f; + float noise = (lowFreq + highFreq) * 2f; + + // Combine the current height, generated noise, start height, and height range parameters, then scale all of it + float layer = ((height + noise - startHeight) / heightRange) * 4f; + if (Single.IsNaN(layer)) + layer = 0f; + layermap[x, y] = Utils.Clamp(layer, 0f, 3f); } } - finally + + #endregion Layer Map + + #region Texture Compositing + + Bitmap output = new Bitmap(256, 256, PixelFormat.Format24bppRgb); + BitmapData outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); + + // Unsafe work as we lock down the source textures for quicker access and access the + // pixel data directly + unsafe { - for (int i = 0; i < 4; i++) - if (detailTexture[i] != null) - detailTexture[i].Dispose(); + // Get handles to all of the texture data arrays + BitmapData[] datas = new BitmapData[] + { + detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat), + detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat), + detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat), + detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) + }; + + // Compute size of each pixel data (used to address into the pixel data array) + int[] comps = new int[] + { + (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, + (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, + (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, + (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3 + }; + + for (int y = 0; y < 256; y++) + { + for (int x = 0; x < 256; x++) + { + float layer = layermap[x, y]; + + // Select two textures + int l0 = (int)Math.Floor(layer); + int l1 = Math.Min(l0 + 1, 3); + + byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0]; + byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1]; + byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3; + + float aB = *(ptrA + 0); + float aG = *(ptrA + 1); + float aR = *(ptrA + 2); + + float bB = *(ptrB + 0); + float bG = *(ptrB + 1); + float bR = *(ptrB + 2); + + float layerDiff = layer - l0; + + // Interpolate between the two selected textures + *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB)); + *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG)); + *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR)); + } + } + + for (int i = 0; i < detailTexture.Length; i++) + detailTexture[i].UnlockBits(datas[i]); } + for (int i = 0; i < detailTexture.Length; i++) + if (detailTexture[i] != null) + detailTexture[i].Dispose(); + output.UnlockBits(outputData); // We generated the texture upside down, so flip it @@ -331,6 +342,17 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap return output; } + public static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight) + { + m_log.DebugFormat("{0} ResizeBitmap. From <{1},{2}> to <{3},{4}>", + LogHeader, b.Width, b.Height, nWidth, nHeight); + Bitmap result = new Bitmap(nWidth, nHeight); + using (Graphics g = Graphics.FromImage(result)) + g.DrawImage(b, 0, 0, nWidth, nHeight); + b.Dispose(); + return result; + } + public static Bitmap SplatSimple(float[] heightmap) { const float BASE_HSV_H = 93f / 360f; diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs index 5e0dfa7..5f2534b 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs @@ -31,21 +31,25 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Reflection; + using CSJ2K; using Nini.Config; using log4net; using Rednettle.Warp3D; using Mono.Addins; -using OpenMetaverse; -using OpenMetaverse.Imaging; -using OpenMetaverse.Rendering; -using OpenMetaverse.StructuredData; + using OpenSim.Framework; 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 OpenMetaverse; +using OpenMetaverse.Assets; +using OpenMetaverse.Imaging; +using OpenMetaverse.Rendering; +using OpenMetaverse.StructuredData; + using WarpRenderer = global::Warp3D.Warp3D; namespace OpenSim.Region.CoreModules.World.Warp3DMap @@ -58,11 +62,22 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +#pragma warning disable 414 + private static string LogHeader = "[WARP 3D IMAGE MODULE]"; +#pragma warning restore 414 + private Scene m_scene; private IRendering m_primMesher; - private IConfigSource m_config; private Dictionary m_colors = new Dictionary(); - private bool m_useAntiAliasing = false; // TODO: Make this a config option + + private IConfigSource m_config; + private bool m_drawPrimVolume = true; // true if should render the prims on the tile + private bool m_textureTerrain = true; // true if to create terrain splatting texture + private bool m_texturePrims = true; // true if should texture the rendered prims + private float m_texturePrimSize = 48f; // size of prim before we consider texturing it + private bool m_renderMeshes = false; // true if to render meshes rather than just bounding boxes + private bool m_useAntiAliasing = false; // true if to anti-alias the rendered image + private bool m_Enabled = false; #region Region Module interface @@ -71,11 +86,27 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap { m_config = source; - IConfig startupConfig = m_config.Configs["Startup"]; - if (startupConfig.GetString("MapImageModule", "MapImageModule") != "Warp3DImageModule") + string[] configSections = new string[] { "Map", "Startup" }; + + if (Util.GetConfigVarFromSections( + m_config, "MapImageModule", configSections, "MapImageModule") != "Warp3DImageModule") return; m_Enabled = true; + + m_drawPrimVolume + = Util.GetConfigVarFromSections(m_config, "DrawPrimOnMapTile", configSections, m_drawPrimVolume); + m_textureTerrain + = Util.GetConfigVarFromSections(m_config, "TextureOnMapTile", configSections, m_textureTerrain); + m_texturePrims + = Util.GetConfigVarFromSections(m_config, "TexturePrims", configSections, m_texturePrims); + m_texturePrimSize + = Util.GetConfigVarFromSections(m_config, "TexturePrimSize", configSections, m_texturePrimSize); + m_renderMeshes + = Util.GetConfigVarFromSections(m_config, "RenderMeshes", configSections, m_renderMeshes); + m_useAntiAliasing + = Util.GetConfigVarFromSections(m_config, "UseAntiAliasing", configSections, m_useAntiAliasing); + } public void AddRegion(Scene scene) @@ -127,33 +158,28 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap public Bitmap CreateMapTile() { - Vector3 camPos = new Vector3(127.5f, 127.5f, 221.7025033688163f); - Viewport viewport = new Viewport(camPos, -Vector3.UnitZ, 1024f, 0.1f, (int)Constants.RegionSize, (int)Constants.RegionSize, (float)Constants.RegionSize, (float)Constants.RegionSize); + // Vector3 camPos = new Vector3(127.5f, 127.5f, 221.7025033688163f); + // Camera above the middle of the region + Vector3 camPos = new Vector3( + m_scene.RegionInfo.RegionSizeX/2 - 0.5f, + m_scene.RegionInfo.RegionSizeY/2 - 0.5f, + 221.7025033688163f); + // Viewport viewing down onto the region + Viewport viewport = new Viewport(camPos, -Vector3.UnitZ, 1024f, 0.1f, + (int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY, + (float)m_scene.RegionInfo.RegionSizeX, (float)m_scene.RegionInfo.RegionSizeY ); + // Fill the viewport and return the image return CreateMapTile(viewport, false); } public Bitmap CreateViewImage(Vector3 camPos, Vector3 camDir, float fov, int width, int height, bool useTextures) { - Viewport viewport = new Viewport(camPos, camDir, fov, (float)Constants.RegionSize, 0.1f, width, height); + Viewport viewport = new Viewport(camPos, camDir, fov, Constants.RegionSize, 0.1f, width, height); return CreateMapTile(viewport, useTextures); } public Bitmap CreateMapTile(Viewport viewport, bool useTextures) { - bool drawPrimVolume = true; - bool textureTerrain = true; - - try - { - IConfig startupConfig = m_config.Configs["Startup"]; - drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", drawPrimVolume); - textureTerrain = startupConfig.GetBoolean("TextureOnMapTile", textureTerrain); - } - catch - { - m_log.Warn("[WARP 3D IMAGE MODULE]: Failed to load StartupConfig"); - } - m_colors.Clear(); int width = viewport.Width; @@ -166,6 +192,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap } WarpRenderer renderer = new WarpRenderer(); + renderer.CreateScene(width, height); renderer.Scene.autoCalcNormals = false; @@ -197,8 +224,8 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap renderer.Scene.addLight("Light2", new warp_Light(new warp_Vector(-1f, -1f, 1f), 0xffffff, 0, 100, 40)); CreateWater(renderer); - CreateTerrain(renderer, textureTerrain); - if (drawPrimVolume) + CreateTerrain(renderer, m_textureTerrain); + if (m_drawPrimVolume) CreateAllPrims(renderer, useTextures); renderer.Render(); @@ -214,6 +241,18 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // afterwards. It's generally regarded as a bad idea to manually GC. If Warp3D is using lots of memory // then this may be some issue with the Warp3D code itself, though it's also quite possible that generating // this map tile simply takes a lot of memory. + foreach (var o in renderer.Scene.objectData.Values) + { + warp_Object obj = (warp_Object)o; + obj.vertexData = null; + obj.triangleData = null; + } + + renderer.Scene.removeAllObjects(); + renderer = null; + viewport = null; + + m_colors.Clear(); GC.Collect(); m_log.Debug("[WARP 3D IMAGE MODULE]: GC.Collect()"); @@ -240,61 +279,74 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap #region Rendering Methods + // Add a water plane to the renderer. private void CreateWater(WarpRenderer renderer) { float waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; - renderer.AddPlane("Water", 256f * 0.5f); - renderer.Scene.sceneobject("Water").setPos(127.5f, waterHeight, 127.5f); + renderer.AddPlane("Water", m_scene.RegionInfo.RegionSizeX * 0.5f); + renderer.Scene.sceneobject("Water").setPos(m_scene.RegionInfo.RegionSizeX/2 - 0.5f, + waterHeight, + m_scene.RegionInfo.RegionSizeY/2 - 0.5f ); - renderer.AddMaterial("WaterColor", ConvertColor(WATER_COLOR)); - renderer.Scene.material("WaterColor").setReflectivity(0); // match water color with standard map module thanks lkalif - renderer.Scene.material("WaterColor").setTransparency((byte)((1f - WATER_COLOR.A) * 255f)); + warp_Material waterColorMaterial = new warp_Material(ConvertColor(WATER_COLOR)); + waterColorMaterial.setReflectivity(0); // match water color with standard map module thanks lkalif + waterColorMaterial.setTransparency((byte)((1f - WATER_COLOR.A) * 255f)); + renderer.Scene.addMaterial("WaterColor", waterColorMaterial); renderer.SetObjectMaterial("Water", "WaterColor"); } + // Add a terrain to the renderer. + // Note that we create a 'low resolution' 256x256 vertex terrain rather than trying for + // full resolution. This saves a lot of memory especially for very large regions. private void CreateTerrain(WarpRenderer renderer, bool textureTerrain) { ITerrainChannel terrain = m_scene.Heightmap; - float[] heightmap = terrain.GetFloatsSerialised(); + + // 'diff' is the difference in scale between the real region size and the size of terrain we're buiding + float diff = (float)m_scene.RegionInfo.RegionSizeX / 256f; warp_Object obj = new warp_Object(256 * 256, 255 * 255 * 2); - for (int y = 0; y < 256; y++) + // Create all the vertices for the terrain + for (float y = 0; y < m_scene.RegionInfo.RegionSizeY; y += diff) { - for (int x = 0; x < 256; x++) + for (float x = 0; x < m_scene.RegionInfo.RegionSizeX; x += diff) { - int v = y * 256 + x; - float height = heightmap[v]; - - warp_Vector pos = ConvertVector(new Vector3(x, y, height)); - obj.addVertex(new warp_Vertex(pos, (float)x / 255f, (float)(255 - y) / 255f)); + warp_Vector pos = ConvertVector(x, y, (float)terrain[(int)x, (int)y]); + obj.addVertex(new warp_Vertex(pos, + x / (float)m_scene.RegionInfo.RegionSizeX, + (((float)m_scene.RegionInfo.RegionSizeY) - y) / m_scene.RegionInfo.RegionSizeY) ); } } - for (int y = 0; y < 256; y++) + // Now that we have all the vertices, make another pass and create + // the normals for each of the surface triangles and + // create the list of triangle indices. + for (float y = 0; y < m_scene.RegionInfo.RegionSizeY; y += diff) { - for (int x = 0; x < 256; x++) + for (float x = 0; x < m_scene.RegionInfo.RegionSizeX; x += diff) { - if (x < 255 && y < 255) + float newX = x / diff; + float newY = y / diff; + if (newX < 255 && newY < 255) { - int v = y * 256 + x; + int v = (int)newY * 256 + (int)newX; - // Normal - Vector3 v1 = new Vector3(x, y, heightmap[y * 256 + x]); - Vector3 v2 = new Vector3(x + 1, y, heightmap[y * 256 + x + 1]); - Vector3 v3 = new Vector3(x, y + 1, heightmap[(y + 1) * 256 + x]); + // Normal for a triangle made up of three adjacent vertices + Vector3 v1 = new Vector3(newX, newY, (float)terrain[(int)x, (int)y]); + Vector3 v2 = new Vector3(newX + 1, newY, (float)terrain[(int)(x + 1), (int)y]); + Vector3 v3 = new Vector3(newX, newY + 1, (float)terrain[(int)x, ((int)(y + 1))]); warp_Vector norm = ConvertVector(SurfaceNormal(v1, v2, v3)); norm = norm.reverse(); obj.vertex(v).n = norm; - // Triangle 1 + // Make two triangles for each of the squares in the grid of vertices obj.addTriangle( v, v + 1, v + 256); - // Triangle 2 obj.addTriangle( v + 256 + 1, v + 256, @@ -309,7 +361,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap float[] startHeights = new float[4]; float[] heightRanges = new float[4]; - RegionSettings regionInfo = m_scene.RegionInfo.RegionSettings; + OpenSim.Framework.RegionSettings regionInfo = m_scene.RegionInfo.RegionSettings; textureIDs[0] = regionInfo.TerrainTexture1; textureIDs[1] = regionInfo.TerrainTexture2; @@ -327,14 +379,12 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap heightRanges[3] = (float)regionInfo.Elevation2NE; uint globalX, globalY; - Utils.LongToUInts(m_scene.RegionInfo.RegionHandle, out globalX, out globalY); + Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out globalX, out globalY); warp_Texture texture; - using ( Bitmap image - = TerrainSplat.Splat( - heightmap, textureIDs, startHeights, heightRanges, + = TerrainSplat.Splat(terrain, textureIDs, startHeights, heightRanges, new Vector3d(globalX, globalY, 0.0), m_scene.AssetService, textureTerrain)) { texture = new warp_Texture(image); @@ -355,7 +405,6 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap m_scene.ForEachSOG( delegate(SceneObjectGroup group) { - CreatePrim(renderer, group.RootPart, useTextures); foreach (SceneObjectPart child in group.Parts) CreatePrim(renderer, child, useTextures); } @@ -372,8 +421,48 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap if (prim.Scale.LengthSquared() < MIN_SIZE * MIN_SIZE) return; + FacetedMesh renderMesh = null; Primitive omvPrim = prim.Shape.ToOmvPrimitive(prim.OffsetPosition, prim.RotationOffset); - FacetedMesh renderMesh = m_primMesher.GenerateFacetedMesh(omvPrim, DetailLevel.Medium); + + if (m_renderMeshes) + { + if (omvPrim.Sculpt != null && omvPrim.Sculpt.SculptTexture != UUID.Zero) + { + // Try fetchinng the asset + byte[] sculptAsset = m_scene.AssetService.GetData(omvPrim.Sculpt.SculptTexture.ToString()); + if (sculptAsset != null) + { + // Is it a mesh? + if (omvPrim.Sculpt.Type == SculptType.Mesh) + { + AssetMesh meshAsset = new AssetMesh(omvPrim.Sculpt.SculptTexture, sculptAsset); + FacetedMesh.TryDecodeFromAsset(omvPrim, meshAsset, DetailLevel.Highest, out renderMesh); + meshAsset = null; + } + else // It's sculptie + { + IJ2KDecoder imgDecoder = m_scene.RequestModuleInterface(); + if (imgDecoder != null) + { + Image sculpt = imgDecoder.DecodeToImage(sculptAsset); + if (sculpt != null) + { + renderMesh = m_primMesher.GenerateFacetedSculptMesh(omvPrim, (Bitmap)sculpt, + DetailLevel.Medium); + sculpt.Dispose(); + } + } + } + } + } + } + + // If not a mesh or sculptie, try the regular mesher + if (renderMesh == null) + { + renderMesh = m_primMesher.GenerateFacetedMesh(omvPrim, DetailLevel.Medium); + } + if (renderMesh == null) return; @@ -432,7 +521,11 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap Primitive.TextureEntryFace teFace = prim.Shape.Textures.GetFace((uint)i); Color4 faceColor = GetFaceColor(teFace); - string materialName = GetOrCreateMaterial(renderer, faceColor); + string materialName = String.Empty; + if (m_texturePrims && prim.Scale.LengthSquared() > m_texturePrimSize*m_texturePrimSize) + materialName = GetOrCreateMaterial(renderer, faceColor, teFace.TextureID); + else + materialName = GetOrCreateMaterial(renderer, faceColor); faceObj.transform(m); faceObj.setPos(primPos); @@ -521,10 +614,59 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap return name; } + public string GetOrCreateMaterial(WarpRenderer renderer, Color4 faceColor, UUID textureID) + { + string materialName = "Color-" + faceColor.ToString() + "-Texture-" + textureID.ToString(); + + if (renderer.Scene.material(materialName) == null) + { + renderer.AddMaterial(materialName, ConvertColor(faceColor)); + if (faceColor.A < 1f) + { + renderer.Scene.material(materialName).setTransparency((byte) ((1f - faceColor.A)*255f)); + } + warp_Texture texture = GetTexture(textureID); + if (texture != null) + renderer.Scene.material(materialName).setTexture(texture); + } + + return materialName; + } + + private warp_Texture GetTexture(UUID id) + { + warp_Texture ret = null; + + byte[] asset = m_scene.AssetService.GetData(id.ToString()); + + if (asset != null) + { + IJ2KDecoder imgDecoder = m_scene.RequestModuleInterface(); + + try + { + using (Bitmap img = (Bitmap)imgDecoder.DecodeToImage(asset)) + ret = new warp_Texture(img); + } + catch (Exception e) + { + m_log.Warn(string.Format("[WARP 3D IMAGE MODULE]: Failed to decode asset {0}, exception ", id), e); + } + } + + return ret; + } + #endregion Rendering Methods #region Static Helpers + // Note: axis change. + private static warp_Vector ConvertVector(float x, float y, float z) + { + return new warp_Vector(x, z, y); + } + private static warp_Vector ConvertVector(Vector3 vector) { return new warp_Vector(vector.X, vector.Z, vector.Y); diff --git a/OpenSim/Region/CoreModules/World/Wind/WindModule.cs b/OpenSim/Region/CoreModules/World/Wind/WindModule.cs index 9de588c..35014f5 100644 --- a/OpenSim/Region/CoreModules/World/Wind/WindModule.cs +++ b/OpenSim/Region/CoreModules/World/Wind/WindModule.cs @@ -216,13 +216,13 @@ namespace OpenSim.Region.CoreModules // FIXME: If console region is root then this will be printed by every module. Currently, there is no // way to prevent this, short of making the entire module shared (which is complete overkill). // One possibility is to return a bool to signal whether the module has completely handled the command - m_log.InfoFormat("[WIND]: Please change to a specific region in order to set Sun parameters."); + MainConsole.Instance.Output("Please change to a specific region in order to set Sun parameters."); return; } if (m_scene.ConsoleScene() != m_scene) { - m_log.InfoFormat("[WIND]: Console Scene is not my scene."); + MainConsole.Instance.Output("Console Scene is not my scene."); return; } } @@ -233,7 +233,9 @@ namespace OpenSim.Region.CoreModules private void HandleConsoleCommand(string module, string[] cmdparams) { ValidateConsole(); - m_log.Info("[WIND] The wind command can be used to change the currently active wind model plugin and update the parameters for wind plugins."); + + MainConsole.Instance.Output( + "The wind command can be used to change the currently active wind model plugin and update the parameters for wind plugins."); } /// @@ -246,7 +248,9 @@ namespace OpenSim.Region.CoreModules if ((cmdparams.Length != 4) || !cmdparams[1].Equals("base")) { - m_log.Info("[WIND] Invalid parameters to change parameters for Wind module base, usage: wind base "); + MainConsole.Instance.Output( + "Invalid parameters to change parameters for Wind module base, usage: wind base "); + return; } @@ -261,7 +265,9 @@ namespace OpenSim.Region.CoreModules } else { - m_log.InfoFormat("[WIND] Invalid value {0} specified for {1}", cmdparams[3], cmdparams[2]); + MainConsole.Instance.OutputFormat( + "Invalid value {0} specified for {1}", cmdparams[3], cmdparams[2]); + return; } @@ -271,22 +277,23 @@ namespace OpenSim.Region.CoreModules if (desiredPlugin.Equals(m_activeWindPlugin.Name)) { - m_log.InfoFormat("[WIND] Wind model plugin {0} is already active", cmdparams[3]); + MainConsole.Instance.OutputFormat("Wind model plugin {0} is already active", cmdparams[3]); + return; } if (m_availableWindPlugins.ContainsKey(desiredPlugin)) { m_activeWindPlugin = m_availableWindPlugins[cmdparams[3]]; - m_log.InfoFormat("[WIND] {0} wind model plugin now active", m_activeWindPlugin.Name); + + MainConsole.Instance.OutputFormat("{0} wind model plugin now active", m_activeWindPlugin.Name); } else { - m_log.InfoFormat("[WIND] Could not find wind model plugin {0}", desiredPlugin); + MainConsole.Instance.OutputFormat("Could not find wind model plugin {0}", desiredPlugin); } break; } - } /// @@ -300,7 +307,7 @@ namespace OpenSim.Region.CoreModules if ((cmdparams.Length != 4) && (cmdparams.Length != 3)) { - m_log.Info("[WIND] Usage: wind [value]"); + MainConsole.Instance.Output("Usage: wind [value]"); return; } @@ -311,16 +318,17 @@ namespace OpenSim.Region.CoreModules { if (!float.TryParse(cmdparams[3], out value)) { - m_log.InfoFormat("[WIND] Invalid value {0}", cmdparams[3]); + MainConsole.Instance.OutputFormat("Invalid value {0}", cmdparams[3]); } try { WindParamSet(plugin, param, value); + MainConsole.Instance.OutputFormat("{0} set to {1}", param, value); } catch (Exception e) { - m_log.InfoFormat("[WIND] {0}", e.Message); + MainConsole.Instance.OutputFormat("{0}", e.Message); } } else @@ -328,11 +336,11 @@ namespace OpenSim.Region.CoreModules try { value = WindParamGet(plugin, param); - m_log.InfoFormat("[WIND] {0} : {1}", param, value); + MainConsole.Instance.OutputFormat("{0} : {1}", param, value); } catch (Exception e) { - m_log.InfoFormat("[WIND] {0}", e.Message); + MainConsole.Instance.OutputFormat("{0}", e.Message); } } @@ -366,13 +374,11 @@ namespace OpenSim.Region.CoreModules { IWindModelPlugin windPlugin = m_availableWindPlugins[plugin]; windPlugin.WindParamSet(param, value); - m_log.InfoFormat("[WIND] {0} set to {1}", param, value); } else { throw new Exception(String.Format("Could not find plugin {0}", plugin)); } - } public float WindParamGet(string plugin, string param) diff --git a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs index 708a9a2..d862f18 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs @@ -49,6 +49,18 @@ namespace OpenSim.Region.CoreModules.World.WorldMap List m_scenes = new List(); List m_Clients; + IWorldMapModule m_WorldMap; + IWorldMapModule WorldMap + { + get + { + if (m_WorldMap == null) + m_WorldMap = m_scene.RequestModuleInterface(); + return m_WorldMap; + } + + } + #region ISharedRegionModule Members public void Initialise(IConfigSource source) { @@ -64,6 +76,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap m_scenes.Add(scene); scene.EventManager.OnNewClient += OnNewClient; m_Clients = new List(); + } public void RemoveRegion(Scene scene) @@ -129,7 +142,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap private void OnMapNameRequest(IClientAPI remoteClient, string mapName, uint flags) { List blocks = new List(); - MapBlockData data; if (mapName.Length < 3 || (mapName.EndsWith("#") && mapName.Length < 4)) { // final block, closing the search result @@ -143,50 +155,51 @@ namespace OpenSim.Region.CoreModules.World.WorldMap } - //m_log.DebugFormat("MAP NAME=({0})", mapName); - - // Hack to get around the fact that ll V3 now drops the port from the - // map name. See https://jira.secondlife.com/browse/VWR-28570 - // - // Caller, use this magic form instead: - // secondlife://http|!!mygrid.com|8002|Region+Name/128/128 - // or url encode if possible. - // the hacks we do with this viewer... - // + List regionInfos = m_scene.GridService.GetRegionsByName(m_scene.RegionInfo.ScopeID, mapName, 20); + string mapNameOrig = mapName; - if (mapName.Contains("|")) - mapName = mapName.Replace('|', ':'); - if (mapName.Contains("+")) - mapName = mapName.Replace('+', ' '); - if (mapName.Contains("!")) - mapName = mapName.Replace('!', '/'); + if (regionInfos.Count == 0) + { + // Hack to get around the fact that ll V3 now drops the port from the + // map name. See https://jira.secondlife.com/browse/VWR-28570 + // + // Caller, use this magic form instead: + // secondlife://http|!!mygrid.com|8002|Region+Name/128/128 + // or url encode if possible. + // the hacks we do with this viewer... + // + if (mapName.Contains("|")) + mapName = mapName.Replace('|', ':'); + if (mapName.Contains("+")) + mapName = mapName.Replace('+', ' '); + if (mapName.Contains("!")) + mapName = mapName.Replace('!', '/'); + + if (mapName != mapNameOrig) + regionInfos = m_scene.GridService.GetRegionsByName(m_scene.RegionInfo.ScopeID, mapName, 20); + } - // try to fetch from GridServer - List regionInfos = m_scene.GridService.GetRegionsByName(m_scene.RegionInfo.ScopeID, mapName, 20); - m_log.DebugFormat("[MAPSEARCHMODULE]: search {0} returned {1} regions. Flags={2}", mapName, regionInfos.Count, flags); + if (regionInfos.Count > 0) { foreach (GridRegion info in regionInfos) { - data = new MapBlockData(); - data.Agents = 0; - data.Access = info.Access; - if (flags == 2) // V2 sends this - data.MapImageId = UUID.Zero; - else - data.MapImageId = info.TerrainImage; - // ugh! V2-3 is very sensitive about the result being - // exactly the same as the requested name - if (regionInfos.Count == 1 && mapNameOrig.Contains("|") || mapNameOrig.Contains("+")) - data.Name = mapNameOrig; + if ((flags & 2) == 2) // V2 sends this + { + List datas = WorldMap.Map2BlockFromGridRegion(info, flags); + // ugh! V2-3 is very sensitive about the result being + // exactly the same as the requested name + if (regionInfos.Count == 1 && (mapName != mapNameOrig)) + datas.ForEach(d => d.Name = mapNameOrig); + + blocks.AddRange(datas); + } else - data.Name = info.RegionName; - data.RegionFlags = 0; // TODO not used? - data.WaterHeight = 0; // not used - data.X = (ushort)(info.RegionLocX / Constants.RegionSize); - data.Y = (ushort)(info.RegionLocY / Constants.RegionSize); - blocks.Add(data); + { + MapBlockData data = WorldMap.MapBlockFromGridRegion(info, flags); + blocks.Add(data); + } } } @@ -204,8 +217,9 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { if (regionInfos.Count == 0) remoteClient.SendAlertMessage("No regions found with that name."); - else if (regionInfos.Count == 1) - remoteClient.SendAlertMessage("Region found!"); + // this seems unnecessary because found regions will show up in the search results + //else if (regionInfos.Count == 1) + // remoteClient.SendAlertMessage("Region found!"); } } @@ -214,7 +228,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap // final block, closing the search result MapBlockData data = new MapBlockData(); data.Agents = 0; - data.Access = 255; + data.Access = (byte)SimAccess.NonExistent; data.MapImageId = UUID.Zero; data.Name = ""; data.RegionFlags = 0; diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index e2f525c..db1187e 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -59,13 +59,18 @@ namespace OpenSim.Region.CoreModules.World.WorldMap [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WorldMapModule")] public class WorldMapModule : INonSharedRegionModule, IWorldMapModule { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +#pragma warning disable 414 + private static string LogHeader = "[WORLD MAP]"; +#pragma warning restore 414 private static readonly string DEFAULT_WORLD_MAP_EXPORT_PATH = "exportmap.jpg"; private static readonly UUID STOP_UUID = UUID.Random(); private static readonly string m_mapLayerPath = "0001/"; + private IMapImageGenerator m_mapImageGenerator; + private IMapImageUploadModule m_mapImageServiceModule; + private OpenSim.Framework.BlockingQueue requests = new OpenSim.Framework.BlockingQueue(); protected Scene m_scene; @@ -81,19 +86,24 @@ namespace OpenSim.Region.CoreModules.World.WorldMap private List m_rootAgents = new List(); private volatile bool threadrunning = false; + private IServiceThrottleModule m_ServiceThrottle; + //private int CacheRegionsDistance = 256; #region INonSharedRegionModule Members public virtual void Initialise (IConfigSource config) { - IConfig startupConfig = config.Configs["Startup"]; - if (startupConfig.GetString("WorldMapModule", "WorldMap") == "WorldMap") + string[] configSections = new string[] { "Map", "Startup" }; + + if (Util.GetConfigVarFromSections( + config, "WorldMapModule", configSections, "WorldMap") == "WorldMap") m_Enabled = true; - blacklistTimeout = startupConfig.GetInt("BlacklistTimeout", 10*60) * 1000; + blacklistTimeout + = Util.GetConfigVarFromSections(config, "BlacklistTimeout", configSections, 10 * 60) * 1000; } - public virtual void AddRegion (Scene scene) + public virtual void AddRegion(Scene scene) { if (!m_Enabled) return; @@ -109,6 +119,11 @@ namespace OpenSim.Region.CoreModules.World.WorldMap "export-map []", "Save an image of the world map", HandleExportWorldMapConsoleCommand); + m_scene.AddCommand( + "Regions", this, "generate map", + "generate map", + "Generates and stores a new maptile.", HandleGenerateMapConsoleCommand); + AddHandlers(); } } @@ -128,8 +143,14 @@ namespace OpenSim.Region.CoreModules.World.WorldMap public virtual void RegionLoaded (Scene scene) { - } + if (!m_Enabled) + return; + m_ServiceThrottle = scene.RequestModuleInterface(); + + m_mapImageGenerator = m_scene.RequestModuleInterface(); + m_mapImageServiceModule = m_scene.RequestModuleInterface(); + } public virtual void Close() { @@ -156,7 +177,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap regionimage = regionimage.Replace("-", ""); m_log.Info("[WORLD MAP]: JPEG Map location: " + m_scene.RegionInfo.ServerURI + "index.php?method=" + regionimage); - MainServer.Instance.AddHTTPHandler(regionimage, OnHTTPGetMapImage); + MainServer.Instance.AddHTTPHandler(regionimage, + new GenericHTTPDOSProtector(OnHTTPGetMapImage, OnHTTPThrottled, new BasicDosProtectorOptions() + { + AllowXForwardedFor = false, + ForgetTimeSpan = TimeSpan.FromMinutes(2), + MaxRequestsInTimeframe = 4, + ReportingName = "MAPDOSPROTECTOR", + RequestTimeSpan = TimeSpan.FromSeconds(10), + ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod + }).Process); MainServer.Instance.AddLLSDHandler( "/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), HandleRemoteMapItemRequest); @@ -167,13 +197,13 @@ namespace OpenSim.Region.CoreModules.World.WorldMap m_scene.EventManager.OnMakeRootAgent += MakeRootAgent; m_scene.EventManager.OnRegionUp += OnRegionUp; - StartThread(new object()); +// StartThread(new object()); } // this has to be called with a lock on m_scene protected virtual void RemoveHandlers() { - StopThread(); +// StopThread(); m_scene.EventManager.OnRegionUp -= OnRegionUp; m_scene.EventManager.OnMakeRootAgent -= MakeRootAgent; @@ -259,15 +289,15 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { List mapBlocks = new List(); ; + // Get regions that are within 8 regions of here List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, - (int)(m_scene.RegionInfo.RegionLocX - 8) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocX + 8) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocY - 8) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocY + 8) * (int)Constants.RegionSize); + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX - 8), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX + 8), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY - 8), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY + 8) ); foreach (GridRegion r in regions) { - MapBlockData block = new MapBlockData(); - MapBlockFromGridRegion(block, r, 0); + MapBlockData block = MapBlockFromGridRegion(r, 0); mapBlocks.Add(block); } avatarPresence.ControllingClient.SendMapBlock(mapBlocks, 0); @@ -351,7 +381,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap // m_log.Debug("[WORLD MAP]: Starting remote MapItem request thread"); - Watchdog.StartThread( + WorkManager.StartThread( process, string.Format("MapItemRequestThread ({0})", m_scene.RegionInfo.RegionName), ThreadPriority.BelowNormal, @@ -387,24 +417,23 @@ namespace OpenSim.Region.CoreModules.World.WorldMap } uint xstart = 0; uint ystart = 0; - Utils.LongToUInts(m_scene.RegionInfo.RegionHandle, out xstart, out ystart); - if (itemtype == 6) // Service 6 right now (MAP_ITEM_AGENTS_LOCATION; green dots) + Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out xstart, out ystart); + if (itemtype == (int)GridItemType.AgentLocations) { if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) { - // Local Map Item Request + // Just requesting map info about the current, local region int tc = Environment.TickCount; List mapitems = new List(); mapItemReply mapitem = new mapItemReply(); if (m_scene.GetRootAgentCount() <= 1) { - mapitem = new mapItemReply(); - mapitem.x = (uint)(xstart + 1); - mapitem.y = (uint)(ystart + 1); - mapitem.id = UUID.Zero; - mapitem.name = Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()); - mapitem.Extra = 0; - mapitem.Extra2 = 0; + mapitem = new mapItemReply( + xstart + 1, + ystart + 1, + UUID.Zero, + Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()), + 0, 0); mapitems.Add(mapitem); } else @@ -414,13 +443,12 @@ namespace OpenSim.Region.CoreModules.World.WorldMap // Don't send a green dot for yourself if (sp.UUID != remoteClient.AgentId) { - mapitem = new mapItemReply(); - mapitem.x = (uint)(xstart + sp.AbsolutePosition.X); - mapitem.y = (uint)(ystart + sp.AbsolutePosition.Y); - mapitem.id = UUID.Zero; - mapitem.name = Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()); - mapitem.Extra = 1; - mapitem.Extra2 = 0; + mapitem = new mapItemReply( + xstart + (uint)sp.AbsolutePosition.X, + ystart + (uint)sp.AbsolutePosition.Y, + UUID.Zero, + Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()), + 1, 0); mapitems.Add(mapitem); } }); @@ -435,7 +463,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); } } - else if (itemtype == 7) // Service 7 (MAP_ITEM_LAND_FOR_SALE) + else if (itemtype == (int)GridItemType.LandForSale) // Service 7 (MAP_ITEM_LAND_FOR_SALE) { if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) { @@ -465,14 +493,14 @@ namespace OpenSim.Region.CoreModules.World.WorldMap float x = (min.X+max.X)/2; float y = (min.Y+max.Y)/2; - mapitem = new mapItemReply(); - mapitem.x = (uint)(xstart + x); - mapitem.y = (uint)(ystart + y); - // mapitem.z = (uint)m_scene.GetGroundHeight(x,y); - mapitem.id = parcel.GlobalID; - mapitem.name = parcel.Name; - mapitem.Extra = parcel.Area; - mapitem.Extra2 = parcel.SalePrice; + mapitem = new mapItemReply( + xstart + (uint)x, + ystart + (uint)y, + parcel.GlobalID, + parcel.Name, + parcel.Area, + parcel.SalePrice + ); mapitems.Add(mapitem); } } @@ -487,7 +515,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); } } - else if (itemtype == 1) // Service 1 (MAP_ITEM_TELEHUB) + else if (itemtype == (int)GridItemType.Telehub) // Service 1 (MAP_ITEM_TELEHUB) { if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) { @@ -497,13 +525,14 @@ namespace OpenSim.Region.CoreModules.World.WorldMap SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject); if (sog != null) { - mapitem = new mapItemReply(); - mapitem.x = (uint)(xstart + sog.AbsolutePosition.X); - mapitem.y = (uint)(ystart + sog.AbsolutePosition.Y); - mapitem.id = UUID.Zero; - mapitem.name = sog.Name; - mapitem.Extra = 0; // color (not used) - mapitem.Extra2 = 0; // 0 = telehub / 1 = infohub + mapitem = new mapItemReply( + xstart + (uint)sog.AbsolutePosition.X, + ystart + (uint)sog.AbsolutePosition.Y, + UUID.Zero, + sog.Name, + 0, // color (not used) + 0 // 0 = telehub / 1 = infohub + ); mapitems.Add(mapitem); remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); @@ -523,7 +552,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap /// public void process() { - const int MAX_ASYNC_REQUESTS = 20; + //const int MAX_ASYNC_REQUESTS = 20; try { while (true) @@ -568,13 +597,44 @@ namespace OpenSim.Region.CoreModules.World.WorldMap Watchdog.RemoveThread(); } + const int MAX_ASYNC_REQUESTS = 20; + /// - /// Enqueues the map item request into the processing thread + /// Enqueues the map item request into the services throttle processing thread /// /// - public void EnqueueMapItemRequest(MapRequestState state) + public void EnqueueMapItemRequest(MapRequestState st) { - requests.Enqueue(state); + + m_ServiceThrottle.Enqueue("map-item", st.regionhandle.ToString() + st.agentID.ToString(), delegate + { + if (st.agentID != UUID.Zero) + { + bool dorequest = true; + lock (m_rootAgents) + { + if (!m_rootAgents.Contains(st.agentID)) + dorequest = false; + } + + if (dorequest && !m_blacklistedregions.ContainsKey(st.regionhandle)) + { + if (nAsyncRequests >= MAX_ASYNC_REQUESTS) // hit the break + { + // AH!!! Recursive ! + // Put this request back in the queue and return + EnqueueMapItemRequest(st); + return; + } + + RequestMapItemsDelegate d = RequestMapItemsAsync; + d.BeginInvoke(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle, RequestMapItemsCompleted, null); + //OSDMap response = RequestMapItemsAsync(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle); + //RequestMapItemsCompleted(response); + Interlocked.Increment(ref nAsyncRequests); + } + } + }); } /// @@ -622,19 +682,14 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { OSDMap mapitem = (OSDMap)itemarray[i]; mapItemReply mi = new mapItemReply(); - mi.x = (uint)mapitem["X"].AsInteger(); - mi.y = (uint)mapitem["Y"].AsInteger(); - mi.id = mapitem["ID"].AsUUID(); - mi.Extra = mapitem["Extra"].AsInteger(); - mi.Extra2 = mapitem["Extra2"].AsInteger(); - mi.name = mapitem["Name"].AsString(); + mi.FromOSD(mapitem); returnitems.Add(mi); } av.ControllingClient.SendMapItemReply(returnitems.ToArray(), mrs.itemtype, mrs.flags); } // Service 7 (MAP_ITEM_LAND_FOR_SALE) - uint itemtype = 7; + uint itemtype = (uint)GridItemType.LandForSale; if (response.ContainsKey(itemtype.ToString())) { @@ -644,19 +699,14 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { OSDMap mapitem = (OSDMap)itemarray[i]; mapItemReply mi = new mapItemReply(); - mi.x = (uint)mapitem["X"].AsInteger(); - mi.y = (uint)mapitem["Y"].AsInteger(); - mi.id = mapitem["ID"].AsUUID(); - mi.Extra = mapitem["Extra"].AsInteger(); - mi.Extra2 = mapitem["Extra2"].AsInteger(); - mi.name = mapitem["Name"].AsString(); + mi.FromOSD(mapitem); returnitems.Add(mi); } av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itemtype, mrs.flags); } // Service 1 (MAP_ITEM_TELEHUB) - itemtype = 1; + itemtype = (uint)GridItemType.Telehub; if (response.ContainsKey(itemtype.ToString())) { @@ -666,12 +716,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { OSDMap mapitem = (OSDMap)itemarray[i]; mapItemReply mi = new mapItemReply(); - mi.x = (uint)mapitem["X"].AsInteger(); - mi.y = (uint)mapitem["Y"].AsInteger(); - mi.id = mapitem["ID"].AsUUID(); - mi.Extra = mapitem["Extra"].AsInteger(); - mi.Extra2 = mapitem["Extra2"].AsInteger(); - mi.name = mapitem["Name"].AsString(); + mi.FromOSD(mapitem); returnitems.Add(mi); } av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itemtype, mrs.flags); @@ -754,7 +799,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap if (httpserver.Length == 0) { uint x = 0, y = 0; - Utils.LongToUInts(regionhandle, out x, out y); + Util.RegionHandleToWorldLoc(regionhandle, out x, out y); GridRegion mreg = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, (int)x, (int)y); if (mreg != null) @@ -861,24 +906,26 @@ namespace OpenSim.Region.CoreModules.World.WorldMap finally { if (os != null) - os.Close(); + os.Dispose(); } string response_mapItems_reply = null; - { // get the response - StreamReader sr = null; + { try { - WebResponse webResponse = mapitemsrequest.GetResponse(); - if (webResponse != null) - { - sr = new StreamReader(webResponse.GetResponseStream()); - response_mapItems_reply = sr.ReadToEnd().Trim(); - } - else + using (WebResponse webResponse = mapitemsrequest.GetResponse()) { - return new OSDMap(); - } + if (webResponse != null) + { + using (Stream s = webResponse.GetResponseStream()) + using (StreamReader sr = new StreamReader(s)) + response_mapItems_reply = sr.ReadToEnd().Trim(); + } + else + { + return new OSDMap(); + } + } } catch (WebException) { @@ -905,11 +952,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap return responseMap; } - finally - { - if (sr != null) - sr.Close(); - } OSD rezResponse = null; try @@ -923,6 +965,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { m_log.InfoFormat("[WORLD MAP]: exception on parse of RequestMapItems reply from {0}: {1}", httpserver, ex.Message); responseMap["connect"] = OSD.FromBoolean(false); + lock (m_blacklistedregions) { if (!m_blacklistedregions.ContainsKey(regionhandle)) @@ -955,7 +998,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap /// public virtual void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) { - //m_log.ErrorFormat("[YYY] RequestMapBlocks {0}={1}={2}={3} {4}", minX, minY, maxX, maxY, flag); if ((flag & 0x10000) != 0) // user clicked on qthe map a tile that isn't visible { List response = new List(); @@ -964,22 +1006,24 @@ namespace OpenSim.Region.CoreModules.World.WorldMap // on an unloaded square. // But make sure: Look whether the one we requested is in there List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, - minX * (int)Constants.RegionSize, - maxX * (int)Constants.RegionSize, - minY * (int)Constants.RegionSize, - maxY * (int)Constants.RegionSize); + (int)Util.RegionToWorldLoc((uint)minX), (int)Util.RegionToWorldLoc((uint)maxX), + (int)Util.RegionToWorldLoc((uint)minY), (int)Util.RegionToWorldLoc((uint)maxY) ); + m_log.DebugFormat("[WORLD MAP MODULE] RequestMapBlocks min=<{0},{1}>, max=<{2},{3}>, flag={4}, cntFound={5}", + minX, minY, maxX, maxY, flag.ToString("X"), regions.Count); if (regions != null) { foreach (GridRegion r in regions) { - if ((r.RegionLocX == minX * (int)Constants.RegionSize) && - (r.RegionLocY == minY * (int)Constants.RegionSize)) + if (r.RegionLocX == Util.RegionToWorldLoc((uint)minX) + && r.RegionLocY == Util.RegionToWorldLoc((uint)minY) ) { // found it => add it to response - MapBlockData block = new MapBlockData(); - MapBlockFromGridRegion(block, r, flag); - response.Add(block); + // Version 2 viewers can handle the larger regions + if ((flag & 2) == 2) + response.AddRange(Map2BlockFromGridRegion(r, flag)); + else + response.Add(MapBlockFromGridRegion(r, flag)); break; } } @@ -991,7 +1035,8 @@ namespace OpenSim.Region.CoreModules.World.WorldMap MapBlockData block = new MapBlockData(); block.X = (ushort)minX; block.Y = (ushort)minY; - block.Access = 254; // means 'simulator is offline' + block.Access = (byte)SimAccess.Down; // means 'simulator is offline' + // block.Access = (byte)SimAccess.NonExistent; response.Add(block); } // The lower 16 bits are an unsigned int16 @@ -1008,39 +1053,94 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { List mapBlocks = new List(); List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, - (minX - 4) * (int)Constants.RegionSize, - (maxX + 4) * (int)Constants.RegionSize, - (minY - 4) * (int)Constants.RegionSize, - (maxY + 4) * (int)Constants.RegionSize); + (int)Util.RegionToWorldLoc((uint)(minX - 4)), (int)Util.RegionToWorldLoc((uint)(maxX + 4)), + (int)Util.RegionToWorldLoc((uint)(minY - 4)), (int)Util.RegionToWorldLoc((uint)(maxY + 4)) ); + //m_log.DebugFormat("{0} GetAndSendBlocks. min=<{1},{2}>, max=<{3},{4}>, cntFound={5}", + // LogHeader, minX, minY, maxX, maxY, regions.Count); foreach (GridRegion r in regions) { - MapBlockData block = new MapBlockData(); - MapBlockFromGridRegion(block, r, flag); - mapBlocks.Add(block); + // Version 2 viewers can handle the larger regions + if ((flag & 2) == 2) + mapBlocks.AddRange(Map2BlockFromGridRegion(r, flag)); + else + mapBlocks.Add(MapBlockFromGridRegion(r, flag)); } remoteClient.SendMapBlock(mapBlocks, flag & 0xffff); return mapBlocks; } - protected void MapBlockFromGridRegion(MapBlockData block, GridRegion r, uint flag) + // Fill a passed MapBlockData from a GridRegion + public MapBlockData MapBlockFromGridRegion(GridRegion r, uint flag) { + MapBlockData block = new MapBlockData(); + block.Access = r.Access; switch (flag & 0xffff) { - case 0: - block.MapImageId = r.TerrainImage; - break; - case 2: - block.MapImageId = r.ParcelImage; - break; - default: - block.MapImageId = UUID.Zero; - break; + case 0: + block.MapImageId = r.TerrainImage; + break; + case 2: + block.MapImageId = r.ParcelImage; + break; + default: + block.MapImageId = UUID.Zero; + break; } block.Name = r.RegionName; - block.X = (ushort)(r.RegionLocX / Constants.RegionSize); - block.Y = (ushort)(r.RegionLocY / Constants.RegionSize); + block.X = (ushort)Util.WorldToRegionLoc((uint)r.RegionLocX); + block.Y = (ushort)Util.WorldToRegionLoc((uint)r.RegionLocY); + block.SizeX = (ushort) r.RegionSizeX; + block.SizeY = (ushort) r.RegionSizeY; + + return block; + } + + public List Map2BlockFromGridRegion(GridRegion r, uint flag) + { + List blocks = new List(); + MapBlockData block = new MapBlockData(); + if (r == null) + { + block.Access = (byte)SimAccess.Down; + block.MapImageId = UUID.Zero; + blocks.Add(block); + } + else + { + block.Access = r.Access; + switch (flag & 0xffff) + { + case 0: + block.MapImageId = r.TerrainImage; + break; + case 2: + block.MapImageId = r.ParcelImage; + break; + default: + block.MapImageId = UUID.Zero; + break; + } + block.Name = r.RegionName; + block.X = (ushort)(r.RegionLocX / Constants.RegionSize); + block.Y = (ushort)(r.RegionLocY / Constants.RegionSize); + block.SizeX = (ushort)r.RegionSizeX; + block.SizeY = (ushort)r.RegionSizeY; + blocks.Add(block); + } + return blocks; + } + + + public Hashtable OnHTTPThrottled(Hashtable keysvals) + { + Hashtable reply = new Hashtable(); + int statuscode = 500; + reply["str_response_string"] = ""; + reply["int_response_code"] = statuscode; + reply["content_type"] = "text/plain"; + return reply; } public Hashtable OnHTTPGetMapImage(Hashtable keysvals) @@ -1052,7 +1152,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap if (myMapImageJPEG.Length == 0) { - MemoryStream imgstream = new MemoryStream(); + MemoryStream imgstream = null; Bitmap mapTexture = new Bitmap(1,1); ManagedImage managedImage; Image image = (Image)mapTexture; @@ -1099,10 +1199,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap image.Dispose(); if (imgstream != null) - { - imgstream.Close(); imgstream.Dispose(); - } } } else @@ -1161,17 +1258,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap List mapBlocks = new List(); List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, - (int)(m_scene.RegionInfo.RegionLocX - 9) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocX + 9) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocY - 9) * (int)Constants.RegionSize, - (int)(m_scene.RegionInfo.RegionLocY + 9) * (int)Constants.RegionSize); + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX - 9), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX + 9), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY - 9), + (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY + 9)); List textures = new List(); List bitImages = new List(); foreach (GridRegion r in regions) { - MapBlockData mapBlock = new MapBlockData(); - MapBlockFromGridRegion(mapBlock, r, 0); + MapBlockData mapBlock = MapBlockFromGridRegion(r, 0); AssetBase texAsset = m_scene.AssetService.Get(mapBlock.MapImageId.ToString()); if (texAsset != null) @@ -1217,12 +1313,34 @@ namespace OpenSim.Region.CoreModules.World.WorldMap m_scene.RegionInfo.RegionName, exportPath); } + public void HandleGenerateMapConsoleCommand(string module, string[] cmdparams) + { + Scene consoleScene = m_scene.ConsoleScene(); + + if (consoleScene != null && consoleScene != m_scene) + return; + + if (m_mapImageGenerator == null) + { + Console.WriteLine("No map image generator available for {0}", m_scene.Name); + return; + } + + using (Bitmap mapbmp = m_mapImageGenerator.CreateMapTile()) + { + GenerateMaptile(mapbmp); + m_mapImageServiceModule.UploadMapTile(m_scene, mapbmp); + } + } + public OSD HandleRemoteMapItemRequest(string path, OSD request, string endpoint) { uint xstart = 0; uint ystart = 0; - Utils.LongToUInts(m_scene.RegionInfo.RegionHandle,out xstart,out ystart); + Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle,out xstart,out ystart); + // m_log.DebugFormat("{0} HandleRemoteMapItemRequest. loc=<{1},{2}>", + // LogHeader, Util.WorldToRegionLoc(xstart), Util.WorldToRegionLoc(ystart)); // Service 6 (MAP_ITEM_AGENTS_LOCATION; green dots) @@ -1337,20 +1455,35 @@ namespace OpenSim.Region.CoreModules.World.WorldMap public void GenerateMaptile() { - // Cannot create a map for a nonexistant heightmap + // Cannot create a map for a nonexistent heightmap if (m_scene.Heightmap == null) return; - //create a texture asset of the terrain - IMapImageGenerator terrain = m_scene.RequestModuleInterface(); - if (terrain == null) - return; + m_log.DebugFormat("[WORLD MAP]: Generating map image for {0}", m_scene.Name); + + using (Bitmap mapbmp = m_mapImageGenerator.CreateMapTile()) + { + // V1 (This Module) + GenerateMaptile(mapbmp); + + // v2/3 (MapImageServiceModule) + m_mapImageServiceModule.UploadMapTile(m_scene, mapbmp); + } + } - m_log.DebugFormat("[WORLD MAP]: Generating map image for {0}", m_scene.RegionInfo.RegionName); + private void GenerateMaptile(Bitmap mapbmp) + { + byte[] data; - byte[] data = terrain.WriteJpeg2000Image(); - if (data == null) + try + { + data = OpenJPEG.EncodeFromImage(mapbmp, true); + } + catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke + { + m_log.Error("[WORLD MAP]: Failed generating terrain map: " + e); return; + } byte[] overlay = GenerateOverlay(); @@ -1448,62 +1581,80 @@ namespace OpenSim.Region.CoreModules.World.WorldMap private Byte[] GenerateOverlay() { - Bitmap overlay = new Bitmap(256, 256); + // These need to be ints for bitmap generation + int regionSizeX = (int)m_scene.RegionInfo.RegionSizeX; + int regionSizeY = (int)m_scene.RegionInfo.RegionSizeY; - bool[,] saleBitmap = new bool[64, 64]; - for (int x = 0 ; x < 64 ; x++) - { - for (int y = 0 ; y < 64 ; y++) - saleBitmap[x, y] = false; - } + int landTileSize = LandManagementModule.LandUnit; + int regionLandTilesX = regionSizeX / landTileSize; + int regionLandTilesY = regionSizeY / landTileSize; - bool landForSale = false; + using (Bitmap overlay = new Bitmap(regionSizeX, regionSizeY)) + { + bool[,] saleBitmap = new bool[regionLandTilesX, regionLandTilesY]; + for (int x = 0; x < regionLandTilesX; x++) + { + for (int y = 0; y < regionLandTilesY; y++) + saleBitmap[x, y] = false; + } - List parcels = m_scene.LandChannel.AllParcels(); + bool landForSale = false; - Color background = Color.FromArgb(0, 0, 0, 0); - SolidBrush transparent = new SolidBrush(background); - Graphics g = Graphics.FromImage(overlay); - g.FillRectangle(transparent, 0, 0, 256, 256); + List parcels = m_scene.LandChannel.AllParcels(); - SolidBrush yellow = new SolidBrush(Color.FromArgb(255, 249, 223, 9)); + Color background = Color.FromArgb(0, 0, 0, 0); - foreach (ILandObject land in parcels) - { - // m_log.DebugFormat("[WORLD MAP]: Parcel {0} flags {1}", land.LandData.Name, land.LandData.Flags); - if ((land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0) + using (Graphics g = Graphics.FromImage(overlay)) { - landForSale = true; + using (SolidBrush transparent = new SolidBrush(background)) + g.FillRectangle(transparent, 0, 0, regionSizeX, regionSizeY); - saleBitmap = land.MergeLandBitmaps(saleBitmap, land.GetLandBitmap()); - } - } + foreach (ILandObject land in parcels) + { + // m_log.DebugFormat("[WORLD MAP]: Parcel {0} flags {1}", land.LandData.Name, land.LandData.Flags); + if ((land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0) + { + landForSale = true; - if (!landForSale) - { - m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName); - return null; - } + saleBitmap = land.MergeLandBitmaps(saleBitmap, land.GetLandBitmap()); + } + } + + if (!landForSale) + { + m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName); + return null; + } - m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName); + m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName); - for (int x = 0 ; x < 64 ; x++) - { - for (int y = 0 ; y < 64 ; y++) + using (SolidBrush yellow = new SolidBrush(Color.FromArgb(255, 249, 223, 9))) + { + for (int x = 0 ; x < regionLandTilesX ; x++) + { + for (int y = 0 ; y < regionLandTilesY ; y++) + { + if (saleBitmap[x, y]) + g.FillRectangle( + yellow, x * landTileSize, + regionSizeX - landTileSize - (y * landTileSize), + landTileSize, + landTileSize); + } + } + } + } + + try { - if (saleBitmap[x, y]) - g.FillRectangle(yellow, x * 4, 252 - (y * 4), 4, 4); + return OpenJPEG.EncodeFromImage(overlay, true); + } + catch (Exception e) + { + m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString()); } } - try - { - return OpenJPEG.EncodeFromImage(overlay, true); - } - catch (Exception e) - { - m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString()); - } return null; } } -- cgit v1.1