first step for multiple storage
							parent
							
								
									a527e63511
								
							
						
					
					
						commit
						ab7039177e
					
				
							
								
								
									
										198
									
								
								src/DataValue.cs
								
								
								
								
							
							
						
						
									
										198
									
								
								src/DataValue.cs
								
								
								
								
							|  | @ -2,6 +2,7 @@ | ||||||
| using Mono.Addins; | using Mono.Addins; | ||||||
| using Nini.Config; | using Nini.Config; | ||||||
| using OpenMetaverse; | using OpenMetaverse; | ||||||
|  | using OpenSim.Modules.DataValue.Storage; | ||||||
| using OpenSim.Region.Framework.Interfaces; | using OpenSim.Region.Framework.Interfaces; | ||||||
| using OpenSim.Region.Framework.Scenes; | using OpenSim.Region.Framework.Scenes; | ||||||
| using System; | using System; | ||||||
|  | @ -26,14 +27,10 @@ namespace OpenSim.Modules.DataValue | ||||||
| 
 | 
 | ||||||
|         private Scene m_scene = null; |         private Scene m_scene = null; | ||||||
|         private IConfig m_config = null; |         private IConfig m_config = null; | ||||||
|         private bool m_enabled = true; |  | ||||||
|         private bool m_enabledRateLimit = true; |  | ||||||
|         private bool m_enabledCompress = true; |  | ||||||
|         private string m_dataValueDirectory = "./ScriptDataValue"; |  | ||||||
|         private IScriptModuleComms m_scriptModule; |         private IScriptModuleComms m_scriptModule; | ||||||
| 
 | 
 | ||||||
|         private Timer m_timer = null; |         private String m_storageTyp = null; | ||||||
|         private int m_rateLimit = 0; |         private iStorage m_storage = null; | ||||||
| 
 | 
 | ||||||
|         public string Name |         public string Name | ||||||
|         { |         { | ||||||
|  | @ -61,206 +58,83 @@ namespace OpenSim.Modules.DataValue | ||||||
|             { |             { | ||||||
|                 m_config = source.Configs["XEngine"]; |                 m_config = source.Configs["XEngine"]; | ||||||
| 
 | 
 | ||||||
|                 if (m_config != null) |                 m_storageTyp = m_config.GetString("DataStorageTyp", "RegionExtras").ToUpper().Trim(); | ||||||
|                 { |  | ||||||
|                     m_dataValueDirectory = m_config.GetString("DataValueStorageDirectory", m_dataValueDirectory); |  | ||||||
|                     m_enabled = m_config.GetBoolean("EnabledDataStorage", m_enabled); |  | ||||||
|                     m_enabledRateLimit = m_config.GetBoolean("EnabledDataStorageRateLimit", m_enabledRateLimit); |  | ||||||
|                     m_enabledCompress = m_config.GetBoolean("EnabledDataStorageCompressing", m_enabledCompress); |  | ||||||
|                      |  | ||||||
|                     m_log.Info("[" + Name + "]: Data storage = " + m_dataValueDirectory); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     m_log.Error("[" + Name + "]: Cant find config."); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|             catch (Exception e) |             catch (Exception e) | ||||||
|             { |             { | ||||||
|                 m_log.ErrorFormat("[" + Name + "]: initialization error: {0}", e.Message); |                 m_log.ErrorFormat("[" + Name + "]: initialization error: {0}", e.Message); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             if (m_enabled) |  | ||||||
|             { |  | ||||||
|                 m_log.Info("[" + Name + "]: module is enabled"); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 m_log.Info("[" + Name + "]: module is disabled"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|             { |  | ||||||
|                 m_log.Info("[" + Name + "]: RateLimit is enabled"); |  | ||||||
| 
 |  | ||||||
|                 m_timer = new Timer(); |  | ||||||
|                 m_timer.Interval = 10000; |  | ||||||
|                 m_timer.Elapsed += resetRateLimit; |  | ||||||
|                 m_timer.Start(); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 m_log.Info("[" + Name + "]: RateLimit is disabled"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private void resetRateLimit(object sender, ElapsedEventArgs e) |  | ||||||
|         { |  | ||||||
|             if(m_rateLimit > 0) |  | ||||||
|                 m_rateLimit = m_rateLimit - 10; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void RegionLoaded(Scene scene) |         public void RegionLoaded(Scene scene) | ||||||
|         { |         { | ||||||
|             if (m_enabled) |             m_log.Info("[" + Name + "]: Load region " + scene.Name); | ||||||
|  | 
 | ||||||
|  |             m_scene = scene; | ||||||
|  | 
 | ||||||
|  |             if (m_storageTyp == "REGIONEXTRAS") | ||||||
|  |                 m_storage = new RegionExtras(m_scene, m_config); | ||||||
|  | 
 | ||||||
|  |             if (m_storageTyp == "FILESYSTEM") | ||||||
|  |                 m_storage = new FileSystem(m_scene, m_config); | ||||||
|  | 
 | ||||||
|  |             if (m_storageTyp == "MYSQL") | ||||||
|  |                 m_storage = new MySQL(m_scene, m_config); | ||||||
|  | 
 | ||||||
|  |             if(m_storage == null) | ||||||
|  |                 m_storage = new RegionExtras(m_scene, m_config); | ||||||
|  | 
 | ||||||
|  |             m_scriptModule = m_scene.RequestModuleInterface<IScriptModuleComms>(); | ||||||
|  |             if (m_scriptModule == null) | ||||||
|             { |             { | ||||||
|                 m_log.Info("[" + Name + "]: Load region " + scene.Name); |                 m_log.ErrorFormat("[" + Name + "]: Failed to load IScriptModuleComms!"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|                 m_scene = scene; |             try | ||||||
|                 m_scriptModule = m_scene.RequestModuleInterface<IScriptModuleComms>(); |             { | ||||||
|                 if (m_scriptModule == null) |                 m_scriptModule.RegisterScriptInvocation(this, "osGetDataValue"); | ||||||
|                 { |                 m_scriptModule.RegisterScriptInvocation(this, "osSetDataValue"); | ||||||
|                     m_log.ErrorFormat("[" + Name + "]: Failed to load IScriptModuleComms!"); |                 m_scriptModule.RegisterScriptInvocation(this, "osDeleteDataValue"); | ||||||
|                     m_enabled = false; |                 m_scriptModule.RegisterScriptInvocation(this, "osCheckDataValue"); | ||||||
|                     return; |             } | ||||||
|                 } |             catch (Exception e) | ||||||
| 
 |             { | ||||||
|                 try |                 m_log.WarnFormat("[" + Name + "]: script method registration failed; {0}", e.Message); | ||||||
|                 { |  | ||||||
|                     m_scriptModule.RegisterScriptInvocation(this, "osGetDataValue"); |  | ||||||
|                     m_scriptModule.RegisterScriptInvocation(this, "osSetDataValue"); |  | ||||||
|                     m_scriptModule.RegisterScriptInvocation(this, "osDeleteDataValue"); |  | ||||||
|                     m_scriptModule.RegisterScriptInvocation(this, "osCheckDataValue"); |  | ||||||
|                 } |  | ||||||
|                 catch (Exception e) |  | ||||||
|                 { |  | ||||||
|                     m_log.WarnFormat("[" + Name + "]: script method registration failed; {0}", e.Message); |  | ||||||
|                     m_enabled = false; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void RemoveRegion(Scene scene) |         public void RemoveRegion(Scene scene) | ||||||
|         { |         { | ||||||
|             m_timer.Stop(); |              | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         #endregion |         #endregion | ||||||
| 
 | 
 | ||||||
|         #region Script Funktions |         #region Script Funktions | ||||||
| 
 | 
 | ||||||
|         private void checkRateLimit() |  | ||||||
|         { |  | ||||||
|             m_rateLimit++; |  | ||||||
| 
 |  | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|             { |  | ||||||
|                 if (m_rateLimit >= 1000) |  | ||||||
|                     System.Threading.Thread.Sleep(300); |  | ||||||
| 
 |  | ||||||
|                 if (m_rateLimit >= 2000) |  | ||||||
|                     System.Threading.Thread.Sleep(600); |  | ||||||
| 
 |  | ||||||
|                 if (m_rateLimit >= 3500) |  | ||||||
|                     System.Threading.Thread.Sleep(900); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private string getFilePath(UUID host, string index) |  | ||||||
|         { |  | ||||||
|             SceneObjectGroup _host = m_scene.GetSceneObjectGroup(host); |  | ||||||
| 
 |  | ||||||
|             if (_host != null) |  | ||||||
|             { |  | ||||||
|                 string _nameSpace = _host.GroupID.ToString().Trim().ToUpper().Replace("-", ""); |  | ||||||
| 
 |  | ||||||
|                 if (!Directory.Exists(m_dataValueDirectory)) |  | ||||||
|                     Directory.CreateDirectory(m_dataValueDirectory); |  | ||||||
| 
 |  | ||||||
|                 if (!Directory.Exists(m_dataValueDirectory + "/" + _nameSpace)) |  | ||||||
|                     Directory.CreateDirectory(m_dataValueDirectory + "/" + _nameSpace); |  | ||||||
| 
 |  | ||||||
|                 string _storageKey = BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(ASCIIEncoding.ASCII.GetBytes(index.Trim().ToUpper()))).Replace("-", ""); |  | ||||||
| 
 |  | ||||||
|                 if(m_enabledCompress) |  | ||||||
|                     return m_dataValueDirectory + "/" + _nameSpace + "/" + _storageKey + ".gz"; |  | ||||||
| 
 |  | ||||||
|                 return m_dataValueDirectory + "/" + _nameSpace + "/" + _storageKey + ".txt"; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return null; |  | ||||||
|         }  |  | ||||||
| 
 |  | ||||||
|         [ScriptInvocation] |         [ScriptInvocation] | ||||||
|         public string osGetDataValue(UUID hostID, UUID scriptID, string key) |         public string osGetDataValue(UUID hostID, UUID scriptID, string key) | ||||||
|         { |         { | ||||||
|             string _filePath = getFilePath(hostID, key); |  | ||||||
| 
 | 
 | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|                 checkRateLimit(); |  | ||||||
| 
 |  | ||||||
|             FileInfo file = new FileInfo(_filePath); |  | ||||||
| 
 |  | ||||||
|             if (file.Exists) |  | ||||||
|             { |  | ||||||
|                 if(m_enabledCompress) |  | ||||||
|                     return Compress.Unzip(File.ReadAllBytes(file.FullName)); |  | ||||||
| 
 |  | ||||||
|                 return File.ReadAllText(file.FullName); |  | ||||||
|             } |  | ||||||
|                  |  | ||||||
| 
 |  | ||||||
|             return ""; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         [ScriptInvocation] |         [ScriptInvocation] | ||||||
|         public void osSetDataValue(UUID hostID, UUID scriptID, string key, string value) |         public void osSetDataValue(UUID hostID, UUID scriptID, string key, string value) | ||||||
|         { |         { | ||||||
|             string _filePath = getFilePath(hostID, key); |  | ||||||
| 
 | 
 | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|                 checkRateLimit(); |  | ||||||
| 
 |  | ||||||
|             FileInfo file = new FileInfo(_filePath); |  | ||||||
| 
 |  | ||||||
|             if (m_enabledCompress) |  | ||||||
|             { |  | ||||||
|                 File.WriteAllBytes(file.FullName, Compress.Zip(value)); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             File.WriteAllText(file.FullName, value); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         [ScriptInvocation] |         [ScriptInvocation] | ||||||
|         public void osDeleteDataValue(UUID hostID, UUID scriptID, string key, string value) |         public void osDeleteDataValue(UUID hostID, UUID scriptID, string key, string value) | ||||||
|         { |         { | ||||||
|             string _filePath = getFilePath(hostID, key); |  | ||||||
| 
 | 
 | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|                 checkRateLimit(); |  | ||||||
| 
 |  | ||||||
|             FileInfo file = new FileInfo(_filePath); |  | ||||||
| 
 |  | ||||||
|             if (file.Exists) |  | ||||||
|                 file.Delete(); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         [ScriptInvocation] |         [ScriptInvocation] | ||||||
|         public int osCheckDataValue(UUID hostID, UUID scriptID, string key) |         public int osCheckDataValue(UUID hostID, UUID scriptID, string key) | ||||||
|         { |         { | ||||||
|             string _filePath = getFilePath(hostID, key); |  | ||||||
| 
 | 
 | ||||||
|             if (m_enabledRateLimit) |  | ||||||
|                 checkRateLimit(); |  | ||||||
| 
 |  | ||||||
|             FileInfo file = new FileInfo(_filePath); |  | ||||||
| 
 |  | ||||||
|             if (file.Exists) |  | ||||||
|                 return 1; |  | ||||||
| 
 |  | ||||||
|             return 0; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         #endregion |         #endregion | ||||||
|  |  | ||||||
|  | @ -0,0 +1,114 @@ | ||||||
|  | using log4net; | ||||||
|  | using Nini.Config; | ||||||
|  | using OpenMetaverse; | ||||||
|  | using OpenSim.Region.Framework.Scenes; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Security.Cryptography; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace OpenSim.Modules.DataValue.Storage | ||||||
|  | { | ||||||
|  |     class FileSystem : iStorage | ||||||
|  |     { | ||||||
|  |         private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||||||
|  | 
 | ||||||
|  |         private Scene m_scene = null; | ||||||
|  | 
 | ||||||
|  |         private bool m_enabledCompress = true; | ||||||
|  |         private string m_dataValueDirectory = "./ScriptDataValue"; | ||||||
|  | 
 | ||||||
|  |         public FileSystem(Scene scene, IConfig config) | ||||||
|  |         { | ||||||
|  |             m_scene = scene; | ||||||
|  | 
 | ||||||
|  |             m_dataValueDirectory = config.Configs["XEngine"].GetString("DataValueStorageDirectory", m_dataValueDirectory); | ||||||
|  |             m_enabledCompress = config.Configs["XEngine"].GetBoolean("EnabledDataStorageCompressing", m_enabledCompress); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool check(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             string _filePath = getFilePath(hostID, key); | ||||||
|  | 
 | ||||||
|  |             FileInfo file = new FileInfo(_filePath); | ||||||
|  | 
 | ||||||
|  |             if (file.Exists) | ||||||
|  |                 return 1; | ||||||
|  | 
 | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string get(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             string _filePath = getFilePath(hostID, key); | ||||||
|  | 
 | ||||||
|  |             FileInfo file = new FileInfo(_filePath); | ||||||
|  | 
 | ||||||
|  |             if (file.Exists) | ||||||
|  |             { | ||||||
|  |                 if (m_enabledCompress) | ||||||
|  |                     return Compress.Unzip(File.ReadAllBytes(file.FullName)); | ||||||
|  | 
 | ||||||
|  |                 return File.ReadAllText(file.FullName); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string remove(string storageID, string key) | ||||||
|  |         { | ||||||
|  |             string _filePath = getFilePath(hostID, key); | ||||||
|  | 
 | ||||||
|  |             FileInfo file = new FileInfo(_filePath); | ||||||
|  | 
 | ||||||
|  |             if (file.Exists) | ||||||
|  |                 file.Delete(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void save(String storageID, string key, string data) | ||||||
|  |         { | ||||||
|  |             string _filePath = getFilePath(hostID, key); | ||||||
|  | 
 | ||||||
|  |             FileInfo file = new FileInfo(_filePath); | ||||||
|  | 
 | ||||||
|  |             if (m_enabledCompress) | ||||||
|  |             { | ||||||
|  |                 File.WriteAllBytes(file.FullName, Compress.Zip(value)); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             File.WriteAllText(file.FullName, value); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |         private string getFilePath(UUID host, string index) | ||||||
|  |         { | ||||||
|  |             SceneObjectGroup _host = m_scene.GetSceneObjectGroup(host); | ||||||
|  | 
 | ||||||
|  |             if (_host != null) | ||||||
|  |             { | ||||||
|  |                 string _nameSpace = _host.GroupID.ToString().Trim().ToUpper().Replace("-", ""); | ||||||
|  | 
 | ||||||
|  |                 if (!Directory.Exists(m_dataValueDirectory)) | ||||||
|  |                     Directory.CreateDirectory(m_dataValueDirectory); | ||||||
|  | 
 | ||||||
|  |                 if (!Directory.Exists(m_dataValueDirectory + "/" + _nameSpace)) | ||||||
|  |                     Directory.CreateDirectory(m_dataValueDirectory + "/" + _nameSpace); | ||||||
|  | 
 | ||||||
|  |                 string _storageKey = BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(ASCIIEncoding.ASCII.GetBytes(index.Trim().ToUpper()))).Replace("-", ""); | ||||||
|  | 
 | ||||||
|  |                 if (m_enabledCompress) | ||||||
|  |                     return m_dataValueDirectory + "/" + _nameSpace + "/" + _storageKey + ".gz"; | ||||||
|  | 
 | ||||||
|  |                 return m_dataValueDirectory + "/" + _nameSpace + "/" + _storageKey + ".txt"; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | using log4net; | ||||||
|  | using Nini.Config; | ||||||
|  | using OpenSim.Region.Framework.Scenes; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace OpenSim.Modules.DataValue.Storage | ||||||
|  | { | ||||||
|  |     class MySQL : iStorage | ||||||
|  |     { | ||||||
|  |         private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||||||
|  | 
 | ||||||
|  |         private Scene m_scene = null; | ||||||
|  | 
 | ||||||
|  |         public MySQL(Scene scene, IConfig config) | ||||||
|  |         { | ||||||
|  |             m_scene = scene; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool check(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             throw new NotImplementedException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string get(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             throw new NotImplementedException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string remove(string storageID, string key) | ||||||
|  |         { | ||||||
|  |             throw new NotImplementedException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void save(String storageID, string key, string data) | ||||||
|  |         { | ||||||
|  |             throw new NotImplementedException(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | using log4net; | ||||||
|  | using Nini.Config; | ||||||
|  | using OpenSim.Region.Framework.Scenes; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace OpenSim.Modules.DataValue.Storage | ||||||
|  | { | ||||||
|  |     class RegionExtras : iStorage | ||||||
|  |     { | ||||||
|  |         private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||||||
|  | 
 | ||||||
|  |         private Scene m_scene = null; | ||||||
|  | 
 | ||||||
|  |         public RegionExtras(Scene scene, IConfig config) | ||||||
|  |         { | ||||||
|  |             m_scene = scene; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public bool check(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             if (m_scene.GetExtraSetting("V:" + key) != "") | ||||||
|  |                 return true; | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string get(String storageID, string key) | ||||||
|  |         { | ||||||
|  |             return m_scene.GetExtraSetting("V:" + storageID + "." + key); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public string remove(string storageID, string key) | ||||||
|  |         { | ||||||
|  |             throw new NotImplementedException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void save(string key, String storageID, string data) | ||||||
|  |         { | ||||||
|  |             m_scene.StoreExtraSetting("V:" + key, data); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace OpenSim.Modules.DataValue.Storage | ||||||
|  | { | ||||||
|  |     interface iStorage | ||||||
|  |     { | ||||||
|  |         void save(String storageID, String key, String data); | ||||||
|  |         string get(String storageID, String key); | ||||||
|  |         string remove(String storageID, String key); | ||||||
|  |         bool check(String storageID, String key); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	 Christopher
						Christopher