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)