From d83e5a47de450ac38ae24b281901c9b10146fb27 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Wed, 9 Aug 2023 22:12:04 -0400 Subject: [PATCH 1/9] llGetWallclock() should return time in PST regardless of what the system timezone is set to. Its also supposed to truncate to whole seconds. The existing implementation did neither. Use TimeZoneInfo to insure we return PST. --- .../Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 61c02ff7116..72c244617d1 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -3113,7 +3113,9 @@ public LSL_Float llGetTimeOfDay() public LSL_Float llGetWallclock() { - return DateTime.Now.TimeOfDay.TotalSeconds; + var tzinfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + var dateTime = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzinfo).DateTime; + return (float)Math.Truncate(dateTime.TimeOfDay.TotalSeconds); } public LSL_Float llGetTime() From 81ecbcb46aab913384ff5a153470347555e645c2 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Thu, 10 Aug 2023 18:12:29 -0400 Subject: [PATCH 2/9] Simplify the return from GetWallclock. LSL_Floats are internally doubles so no need to cast to float. --- .../Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 72c244617d1..90f9efe83f2 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -3115,7 +3115,7 @@ public LSL_Float llGetWallclock() { var tzinfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); var dateTime = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzinfo).DateTime; - return (float)Math.Truncate(dateTime.TimeOfDay.TotalSeconds); + return Math.Truncate(dateTime.TimeOfDay.TotalSeconds); } public LSL_Float llGetTime() From 423f83dee74e1d5089e8d6535a085a84a5f2e047 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Sun, 13 Aug 2023 13:59:04 -0400 Subject: [PATCH 3/9] Fix for llGetWallclock() to return PST as wallclock time regardless of the current setting of the system clock. Requires setting GetWallclockTimeZone in the OpenSim.ini XEngine or YEngine config sections respectively to define the timezone to use (the name can vary depending on system type and configuration). The default is that GetWallclockTimeZone is undefined and the system local time will be returned which was the previous default behaviour. --- OpenSim/Framework/VersionInfo.cs | 2 +- .../Shared/Api/Implementation/LSL_Api.cs | 16 +++++++++++++--- bin/OpenSim.ini.example | 13 ++++++++++++- bin/OpenSimDefaults.ini | 13 +++++++++++-- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index e5b85474cb2..20147584415 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8619"; + public const string Release = "8624"; public const Flavour VERSION_FLAVOUR = Flavour.Dev; diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 90f9efe83f2..01c59bbba95 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -114,6 +114,7 @@ public int LlRequestAgentDataCacheTimeoutMs protected float m_recoilScaleFactor = 0.0f; protected bool m_AllowGodFunctions; + protected string m_GetWallclockTimeZone = String.Empty; // Defaults to UTC protected double m_timer = Util.GetTimeStampMS(); protected bool m_waitingForScriptAnswer = false; protected bool m_automaticLinkPermission = false; @@ -431,6 +432,8 @@ private void LoadConfig() m_automaticLinkPermission = seConfig.GetBoolean("AutomaticLinkPermission", m_automaticLinkPermission); m_notecardLineReadCharsMax = seConfig.GetInt("NotecardLineReadCharsMax", m_notecardLineReadCharsMax); + m_GetWallclockTimeZone = seConfig.GetString("GetWallclockTimeZone", m_GetWallclockTimeZone); + // Rezzing an object with a velocity can create recoil. This feature seems to have been // removed from recent versions of SL. The code computes recoil (vel*mass) and scales // it by this factor. May be zero to turn off recoil all together. @@ -3113,9 +3116,16 @@ public LSL_Float llGetTimeOfDay() public LSL_Float llGetWallclock() { - var tzinfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); - var dateTime = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzinfo).DateTime; - return Math.Truncate(dateTime.TimeOfDay.TotalSeconds); + DateTimeOffset dateTimeOffset = DateTimeOffset.Now; + + if (string.IsNullOrEmpty(m_GetWallclockTimeZone) == false) + { + dateTimeOffset = + TimeZoneInfo.ConvertTimeBySystemTimeZoneId( + DateTimeOffset.UtcNow, m_GetWallclockTimeZone); + } + + return Math.Truncate(dateTimeOffset.DateTime.TimeOfDay.TotalSeconds); } public LSL_Float llGetTime() diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index d3c31e447d1..e2114ca3ae7 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -1017,6 +1017,12 @@ ;; Increasing this to large values potentially opens ;; up the system to malicious scripters ; NotecardLineReadCharsMax = 255 + + ; llGetWallclock returns a float that is the time in seconds since midnight Pacific time (PST/PDT), truncated to whole seconds. + ; How you specify that time zone varies based on system type (Linux, Windows or Mac). If this value is not set (the default) + ; we will return whatever the system time is. You can define the timezone id for PST if you prefer that behaviour. + ; Normal default for Linux is "America/Los_Angeles" from the ICU database and for Windows is: "Pacific Standard Time" + ; GetWallclockTimeZone = "America/Los_Angeles" ;# {SensorMaxRange} {} {Sensor range} {} 96.0 ;; Sensor settings @@ -1131,6 +1137,12 @@ ;# {SensorMaxResults} {} {Max sensor results returned?} {} ; SensorMaxResults = 16 + ; llGetWallclock returns a float that is the time in seconds since midnight Pacific time (PST/PDT), truncated to whole seconds. + ; How you specify that time zone varies based on system type (Linux, Windows or Mac). If this value is not set (the default) + ; we will return whatever the system time is. You can define the timezone id for PST if you prefer that behaviour. + ; Normal default for Linux is "America/Los_Angeles" from the ICU database and for Windows is: "Pacific Standard Time" + ; GetWallclockTimeZone = "America/Los_Angeles" + ;# {DisableUndergroundMovement} {} {Disable underground movement of prims} {true false} true ;; Disable underground movement of prims (default true); set to ;; false to allow script controlled underground positioning of @@ -1147,7 +1159,6 @@ ;; If this INI file is not included, the OSSL functions are disabled. Include-osslDefaultEnable = "config-include/osslDefaultEnable.ini" - [FreeSwitchVoice] ;; In order for this to work you need a functioning FreeSWITCH PBX set up. ;; Configuration details at http://opensimulator.org/wiki/Freeswitch_Module diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 3fc3c637f28..c03850a6548 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -1783,6 +1783,11 @@ ; prims ; DisableUndergroundMovement = true + ; llGetWallclock returns a float that is the time in seconds since midnight Pacific time (PST/PDT), truncated to whole seconds. + ; How you specify that time zone varies based on system type (Linux, Windows or Mac). If this value is not set (the default) + ; we will return whatever the system time is. You can define the timezone id for PST if you prefer that behaviour. + ; Normal default for Linux is "America/Los_Angeles" from the ICU database and for Windows is: "Pacific Standard Time" + ; GetWallclockTimeZone = "America/Los_Angeles" ; scripts states and cache parent folder location ;ScriptEnginesPath="ScriptEngines" @@ -1830,7 +1835,6 @@ ; This does not delete cached scripts state. ; DeleteScriptsOnStartup = true - ; Controls whether scripts are stopped by aborting their threads externally (abort) ; or by co-operative checks inserted by OpenSimulator into compiled script (co-op). ; co-op will be more stable as aborting threads can cause instability. @@ -1849,7 +1853,6 @@ ; Compile debug info (line numbers) into the script assemblies CompileWithDebugInformation = true - ; Interval (s) between background save of script states SaveInterval = 120 @@ -1881,6 +1884,12 @@ ; rounded up to this minimum interval. ; MinTimerInterval = 0.5 + ; llGetWallclock returns a float that is the time in seconds since midnight Pacific time (PST/PDT), truncated to whole seconds. + ; How you specify that time zone varies based on system type (Linux, Windows or Mac). If this value is not set (the default) + ; we will return whatever the system time is. You can define the timezone id for PST if you prefer that behaviour. + ; Normal default for Linux is "America/Los_Angeles" from the ICU database and for Windows is: "Pacific Standard Time" + ; GetWallclockTimeZone = "America/Los_Angeles" + ; Sensor settings ;SensorMaxRange = 96.0 ;SensorMaxResults = 16 From c27624d9eb790382adf1815a27a9bbeb2d359f38 Mon Sep 17 00:00:00 2001 From: Rene Vega Date: Sun, 24 Sep 2023 12:53:05 -0700 Subject: [PATCH 4/9] =?UTF-8?q?Fix=20clamp:=20focus=20of=20the=20image=20(?= =?UTF-8?q?hardness=20/=20softness)=20=E2=80=93=20range=20-20=20to=20+20.?= =?UTF-8?q?=20(#62)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs index c24903c8ac5..cd92adebfed 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs @@ -4021,7 +4021,7 @@ private void SetProjectionParams(SceneObjectPart obj, LSL_Integer llprojection, obj.Shape.ProjectionEntry = true; obj.Shape.ProjectionTextureUUID = texID; obj.Shape.ProjectionFOV = Util.Clamp((float)fov, 0, 3.0f); - obj.Shape.ProjectionFocus = Util.Clamp((float)focus, 0, 20.0f); + obj.Shape.ProjectionFocus = Util.Clamp((float)focus, -20.0f, 20.0f); obj.Shape.ProjectionAmbiance = Util.Clamp((float)amb, 0, 1.0f); obj.ParentGroup.HasGroupChanged = true; From 4083fab229045e0b7a002accacb642cfe0680f99 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Sat, 14 Oct 2023 21:30:30 -0400 Subject: [PATCH 5/9] Next release is OpenSim-NGC Tranquillity --- OpenSim/Framework/VersionInfo.cs | 22 ++++--------------- OpenSim/Framework/VersionInfo.tt | 20 +++-------------- .../GloebitMoneyModule/GloebitMoneyModule.cs | 11 ---------- 3 files changed, 7 insertions(+), 46 deletions(-) diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 20147584415..5371ae52af7 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,30 +39,16 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8624"; - - public const Flavour VERSION_FLAVOUR = Flavour.Dev; - - public enum Flavour - { - Unknown, - Dev, - RC1, - RC2, - RC3, - Release, - Post_Fixes, - Extended - } + public const string Release = "8688"; public static string Version { - get { return GetVersionString(VersionNumber, Release, VERSION_FLAVOUR); } + get { return GetVersionString(VersionNumber, Release); } } - public static string GetVersionString(string versionNumber, string release, Flavour flavour) + public static string GetVersionString(string versionNumber, string release) { - string versionString = "OpenSim-NGC " + versionNumber + "." + release + " Yeti " + flavour; + string versionString = "OpenSim-NGC Tranquillity " + versionNumber + "." + release; return versionString.PadRight(VERSIONINFO_VERSION_LENGTH); } diff --git a/OpenSim/Framework/VersionInfo.tt b/OpenSim/Framework/VersionInfo.tt index 52060856d05..b8a22e42b2c 100644 --- a/OpenSim/Framework/VersionInfo.tt +++ b/OpenSim/Framework/VersionInfo.tt @@ -41,28 +41,14 @@ namespace OpenSim public const string AssemblyVersionNumber = "0.9.2.2"; public const string Release = "<#= (int)DateTimeOffset.UtcNow.Subtract(new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalDays #>"; - public const Flavour VERSION_FLAVOUR = Flavour.Dev; - - public enum Flavour - { - Unknown, - Dev, - RC1, - RC2, - RC3, - Release, - Post_Fixes, - Extended - } - public static string Version { - get { return GetVersionString(VersionNumber, Release, VERSION_FLAVOUR); } + get { return GetVersionString(VersionNumber, Release); } } - public static string GetVersionString(string versionNumber, string release, Flavour flavour) + public static string GetVersionString(string versionNumber, string release) { - string versionString = "OpenSim-NGC " + versionNumber + "." + release + " Yeti " + flavour; + string versionString = "OpenSim-NGC Tranquillity " + versionNumber + "." + release; return versionString.PadRight(VERSIONINFO_VERSION_LENGTH); } diff --git a/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs b/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs index 8abd4c19b60..9c181966a92 100644 --- a/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs +++ b/addon-modules/Gloebit/GloebitMoneyModule/GloebitMoneyModule.cs @@ -725,17 +725,6 @@ public void PostInitialise() // 0.9.0.1, 0.9.0.2, etc. detectedOSVersion = "=>0.9.0.1"; m_newLandPassFlow = true; - } else { - // Need to pull version flavour and check it. - // TODO: may need to split on spaces or hyphens and then pull last field because flavour is not friggin public - char[] dChars = { '-', ' ' }; - string[] versionParts = m_opensimVersion.Split(dChars, System.StringSplitOptions.RemoveEmptyEntries); - string flavour = versionParts[versionParts.Length - 1]; // TODO: do we every have to worry about this being length 0? - if (flavour == OpenSim.VersionInfo.Flavour.Release.ToString()) { - // 0.9.0 release - detectedOSVersion = "=0.9.0"; - m_newLandPassFlow = true; - } } // TODO: Unclear if post-fixes is a necessary flavour check yet. } else { From 8c9ccc06ed2976ab37302c430da207c7a4a677e2 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Thu, 16 Nov 2023 12:52:59 -0500 Subject: [PATCH 6/9] LinkSet Data feature from SL (#64) * Manually applied the Linkset Data patch from zontreck (Aria) that had been posted to OpenSim Core Mantis with permission. I had to make a few small changes because this branch is still targetting Mono and only implements C# 7.3 and a few C# 8.0 features were used. It's otherwise essentially as it was submitted. * Add using around binary compression of linksetdata so that Dispose is called correctly on completion. Remove the IsRoot check in BuildPrims for linksetdata. At this stage we don't yet have a fully instantiated prim so the SOG is undefined. * Declare and set linksetdata to null when updating prims table where there is no linkdata on a prim. Previously it was left off an being explicitely set and the connector considers that an error. * Update the script syntax XML * Apparently forgot to add the Read and Delete protected methods * Rewrite null checks for older dot net * Update linkset data accounting on reset * Rename ProtectedData to LinksetDataEntry * Fix protected attribute not being set on new entries * Add the new linkset data functions * Finish adding remaining linkset data functions * Fix a compile error due to typo * Parity: Don't send value on write protected in event * Do some code cleanup and fixes in multi-delete function * Parity: When testing passwords, even for unsecured values, treat as secured * Parity: Fix some return values * Oops... compile error * Fix up a few CodqQL scanner errors. * For LinkSetData Find and returning a list of keys a count value of < 1 is an indication to return all available. Adjust the code to behave that way. It was originally looking at -1 as an indication to do that. * Reformat LinksetDataEntry to be consistent with coding standards. Function and Property names are MixedCase with Initial upper case letter. Changed references to empty strings to use string.Empty and tests to use string.IsNullOrEmpty in the LinksetDataEntry data model class. * Change the underlying storage to a SortedList so we return keys in alphabetic order as required. This is a lightweight list with Dictionary behaviour added. Mark the SOG dirty on operations that change the LinksetData field so persistence is reliable. Rework the support functions to use string.Empty and string.IsNullOrEmpty where needed. Cleaned up what should be the last of the CodeQL comments. * Fix list handling in llLinksetDataDeleteFound, we can't delete keys from a list we are traversing. Also a potential lock contention issue. * Update LSL support for LinksetData to use string.Empty where appropriate * Rework the scheme migration to use MEDIUMTEXT for the linksetdata field and to serialize/deserialize to/from JSON. We calculate size based on the size of the serialization which means there is a little bit of overhead for each field. We'll also let things go slightly over 128k to simplify the cost accounting but the field we're using can store well beyond 128k so thats not an issue. * Revert to using a binary count for the space accounting for the linksetData field. This way we match more closely SL semantics. We'll still serialize in json text format when writing to the database. * For llLinksetDeleteFound take a copy of the KVP's so we can delete as we iterate the copied list. deleted and not_deleted only apply to matches, not all the entries. Thx to Aria for the review comments. * Add missing stub functions * Cleaned unused System.Text.Json references in Data namespaces. Added it to Opensim.Region.Framework --------- Co-authored-by: zontreck --- OpenSim/Data/MySQL/MySQLSimulationData.cs | 20 +- OpenSim/Data/MySQL/OpenSim.Data.MySQL.csproj | 2 +- .../MySQL/Resources/RegionStore.migrations | 8 + OpenSim/Data/OpenSim.Data.csproj | 2 +- OpenSim/Data/PGSQL/PGSQLSimulationData.cs | 17 +- .../PGSQL/Resources/RegionStore.migrations | 5 + .../SQLite/Resources/RegionStore.migrations | 5 + OpenSim/Data/SQLite/SQLiteSimulationData.cs | 9 + OpenSim/Framework/VersionInfo.cs | 2 +- .../Framework/OpenSim.Region.Framework.csproj | 1 + .../Framework/Scenes/LinksetDataEntry.cs | 39 ++ .../Framework/Scenes/SceneObjectGroup.cs | 7 +- .../Framework/Scenes/SceneObjectPart.cs | 334 +++++++++++++++++- .../Serialization/SceneObjectSerializer.cs | 22 ++ .../Shared/Api/Implementation/LSL_Api.cs | 157 +++++++- .../Shared/Api/Interface/ILSL_Api.cs | 24 +- .../Shared/Api/Runtime/LSL_Constants.cs | 12 + .../Shared/Api/Runtime/LSL_Stub.cs | 77 ++++ .../ScriptEngine/YEngine/MMRIEventHandlers.cs | 1 + .../YEngine/MMRScriptEventCode.cs | 3 +- .../ScriptEngine/YEngine/XMRInstMain.cs | 3 +- .../OpenSim.Data.MySQL.MoneyData.csproj | 1 + bin/ScriptSyntax.xml | 322 ++++++++++++++++- 23 files changed, 1049 insertions(+), 24 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs index 0e6e1432327..a054e12782f 100644 --- a/OpenSim/Data/MySQL/MySQLSimulationData.cs +++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs @@ -181,7 +181,8 @@ public virtual void StoreObject(SceneObjectGroup obj, UUID regionUUID) "AttachedPosY, AttachedPosZ, " + "PhysicsShapeType, Density, GravityModifier, " + "Friction, Restitution, Vehicle, PhysInertia, DynAttrs, " + - "RotationAxisLocks, sopanims, sitactrange, pseudocrc" + + "RotationAxisLocks, sopanims, sitactrange, pseudocrc, " + + "linksetdata" + ") values (" + "?UUID, " + "?CreationDate, ?Name, ?Text, " + "?Description, ?SitName, ?TouchName, " + @@ -216,7 +217,8 @@ public virtual void StoreObject(SceneObjectGroup obj, UUID regionUUID) "?AttachedPosY, ?AttachedPosZ, " + "?PhysicsShapeType, ?Density, ?GravityModifier, " + "?Friction, ?Restitution, ?Vehicle, ?PhysInertia, ?DynAttrs," + - "?RotationAxisLocks, ?sopanims, ?sitactrange, ?pseudocrc)"; + "?RotationAxisLocks, ?sopanims, ?sitactrange, ?pseudocrc, " + + "?linksetdata)"; FillPrimCommand(cmd, prim, obj.UUID, regionUUID); @@ -1202,6 +1204,11 @@ private SceneObjectPart BuildPrim(IDataReader row) if(pseudocrc != 0) prim.PseudoCRC = pseudocrc; + if (!(row["linksetdata"] is DBNull)) + { + prim.DeserializeLinksetData((string)row["linksetdata"]); + } + return prim; } @@ -1620,6 +1627,15 @@ private void FillPrimCommand(MySqlCommand cmd, SceneObjectPart prim, UUID sceneG cmd.Parameters.AddWithValue("sitactrange", prim.SitActiveRange); cmd.Parameters.AddWithValue("pseudocrc", prim.PseudoCRC); + + if (prim.HasLinksetData) + { + cmd.Parameters.AddWithValue("linksetdata", prim.SerializeLinksetData()); + } + else + { + cmd.Parameters.AddWithValue("linksetdata", null); + } } /// diff --git a/OpenSim/Data/MySQL/OpenSim.Data.MySQL.csproj b/OpenSim/Data/MySQL/OpenSim.Data.MySQL.csproj index 6c298076d00..7ddb21b2248 100644 --- a/OpenSim/Data/MySQL/OpenSim.Data.MySQL.csproj +++ b/OpenSim/Data/MySQL/OpenSim.Data.MySQL.csproj @@ -44,7 +44,7 @@ - + diff --git a/OpenSim/Data/MySQL/Resources/RegionStore.migrations b/OpenSim/Data/MySQL/Resources/RegionStore.migrations index d1f62dcdb4b..da5fc0c6314 100644 --- a/OpenSim/Data/MySQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/MySQL/Resources/RegionStore.migrations @@ -550,3 +550,11 @@ BEGIN; ALTER TABLE `land` ADD COLUMN `environment` MEDIUMTEXT default NULL; COMMIT; + :VERSION 64 #----- material overrides + ALTER TABLE `primshapes` ADD COLUMN `MatOvrd` blob default NULL; + COMMIT; + +:VERSION 65 #----- add linkset data storage column +BEGIN; +ALTER TABLE `prims` ADD COLUMN `linksetdata` MEDIUMTEXT default NULL; +COMMIT; \ No newline at end of file diff --git a/OpenSim/Data/OpenSim.Data.csproj b/OpenSim/Data/OpenSim.Data.csproj index 9bc07d0f0c9..f21ab12753f 100644 --- a/OpenSim/Data/OpenSim.Data.csproj +++ b/OpenSim/Data/OpenSim.Data.csproj @@ -51,7 +51,7 @@ - + diff --git a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs index d78d1a87362..9d715ac712f 100755 --- a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs +++ b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs @@ -355,9 +355,9 @@ UPDATE prims SET ""ClickAction"" = :ClickAction, ""Material"" = :Material, ""CollisionSound"" = :CollisionSound, ""CollisionSoundVolume"" = :CollisionSoundVolume, ""PassTouches"" = :PassTouches, ""LinkNumber"" = :LinkNumber, ""MediaURL"" = :MediaURL, ""DynAttrs"" = :DynAttrs, ""Vehicle"" = :Vehicle, ""PhysInertia"" = :PhysInertia, ""standtargetx"" =:standtargetx, ""standtargety"" =:standtargety, ""standtargetz"" =:standtargetz, - ""sitactrange"" =:sitactrange, ""pseudocrc"" = :pseudocrc, ""sopanims"" = :sopanims - - WHERE ""UUID"" = :UUID ; + ""sitactrange"" =:sitactrange, ""pseudocrc"" = :pseudocrc, ""sopanims"" = :sopanims, + ""linksetdata"" =:linksetdata + WHERE ""UUID"" = :UUID ; INSERT INTO prims ( @@ -371,7 +371,7 @@ INSERT INTO ""ForceMouselook"", ""ScriptAccessPin"", ""AllowedDrop"", ""DieAtEdge"", ""SalePrice"", ""SaleType"", ""ColorR"", ""ColorG"", ""ColorB"", ""ColorA"", ""ParticleSystem"", ""ClickAction"", ""Material"", ""CollisionSound"", ""CollisionSoundVolume"", ""PassTouches"", ""LinkNumber"", ""MediaURL"", ""DynAttrs"", ""PhysicsShapeType"", ""Density"", ""GravityModifier"", ""Friction"", ""Restitution"", ""PassCollisions"", ""RotationAxisLocks"", ""RezzerID"" , ""Vehicle"", ""PhysInertia"", - ""standtargetx"", ""standtargety"", ""standtargetz"", ""sitactrange"", ""pseudocrc"", ""sopanims"" + ""standtargetx"", ""standtargety"", ""standtargetz"", ""sitactrange"", ""pseudocrc"", ""sopanims"", ""linksetdata"" ) Select :UUID, :CreationDate, :Name, :Text, :Description, :SitName, :TouchName, :ObjectFlags, :OwnerMask, :NextOwnerMask, :GroupMask, :EveryoneMask, :BaseMask, :PositionX, :PositionY, :PositionZ, :GroupPositionX, :GroupPositionY, :GroupPositionZ, :VelocityX, @@ -383,7 +383,7 @@ INSERT INTO :ForceMouselook, :ScriptAccessPin, :AllowedDrop, :DieAtEdge, :SalePrice, :SaleType, :ColorR, :ColorG, :ColorB, :ColorA, :ParticleSystem, :ClickAction, :Material, :CollisionSound, :CollisionSoundVolume, :PassTouches, :LinkNumber, :MediaURL, :DynAttrs, :PhysicsShapeType, :Density, :GravityModifier, :Friction, :Restitution, :PassCollisions, :RotationAxisLocks, :RezzerID, :Vehicle, :PhysInertia, - :standtargetx, :standtargety, :standtargetz,:sitactrange, :pseudocrc, :sopanims + :standtargetx, :standtargety, :standtargetz,:sitactrange, :pseudocrc, :sopanims, :LinksetData where not EXISTS (SELECT ""UUID"" FROM prims WHERE ""UUID"" = :UUID); "; @@ -1406,6 +1406,11 @@ private static SceneObjectPart BuildPrim(IDataRecord primRow) prim.Animations = null; } + if ((primRow["LinksetData"] is DBNull) == false) + { + prim.DeserializeLinksetData(((string)primRow["linksetdata"])); + } + return prim; } @@ -1870,6 +1875,8 @@ private NpgsqlParameter[] CreatePrimParameters(SceneObjectPart prim, UUID sceneG else parameters.Add(_Database.CreateParameter("sopanims", null)); + parameters.Add(_Database.CreateParameter("linksetdata", prim.SerializeLinksetData())); + return parameters.ToArray(); } diff --git a/OpenSim/Data/PGSQL/Resources/RegionStore.migrations b/OpenSim/Data/PGSQL/Resources/RegionStore.migrations index 1def32ef1f4..ea172f13783 100644 --- a/OpenSim/Data/PGSQL/Resources/RegionStore.migrations +++ b/OpenSim/Data/PGSQL/Resources/RegionStore.migrations @@ -1261,3 +1261,8 @@ BEGIN; ALTER TABLE `prims` ADD COLUMN `sopanims` bytea NULL; ALTER TABLE `primshapes` ADD COLUMN `MatOvrd` bytea NULL; COMMIT; + +:VERSION 53 #----- add linkset data storage column +BEGIN; +ALTER TABLE `prims` ADD COLUMN `linksetdata` varchar default NULL; +COMMIT; diff --git a/OpenSim/Data/SQLite/Resources/RegionStore.migrations b/OpenSim/Data/SQLite/Resources/RegionStore.migrations index 448fc962714..84e35c7e548 100644 --- a/OpenSim/Data/SQLite/Resources/RegionStore.migrations +++ b/OpenSim/Data/SQLite/Resources/RegionStore.migrations @@ -406,3 +406,8 @@ BEGIN; ALTER TABLE `prims` ADD COLUMN `sopanims` blob default NULL; ALTER TABLE `primshapes` ADD COLUMN `MatOvrd` blob default NULL; COMMIT; + +:VERSION 41 #----- add linkset data storage column +BEGIN; +ALTER TABLE `prims` ADD COLUMN `linksetdata` TEXT default NULL; +COMMIT; \ No newline at end of file diff --git a/OpenSim/Data/SQLite/SQLiteSimulationData.cs b/OpenSim/Data/SQLite/SQLiteSimulationData.cs index 786532bef97..30b4c4d3c3b 100644 --- a/OpenSim/Data/SQLite/SQLiteSimulationData.cs +++ b/OpenSim/Data/SQLite/SQLiteSimulationData.cs @@ -1251,6 +1251,8 @@ private static DataTable createPrimTable() createCol(prims, "pseudocrc", typeof(int)); createCol(prims, "sopanims", typeof(byte[])); + createCol(prims, "linksetdata", typeof(string)); + // Add in contraints prims.PrimaryKey = new DataColumn[] { prims.Columns["UUID"] }; @@ -1810,6 +1812,11 @@ private SceneObjectPart buildPrim(DataRow row) prim.Animations = null; } + if (!(row["linksetdata"] is DBNull)) + { + prim.DeserializeLinksetData((string)row["LinksetData"]); + } + return prim; } @@ -2199,6 +2206,8 @@ private static void fillPrimRow(DataRow row, SceneObjectPart prim, UUID sceneGro row["pseudocrc"] = prim.PseudoCRC; row["sopanims"] = prim.SerializeAnimations(); + + row["linksetdata"] = prim.SerializeLinksetData(); } /// diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 5371ae52af7..12af4d47c66 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8688"; + public const string Release = "8719"; public static string Version { diff --git a/OpenSim/Region/Framework/OpenSim.Region.Framework.csproj b/OpenSim/Region/Framework/OpenSim.Region.Framework.csproj index e4aebe25752..e5db07bede5 100644 --- a/OpenSim/Region/Framework/OpenSim.Region.Framework.csproj +++ b/OpenSim/Region/Framework/OpenSim.Region.Framework.csproj @@ -82,5 +82,6 @@ + \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs b/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs new file mode 100644 index 00000000000..68ab7499728 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; + +namespace OpenSim.Region.Framework.Scenes +{ + public class LinksetDataEntry + { + public LinksetDataEntry(string value, string password) + { + this.Value = value; + this.Password = password; + } + + public string Value { get; private set; } + public string Password { get; private set; } = string.Empty; + + public bool IsProtected() + { + return (string.IsNullOrEmpty(this.Password) == false); + } + + public bool CheckPassword(string pass) + { + // A undocumented caveat for LinksetData appears to be that even for unprotected values, if a pass is provided, it is still treated as protected + if (this.Password == pass) + return true; + else + return false; + } + + public string CheckPasswordAndGetValue(string pass) + { + if (string.IsNullOrEmpty(this.Password) || (this.Password == pass)) + return this.Value; + else + return string.Empty; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index e7d6346c121..e7b50411371 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -88,9 +88,10 @@ public enum ScriptEventCode : int http_request = 38, path_update = 40, + linkset_data = 41, // marks highest numbered event - Size = 41 + Size = 42 } // this is not the right place for this @@ -145,7 +146,9 @@ public enum scriptEvents : ulong anytouch = touch | touch_end | touch_start, anyTarget = at_target | not_at_target | at_rot_target | not_at_rot_target, anyobjcollision = collision | collision_end | collision_start, - anylandcollision = land_collision | land_collision_end | land_collision_start + anylandcollision = land_collision | land_collision_end | land_collision_start, + + linkset_data = 1UL << 41 } public struct scriptPosTarget diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 4fdca98b675..815e289625b 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -29,9 +29,12 @@ using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; using System.Reflection; using System.Threading; using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; using log4net; @@ -42,6 +45,10 @@ using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Region.PhysicsModules.SharedBase; using PermissionMask = OpenSim.Framework.PermissionMask; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Data.Entity.ModelConfiguration.Configuration; +using System.Runtime.Serialization.Json; +using OpenMetaverse.Rendering; namespace OpenSim.Region.Framework.Scenes { @@ -196,6 +203,12 @@ public bool IsSitTargetSet /// public PhysicsActor PhysActor { get; set; } + [XmlIgnore] + public SortedList LinksetData = null; + //private Dictionary LinksetData = null; + + private static readonly object linksetDataLock = new object(); + //Xantor 20080528 Sound stuff: // Note: This isn't persisted in the database right now, as the fields for that aren't just there yet. // Not a big problem as long as the script that sets it remains in the prim on startup. @@ -2231,6 +2244,7 @@ public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkN dupe.PhysActor.LocalID = plocalID; dupe.PseudoCRC = (int)(DateTime.UtcNow.Ticks); + dupe.DeserializeLinksetData(SerializeLinksetData()); ParentGroup.Scene.EventManager.TriggerOnSceneObjectPartCopy(dupe, this, userExposed); @@ -5765,7 +5779,7 @@ public Byte[] SerializeAnimations() public void DeSerializeAnimations(Byte[] data) { - if(data == null) + if (data == null) { Animations = null; AnimationsNames = null; @@ -5810,10 +5824,326 @@ public void DeSerializeAnimations(Byte[] data) AnimationsNames = null; } + /// + /// FindLinksetDataKeys - Given a Regex pattern and start, count return the + /// list of matchingkeys in the LinksetData store. + /// + /// A Regex pattern to match + /// starting offset into the list of keys + /// how many to return, < 1 means all keys + /// + public string[] FindLinksetDataKeys(string pattern, int start, int count) + { + List all_keys = new List(GetLinksetDataSubList(0, 0)); + Regex rx = new Regex(pattern, RegexOptions.CultureInvariant); + + if (count < 1) + count = all_keys.Count; + + List matches = new List(); + foreach (var str in all_keys) + { + if (rx.IsMatch(str)) + matches.Add(str); + } + + return matches.Skip(start).Take(count).ToArray(); + } + + /// + /// Adds or updates a entry to linkset data + /// + /// + /// -1 if the password did not match + /// -1 is the data was protected + /// 0 if the data was successfully added or updated + /// 1 if the data could not be added or updated due to memory + /// 2 if the data is unchanged + /// + public int AddOrUpdateLinksetDataKey(string key, string value, string pass) + { + lock (linksetDataLock) + { + if (LinksetData == null) + LinksetData = new SortedList(); + + if (LinksetDataOverLimit) + return 1; + + LinksetDataEntry entry = null; + if (LinksetData.TryGetValue(key, out entry) == true) + { + if (entry.CheckPassword(pass) == false) + return -1; + + if (entry.Value == value) + return 2; + } + + // Add New or Update handled here. + LinksetDataEntry newEntry = new LinksetDataEntry(value, pass); + LinksetData[key] = newEntry; + + UpdateLinksetDataAccounting(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + return 0; + } + } + + /// + /// Reads a value from the key value pair + /// + /// The key value we're retrieving + /// The password for a protected field (or string.Empty if not protected) + /// Blank if no key or pass mismatch. Value if no password or pass matches + public string ReadLinksetData(string key, string pass) + { + lock (linksetDataLock) + { + if (LinksetData == null) + return string.Empty; + + LinksetDataEntry entry; + if (LinksetData.TryGetValue(key, out entry) == true) + return entry.CheckPasswordAndGetValue(pass); + + return string.Empty; + } + } + + /// + /// Deletes a named key from the key value store + /// + /// The key value we're removing + /// The password for a protected field (or string.Empty if not protected) + /// + /// 0 if successful. + /// 1 if not due to the password. + /// -1 if no such key was found + /// + public int DeleteLinksetDataKey(string key, string pass) + { + lock (linksetDataLock) + { + if (LinksetData == null) + return -1; + + LinksetDataEntry entry; + if (LinksetData.TryGetValue(key, out entry) == false) + return -1; + + if (entry.CheckPassword(pass) == false) + return 1; + + LinksetData.Remove(key); + + UpdateLinksetDataAccounting(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + return 0; + } + } + + /// + /// GetLinksetDataSubList - Return a subset of key values from start for count + /// + /// Offset in the list for the first key + /// How many keys to return, < 1 means all keys + /// + public string[] GetLinksetDataSubList(int start, int count) + { + lock (linksetDataLock) + { + if (LinksetData == null) + return new string[0]; + + if (count < 1) + count = LinksetData.Count; + + List ret = LinksetData.Keys.Skip(start).Take(count).ToList(); + return ret.ToArray(); + } + } + + /// + /// ResetLinksetData - If unallocated leave it that way, otherwise + /// clear the list and update the accounting. + /// + public void ResetLinksetData() + { + lock (linksetDataLock) + { + if (LinksetData == null) + return; + + LinksetData.Clear(); + + UpdateLinksetDataAccounting(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + } + } + + /// + /// LinksetDataCountMatches - Return a count of the # of keys that match pattern. + /// + /// The Regex pattern to match + /// An integer count or zero if none + public int LinksetDataCountMatches(string pattern) + { + lock (linksetDataLock) + { + if (LinksetData == null) + return 0; + + Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); + + int count = 0; + foreach (var kvp in LinksetData) + { + if (reg.IsMatch(kvp.Key)) + count++; + } + + return count; + } + } + + public string[] LinksetDataMultiDelete(string pattern, string pass, out int deleted, out int not_deleted) + { + lock (linksetDataLock) + { + deleted = 0; + not_deleted = 0; + + if (LinksetData == null) + return new string[0]; + + Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); + List matches = new List(); + + // Take a copy so we can delete as we iterate + foreach (var kvp in LinksetData.ToArray()) + { + if (reg.IsMatch(kvp.Key)) + { + if (kvp.Value.CheckPassword(pass)) + { + LinksetData.Remove(kvp.Key); + matches.Add(kvp.Key); + deleted += 1; + } + else + { + not_deleted += 1; + } + } + } + + if (deleted > 0) + { + UpdateLinksetDataAccounting(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + } + + return matches.ToArray(); + } + } + + /// + /// Recalculates the amount of memory used by linkset data. + /// Caller should be holding the linksetData lock + /// + private void UpdateLinksetDataAccounting() + { + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter bw = new BinaryWriter(ms)) + { + foreach (var entry in LinksetData) + { + bw.Write(Encoding.UTF8.GetBytes(entry.Key)); + bw.Write(Encoding.UTF8.GetBytes(entry.Value.Value)); + + // For parity, the pass adds 32 bytes regardless of the length. See LL caveats + if (entry.Value.IsProtected()) + bw.Write(new byte[32]); + } + } + + linksetDataBytesUsed = ms.ToArray().Length; + linksetDataBytesFree = LINKSETDATA_MAX - linksetDataBytesUsed; + } + } + + public const int LINKSETDATA_MAX = 131072; // 128 KB + + public int linksetDataBytesUsed; + public int linksetDataBytesFree = LINKSETDATA_MAX; // Default + + public bool HasLinksetData + { + get + { + if (LinksetData == null) + return false; + + return LinksetData.Count > 0; + } + } + + public bool LinksetDataOverLimit + { + get { return linksetDataBytesFree < 0; } + } + + public int LinksetDataKeys + { + get + { + if (LinksetData == null) + return 0; + + return LinksetData.Count; + } + } + + public string SerializeLinksetData() + { + if (!IsRoot || LinksetData == null) + return null; + + lock (linksetDataLock) + { + return JsonSerializer.Serialize>(LinksetData); + } + } + + public void DeserializeLinksetData(string data) + { + if (data == null || data.Length == 0) + return; + + lock (linksetDataLock) + { + LinksetData = JsonSerializer.Deserialize>(data); + UpdateLinksetDataAccounting(); + } + } + public bool GetOwnerName(out string FirstName, out string LastName) { - if(ParentGroup != null) + if (ParentGroup != null) return ParentGroup.GetOwnerName(out FirstName, out LastName); + FirstName = string.Empty; LastName = string.Empty; return false; diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs index f238777334f..dbe51a891a8 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs @@ -533,6 +533,7 @@ static SceneObjectSerializer() m_SOPXmlProcessors.Add("SOPAnims", ProcessSOPAnims); m_SOPXmlProcessors.Add("SitActRange", ProcessSitActRange); + m_SOPXmlProcessors.Add("LinksetData", ProcessLinksetData); #endregion @@ -832,6 +833,24 @@ private static void ProcessSitActRange(SceneObjectPart obj, XmlReader reader) obj.SitActiveRange = reader.ReadElementContentAsFloat("SitActRange", String.Empty); } + private static void ProcessLinksetData(SceneObjectPart obj, XmlReader reader) + { + try + { + string data = reader.ReadElementContentAsString(); + if (string.IsNullOrEmpty(data)) + return; + + obj.DeserializeLinksetData(data); + } + catch + { + m_log.DebugFormat( + "[SceneObjectSerializer]: Exception while processing linksetdata for object part {0} {1}.", + obj.Name, obj.UUID); + } + } + private static void ProcessVehicle(SceneObjectPart obj, XmlReader reader) { SOPVehicle vehicle = SOPVehicle.FromXml2(reader); @@ -1669,6 +1688,9 @@ public static void SOPToXml2(XmlTextWriter writer, SceneObjectPart sop, Dictiona } if(Math.Abs(sop.SitActiveRange) > 1e-5) writer.WriteElementString("SitActRange", sop.SitActiveRange.ToString(Culture.FormatProvider)); + + writer.WriteElementString("LinksetData", sop.SerializeLinksetData()); + writer.WriteEndElement(); } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 01c59bbba95..2fd4ccaa110 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -18779,7 +18779,6 @@ public LSL_String llReplaceSubString(LSL_String src, LSL_String pattern, LSL_Str count = -1; } - try { if (string.IsNullOrEmpty(src.m_string)) @@ -18799,7 +18798,161 @@ public LSL_String llReplaceSubString(LSL_String src, LSL_String pattern, LSL_Str return src; } } - } + + public LSL_Integer llLinksetDataWrite(LSL_String name, LSL_String value) + { + return llLinksetDataWriteProtected(name, value, string.Empty); + } + + public LSL_Integer llLinksetDataWriteProtected(LSL_String name, LSL_String value, LSL_String pass) + { + if (name == "") + return ScriptBaseClass.LINKSETDATA_ENOKEY; + + var rootPrim = m_host.ParentGroup.RootPart; + + int ret = rootPrim.AddOrUpdateLinksetDataKey(name, value, pass); + object[] parameters = new object[] + { + new LSL_Integer(ScriptBaseClass.LINKSETDATA_UPDATE), name, string.Empty + }; + + if (ret == 0) + { + m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, + new EventParams("linkset_data", parameters, Array.Empty())); + + return ScriptBaseClass.LINKSETDATA_OK; + } + else + { + if (ret == 1) + { + return ScriptBaseClass.LINKSETDATA_EMEMORY; + } + else if (ret == 2) + { + return ScriptBaseClass.LINKSETDATA_NOUPDATE; + } + else + { + return ScriptBaseClass.LINKSETDATA_EPROTECTED; + } + } + } + + public void llLinksetDataReset() + { + var rootPrim = m_host.ParentGroup.RootPart; + + rootPrim.ResetLinksetData(); + + object[] parameters = new object[] + { + new LSL_Integer(ScriptBaseClass.LINKSETDATA_RESET), new LSL_String(string.Empty), new LSL_String(string.Empty) + }; + + EventParams linksetdata_params = new EventParams("linkset_data", parameters, Array.Empty()); + m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, linksetdata_params); + } + + public LSL_Integer llLinksetDataAvailable() + { + var rootPrim = m_host.ParentGroup.RootPart; + return new LSL_Integer(rootPrim.linksetDataBytesFree); + } + + public LSL_Integer llLinksetDataCountKeys() + { + var rootPrim = m_host.ParentGroup.RootPart; + return new LSL_Integer(rootPrim.LinksetDataKeys); + } + + public LSL_Integer llLinksetDataDelete(LSL_String name) + { + return llLinksetDataDeleteProtected(name, string.Empty); + } + + public LSL_List llLinksetDataDeleteFound(LSL_String pattern, LSL_String pass) + { + int deleted = 0; + int not_deleted = 0; + var root = m_host.ParentGroup.RootPart; + + var matches = root.LinksetDataMultiDelete(pattern, pass, out deleted, out not_deleted); + string removed_keys = String.Join(",", matches); + + object[] parameters = new object[] + { + new LSL_Integer(ScriptBaseClass.LINKSETDATA_MULTIDELETE), new LSL_String(removed_keys), new LSL_String(string.Empty) + }; + + if (deleted > 0) + { + m_ScriptEngine.PostObjectEvent(m_host.LocalId, new EventParams("linkset_data", parameters, Array.Empty())); + } + + return new LSL_List(new object[] + { + new LSL_Integer(deleted), new LSL_Integer(not_deleted) + }); + } + + public LSL_Integer llLinksetDataCountFound(LSL_String pattern) + { + var root = m_host.ParentGroup.RootPart; + return root.LinksetDataCountMatches(pattern); + } + + public LSL_Integer llLinksetDataDeleteProtected(LSL_String name, LSL_String pass) + { + var rootPrim = m_host.ParentGroup.RootPart; + int ret = rootPrim.DeleteLinksetDataKey(name, pass); + object[] parameters; + + if (ret == 0) + { + parameters = new object[] + { + new LSL_Integer(ScriptBaseClass.LINKSETDATA_OK), name, new LSL_String(string.Empty) + }; + } + else if (ret == 1) + { + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_EPROTECTED); + } + else + { + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_NOTFOUND); + } + + m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, new EventParams("linkset_data", parameters, Array.Empty())); + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_OK); + } + + public LSL_List llLinksetDataFindKeys(LSL_String pattern, LSL_Integer start, LSL_Integer count) + { + var rootPrim = m_host.ParentGroup.RootPart; + return new LSL_List(rootPrim.FindLinksetDataKeys(pattern, start, count)); + } + + public LSL_List llLinksetDataListKeys(LSL_Integer start, LSL_Integer count) + { + var rootPrim = m_host.ParentGroup.RootPart; + return new LSL_List(rootPrim.GetLinksetDataSubList(start, count)); + } + + public LSL_String llLinksetDataRead(LSL_String name) + { + return llLinksetDataReadProtected(name, string.Empty); + } + + public LSL_String llLinksetDataReadProtected(LSL_String name, LSL_String pass) + { + var rootPrim = m_host.ParentGroup.RootPart; + return new LSL_String(rootPrim.ReadLinksetData(name, pass)); + } + } public class NotecardCache { diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs index 6ea61f2321c..55a8865e769 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/ILSL_Api.cs @@ -489,10 +489,24 @@ public interface ILSL_Api LSL_String llReplaceSubString(LSL_String src, LSL_String pattern, LSL_String replacement, int count); - void llLinkAdjustSoundVolume(LSL_Integer linknumber, LSL_Float volume); - void llLinkStopSound(LSL_Integer linknumber); - void llLinkPlaySound(LSL_Integer linknumber, string sound, double volume); - void llLinkSetSoundQueueing(int linknumber, int queue); - void llLinkSetSoundRadius(int linknumber, double radius); + void llLinkAdjustSoundVolume(LSL_Integer linknumber, LSL_Float volume); + void llLinkStopSound(LSL_Integer linknumber); + void llLinkPlaySound(LSL_Integer linknumber, string sound, double volume); + void llLinkSetSoundQueueing(int linknumber, int queue); + void llLinkSetSoundRadius(int linknumber, double radius); + + LSL_Integer llLinksetDataWrite(LSL_String name, LSL_String value); + LSL_Integer llLinksetDataWriteProtected(LSL_String name, LSL_String value, LSL_String pass); + void llLinksetDataReset(); + LSL_Integer llLinksetDataAvailable(); + LSL_Integer llLinksetDataCountKeys(); + LSL_Integer llLinksetDataDelete(LSL_String name); + LSL_Integer llLinksetDataDeleteProtected(LSL_String name, LSL_String pass); + LSL_List llLinksetDataDeleteFound(LSL_String pattern, LSL_String pass); + LSL_Integer llLinksetDataCountFound(LSL_String pattern); + LSL_List llLinksetDataFindKeys(LSL_String pattern, LSL_Integer start, LSL_Integer count); + LSL_List llLinksetDataListKeys(LSL_Integer start, LSL_Integer count); + LSL_String llLinksetDataRead(LSL_String name); + LSL_String llLinksetDataReadProtected(LSL_String name, LSL_String pass); } } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs index d44c58126a1..0babf18bfb3 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs @@ -1001,5 +1001,17 @@ public partial class ScriptBaseClass public const int NPCLOOKAT_FOCUS = 8; public const int NPCLOOKAT_MOUSELOOK = 9; public const int NPCLOOKAT_CLEAR = 10; + + public const int LINKSETDATA_RESET = 0; + public const int LINKSETDATA_UPDATE = 1; + public const int LINKSETDATA_DELETE = 2; + public const int LINKSETDATA_MULTIDELETE = 3; + + public const int LINKSETDATA_OK = 0; + public const int LINKSETDATA_EMEMORY = 1; + public const int LINKSETDATA_ENOKEY = 2; + public const int LINKSETDATA_EPROTECTED = 3; + public const int LINKSETDATA_NOTFOUND = 4; + public const int LINKSETDATA_NOUPDATE = 5; } } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs index 4edf92bb5df..88b712bc456 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Stub.cs @@ -2242,5 +2242,82 @@ public void llLinkSetSoundRadius(int linknumber, double radius) { m_LSL_Functions.llLinkSetSoundRadius(linknumber, radius); } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataWrite(LSL_String name, LSL_String value) + { + return m_LSL_Functions.llLinksetDataWrite(name, value); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataWriteProtected(LSL_String name, LSL_String value, LSL_String pass) + { + return m_LSL_Functions.llLinksetDataWriteProtected(name, value, pass); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataAvailable() + { + return m_LSL_Functions.llLinksetDataAvailable(); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public void llLinksetDataReset() + { + m_LSL_Functions.llLinksetDataReset(); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataCountKeys() + { + return m_LSL_Functions.llLinksetDataCountKeys(); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataDelete(LSL_String name) + { + return m_LSL_Functions.llLinksetDataDelete(name); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_Integer llLinksetDataDeleteProtected(LSL_String name, LSL_String pass) + { + return m_LSL_Functions.llLinksetDataDeleteProtected(name, pass); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_List llLinksetDataFindKeys(LSL_String pattern, LSL_Integer start, LSL_Integer count) + { + return m_LSL_Functions.llLinksetDataFindKeys(pattern, start,count); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_List llLinksetDataListKeys(LSL_Integer start, LSL_Integer count) + { + return m_LSL_Functions.llLinksetDataListKeys(start,count); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_String llLinksetDataRead(LSL_String name) + { + return m_LSL_Functions.llLinksetDataRead(name); + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public LSL_String llLinksetDataReadProtected(LSL_String name, LSL_String pass) + { + return m_LSL_Functions.llLinksetDataReadProtected(name, pass); + } + + public LSL_Integer llLinksetDataCountFound(LSL_String pattern) + { + return m_LSL_Functions.llLinksetDataCountFound(pattern); + } + + public LSL_List llLinksetDataDeleteFound(LSL_String pattern, LSL_String pass) + { + return m_LSL_Functions.llLinksetDataDeleteFound(pattern, pass); + } + } } diff --git a/OpenSim/Region/ScriptEngine/YEngine/MMRIEventHandlers.cs b/OpenSim/Region/ScriptEngine/YEngine/MMRIEventHandlers.cs index 905a23072fd..82d859676d8 100644 --- a/OpenSim/Region/ScriptEngine/YEngine/MMRIEventHandlers.cs +++ b/OpenSim/Region/ScriptEngine/YEngine/MMRIEventHandlers.cs @@ -74,5 +74,6 @@ public interface IEventHandlers void transaction_result(string id, int success, string data); void path_update(int type, LSL_List data); void region_cross(LSL_Vector newpos, LSL_Vector oldpos); + void linkset_data(LSL_Integer action, string name, string value); } } diff --git a/OpenSim/Region/ScriptEngine/YEngine/MMRScriptEventCode.cs b/OpenSim/Region/ScriptEngine/YEngine/MMRScriptEventCode.cs index 775e0a6a629..b854437df1b 100644 --- a/OpenSim/Region/ScriptEngine/YEngine/MMRScriptEventCode.cs +++ b/OpenSim/Region/ScriptEngine/YEngine/MMRScriptEventCode.cs @@ -83,8 +83,9 @@ public enum ScriptEventCode: int http_request = 38, // path_update = 40, + linkset_data = 41, // marks highest numbered event - Size = 41 + Size = 42 } } diff --git a/OpenSim/Region/ScriptEngine/YEngine/XMRInstMain.cs b/OpenSim/Region/ScriptEngine/YEngine/XMRInstMain.cs index ffb15b6d2e5..36d62e315ac 100644 --- a/OpenSim/Region/ScriptEngine/YEngine/XMRInstMain.cs +++ b/OpenSim/Region/ScriptEngine/YEngine/XMRInstMain.cs @@ -241,7 +241,8 @@ public partial class XMRInstance: XMRInstAbstract, IDisposable {"on_rez", ScriptEventCode.on_rez}, {"sensor", ScriptEventCode.sensor}, {"http_request", ScriptEventCode.http_request}, - {"path_update", ScriptEventCode.path_update} + {"path_update", ScriptEventCode.path_update}, + {"linkset_data", ScriptEventCode.linkset_data} }; } } diff --git a/addon-modules/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData.csproj b/addon-modules/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData.csproj index 2285e1edf4f..1cd26b1d009 100644 --- a/addon-modules/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData.csproj +++ b/addon-modules/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData/OpenSim.Data.MySQL.MoneyData.csproj @@ -10,6 +10,7 @@ + diff --git a/bin/ScriptSyntax.xml b/bin/ScriptSyntax.xml index d10146dd619..249495ab2fe 100644 --- a/bin/ScriptSyntax.xml +++ b/bin/ScriptSyntax.xml @@ -1,4 +1,4 @@ -face0493-6022-3cf1-bdbe-1ae375f7f2da +f0eb469c-b2f3-4d64-802a-1ac709cb91e6 llsd-lsl-syntax-version2 controls @@ -311,6 +311,31 @@ face0493-6022-3cf1-bdbe-1ae375f7f2da tooltipTriggered by llTransferMoney() function + linkset_data + arguments + + action + + type + integer + + + + name + + type + string + + + + value + + type + string + + + + constants @@ -3461,6 +3486,76 @@ face0493-6022-3cf1-bdbe-1ae375f7f2da typevector value>0.0,0.0,0.0< + LINKSETDATA_OK + + type + integer + value + 0 + + LINKSETDATA_EMEMORY + + type + integer + value + 1 + + LINKSETDATA_ENOKEY + + type + integer + value + 2 + + LINKSETDATA_EPROTECTED + + type + integer + value + 3 + + LINKSETDATA_NOTFOUND + + type + integer + value + 4 + + LINKSETDATA_NOUPDATE + + type + integer + value + 5 + + LINKSETDATA_RESET + + type + integer + value + 0 + + LINKSETDATA_UPDATE + + type + integer + value + 1 + + LINKSETDATA_DELETE + + type + integer + value + 2 + + LINKSETDATA_MULTIDELETE + + type + integer + value + 3 + functions @@ -4852,6 +4947,231 @@ face0493-6022-3cf1-bdbe-1ae375f7f2da volumetypefloat + llLinksetDataWrite + + return + integer + arguments + + + name + + type + string + + + + value + + type + string + + + + + llLinksetDataWriteProtected + + return + integer + arguments + + + name + + type + string + + + + value + + type + string + + + + pass + + type + string + + + + + llLinksetDataRead + + return + string + arguments + + + name + + type + string + + + + + llLinksetDataReadProtected + + return + string + arguments + + + name + + type + string + + + + pass + + type + string + + + + + llLinksetDataDelete + + arguments + + + name + + type + string + + + + + llLinksetDataDeleteProtected + + arguments + + + name + + type + string + + + + pass + + type + string + + + + + llLinksetDataDeleteFound + + return + integer + arguments + + + pattern + + type + string + + + + pass + + type + string + + + + + llLinksetDataReset + + arguments + + llLinksetDataAvailable + + return + integer + arguments + + llLinksetDataCountKeys + + return + integer + arguments + + llLinksetDataCountFound + + return + integer + arguments + + + pattern + + type + string + + + + + llLinksetDataListKeys + + return + list + arguments + + + first + + type + integer + + + + count + + type + integer + + + + + llLinksetDataFindKeys + + return + list + arguments + + + regex + + type + string + + + + first + + type + integer + + + + count + + type + integer + + + + llLinkSetSoundQueueing arguments From ad6defed03dcd156f12ece2850d7fa8696938cc2 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Tue, 21 Nov 2023 09:38:44 -0500 Subject: [PATCH 7/9] Bump version number --- OpenSim/Framework/VersionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 12af4d47c66..b0ab2faa8cc 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8719"; + public const string Release = "8720"; public static string Version { From be88cb833294f69cd08b046632d850c3baab8934 Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Sun, 3 Dec 2023 11:57:51 -0500 Subject: [PATCH 8/9] Bug fixes for LinkSet Data related to matching SL behavior (#68) * For YEngine when posting events for linkset_data, deliver the event to all scripts in all prims. Similar to attach which is currently handled here. Clean up some of the special case handling in LinksetData Write. Writing a key with no value is translated to a delete. We weren't handling that case. Also returned key value on a write, we were previously returning a null string. * Bumped version. Refactored LinksetData support from the SOP into a seperate LinkssetData class. The SOP is one of those things thats already WAY to complex so hopefully this should make maintenance easier. Added code to merge LinksetData stores in order to handle linking 2 SOGs that both have LinksetData. Adjusted serialization handling so the database still stores just the KVPs and the wrapper class is constructed from that. Keeps it compatible with current saved data. * Address some CodeQL comments. Rework Merge to address an issue with shared entries. Still need to address shift-copy issues. * Bump version. Add code to LinksetData and LinksetEntry to do a deep copy. We use that when doing a copy of prims because otherwise the dictionary ends being referenced from both prims on a shift copy, etc. Also used on a merge to create new entries. Fixed some of the accounting code which was incorrectly reporting usage and terminating a copy early on a merge. * Address CodeQL issues, cleaner handling of possible null values. --- OpenSim/Data/MySQL/MySQLSimulationData.cs | 4 +- OpenSim/Framework/VersionInfo.cs | 2 +- .../OpenSim.Region.Framework.Tests.csproj | 2 + .../Region/Framework/Scenes/LinksetData.cs | 417 ++++++++++++++++++ .../Framework/Scenes/LinksetDataEntry.cs | 39 -- .../Framework/Scenes/SceneObjectGroup.cs | 20 +- .../Framework/Scenes/SceneObjectPart.cs | 317 +------------ .../Shared/Api/Implementation/LSL_Api.cs | 172 ++++++-- .../Region/ScriptEngine/YEngine/XMREngine.cs | 4 +- 9 files changed, 582 insertions(+), 395 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/LinksetData.cs delete mode 100644 OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs index a054e12782f..c2655193fb5 100644 --- a/OpenSim/Data/MySQL/MySQLSimulationData.cs +++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs @@ -1620,7 +1620,7 @@ private void FillPrimCommand(MySqlCommand cmd, SceneObjectPart prim, UUID sceneG cmd.Parameters.AddWithValue("Restitution", prim.Restitution); cmd.Parameters.AddWithValue("RotationAxisLocks", prim.RotationAxisLocks); - if (prim.Animations!= null) + if (prim.Animations != null) cmd.Parameters.AddWithValue("sopanims", prim.SerializeAnimations()); else cmd.Parameters.AddWithValue("sopanims", null); @@ -1628,7 +1628,7 @@ private void FillPrimCommand(MySqlCommand cmd, SceneObjectPart prim, UUID sceneG cmd.Parameters.AddWithValue("sitactrange", prim.SitActiveRange); cmd.Parameters.AddWithValue("pseudocrc", prim.PseudoCRC); - if (prim.HasLinksetData) + if (prim.LinksetData != null) { cmd.Parameters.AddWithValue("linksetdata", prim.SerializeLinksetData()); } diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index b0ab2faa8cc..5dc3165fbce 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8720"; + public const string Release = "8736"; public static string Version { diff --git a/OpenSim/Region/Framework/OpenSim.Region.Framework.Tests.csproj b/OpenSim/Region/Framework/OpenSim.Region.Framework.Tests.csproj index 61fdd443b09..4e8660ce851 100644 --- a/OpenSim/Region/Framework/OpenSim.Region.Framework.Tests.csproj +++ b/OpenSim/Region/Framework/OpenSim.Region.Framework.Tests.csproj @@ -141,6 +141,8 @@ + + diff --git a/OpenSim/Region/Framework/Scenes/LinksetData.cs b/OpenSim/Region/Framework/Scenes/LinksetData.cs new file mode 100644 index 00000000000..8f8278a7b29 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/LinksetData.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace OpenSim.Region.Framework.Scenes +{ + public class LinksetData + { + public const int LINKSETDATA_MAX = 131072; + + private static readonly object linksetDataLock = new object(); + + public LinksetData() + { + Data = new SortedList(); + + LinksetDataBytesFree = LINKSETDATA_MAX; + LinksetDataBytesUsed = 0; + } + + public SortedList Data { get; private set; } = null; + + public int LinksetDataBytesFree { get; private set; } = LINKSETDATA_MAX; + public int LinksetDataBytesUsed { get; private set; } = 0; + + // Deep Copy of Linkset Data + public LinksetData Copy() + { + lock (linksetDataLock) + { + var copy = new LinksetData(); + foreach (var entry in Data) + { + var key = String.Copy(entry.Key); + var val = entry.Value.Copy(); + copy.Data.Add(key, val); + copy.LinksetDataAccountingDelta(val.GetCost(key)); + } + + return copy; + } + } + + /// + /// Adds or updates a entry to linkset data + /// + /// + /// -1 if the password did not match + /// -1 is the data was protected + /// 0 if the data was successfully added or updated + /// 1 if the data could not be added or updated due to memory + /// 2 if the data is unchanged + /// + public int AddOrUpdateLinksetDataKey(string key, string value, string pass) + { + lock (linksetDataLock) + { + if (LinksetDataOverLimit()) + return 1; + + LinksetDataEntry entry = null; + if (Data.TryGetValue(key, out entry)) + { + if (!entry.CheckPassword(pass)) + return -1; + + if (entry.Value == value) + return 2; + + // Subtract out the old entry + LinksetDataAccountingDelta(-entry.GetCost(key)); + } + + // Add New or Update handled here. + LinksetDataEntry newEntry = new LinksetDataEntry(value, pass); + Data[key] = newEntry; + + // Add the cost for the newply created entry + LinksetDataAccountingDelta(newEntry.GetCost(key)); + + return 0; + } + } + + /// + /// Deletes a named key from the key value store + /// + /// The key value we're removing + /// The password for a protected field (or string.Empty if not protected) + /// + /// 0 if successful. + /// 1 if not due to the password. + /// -1 if no such key was found + /// + public int DeleteLinksetDataKey(string key, string pass) + { + lock (linksetDataLock) + { + if (Data.Count <= 0) + return -1; + + LinksetDataEntry entry; + if (!Data.TryGetValue(key, out entry)) + return -1; + + if (!entry.CheckPassword(pass)) + return 1; + + Data.Remove(key); + LinksetDataAccountingDelta(-entry.GetCost(key)); + + return 0; + } + } + + public void DeserializeLinksetData(string data) + { + if (data == null || data.Length == 0) + return; + + //? Need to adjust accounting + lock (linksetDataLock) + { + Data = JsonSerializer.Deserialize>(data); + } + } + + /// + /// FindLinksetDataKeys - Given a Regex pattern and start, count return the + /// list of matchingkeys in the LinksetData store. + /// + /// A Regex pattern to match + /// starting offset into the list of keys + /// how many to return, < 1 means all keys + /// + public string[] FindLinksetDataKeys(string pattern, int start, int count) + { + List all_keys = new List(GetLinksetDataSubList(0, 0)); + Regex rx = new Regex(pattern, RegexOptions.CultureInvariant); + + if (count < 1) + count = all_keys.Count; + + List matches = new List(); + foreach (var str in all_keys) + { + if (rx.IsMatch(str)) + matches.Add(str); + } + + return matches.Skip(start).Take(count).ToArray(); + } + + /// + /// GetLinksetDataSubList - Return a subset of key values from start for count + /// + /// Offset in the list for the first key + /// How many keys to return, < 1 means all keys + /// + public string[] GetLinksetDataSubList(int start, int count) + { + lock (linksetDataLock) + { + if (Data.Count <= 0) + return new string[0]; + + if (count < 1) + count = Data.Count; + + List ret = Data.Keys.Skip(start).Take(count).ToList(); + + return ret.ToArray(); + } + } + + public bool HasLinksetData() + { + return Data.Count > 0; + } + + /// + /// LinksetDataCountMatches - Return a count of the # of keys that match pattern. + /// + /// The Regex pattern to match + /// An integer count or zero if none + public int LinksetDataCountMatches(string pattern) + { + lock (linksetDataLock) + { + if (Data.Count <= 0) + return 0; + + Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); + + int count = 0; + foreach (var kvp in Data) + { + if (reg.IsMatch(kvp.Key)) + count++; + } + + return count; + } + } + + public int LinksetDataKeys() + { + return Data.Count; + } + + public string[] LinksetDataMultiDelete(string pattern, string pass, out int deleted, out int not_deleted) + { + lock (linksetDataLock) + { + deleted = 0; + not_deleted = 0; + + if (Data.Count <= 0) + return new string[0]; + + Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); + List matches = new List(); + + // Take a copy so we can delete as we iterate + foreach (var kvp in Data.ToArray()) + { + if (reg.IsMatch(kvp.Key)) + { + if (kvp.Value.CheckPassword(pass)) + { + Data.Remove(kvp.Key); + matches.Add(kvp.Key); + + LinksetDataAccountingDelta(-kvp.Value.GetCost(kvp.Key)); + deleted += 1; + } + else + { + not_deleted += 1; + } + } + } + + return matches.ToArray(); + } + } + + public bool LinksetDataOverLimit() + { + return (LinksetDataBytesFree <= 0); + } + + /// + /// Merge the linksetData present in another Linkset into this one. + /// The current root will have the new linkset for the merged sog. + /// If a key is present in our linksetData it wins, dont overide it. + /// + /// + public void MergeLinksetData(LinksetData otherLinksetData) + { + // Nothing to merge? + if (otherLinksetData == null) + return; + + lock (linksetDataLock) + { + foreach (var kvp in otherLinksetData.Data) + { + // If its already present skip it + if (Data.ContainsKey(kvp.Key)) + continue; + + // Do we have space for another entry? + if (LinksetDataOverLimit()) + break; + + var key = string.Copy(kvp.Key); + var value = kvp.Value.Copy(); + + Data.Add(key, value); + LinksetDataAccountingDelta(value.GetCost(key)); + } + + // Clear the LinksetData entries from the "other" SOG + otherLinksetData.Data.Clear(); + + otherLinksetData.LinksetDataBytesFree = LINKSETDATA_MAX; + otherLinksetData.LinksetDataBytesUsed = 0; + } + } + + /// + /// Reads a value from the key value pair + /// + /// The key value we're retrieving + /// The password for a protected field (or string.Empty if not protected) + /// Blank if no key or pass mismatch. Value if no password or pass matches + public string ReadLinksetData(string key, string pass) + { + lock (linksetDataLock) + { + if (Data.Count <= 0) + return string.Empty; + + LinksetDataEntry entry; + if (Data.TryGetValue(key, out entry)) + { + return entry.CheckPasswordAndGetValue(pass); + } + + return string.Empty; + } + } + + /// + /// ResetLinksetData - clear the list and update the accounting. + /// + public void ResetLinksetData() + { + lock (linksetDataLock) + { + if (Data.Count <= 0) + return; + + Data.Clear(); + + LinksetDataBytesFree = LINKSETDATA_MAX; + LinksetDataBytesUsed = 0; + } + } + + public string SerializeLinksetData() + { + lock (linksetDataLock) + { + return JsonSerializer.Serialize>(Data); + } + } + + /// + /// Add/Subtract an integer value from the current data allocated for the Linkset. + /// + /// An integer value, positive adds, negative subtracts delta bytes. + private void LinksetDataAccountingDelta(int delta) + { + LinksetDataBytesUsed += delta; + LinksetDataBytesFree = LINKSETDATA_MAX - LinksetDataBytesUsed; + + if (LinksetDataBytesFree < 0) + LinksetDataBytesFree = 0; + } + } + + public class LinksetDataEntry + { + public LinksetDataEntry(string value, string password) + { + Value = value; + Password = password; + } + + public string Password { get; private set; } = string.Empty; + + public string Value { get; private set; } + + public bool CheckPassword(string pass) + { + // A undocumented caveat for LinksetData appears to be that even for unprotected values, + // if a pass is provided, it is still treated as protected + return string.IsNullOrEmpty(Password) || (Password == pass); + } + + public string CheckPasswordAndGetValue(string pass) + { + return CheckPassword(pass) ? Value : string.Empty; + } + + // Deep Copy of Current Entry + public LinksetDataEntry Copy() + { + string value = String.IsNullOrEmpty(Value) ? null : string.Copy(Value); + string password = String.IsNullOrEmpty(Password) ? null : string.Copy(Password); + + return new LinksetDataEntry(value, password); + } + + /// + /// Calculate the cost in bytes for this entry. Adds in the passed in key and + /// if a password is supplied uses 32 bytes minimum unless the password is longer. + /// + /// The string key value associated with this entry. + /// int - cost of this entry in bytes + public int GetCost(string key) + { + int cost = 0; + + cost += Encoding.UTF8.GetBytes(key).Length; + cost += Encoding.UTF8.GetBytes(this.Value).Length; + + if (!string.IsNullOrEmpty(this.Password)) + { + // For parity, the pass adds 32 bytes regardless of the length. See LL caveats + cost += Math.Max(Encoding.UTF8.GetBytes(this.Password).Length, 32); + } + + return cost; + } + + public bool IsProtected() + { + return !string.IsNullOrEmpty(Password); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs b/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs deleted file mode 100644 index 68ab7499728..00000000000 --- a/OpenSim/Region/Framework/Scenes/LinksetDataEntry.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.IO; - -namespace OpenSim.Region.Framework.Scenes -{ - public class LinksetDataEntry - { - public LinksetDataEntry(string value, string password) - { - this.Value = value; - this.Password = password; - } - - public string Value { get; private set; } - public string Password { get; private set; } = string.Empty; - - public bool IsProtected() - { - return (string.IsNullOrEmpty(this.Password) == false); - } - - public bool CheckPassword(string pass) - { - // A undocumented caveat for LinksetData appears to be that even for unprotected values, if a pass is provided, it is still treated as protected - if (this.Password == pass) - return true; - else - return false; - } - - public string CheckPasswordAndGetValue(string pass) - { - if (string.IsNullOrEmpty(this.Password) || (this.Password == pass)) - return this.Value; - else - return string.Empty; - } - } -} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index e7b50411371..c46dead5f5f 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -2664,10 +2664,17 @@ public SceneObjectGroup Copy(bool userExposed) public void CopyRootPart(SceneObjectPart part, UUID cAgentID, UUID cGroupID, bool userExposed) { SceneObjectPart newpart = part.Copy(m_scene.AllocateLocalId(), OwnerID, GroupID, 0, userExposed); -// SceneObjectPart newpart = part.Copy(part.LocalId, OwnerID, GroupID, 0, userExposed); -// newpart.LocalId = m_scene.AllocateLocalId(); + // SceneObjectPart newpart = part.Copy(part.LocalId, OwnerID, GroupID, 0, userExposed); + // newpart.LocalId = m_scene.AllocateLocalId(); + + // If the rootpart we're copying has LinksetData do a deep copy of that to the new rootpart. + if (part.LinksetData != null) + { + newpart.LinksetData = part.LinksetData.Copy(); + } SetRootPart(newpart); + if (userExposed) RootPart.Velocity = Vector3.Zero; // In case source is moving } @@ -3252,6 +3259,15 @@ public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) // 'linkPart' == the root of the group being linked into this group SceneObjectPart linkPart = objectGroup.m_rootPart; + // Merge linksetData if there is any + if (linkPart.LinksetData != null) + { + if (m_rootPart.LinksetData == null) + m_rootPart.LinksetData = new LinksetData(); + + m_rootPart.LinksetData.MergeLinksetData(linkPart.LinksetData); + } + if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; if (linkPart.PhysActor != null) diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 815e289625b..d55028b53d9 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -49,6 +49,7 @@ using System.Data.Entity.ModelConfiguration.Configuration; using System.Runtime.Serialization.Json; using OpenMetaverse.Rendering; +using System.Web.Configuration; namespace OpenSim.Region.Framework.Scenes { @@ -204,10 +205,7 @@ public bool IsSitTargetSet public PhysicsActor PhysActor { get; set; } [XmlIgnore] - public SortedList LinksetData = null; - //private Dictionary LinksetData = null; - - private static readonly object linksetDataLock = new object(); + public LinksetData LinksetData { get; set; } = null; //Xantor 20080528 Sound stuff: // Note: This isn't persisted in the database right now, as the fields for that aren't just there yet. @@ -5824,319 +5822,28 @@ public void DeSerializeAnimations(Byte[] data) AnimationsNames = null; } - /// - /// FindLinksetDataKeys - Given a Regex pattern and start, count return the - /// list of matchingkeys in the LinksetData store. - /// - /// A Regex pattern to match - /// starting offset into the list of keys - /// how many to return, < 1 means all keys - /// - public string[] FindLinksetDataKeys(string pattern, int start, int count) - { - List all_keys = new List(GetLinksetDataSubList(0, 0)); - Regex rx = new Regex(pattern, RegexOptions.CultureInvariant); - - if (count < 1) - count = all_keys.Count; - - List matches = new List(); - foreach (var str in all_keys) - { - if (rx.IsMatch(str)) - matches.Add(str); - } - - return matches.Skip(start).Take(count).ToArray(); - } - - /// - /// Adds or updates a entry to linkset data - /// - /// - /// -1 if the password did not match - /// -1 is the data was protected - /// 0 if the data was successfully added or updated - /// 1 if the data could not be added or updated due to memory - /// 2 if the data is unchanged - /// - public int AddOrUpdateLinksetDataKey(string key, string value, string pass) - { - lock (linksetDataLock) - { - if (LinksetData == null) - LinksetData = new SortedList(); - - if (LinksetDataOverLimit) - return 1; - - LinksetDataEntry entry = null; - if (LinksetData.TryGetValue(key, out entry) == true) - { - if (entry.CheckPassword(pass) == false) - return -1; - - if (entry.Value == value) - return 2; - } - - // Add New or Update handled here. - LinksetDataEntry newEntry = new LinksetDataEntry(value, pass); - LinksetData[key] = newEntry; - - UpdateLinksetDataAccounting(); - - if (ParentGroup != null) - ParentGroup.HasGroupChanged = true; - - return 0; - } - } - - /// - /// Reads a value from the key value pair - /// - /// The key value we're retrieving - /// The password for a protected field (or string.Empty if not protected) - /// Blank if no key or pass mismatch. Value if no password or pass matches - public string ReadLinksetData(string key, string pass) - { - lock (linksetDataLock) - { - if (LinksetData == null) - return string.Empty; - - LinksetDataEntry entry; - if (LinksetData.TryGetValue(key, out entry) == true) - return entry.CheckPasswordAndGetValue(pass); - - return string.Empty; - } - } - - /// - /// Deletes a named key from the key value store - /// - /// The key value we're removing - /// The password for a protected field (or string.Empty if not protected) - /// - /// 0 if successful. - /// 1 if not due to the password. - /// -1 if no such key was found - /// - public int DeleteLinksetDataKey(string key, string pass) - { - lock (linksetDataLock) - { - if (LinksetData == null) - return -1; - - LinksetDataEntry entry; - if (LinksetData.TryGetValue(key, out entry) == false) - return -1; - - if (entry.CheckPassword(pass) == false) - return 1; - - LinksetData.Remove(key); - - UpdateLinksetDataAccounting(); - - if (ParentGroup != null) - ParentGroup.HasGroupChanged = true; - - return 0; - } - } - - /// - /// GetLinksetDataSubList - Return a subset of key values from start for count - /// - /// Offset in the list for the first key - /// How many keys to return, < 1 means all keys - /// - public string[] GetLinksetDataSubList(int start, int count) - { - lock (linksetDataLock) - { - if (LinksetData == null) - return new string[0]; - - if (count < 1) - count = LinksetData.Count; - - List ret = LinksetData.Keys.Skip(start).Take(count).ToList(); - return ret.ToArray(); - } - } - - /// - /// ResetLinksetData - If unallocated leave it that way, otherwise - /// clear the list and update the accounting. - /// - public void ResetLinksetData() - { - lock (linksetDataLock) - { - if (LinksetData == null) - return; - - LinksetData.Clear(); - - UpdateLinksetDataAccounting(); - - if (ParentGroup != null) - ParentGroup.HasGroupChanged = true; - } - } - - /// - /// LinksetDataCountMatches - Return a count of the # of keys that match pattern. - /// - /// The Regex pattern to match - /// An integer count or zero if none - public int LinksetDataCountMatches(string pattern) - { - lock (linksetDataLock) - { - if (LinksetData == null) - return 0; - - Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); - - int count = 0; - foreach (var kvp in LinksetData) - { - if (reg.IsMatch(kvp.Key)) - count++; - } - - return count; - } - } - - public string[] LinksetDataMultiDelete(string pattern, string pass, out int deleted, out int not_deleted) - { - lock (linksetDataLock) - { - deleted = 0; - not_deleted = 0; - - if (LinksetData == null) - return new string[0]; - - Regex reg = new Regex(pattern, RegexOptions.CultureInvariant); - List matches = new List(); - - // Take a copy so we can delete as we iterate - foreach (var kvp in LinksetData.ToArray()) - { - if (reg.IsMatch(kvp.Key)) - { - if (kvp.Value.CheckPassword(pass)) - { - LinksetData.Remove(kvp.Key); - matches.Add(kvp.Key); - deleted += 1; - } - else - { - not_deleted += 1; - } - } - } - - if (deleted > 0) - { - UpdateLinksetDataAccounting(); - - if (ParentGroup != null) - ParentGroup.HasGroupChanged = true; - } - - return matches.ToArray(); - } - } - - /// - /// Recalculates the amount of memory used by linkset data. - /// Caller should be holding the linksetData lock - /// - private void UpdateLinksetDataAccounting() - { - using (MemoryStream ms = new MemoryStream()) - { - using (BinaryWriter bw = new BinaryWriter(ms)) - { - foreach (var entry in LinksetData) - { - bw.Write(Encoding.UTF8.GetBytes(entry.Key)); - bw.Write(Encoding.UTF8.GetBytes(entry.Value.Value)); - - // For parity, the pass adds 32 bytes regardless of the length. See LL caveats - if (entry.Value.IsProtected()) - bw.Write(new byte[32]); - } - } - - linksetDataBytesUsed = ms.ToArray().Length; - linksetDataBytesFree = LINKSETDATA_MAX - linksetDataBytesUsed; - } - } - - public const int LINKSETDATA_MAX = 131072; // 128 KB - - public int linksetDataBytesUsed; - public int linksetDataBytesFree = LINKSETDATA_MAX; // Default - - public bool HasLinksetData - { - get - { - if (LinksetData == null) - return false; - - return LinksetData.Count > 0; - } - } - - public bool LinksetDataOverLimit - { - get { return linksetDataBytesFree < 0; } - } - - public int LinksetDataKeys - { - get - { - if (LinksetData == null) - return 0; - - return LinksetData.Count; - } - } - public string SerializeLinksetData() { - if (!IsRoot || LinksetData == null) - return null; - - lock (linksetDataLock) + if (IsRoot && (LinksetData != null)) { - return JsonSerializer.Serialize>(LinksetData); + if (LinksetData.HasLinksetData()) + return LinksetData.SerializeLinksetData(); } + + return string.Empty; } public void DeserializeLinksetData(string data) { - if (data == null || data.Length == 0) + if (string.IsNullOrEmpty(data) || data.Length == 0) return; - lock (linksetDataLock) + if (LinksetData == null) { - LinksetData = JsonSerializer.Deserialize>(data); - UpdateLinksetDataAccounting(); + LinksetData = new LinksetData(); } + + LinksetData.DeserializeLinksetData(data); } public bool GetOwnerName(out string FirstName, out string LastName) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 2fd4ccaa110..2593a01d77e 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -18801,27 +18801,42 @@ public LSL_String llReplaceSubString(LSL_String src, LSL_String pattern, LSL_Str public LSL_Integer llLinksetDataWrite(LSL_String name, LSL_String value) { - return llLinksetDataWriteProtected(name, value, string.Empty); + return llLinksetDataWriteProtected(name, value, new LSL_String(string.Empty)); } public LSL_Integer llLinksetDataWriteProtected(LSL_String name, LSL_String value, LSL_String pass) { - if (name == "") + if (string.IsNullOrEmpty(name)) return ScriptBaseClass.LINKSETDATA_ENOKEY; + // If value is empty this becomes a key delete operation + if (string.IsNullOrEmpty(value)) + { + return llLinksetDataDeleteProtected(name, pass); + } + var rootPrim = m_host.ParentGroup.RootPart; - - int ret = rootPrim.AddOrUpdateLinksetDataKey(name, value, pass); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + int ret = rootPrim.LinksetData.AddOrUpdateLinksetDataKey(name, value, pass); + object[] parameters = new object[] { - new LSL_Integer(ScriptBaseClass.LINKSETDATA_UPDATE), name, string.Empty + new LSL_Integer(ScriptBaseClass.LINKSETDATA_UPDATE), name, value }; if (ret == 0) { - m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, + m_ScriptEngine.PostObjectEvent( + rootPrim.LocalId, new EventParams("linkset_data", parameters, Array.Empty())); + rootPrim.ParentGroup.HasGroupChanged = true; + return ScriptBaseClass.LINKSETDATA_OK; } else @@ -18844,52 +18859,123 @@ public LSL_Integer llLinksetDataWriteProtected(LSL_String name, LSL_String value public void llLinksetDataReset() { var rootPrim = m_host.ParentGroup.RootPart; - - rootPrim.ResetLinksetData(); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + rootPrim.LinksetData.ResetLinksetData(); object[] parameters = new object[] { new LSL_Integer(ScriptBaseClass.LINKSETDATA_RESET), new LSL_String(string.Empty), new LSL_String(string.Empty) }; - - EventParams linksetdata_params = new EventParams("linkset_data", parameters, Array.Empty()); - m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, linksetdata_params); + + m_ScriptEngine.PostObjectEvent( + rootPrim.LocalId, + new EventParams("linkset_data", parameters, Array.Empty())); + + rootPrim.ParentGroup.HasGroupChanged = true; } public LSL_Integer llLinksetDataAvailable() { var rootPrim = m_host.ParentGroup.RootPart; - return new LSL_Integer(rootPrim.linksetDataBytesFree); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + return new LSL_Integer(rootPrim.LinksetData.LinksetDataBytesFree); } public LSL_Integer llLinksetDataCountKeys() { var rootPrim = m_host.ParentGroup.RootPart; - return new LSL_Integer(rootPrim.LinksetDataKeys); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + return new LSL_Integer(rootPrim.LinksetData.LinksetDataKeys()); } public LSL_Integer llLinksetDataDelete(LSL_String name) { - return llLinksetDataDeleteProtected(name, string.Empty); + return llLinksetDataDeleteProtected(name, new LSL_String(string.Empty)); + } + + public LSL_Integer llLinksetDataDeleteProtected(LSL_String name, LSL_String pass) + { + var rootPrim = m_host.ParentGroup.RootPart; + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + int ret = rootPrim.LinksetData.DeleteLinksetDataKey(name, pass); + + object[] parameters; + + if (ret == 0) + { + parameters = new object[] + { + new LSL_Integer(ScriptBaseClass.LINKSETDATA_DELETE), name, new LSL_String(string.Empty) + }; + + m_ScriptEngine.PostObjectEvent( + rootPrim.LocalId, + new EventParams("linkset_data", parameters, Array.Empty())); + + rootPrim.ParentGroup.HasGroupChanged = true; + + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_OK); + } + else if (ret == 1) + { + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_EPROTECTED); + } + else + { + return new LSL_Integer(ScriptBaseClass.LINKSETDATA_NOTFOUND); + } } public LSL_List llLinksetDataDeleteFound(LSL_String pattern, LSL_String pass) { int deleted = 0; int not_deleted = 0; - var root = m_host.ParentGroup.RootPart; + var rootPrim = m_host.ParentGroup.RootPart; + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + var matches = rootPrim.LinksetData.LinksetDataMultiDelete(pattern, pass, out deleted, out not_deleted); - var matches = root.LinksetDataMultiDelete(pattern, pass, out deleted, out not_deleted); string removed_keys = String.Join(",", matches); object[] parameters = new object[] { - new LSL_Integer(ScriptBaseClass.LINKSETDATA_MULTIDELETE), new LSL_String(removed_keys), new LSL_String(string.Empty) + new LSL_Integer(ScriptBaseClass.LINKSETDATA_MULTIDELETE), + new LSL_String(removed_keys), + new LSL_String(string.Empty) }; if (deleted > 0) { - m_ScriptEngine.PostObjectEvent(m_host.LocalId, new EventParams("linkset_data", parameters, Array.Empty())); + m_ScriptEngine.PostObjectEvent( + m_host.LocalId, + new EventParams("linkset_data", parameters, Array.Empty())); + + rootPrim.ParentGroup.HasGroupChanged = true; + } return new LSL_List(new object[] @@ -18899,47 +18985,39 @@ public LSL_List llLinksetDataDeleteFound(LSL_String pattern, LSL_String pass) } public LSL_Integer llLinksetDataCountFound(LSL_String pattern) - { - var root = m_host.ParentGroup.RootPart; - return root.LinksetDataCountMatches(pattern); - } - - public LSL_Integer llLinksetDataDeleteProtected(LSL_String name, LSL_String pass) { var rootPrim = m_host.ParentGroup.RootPart; - int ret = rootPrim.DeleteLinksetDataKey(name, pass); - object[] parameters; - if (ret == 0) - { - parameters = new object[] - { - new LSL_Integer(ScriptBaseClass.LINKSETDATA_OK), name, new LSL_String(string.Empty) - }; - } - else if (ret == 1) + if (rootPrim.LinksetData == null) { - return new LSL_Integer(ScriptBaseClass.LINKSETDATA_EPROTECTED); - } - else - { - return new LSL_Integer(ScriptBaseClass.LINKSETDATA_NOTFOUND); + rootPrim.LinksetData = new LinksetData(); } - m_ScriptEngine.PostObjectEvent(rootPrim.LocalId, new EventParams("linkset_data", parameters, Array.Empty())); - return new LSL_Integer(ScriptBaseClass.LINKSETDATA_OK); + return rootPrim.LinksetData.LinksetDataCountMatches(pattern); } public LSL_List llLinksetDataFindKeys(LSL_String pattern, LSL_Integer start, LSL_Integer count) { var rootPrim = m_host.ParentGroup.RootPart; - return new LSL_List(rootPrim.FindLinksetDataKeys(pattern, start, count)); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + return new LSL_List(rootPrim.LinksetData.FindLinksetDataKeys(pattern, start, count)); } public LSL_List llLinksetDataListKeys(LSL_Integer start, LSL_Integer count) { var rootPrim = m_host.ParentGroup.RootPart; - return new LSL_List(rootPrim.GetLinksetDataSubList(start, count)); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + return new LSL_List(rootPrim.LinksetData.GetLinksetDataSubList(start, count)); } public LSL_String llLinksetDataRead(LSL_String name) @@ -18950,7 +19028,13 @@ public LSL_String llLinksetDataRead(LSL_String name) public LSL_String llLinksetDataReadProtected(LSL_String name, LSL_String pass) { var rootPrim = m_host.ParentGroup.RootPart; - return new LSL_String(rootPrim.ReadLinksetData(name, pass)); + + if (rootPrim.LinksetData == null) + { + rootPrim.LinksetData = new LinksetData(); + } + + return new LSL_String(rootPrim.LinksetData.ReadLinksetData(name, pass)); } } diff --git a/OpenSim/Region/ScriptEngine/YEngine/XMREngine.cs b/OpenSim/Region/ScriptEngine/YEngine/XMREngine.cs index fdbe207850f..59b40d52261 100644 --- a/OpenSim/Region/ScriptEngine/YEngine/XMREngine.cs +++ b/OpenSim/Region/ScriptEngine/YEngine/XMREngine.cs @@ -935,11 +935,11 @@ public bool PostObjectEvent(uint localID, EventParams parms) TraceCalls("[YEngine]: YEngine.PostObjectEvent({0},{1})", localID.ToString(), parms.EventName); - // In SecondLife, attach events go to all scripts of all prims + // In SecondLife, attach and linkset_data events go to all scripts of all prims // in a linked object. So here we duplicate that functionality, // as all we ever get is a single attach event for the whole // object. - if(parms.EventName == "attach") + if ((parms.EventName == "attach") || (parms.EventName == "linkset_data")) { bool posted = false; foreach(SceneObjectPart primpart in part.ParentGroup.Parts) From 9ff674bc5e338cbcd30617d5d6723163c6211ebc Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Mon, 4 Dec 2023 11:43:55 -0500 Subject: [PATCH 9/9] Bump version in prep for a release --- OpenSim/Framework/VersionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 5dc3165fbce..4391af35837 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.2.2"; public const string AssemblyVersionNumber = "0.9.2.2"; - public const string Release = "8736"; + public const string Release = "8738"; public static string Version {