diff --git a/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs b/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs new file mode 100644 index 0000000000..baac6e827f --- /dev/null +++ b/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs @@ -0,0 +1,48 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Reflection; +using OpenMetaverse; + +namespace OpenSim.Region.Framework.Interfaces +{ + public delegate void TakeValueCallback(string s); + + public interface IJsonStoreModule + { + bool CreateStore(string value, out UUID result); + bool DestroyStore(UUID storeID); + bool TestPath(UUID storeID, string path, bool useJson); + bool SetValue(UUID storeID, string path, string value, bool useJson); + bool RemoveValue(UUID storeID, string path); + bool GetValue(UUID storeID, string path, bool useJson, out string value); + + void TakeValue(UUID storeID, string path, bool useJson, TakeValueCallback cback); + void ReadValue(UUID storeID, string path, bool useJson, TakeValueCallback cback); + } +} diff --git a/OpenSim/Region/Framework/Scenes/SceneGraph.cs b/OpenSim/Region/Framework/Scenes/SceneGraph.cs index cd4bd42b61..0a1b921d30 100644 --- a/OpenSim/Region/Framework/Scenes/SceneGraph.cs +++ b/OpenSim/Region/Framework/Scenes/SceneGraph.cs @@ -101,6 +101,9 @@ namespace OpenSim.Region.Framework.Scenes /// protected internal Dictionary SceneObjectGroupsByLocalPartID = new Dictionary(); + /// + /// Lock to prevent object group update, linking and delinking operations from running concurrently. + /// private Object m_updateLock = new Object(); #endregion diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 6f3a0848f9..7bd6c11d27 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -2584,6 +2584,10 @@ namespace OpenSim.Region.Framework.Scenes /// /// Link the prims in a given group to this group /// + /// + /// Do not call this method directly - use Scene.LinkObjects() instead to avoid races between threads. + /// FIXME: There are places where scripts call these methods directly without locking. This is a potential race condition. + /// /// The group of prims which should be linked to this group public void LinkToGroup(SceneObjectGroup objectGroup) { @@ -2714,6 +2718,11 @@ namespace OpenSim.Region.Framework.Scenes /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// + /// + /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race + /// condition. But currently there is no + /// alternative method that does take a lonk to delink a single prim. + /// /// /// The object group of the newly delinked prim. Null if part could not be found public SceneObjectGroup DelinkFromGroup(uint partID) @@ -2725,6 +2734,11 @@ namespace OpenSim.Region.Framework.Scenes /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// + /// + /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race + /// condition. But currently there is no + /// alternative method that does take a lonk to delink a single prim. + /// /// /// /// The object group of the newly delinked prim. Null if part could not be found @@ -2750,6 +2764,11 @@ namespace OpenSim.Region.Framework.Scenes /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// + /// + /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race + /// condition. But currently there is no + /// alternative method that does take a lonk to delink a single prim. + /// /// /// /// The object group of the newly delinked prim. diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs index fffa3bd152..9fea3c63a7 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs @@ -111,8 +111,44 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(childPart.GetWorldPosition(), Is.EqualTo(childPosition)); Assert.That(childPart.RelativePosition, Is.EqualTo(childOffsetPosition)); Assert.That(childPart.OffsetPosition, Is.EqualTo(childOffsetPosition)); + } - // TODO: Write test for child part position after rotation. + [Test] + public void TestGetChildPartPositionAfterObjectRotation() + { + TestHelpers.InMethod(); + + Vector3 rootPartPosition = new Vector3(10, 20, 30); + Vector3 childOffsetPosition = new Vector3(2, 3, 4); + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(2, m_ownerId, "obj1", 0x10); + so.AbsolutePosition = rootPartPosition; + so.Parts[1].OffsetPosition = childOffsetPosition; + + m_scene.AddNewSceneObject(so, false); + + so.UpdateGroupRotationR(Quaternion.CreateFromEulers(0, 0, -90 * Utils.DEG_TO_RAD)); + + // Calculate child absolute position. + Vector3 rotatedChildOffsetPosition + = new Vector3(childOffsetPosition.Y, -childOffsetPosition.X, childOffsetPosition.Z); + + Vector3 childPosition = new Vector3(rootPartPosition + rotatedChildOffsetPosition); + + SceneObjectPart childPart = so.Parts[1]; + + // FIXME: Should be childPosition after rotation? + Assert.That(childPart.AbsolutePosition, Is.EqualTo(rootPartPosition + childOffsetPosition)); + + Assert.That(childPart.GroupPosition, Is.EqualTo(rootPartPosition)); + Assert.That(childPart.GetWorldPosition(), Is.EqualTo(childPosition)); + + // Relative to root part as (0, 0, 0) + Assert.That(childPart.RelativePosition, Is.EqualTo(childOffsetPosition)); + + // Relative to root part as (0, 0, 0) + Assert.That(childPart.OffsetPosition, Is.EqualTo(childOffsetPosition)); } } } \ No newline at end of file diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs new file mode 100644 index 0000000000..34894badf7 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStore.cs @@ -0,0 +1,500 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using Mono.Addins; + +using System; +using System.Reflection; +using System.Threading; +using System.Text; +using System.Net; +using System.Net.Sockets; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore +{ + public class JsonStore + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private OSD m_ValueStore; + + protected class TakeValueCallbackClass + { + public string Path { get; set; } + public bool UseJson { get; set; } + public TakeValueCallback Callback { get; set; } + + public TakeValueCallbackClass(string spath, bool usejson, TakeValueCallback cback) + { + Path = spath; + UseJson = usejson; + Callback = cback; + } + } + + protected List m_TakeStore; + protected List m_ReadStore; + + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStore() : this("") {} + + public JsonStore(string value) + { + m_TakeStore = new List(); + m_ReadStore = new List(); + + if (String.IsNullOrEmpty(value)) + m_ValueStore = new OSDMap(); + else + m_ValueStore = OSDParser.DeserializeJson(value); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool TestPath(string expr, bool useJson) + { + Stack path = ParsePathExpression(expr); + OSD result = ProcessPathExpression(m_ValueStore,path); + + if (result == null) + return false; + + if (useJson || result.Type == OSDType.String) + return true; + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool GetValue(string expr, out string value, bool useJson) + { + Stack path = ParsePathExpression(expr); + OSD result = ProcessPathExpression(m_ValueStore,path); + return ConvertOutputValue(result,out value,useJson); + } + + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool RemoveValue(string expr) + { + return SetValueFromExpression(expr,null); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool SetValue(string expr, string value, bool useJson) + { + OSD ovalue = useJson ? OSDParser.DeserializeJson(value) : new OSDString(value); + return SetValueFromExpression(expr,ovalue); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool TakeValue(string expr, bool useJson, TakeValueCallback cback) + { + Stack path = ParsePathExpression(expr); + string pexpr = PathExpressionToKey(path); + + OSD result = ProcessPathExpression(m_ValueStore,path); + if (result == null) + { + m_TakeStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); + return false; + } + + string value = String.Empty; + if (! ConvertOutputValue(result,out value,useJson)) + { + // the structure does not match the request so i guess we'll wait + m_TakeStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); + return false; + } + + SetValueFromExpression(expr,null); + cback(value); + + return true; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool ReadValue(string expr, bool useJson, TakeValueCallback cback) + { + Stack path = ParsePathExpression(expr); + string pexpr = PathExpressionToKey(path); + + OSD result = ProcessPathExpression(m_ValueStore,path); + if (result == null) + { + m_ReadStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); + return false; + } + + string value = String.Empty; + if (! ConvertOutputValue(result,out value,useJson)) + { + // the structure does not match the request so i guess we'll wait + m_ReadStore.Add(new TakeValueCallbackClass(pexpr,useJson,cback)); + return false; + } + + cback(value); + + return true; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected bool SetValueFromExpression(string expr, OSD ovalue) + { + Stack path = ParsePathExpression(expr); + if (path.Count == 0) + { + m_ValueStore = ovalue; + return true; + } + + string pkey = path.Pop(); + string pexpr = PathExpressionToKey(path); + if (pexpr != "") + pexpr += "."; + + OSD result = ProcessPathExpression(m_ValueStore,path); + if (result == null) + return false; + + Regex aPattern = new Regex("\\[([0-9]+|\\+)\\]"); + MatchCollection amatches = aPattern.Matches(pkey,0); + + if (amatches.Count > 0) + { + if (result.Type != OSDType.Array) + return false; + + OSDArray amap = result as OSDArray; + + Match match = amatches[0]; + GroupCollection groups = match.Groups; + string akey = groups[1].Value; + + if (akey == "+") + { + string npkey = String.Format("[{0}]",amap.Count); + + amap.Add(ovalue); + InvokeNextCallback(pexpr + npkey); + return true; + } + + int aval = Convert.ToInt32(akey); + if (0 <= aval && aval < amap.Count) + { + if (ovalue == null) + amap.RemoveAt(aval); + else + { + amap[aval] = ovalue; + InvokeNextCallback(pexpr + pkey); + } + return true; + } + + return false; + } + + Regex hPattern = new Regex("{([^}]+)}"); + MatchCollection hmatches = hPattern.Matches(pkey,0); + + if (hmatches.Count > 0) + { + Match match = hmatches[0]; + GroupCollection groups = match.Groups; + string hkey = groups[1].Value; + + if (result is OSDMap) + { + OSDMap hmap = result as OSDMap; + if (ovalue != null) + { + hmap[hkey] = ovalue; + InvokeNextCallback(pexpr + pkey); + } + else if (hmap.ContainsKey(hkey)) + hmap.Remove(hkey); + + return true; + } + + return false; + } + + // Shouldn't get here if the path was checked correctly + m_log.WarnFormat("[JsonStore] invalid path expression"); + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected bool InvokeNextCallback(string pexpr) + { + // Process all of the reads that match the expression first + List reads = + m_ReadStore.FindAll(delegate(TakeValueCallbackClass tb) { return pexpr.StartsWith(tb.Path); }); + + foreach (TakeValueCallbackClass readcb in reads) + { + m_ReadStore.Remove(readcb); + ReadValue(readcb.Path,readcb.UseJson,readcb.Callback); + } + + // Process one take next + TakeValueCallbackClass takecb = + m_TakeStore.Find(delegate(TakeValueCallbackClass tb) { return pexpr.StartsWith(tb.Path); }); + + if (takecb != null) + { + m_TakeStore.Remove(takecb); + TakeValue(takecb.Path,takecb.UseJson,takecb.Callback); + + return true; + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// Parse the path expression and put the components into a stack. We + /// use a stack because we process the path in inverse order later + /// + // ----------------------------------------------------------------- + protected static Stack ParsePathExpression(string path) + { + Stack m_path = new Stack(); + + // add front and rear separators + path = "." + path + "."; + + // add separators for quoted paths + Regex pass1 = new Regex("{[^}]+}"); + path = pass1.Replace(path,".$0.",-1,0); + + // add separators for array references + Regex pass2 = new Regex("(\\[[0-9]+\\]|\\[\\+\\])"); + path = pass2.Replace(path,".$0.",-1,0); + + // add quotes to bare identifier + Regex pass3 = new Regex("\\.([a-zA-Z]+)"); + path = pass3.Replace(path,".{$1}",-1,0); + + // remove extra separators + Regex pass4 = new Regex("\\.+"); + path = pass4.Replace(path,".",-1,0); + + Regex validate = new Regex("^\\.(({[^}]+}|\\[[0-9]+\\]|\\[\\+\\])\\.)+$"); + if (validate.IsMatch(path)) + { + Regex parser = new Regex("\\.({[^}]+}|\\[[0-9]+\\]|\\[\\+\\]+)"); + MatchCollection matches = parser.Matches(path,0); + foreach (Match match in matches) + m_path.Push(match.Groups[1].Value); + } + + return m_path; + } + + // ----------------------------------------------------------------- + /// + /// + /// + /// path is a stack where the top level of the path is at the bottom of the stack + // ----------------------------------------------------------------- + protected static OSD ProcessPathExpression(OSD map, Stack path) + { + if (path.Count == 0) + return map; + + string pkey = path.Pop(); + + OSD rmap = ProcessPathExpression(map,path); + if (rmap == null) + return null; + + // ---------- Check for an array index ---------- + Regex aPattern = new Regex("\\[([0-9]+)\\]"); + MatchCollection amatches = aPattern.Matches(pkey,0); + + if (amatches.Count > 0) + { + if (rmap.Type != OSDType.Array) + { + m_log.WarnFormat("[JsonStore] wrong type for key {2}, expecting {0}, got {1}",OSDType.Array,rmap.Type,pkey); + return null; + } + + OSDArray amap = rmap as OSDArray; + + Match match = amatches[0]; + GroupCollection groups = match.Groups; + string akey = groups[1].Value; + int aval = Convert.ToInt32(akey); + + if (aval < amap.Count) + return (OSD) amap[aval]; + + return null; + } + + // ---------- Check for a hash index ---------- + Regex hPattern = new Regex("{([^}]+)}"); + MatchCollection hmatches = hPattern.Matches(pkey,0); + + if (hmatches.Count > 0) + { + if (rmap.Type != OSDType.Map) + { + m_log.WarnFormat("[JsonStore] wrong type for key {2}, expecting {0}, got {1}",OSDType.Map,rmap.Type,pkey); + return null; + } + + OSDMap hmap = rmap as OSDMap; + + Match match = hmatches[0]; + GroupCollection groups = match.Groups; + string hkey = groups[1].Value; + + if (hmap.ContainsKey(hkey)) + return (OSD) hmap[hkey]; + + return null; + } + + // Shouldn't get here if the path was checked correctly + m_log.WarnFormat("[JsonStore] Path type (unknown) does not match the structure"); + return null; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected static bool ConvertOutputValue(OSD result, out string value, bool useJson) + { + value = String.Empty; + + // If we couldn't process the path + if (result == null) + return false; + + if (useJson) + { + // The path pointed to an intermediate hash structure + if (result.Type == OSDType.Map) + { + value = OSDParser.SerializeJsonString(result as OSDMap); + return true; + } + + // The path pointed to an intermediate hash structure + if (result.Type == OSDType.Array) + { + value = OSDParser.SerializeJsonString(result as OSDArray); + return true; + } + + value = "'" + result.AsString() + "'"; + return true; + } + + if (result.Type == OSDType.String) + { + value = result.AsString(); + return true; + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected static string PathExpressionToKey(Stack path) + { + if (path.Count == 0) + return ""; + + string pkey = ""; + foreach (string k in path) + pkey = (pkey == "") ? k : (k + "." + pkey); + + return pkey; + } + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs new file mode 100644 index 0000000000..311531c8d4 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs @@ -0,0 +1,430 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using Mono.Addins; + +using System; +using System.Reflection; +using System.Threading; +using System.Text; +using System.Net; +using System.Net.Sockets; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections.Generic; +using System.Text.RegularExpressions; + + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreModule")] + + public class JsonStoreModule : INonSharedRegionModule, IJsonStoreModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IConfig m_config = null; + private bool m_enabled = false; + private Scene m_scene = null; + + private Dictionary m_JsonValueStore; + private UUID m_sharedStore; + +#region IRegionModule Members + + // ----------------------------------------------------------------- + /// + /// Name of this shared module is it's class name + /// + // ----------------------------------------------------------------- + public string Name + { + get { return this.GetType().Name; } + } + + // ----------------------------------------------------------------- + /// + /// Initialise this shared module + /// + /// this region is getting initialised + /// nini config, we are not using this + // ----------------------------------------------------------------- + public void Initialise(IConfigSource config) + { + try + { + if ((m_config = config.Configs["JsonStore"]) == null) + { + // There is no configuration, the module is disabled + // m_log.InfoFormat("[JsonStore] no configuration info"); + return; + } + + m_enabled = m_config.GetBoolean("Enabled", m_enabled); + } + catch (Exception e) + { + m_log.ErrorFormat("[JsonStore] initialization error: {0}",e.Message); + return; + } + + if (m_enabled) + m_log.DebugFormat("[JsonStore] module is enabled"); + } + + // ----------------------------------------------------------------- + /// + /// everything is loaded, perform post load configuration + /// + // ----------------------------------------------------------------- + public void PostInitialise() + { + } + + // ----------------------------------------------------------------- + /// + /// Nothing to do on close + /// + // ----------------------------------------------------------------- + public void Close() + { + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void AddRegion(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + + m_sharedStore = UUID.Zero; + m_JsonValueStore = new Dictionary(); + m_JsonValueStore.Add(m_sharedStore,new JsonStore("")); + } + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void RemoveRegion(Scene scene) + { + // need to remove all references to the scene in the subscription + // list to enable full garbage collection of the scene object + } + + // ----------------------------------------------------------------- + /// + /// Called when all modules have been added for a region. This is + /// where we hook up events + /// + // ----------------------------------------------------------------- + public void RegionLoaded(Scene scene) + { + if (m_enabled) {} + } + + /// ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public Type ReplaceableInterface + { + get { return null; } + } + +#endregion + +#region ScriptInvocationInteface + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool CreateStore(string value, out UUID result) + { + result = UUID.Zero; + + if (! m_enabled) return false; + + UUID uuid = UUID.Random(); + JsonStore map = null; + + try + { + map = new JsonStore(value); + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] Unable to initialize store from {0}; {1}",value,e.Message); + return false; + } + + lock (m_JsonValueStore) + m_JsonValueStore.Add(uuid,map); + + result = uuid; + return true; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool DestroyStore(UUID storeID) + { + if (! m_enabled) return false; + + lock (m_JsonValueStore) + m_JsonValueStore.Remove(storeID); + + return true; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool TestPath(UUID storeID, string path, bool useJson) + { + if (! m_enabled) return false; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + m_log.InfoFormat("[JsonStore] Missing store {0}",storeID); + return true; + } + } + + try + { + lock (map) + return map.TestPath(path,useJson); + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] Path test failed for {0} in {1}; {2}",path,storeID,e.Message); + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool SetValue(UUID storeID, string path, string value, bool useJson) + { + if (! m_enabled) return false; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + m_log.InfoFormat("[JsonStore] Missing store {0}",storeID); + return false; + } + } + + try + { + lock (map) + if (map.SetValue(path,value,useJson)) + return true; + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] Unable to assign {0} to {1} in {2}; {3}",value,path,storeID,e.Message); + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool RemoveValue(UUID storeID, string path) + { + if (! m_enabled) return false; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + m_log.InfoFormat("[JsonStore] Missing store {0}",storeID); + return false; + } + } + + try + { + lock (map) + if (map.RemoveValue(path)) + return true; + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] Unable to remove {0} in {1}; {2}",path,storeID,e.Message); + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public bool GetValue(UUID storeID, string path, bool useJson, out string value) + { + value = String.Empty; + + if (! m_enabled) return false; + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + return false; + } + + try + { + lock (map) + { + return map.GetValue(path, out value, useJson); + } + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.Message); + } + + return false; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public void TakeValue(UUID storeID, string path, bool useJson, TakeValueCallback cback) + { + if (! m_enabled) + { + cback(String.Empty); + return; + } + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + cback(String.Empty); + return; + } + } + + try + { + lock (map) + { + map.TakeValue(path, useJson, cback); + return; + } + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.ToString()); + } + + cback(String.Empty); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public void ReadValue(UUID storeID, string path, bool useJson, TakeValueCallback cback) + { + if (! m_enabled) + { + cback(String.Empty); + return; + } + + JsonStore map = null; + lock (m_JsonValueStore) + { + if (! m_JsonValueStore.TryGetValue(storeID,out map)) + { + cback(String.Empty); + return; + } + } + + try + { + lock (map) + { + map.ReadValue(path, useJson, cback); + return; + } + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStore] unable to retrieve value; {0}",e.ToString()); + } + + cback(String.Empty); + } + +#endregion + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs new file mode 100644 index 0000000000..eda2aef9ed --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs @@ -0,0 +1,490 @@ +/* + * Copyright (c) Contributors + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using Mono.Addins; + +using System; +using System.Reflection; +using System.Threading; +using System.Text; +using System.Net; +using System.Net.Sockets; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreScriptModule")] + + public class JsonStoreScriptModule : INonSharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IConfig m_config = null; + private bool m_enabled = false; + private Scene m_scene = null; + + private IScriptModuleComms m_comms; + private IJsonStoreModule m_store; + +#region IRegionModule Members + + // ----------------------------------------------------------------- + /// + /// Name of this shared module is it's class name + /// + // ----------------------------------------------------------------- + public string Name + { + get { return this.GetType().Name; } + } + + // ----------------------------------------------------------------- + /// + /// Initialise this shared module + /// + /// this region is getting initialised + /// nini config, we are not using this + // ----------------------------------------------------------------- + public void Initialise(IConfigSource config) + { + try + { + if ((m_config = config.Configs["JsonStore"]) == null) + { + // There is no configuration, the module is disabled + // m_log.InfoFormat("[JsonStoreScripts] no configuration info"); + return; + } + + m_enabled = m_config.GetBoolean("Enabled", m_enabled); + } + catch (Exception e) + { + m_log.ErrorFormat("[JsonStoreScripts] initialization error: {0}",e.Message); + return; + } + + if (m_enabled) + m_log.DebugFormat("[JsonStoreScripts] module is enabled"); + } + + // ----------------------------------------------------------------- + /// + /// everything is loaded, perform post load configuration + /// + // ----------------------------------------------------------------- + public void PostInitialise() + { + } + + // ----------------------------------------------------------------- + /// + /// Nothing to do on close + /// + // ----------------------------------------------------------------- + public void Close() + { + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void AddRegion(Scene scene) + { + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void RemoveRegion(Scene scene) + { + // need to remove all references to the scene in the subscription + // list to enable full garbage collection of the scene object + } + + // ----------------------------------------------------------------- + /// + /// Called when all modules have been added for a region. This is + /// where we hook up events + /// + // ----------------------------------------------------------------- + public void RegionLoaded(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + m_comms = m_scene.RequestModuleInterface(); + if (m_comms == null) + { + m_log.ErrorFormat("[JsonStoreScripts] ScriptModuleComms interface not defined"); + m_enabled = false; + return; + } + + m_store = m_scene.RequestModuleInterface(); + if (m_store == null) + { + m_log.ErrorFormat("[JsonStoreScripts] JsonModule interface not defined"); + m_enabled = false; + return; + } + + m_comms.RegisterScriptInvocation(this,"JsonCreateStore"); + m_comms.RegisterScriptInvocation(this,"JsonDestroyStore"); + + m_comms.RegisterScriptInvocation(this,"JsonReadNotecard"); + m_comms.RegisterScriptInvocation(this,"JsonWriteNotecard"); + + m_comms.RegisterScriptInvocation(this,"JsonTestPath"); + m_comms.RegisterScriptInvocation(this,"JsonTestPathJson"); + + m_comms.RegisterScriptInvocation(this,"JsonGetValue"); + m_comms.RegisterScriptInvocation(this,"JsonGetValueJson"); + + m_comms.RegisterScriptInvocation(this,"JsonTakeValue"); + m_comms.RegisterScriptInvocation(this,"JsonTakeValueJson"); + + m_comms.RegisterScriptInvocation(this,"JsonReadValue"); + m_comms.RegisterScriptInvocation(this,"JsonReadValueJson"); + + m_comms.RegisterScriptInvocation(this,"JsonSetValue"); + m_comms.RegisterScriptInvocation(this,"JsonSetValueJson"); + + m_comms.RegisterScriptInvocation(this,"JsonRemoveValue"); + } + } + + /// ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public Type ReplaceableInterface + { + get { return null; } + } + +#endregion + +#region ScriptInvocationInteface + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected void GenerateRuntimeError(string msg) + { + throw new Exception("JsonStore Runtime Error: " + msg); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected UUID JsonCreateStore(UUID hostID, UUID scriptID, string value) + { + UUID uuid = UUID.Zero; + if (! m_store.CreateStore(value, out uuid)) + GenerateRuntimeError("Failed to create Json store"); + + return uuid; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected int JsonDestroyStore(UUID hostID, UUID scriptID, UUID storeID) + { + return m_store.DestroyStore(storeID) ? 1 : 0; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected UUID JsonReadNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, UUID assetID) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonReadNotecard(reqID,hostID,scriptID,storeID,path,assetID); }); + return reqID; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected UUID JsonWriteNotecard(UUID hostID, UUID scriptID, UUID storeID, string path, string name) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonWriteNotecard(reqID,hostID,scriptID,storeID,path,name); }); + return reqID; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected int JsonTestPath(UUID hostID, UUID scriptID, UUID storeID, string path) + { + return m_store.TestPath(storeID,path,false) ? 1 : 0; + } + + protected int JsonTestPathJson(UUID hostID, UUID scriptID, UUID storeID, string path) + { + return m_store.TestPath(storeID,path,true) ? 1 : 0; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected int JsonSetValue(UUID hostID, UUID scriptID, UUID storeID, string path, string value) + { + return m_store.SetValue(storeID,path,value,false) ? 1 : 0; + } + + protected int JsonSetValueJson(UUID hostID, UUID scriptID, UUID storeID, string path, string value) + { + return m_store.SetValue(storeID,path,value,true) ? 1 : 0; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected int JsonRemoveValue(UUID hostID, UUID scriptID, UUID storeID, string path) + { + return m_store.RemoveValue(storeID,path) ? 1 : 0; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected string JsonGetValue(UUID hostID, UUID scriptID, UUID storeID, string path) + { + string value = String.Empty; + m_store.GetValue(storeID,path,false,out value); + return value; + } + + protected string JsonGetValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + { + string value = String.Empty; + m_store.GetValue(storeID,path,true, out value); + return value; + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected UUID JsonTakeValue(UUID hostID, UUID scriptID, UUID storeID, string path) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonTakeValue(scriptID,reqID,storeID,path,false); }); + return reqID; + } + + protected UUID JsonTakeValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonTakeValue(scriptID,reqID,storeID,path,true); }); + return reqID; + } + + private void DoJsonTakeValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) + { + try + { + m_store.TakeValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); + return; + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStoreScripts] unable to retrieve value; {0}",e.ToString()); + } + + DispatchValue(scriptID,reqID,String.Empty); + } + + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected UUID JsonReadValue(UUID hostID, UUID scriptID, UUID storeID, string path) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonReadValue(scriptID,reqID,storeID,path,false); }); + return reqID; + } + + protected UUID JsonReadValueJson(UUID hostID, UUID scriptID, UUID storeID, string path) + { + UUID reqID = UUID.Random(); + Util.FireAndForget(delegate(object o) { DoJsonReadValue(scriptID,reqID,storeID,path,true); }); + return reqID; + } + + private void DoJsonReadValue(UUID scriptID, UUID reqID, UUID storeID, string path, bool useJson) + { + try + { + m_store.ReadValue(storeID,path,useJson,delegate(string value) { DispatchValue(scriptID,reqID,value); }); + return; + } + catch (Exception e) + { + m_log.InfoFormat("[JsonStoreScripts] unable to retrieve value; {0}",e.ToString()); + } + + DispatchValue(scriptID,reqID,String.Empty); + } + +#endregion + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + protected void DispatchValue(UUID scriptID, UUID reqID, string value) + { + m_comms.DispatchReply(scriptID,1,value,reqID.ToString()); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + private void DoJsonReadNotecard(UUID reqID, UUID hostID, UUID scriptID, UUID storeID, string path, UUID assetID) + { + AssetBase a = m_scene.AssetService.Get(assetID.ToString()); + if (a == null) + GenerateRuntimeError(String.Format("Unable to find notecard asset {0}",assetID)); + + if (a.Type != (sbyte)AssetType.Notecard) + GenerateRuntimeError(String.Format("Invalid notecard asset {0}",assetID)); + + m_log.DebugFormat("[JsonStoreScripts] read notecard in context {0}",storeID); + + try + { + System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding(); + string jsondata = SLUtil.ParseNotecardToString(enc.GetString(a.Data)); + int result = m_store.SetValue(storeID,path,jsondata,true) ? 1 : 0; + m_comms.DispatchReply(scriptID,result,"",reqID.ToString()); + return; + } + catch (Exception e) + { + m_log.WarnFormat("[JsonStoreScripts] Json parsing failed; {0}",e.Message); + } + + GenerateRuntimeError(String.Format("Json parsing failed for {0}",assetID.ToString())); + m_comms.DispatchReply(scriptID,0,"",reqID.ToString()); + } + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + private void DoJsonWriteNotecard(UUID reqID, UUID hostID, UUID scriptID, UUID storeID, string path, string name) + { + string data; + if (! m_store.GetValue(storeID,path,true, out data)) + { + m_comms.DispatchReply(scriptID,0,UUID.Zero.ToString(),reqID.ToString()); + return; + } + + SceneObjectPart host = m_scene.GetSceneObjectPart(hostID); + + // Create new asset + UUID assetID = UUID.Random(); + AssetBase asset = new AssetBase(assetID, name, (sbyte)AssetType.Notecard, host.OwnerID.ToString()); + asset.Description = "Json store"; + + int textLength = data.Length; + data = "Linden text version 2\n{\nLLEmbeddedItems version 1\n{\ncount 0\n}\nText length " + + textLength.ToString() + "\n" + data + "}\n"; + + asset.Data = Util.UTF8.GetBytes(data); + m_scene.AssetService.Store(asset); + + // Create Task Entry + TaskInventoryItem taskItem = new TaskInventoryItem(); + + taskItem.ResetIDs(host.UUID); + taskItem.ParentID = host.UUID; + taskItem.CreationDate = (uint)Util.UnixTimeSinceEpoch(); + taskItem.Name = asset.Name; + taskItem.Description = asset.Description; + taskItem.Type = (int)AssetType.Notecard; + taskItem.InvType = (int)InventoryType.Notecard; + taskItem.OwnerID = host.OwnerID; + taskItem.CreatorID = host.OwnerID; + taskItem.BasePermissions = (uint)PermissionMask.All; + taskItem.CurrentPermissions = (uint)PermissionMask.All; + taskItem.EveryonePermissions = 0; + taskItem.NextPermissions = (uint)PermissionMask.All; + taskItem.GroupID = host.GroupID; + taskItem.GroupPermissions = 0; + taskItem.Flags = 0; + taskItem.PermsGranter = UUID.Zero; + taskItem.PermsMask = 0; + taskItem.AssetID = asset.FullID; + + host.Inventory.AddInventoryItem(taskItem, false); + + m_comms.DispatchReply(scriptID,1,assetID.ToString(),reqID.ToString()); + } + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs b/OpenSim/Region/OptionalModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs index cab30dec25..74a85e2cd5 100644 --- a/OpenSim/Region/OptionalModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs @@ -38,7 +38,7 @@ using OpenMetaverse; using System.Linq; using System.Linq.Expressions; -namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms +namespace OpenSim.Region.OptionalModules.Scripting.ScriptModuleComms { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "ScriptModuleCommsModule")] class ScriptModuleCommsModule : INonSharedRegionModule, IScriptModuleComms diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index babd0ec3a3..65ec445d16 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -1560,6 +1560,12 @@ MaptileURL = "http://www.mygrid.com/Grid/" RefreshTime = 3600 +;; +;; JsonStore module provides structured store for scripts +;; +[JsonStore] +Enabled = False + ;; ;; These are defaults that are overwritten below in [Architecture]. ;; These defaults allow OpenSim to work out of the box with