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)