From 85654f82a515df99d01dd2d2f3b619747a6cc5db Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 12:25:04 -0500 Subject: First cut of AutoBackupModule; only compile-tested so far --- .../Resources/OptionalModules.addin.xml | 1 + .../World/AutoBackup/AutoBackupModule.cs | 540 +++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml index 5eea286..8691343 100644 --- a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml +++ b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml @@ -13,5 +13,6 @@ + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs new file mode 100644 index 0000000..ed21e41 --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -0,0 +1,540 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * 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 OpenSimulator 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.IO; +using System.Timers; +using System.Diagnostics; +using System.Reflection; +using System.Collections.Generic; +using log4net; +using Nini; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + + +/* + * Config Settings Documentation. + * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + * This is the only required option for enabling auto-backup; the other options have sane defaults. + * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + * The number of minutes between each backup attempt. + * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + * AutoBackupBusyCheck: True/False. Default: True. + * If True, we will only take an auto-backup if a set of conditions are met. + * These conditions are heuristics to try and avoid taking a backup when the sim is busy. + * AutoBackupScript: String. Default: not specified (disabled). + * File path to an executable script or binary to run when an automatic backup is taken. + * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + * argv[1] of the executed file/script will be the file name of the generated OAR. + * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + * AutoBackupNaming: string. Default: Time. + * One of three strings (case insensitive): + * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + * AutoBackupDir: String. Default: "." (the current directory). + * A directory (absolute or relative) where backups should be saved. + * */ + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + + public enum NamingType + { + TIME, + SEQUENTIAL, + OVERWRITE + }; + + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase + { + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + //AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + private readonly IScene m_scene; + private bool m_enabled = false; + private NamingType m_naming = NamingType.TIME; + private Timer m_timer = null; + private bool m_busycheck = true; + private string m_script = null; + private string m_dir = "."; + + public AutoBackupModuleState(IScene scene) + { + m_scene = scene; + if(scene == null) + throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + } + + public void SetEnabled(bool b) + { + m_enabled = b; + } + + public bool GetEnabled() + { + return m_enabled; + } + + public Timer GetTimer() + { + return m_timer; + } + + public void SetTimer(Timer t) + { + m_timer = t; + } + + public bool GetBusyCheck() + { + return m_busycheck; + } + + public void SetBusyCheck(bool b) + { + m_busycheck = b; + } + + + public string GetScript() + { + return m_script; + } + + public void SetScript(string s) + { + m_script = s; + } + + public string GetBackupDir() + { + return m_dir; + } + + public void SetBackupDir(string s) + { + m_dir = s; + } + + public NamingType GetNamingType() + { + return m_naming; + } + + public void SetNamingType(NamingType n) + { + m_naming = n; + } + } + + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + //Also helps if you don't want AutoBackup at all + readonly Dictionary states = new Dictionary(4); + readonly Dictionary timers = new Dictionary(1); + readonly Dictionary> timerMap = new Dictionary>(1); + + public AutoBackupModule () + { + + } + + #region IRegionModuleBase implementation + void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) + { + //I have no overall config settings to care about. + } + + void IRegionModuleBase.Close () + { + //We don't want any timers firing while the sim's coming down; strange things may happen. + StopAllTimers(); + } + + void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) + { + //NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) + { + AutoBackupModuleState abms = states[scene]; + Timer timer = abms.GetTimer(); + List list = timerMap[timer]; + list.Remove(scene); + if(list.Count == 0) + { + timerMap.Remove(timer); + timers.Remove(timer.Interval); + timer.Close(); + } + } + + void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) + { + //This really ought not to happen, but just in case, let's pretend it didn't... + if(scene == null) + return; + + AutoBackupModuleState st = new AutoBackupModuleState(scene); + states.Add(scene, st); + + //Read the config settings and set variables. + IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + st.SetEnabled(config.GetBoolean("AutoBackup", false)); + if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + return; + + //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = config.GetDouble("AutoBackupInterval", 720); + if(timers.ContainsKey(interval)) + { + st.SetTimer(timers[interval]); + } + else + { + st.SetTimer(new Timer(interval)); + timers.Add(interval, st.GetTimer()); + st.GetTimer().Elapsed += HandleElapsed; + } + + //Add the current region to the list of regions tied to this timer. + if(timerMap.ContainsKey(st.GetTimer())) + { + timerMap[st.GetTimer()].Add(scene); + } + else + { + List scns = new List(1); + timerMap.Add(st.GetTimer(), scns); + } + + st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + + //Set file naming algorithm + string namingtype = config.GetString("AutoBackupNaming", "Time"); + if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.TIME); + } + else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.SEQUENTIAL); + } + else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.OVERWRITE); + } + else + { + m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType(NamingType.TIME); + } + + st.SetScript(config.GetString("AutoBackupScript", null)); + st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + + //Let's give the user *one* convenience and auto-mkdir + if(st.GetBackupDir() != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); + if(!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch(Exception e) + { + m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + void HandleElapsed (object sender, ElapsedEventArgs e) + { + bool heuristicsRun = false; + bool heuristicsPassed = false; + foreach(IScene scene in timerMap[(Timer)sender]) + { + AutoBackupModuleState state = states[scene]; + bool heuristics = state.GetBusyCheck(); + + //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if((heuristics && heuristicsRun && heuristicsPassed) + || !heuristics) + { + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + if(savePath == null) + { + m_log.Warn("savePath is null in HandleElapsed"); + continue; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + else if(heuristics && heuristicsRun && !heuristicsPassed) + { + continue; + } + //Logical Deduction: heuristics are on but haven't been run + else + { + heuristicsPassed = RunHeuristics(); + heuristicsRun = true; + if(!heuristicsPassed) + continue; + } + } + } + + string IRegionModuleBase.Name { + get { + return "AutoBackupModule"; + } + } + + Type IRegionModuleBase.ReplaceableInterface { + get { + return null; + } + } + + #endregion + #region ISharedRegionModule implementation + void ISharedRegionModule.PostInitialise () + { + //I don't care right now. + } + + #endregion + + //Is this even needed? + public bool IsSharedModule + { + get { return true; } + } + + private string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch(naming) + { + case NamingType.OVERWRITE: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + return path.FullName; + case NamingType.TIME: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.SEQUENTIAL: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + break; + } + + return path.FullName; + } + + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. + //(Terrible reference to ) + //This format may turn out to be too unwieldy to keep... + //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + //Sequential numbers, right? Ugh. Almost makes TOO much sense. + private string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + //Get the next logical file name + //I really shouldn't put fields here, but for now.... ;) + private string m_dirName = null; + private string m_regionName = null; + private string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + m_dirName = dirName; + m_regionName = regionName; + long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); + biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + + uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if(uniqueFile.Exists) + { + //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! + //Now you get to pay the performance cost :) + uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + } + + return uniqueFile.FullName; + } + + private bool RunHeuristics() + { + return true; + } + + private void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if(scriptName == null || scriptName.Length <= 0) + return; + + try + { + FileInfo fi = new FileInfo(scriptName); + if(fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch(Exception e) + { + m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach(Timer t in timerMap.Keys) + { + t.Close(); + } + } + + /* Find the largest value for which the predicate returns true. + * We use a bisection algorithm (half interval) to make the algorithm scalable. + * The worst-case complexity is about O(log(n)^2) in practice. + * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. + * The number of predicate invocations only hits a few hundred when the maximized value + * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. + * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. + * The Predicate parameter must be a function that accepts a long and returns a bool. + * */ + public long HalfIntervalMaximize(long start, Predicate pred) + { + long prev = start, curr = start, biggest = 0; + + if(start < 0) + throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + + do + { + if(pred(curr)) + { + if(curr > biggest) + { + biggest = curr; + } + prev = curr; + if(curr == 0) + { + //Special case because 0 * 2 = 0 :) + curr = 1; + } + else + { + //Look deeper + curr *= 2; + } + } + else + { + // We went too far, back off halfway + curr = (curr + prev) / 2; + } + } + while(curr - prev > 0); + + return biggest; + } + + public bool FileExistsTest(long num) + { + FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + return test.Exists; + } + + + //Very slow, hence why we try the HalfIntervalMaximize first! + public FileInfo UniqueFileSearchLinear(long start) + { + long l = start; + FileInfo retval = null; + do + { + retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } + while(retval.Exists); + + return retval; + } +} + +} + -- cgit v1.1 From dfa63ff0313610e1c2d262f7e660af94134159f6 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 20:15:38 -0500 Subject: Let GetNextFile do all the string-building work for SEQUENTIAL. --- OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ed21e41..f8d9060 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -364,7 +364,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + path = new FileInfo(GetNextFile(baseDir, regionName)); return path.FullName; default: m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); -- cgit v1.1 From 99e82602826e7d100d04a4bb229188be240db1ad Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:08:19 -0500 Subject: Add [Modules] option for unconditionally disabling entire module globally (for easy configuration) --- .../World/AutoBackup/AutoBackupModule.cs | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index f8d9060..7593b95 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,6 +40,9 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. + * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * In the [Modules] section: + * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! * EACH REGION in e.g. Regions/Regions.ini can have the following options: * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. @@ -166,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () { @@ -175,11 +179,24 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region IRegionModuleBase implementation void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { - //I have no overall config settings to care about. + //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); + if (m_Enabled) + { + m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + + } } void IRegionModuleBase.Close () { + if(!m_Enabled) + return; + //We don't want any timers firing while the sim's coming down; strange things may happen. StopAllTimers(); } @@ -191,6 +208,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + AutoBackupModuleState abms = states[scene]; Timer timer = abms.GetTimer(); List list = timerMap[timer]; @@ -205,6 +225,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + //This really ought not to happen, but just in case, let's pretend it didn't... if(scene == null) return; -- cgit v1.1 From 06a4810d210fcbfe1a859317698f791129ecdaf1 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:29:45 -0500 Subject: Fix config source by taking it from Initialize --- OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7593b95..68cf219 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -169,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private IConfigSource m_configSource = null; private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () @@ -180,6 +181,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + m_configSource = source; IConfig moduleConfig = source.Configs["Modules"]; if (moduleConfig != null) { @@ -236,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; -- cgit v1.1 From 3c9bf5c476eb294748b53e0c65e7880c26c108ab Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:47:05 -0500 Subject: Fix config source stuff harder (debug console prints only; no fix yet) --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68cf219..ebad12f 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,11 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); + AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + if(config == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); + foreach(IConfig c in m_configSource.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); + } + throw new NullReferenceException("This is debug code"); + } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; -- cgit v1.1 From e5c08a553c4cbf0485d7eb93fc891e002ae04254 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:55:32 -0500 Subject: More debug messages --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ebad12f..21e48df 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -248,6 +248,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); } + + if(scene.Config != null) + { + m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); + IConfigSource tmp = scene.Config; + foreach(IConfig d in tmp.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); + } + } throw new NullReferenceException("This is debug code"); } st.SetEnabled(config.GetBoolean("AutoBackup", false)); -- cgit v1.1 From 7eac0af5590a450752a9fd2c57abe7d1a6d84c4c Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:07:54 -0500 Subject: Fix an actual bug in the timer interval calculation (minutes -> msec) --- .../World/AutoBackup/AutoBackupModule.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 21e48df..4899718 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -240,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + IConfig config = scene.Config.Configs["Startup"]; if(config == null) { m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); @@ -258,11 +258,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); } } - throw new NullReferenceException("This is debug code"); + throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + { return; + } + else + { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = config.GetDouble("AutoBackupInterval", 720); @@ -272,7 +278,13 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } else { - st.SetTimer(new Timer(interval)); + //0 or negative interval == do nothing. + if(interval <= 0.0) + { + st.SetEnabled(false); + return; + } + st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; } -- cgit v1.1 From c2658c2f14d75c0c90f40c3673eb35677bcf2cb7 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:14:43 -0500 Subject: More relevant console messages, and maybe fix config problem --- .../World/AutoBackup/AutoBackupModule.cs | 25 ++++++---------------- 1 file changed, 6 insertions(+), 19 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4899718..4735620 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,35 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; - m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); - AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["Startup"]; + IConfig config = scene.Config.Configs["AutoBackupModule"]; if(config == null) { - m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); - foreach(IConfig c in m_configSource.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); - } - - if(scene.Config != null) - { - m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); - IConfigSource tmp = scene.Config; - foreach(IConfig d in tmp.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); - } - } - throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good + //No config settings for this, let's just give up. + st.SetEnabled(false); + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + return; } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } else -- cgit v1.1 From 2da9bb3ca2fbd4294409b74733f30b79bd48e2ec Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:31:10 -0500 Subject: Try to fix config one more time. Note that the way we specify settings has changed significantly here. --- .../World/AutoBackup/AutoBackupModule.cs | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4735620..0869b0c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,10 +40,13 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: * In the [Modules] section: * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + * VERY IMPORTANT: You must create the key name as follows: . + * Example: My region is named Foo. + * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -190,7 +193,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); } - } } @@ -234,19 +236,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + string sRegionName = scene.RegionInfo.RegionName; AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["AutoBackupModule"]; + IConfig config = m_configSource.Configs["AutoBackupModule"]; if(config == null) { - //No config settings for this, let's just give up. + //No config settings for any regions, let's just give up. st.SetEnabled(false); m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean("AutoBackup", false)); + st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); @@ -258,7 +261,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble("AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); @@ -287,10 +290,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timerMap.Add(st.GetTimer(), scns); } - st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString("AutoBackupNaming", "Time"); + string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { st.SetNamingType(NamingType.TIME); @@ -309,8 +312,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetNamingType(NamingType.TIME); } - st.SetScript(config.GetString("AutoBackupScript", null)); - st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir if(st.GetBackupDir() != ".") -- cgit v1.1 From 7fa8ed0c47b3b0a5c87cd27b2620aeef05a7fe86 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:48:02 -0500 Subject: Config works, but timer isn't firing. Hmm. --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 0869b0c..68c8301 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -246,25 +246,26 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //No config settings for any regions, let's just give up. st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } else { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); + m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); } else { @@ -274,9 +275,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes + st.SetTimer(new Timer(interval)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; + m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. -- cgit v1.1 From bb1f03abc6a3c12b4f511e4f212f5673c4d31dc4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:57:48 -0500 Subject: Make timer auto-respawn. --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68c8301..58b93c0 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -275,9 +275,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval)); //Milliseconds -> minutes - timers.Add(interval, st.GetTimer()); - st.GetTimer().Elapsed += HandleElapsed; + Timer tim = new Timer(interval); + st.SetTimer(tim); //Milliseconds -> minutes + timers.Add(interval, tim); + tim.Elapsed += HandleElapsed; + tim.AutoReset = True; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } @@ -351,6 +353,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { IRegionArchiverModule iram = scene.RequestModuleInterface(); string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); if(savePath == null) { m_log.Warn("savePath is null in HandleElapsed"); -- cgit v1.1 From cb049aa15f64fec5ba4b08aedddf98b68ab5044e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:58:42 -0500 Subject: s/True/true --- OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 58b93c0..ba5e24b 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -279,7 +279,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetTimer(tim); //Milliseconds -> minutes timers.Add(interval, tim); tim.Elapsed += HandleElapsed; - tim.AutoReset = True; + tim.AutoReset = true; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } -- cgit v1.1 From d3511ca592eff780e6d707680ac1d20a24b9fc58 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 09:03:11 -0500 Subject: Start the timer. (Could it be that simple?) --- OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ba5e24b..77126c7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -280,6 +280,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Add(interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; + tim.Start(); m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } -- cgit v1.1 From 34b6904939dd551aafa68ea8e02dc5177d395b3b Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 26 Feb 2011 22:09:19 -0500 Subject: First working commit of AutoBackupModule. It seems to do something! Heuristics are still TODO, but this is alpha 1. --- .../World/AutoBackup/AutoBackupModule.cs | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 77126c7..e3686ac 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -281,7 +281,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start(); - m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. @@ -292,6 +292,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup else { List scns = new List(1); + scns.Add(scene); timerMap.Add(st.GetTimer(), scns); } @@ -343,6 +344,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { bool heuristicsRun = false; bool heuristicsPassed = false; + if(!timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + } foreach(IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; @@ -352,16 +357,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); - m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("savePath is null in HandleElapsed"); - continue; - } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + doRegionBackup(scene); } //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! else if(heuristics && heuristicsRun && !heuristicsPassed) @@ -375,9 +371,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup heuristicsRun = true; if(!heuristicsPassed) continue; + doRegionBackup(scene); } } } + + void doRegionBackup(IScene scene) + { + AutoBackupModuleState state = states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + if(savePath == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } string IRegionModuleBase.Name { get { -- cgit v1.1 From a01c44e74de669ea2643b8bfe76a7e78ca4740a4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:04:54 -0500 Subject: Be smarter about stopping timers. Cleanup formatting. Use a boolean flag to tell timers that fire after IRegionModuleBase.Close() is called that they should not execute. Also, I used MonoDevelop's auto-formatting feature to format the code uniformly. No guarantee about variable names though. --- .../World/AutoBackup/AutoBackupModule.cs | 502 +++++++++------------ 1 file changed, 225 insertions(+), 277 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e3686ac..54b9b09 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -73,20 +73,19 @@ using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - + public enum NamingType { TIME, SEQUENTIAL, OVERWRITE - }; - + } + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase { - - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + + private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); + //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { @@ -97,84 +96,87 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_busycheck = true; private string m_script = null; private string m_dir = "."; - - public AutoBackupModuleState(IScene scene) + + public AutoBackupModuleState (IScene scene) { m_scene = scene; - if(scene == null) - throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + if (scene == null) + throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); } - - public void SetEnabled(bool b) + + public void SetEnabled (bool b) { - m_enabled = b; + m_enabled = b; } - - public bool GetEnabled() + + public bool GetEnabled () { - return m_enabled; + return m_enabled; } - - public Timer GetTimer() + + public Timer GetTimer () { - return m_timer; + return m_timer; } - - public void SetTimer(Timer t) + + public void SetTimer (Timer t) { - m_timer = t; + m_timer = t; } - - public bool GetBusyCheck() + + public bool GetBusyCheck () { - return m_busycheck; + return m_busycheck; } - - public void SetBusyCheck(bool b) + + public void SetBusyCheck (bool b) { - m_busycheck = b; + m_busycheck = b; } - - - public string GetScript() + + + public string GetScript () { - return m_script; + return m_script; } - - public void SetScript(string s) + + public void SetScript (string s) { - m_script = s; + m_script = s; } - - public string GetBackupDir() + + public string GetBackupDir () { - return m_dir; + return m_dir; } - - public void SetBackupDir(string s) + + public void SetBackupDir (string s) { - m_dir = s; + m_dir = s; } - - public NamingType GetNamingType() + + public NamingType GetNamingType () { return m_naming; } - - public void SetNamingType(NamingType n) + + public void SetNamingType (NamingType n) { - m_naming = n; + m_naming = n; } } - + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary(4); - readonly Dictionary timers = new Dictionary(1); - readonly Dictionary> timerMap = new Dictionary>(1); + readonly Dictionary states = new Dictionary (4); + readonly Dictionary timers = new Dictionary (1); + readonly Dictionary> timerMap = new Dictionary> (1); private IConfigSource m_configSource = null; - private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - + private bool m_Enabled = false; + //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed = false; + //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. public AutoBackupModule () { @@ -185,24 +187,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) - { - m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); - if (m_Enabled) - { - m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); - } - } + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) { + m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); + if (m_Enabled) { + m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + } } void IRegionModuleBase.Close () { - if(!m_Enabled) + if (!m_Enabled) return; //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers(); + StopAllTimers (); } void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) @@ -212,327 +212,286 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer(); + Timer timer = abms.GetTimer (); List list = timerMap[timer]; - list.Remove(scene); - if(list.Count == 0) - { - timerMap.Remove(timer); - timers.Remove(timer.Interval); - timer.Close(); + list.Remove (scene); + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); } } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; //This really ought not to happen, but just in case, let's pretend it didn't... - if(scene == null) + if (scene == null) return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState(scene); - states.Add(scene, st); + AutoBackupModuleState st = new AutoBackupModuleState (scene); + states.Add (scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; - if(config == null) - { + if (config == null) { //No config settings for any regions, let's just give up. - st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (false); + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); - if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + //If you don't want AutoBackup, we stop. + if (!st.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; - } - else - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + } else { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; - if(timers.ContainsKey(interval)) - { - st.SetTimer(timers[interval]); - m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); - } - else - { + double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + if (timers.ContainsKey (interval)) { + st.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + } else { //0 or negative interval == do nothing. - if(interval <= 0.0) - { - st.SetEnabled(false); + if (interval <= 0.0) { + st.SetEnabled (false); return; } - Timer tim = new Timer(interval); - st.SetTimer(tim); //Milliseconds -> minutes - timers.Add(interval, tim); - tim.Elapsed += HandleElapsed; + Timer tim = new Timer (interval); + st.SetTimer (tim); + //Milliseconds -> minutes + timers.Add (interval, tim); + tim.Elapsed += HandleElapsed; tim.AutoReset = true; - tim.Start(); + tim.Start (); //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if(timerMap.ContainsKey(st.GetTimer())) - { - timerMap[st.GetTimer()].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - timerMap.Add(st.GetTimer(), scns); + if (timerMap.ContainsKey (st.GetTimer ())) { + timerMap[st.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (st.GetTimer (), scns); } - st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); + st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); - if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.TIME); - } - else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.SEQUENTIAL); - } - else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.OVERWRITE); - } - else - { - m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType(NamingType.TIME); + string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); + if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.TIME); + } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.SEQUENTIAL); + } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.OVERWRITE); + } else { + m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType (NamingType.TIME); } - st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); + st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir - if(st.GetBackupDir() != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); - if(!dirinfo.Exists) - { - dirinfo.Create(); + if (st.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); } - } - catch(Exception e) - { - m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); } } } void HandleElapsed (object sender, ElapsedEventArgs e) { + if (m_closed) + return; bool heuristicsRun = false; bool heuristicsPassed = false; - if(!timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + if (!timerMap.ContainsKey ((Timer)sender)) { + m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach(IScene scene in timerMap[(Timer)sender]) - { + foreach (IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck(); + bool heuristics = state.GetBusyCheck (); //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if((heuristics && heuristicsRun && heuristicsPassed) - || !heuristics) - { - doRegionBackup(scene); - } + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { + doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - else if(heuristics && heuristicsRun && !heuristicsPassed) - { + } else if (heuristics && heuristicsRun && !heuristicsPassed) { continue; - } //Logical Deduction: heuristics are on but haven't been run - else - { - heuristicsPassed = RunHeuristics(); + } else { + heuristicsPassed = RunHeuristics (); heuristicsRun = true; - if(!heuristicsPassed) + if (!heuristicsPassed) continue; - doRegionBackup(scene); + doRegionBackup (scene); } } } - - void doRegionBackup(IScene scene) + + void doRegionBackup (IScene scene) { AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + IRegionArchiverModule iram = scene.RequestModuleInterface (); + string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + if (savePath == null) { + m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + iram.ArchiveRegion (savePath, null); + ExecuteScript (state.GetScript (), savePath); } string IRegionModuleBase.Name { - get { - return "AutoBackupModule"; - } + get { return "AutoBackupModule"; } } Type IRegionModuleBase.ReplaceableInterface { - get { - return null; - } + get { return null; } } - + #endregion #region ISharedRegionModule implementation void ISharedRegionModule.PostInitialise () { //I don't care right now. } - + #endregion - + //Is this even needed? - public bool IsSharedModule - { - get { return true; } - } - - private string BuildOarPath(string regionName, string baseDir, NamingType naming) + public bool IsSharedModule { + get { return true; } + } + + private string BuildOarPath (string regionName, string baseDir, NamingType naming) { FileInfo path = null; - switch(naming) - { + switch (naming) { case NamingType.OVERWRITE: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); return path.FullName; case NamingType.TIME: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(GetNextFile(baseDir, regionName)); + path = new FileInfo (GetNextFile (baseDir, regionName)); return path.FullName; default: - m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); break; } return path.FullName; } - + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. //(Terrible reference to ) //This format may turn out to be too unwieldy to keep... //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString() + private string GetTimeString () { - StringWriter sw = new StringWriter(); - sw.Write("_"); + StringWriter sw = new StringWriter (); + sw.Write ("_"); DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); + sw.Write (now.Year); + sw.Write ("y_"); + sw.Write (now.Month); + sw.Write ("M_"); + sw.Write (now.Day); + sw.Write ("d_"); + sw.Write (now.Hour); + sw.Write ("h_"); + sw.Write (now.Minute); + sw.Write ("m_"); + sw.Write (now.Second); + sw.Write ("s"); + sw.Flush (); + string output = sw.ToString (); + sw.Close (); return output; } - + //Get the next logical file name //I really shouldn't put fields here, but for now.... ;) private string m_dirName = null; private string m_regionName = null; - private string GetNextFile(string dirName, string regionName) + private string GetNextFile (string dirName, string regionName) { FileInfo uniqueFile = null; m_dirName = dirName; m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); - biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - - uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if(uniqueFile.Exists) - { + long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if (uniqueFile.Exists) { //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + uniqueFile = UniqueFileSearchLinear (biggestExistingFile); } return uniqueFile.FullName; } - - private bool RunHeuristics() + + private bool RunHeuristics () { return true; } - - private void ExecuteScript(string scriptName, string savePath) + + private void ExecuteScript (string scriptName, string savePath) { //Fast path out - if(scriptName == null || scriptName.Length <= 0) + if (scriptName == null || scriptName.Length <= 0) return; - try - { - FileInfo fi = new FileInfo(scriptName); - if(fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); + try { + FileInfo fi = new FileInfo (scriptName); + if (fi.Exists) { + ProcessStartInfo psi = new ProcessStartInfo (scriptName); psi.Arguments = savePath; psi.CreateNoWindow = true; - Process proc = Process.Start(psi); + Process proc = Process.Start (psi); proc.ErrorDataReceived += HandleProcErrorDataReceived; } - } - catch(Exception e) - { - m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } catch (Exception e) { + m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); } } void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) { - m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); } - - private void StopAllTimers() + + private void StopAllTimers () { - foreach(Timer t in timerMap.Keys) - { - t.Close(); + foreach (Timer t in timerMap.Keys) { + t.Close (); } + m_closed = true; } - + /* Find the largest value for which the predicate returns true. * We use a bisection algorithm (half interval) to make the algorithm scalable. * The worst-case complexity is about O(log(n)^2) in practice. @@ -542,65 +501,54 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. * The Predicate parameter must be a function that accepts a long and returns a bool. * */ - public long HalfIntervalMaximize(long start, Predicate pred) + public long HalfIntervalMaximize (long start, Predicate pred) { long prev = start, curr = start, biggest = 0; - if(start < 0) - throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + if (start < 0) + throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - do - { - if(pred(curr)) - { - if(curr > biggest) - { + do { + if (pred (curr)) { + if (curr > biggest) { biggest = curr; } prev = curr; - if(curr == 0) - { + if (curr == 0) { //Special case because 0 * 2 = 0 :) curr = 1; - } - else - { + } else { //Look deeper curr *= 2; } - } - else - { + } else { // We went too far, back off halfway curr = (curr + prev) / 2; } - } - while(curr - prev > 0); + } while (curr - prev > 0); return biggest; } - - public bool FileExistsTest(long num) + + public bool FileExistsTest (long num) { - FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); return test.Exists; } - - + + //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear(long start) + public FileInfo UniqueFileSearchLinear (long start) { long l = start; FileInfo retval = null; - do - { - retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } - while(retval.Exists); + do { + retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } while (retval.Exists); return retval; } -} + } } -- cgit v1.1 From 018645f9f843d1e807a69a63b7dd82c294885eff Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:45:50 -0500 Subject: First pass at busy heuristics. Compile-tested only. --- .../World/AutoBackup/AutoBackupModule.cs | 69 ++++++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 54b9b09..98127b7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -35,7 +35,9 @@ using log4net; using Nini; using Nini.Config; using OpenSim.Framework; +using OpenSim.Framework.Statistics; using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; /* @@ -319,6 +321,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void HandleElapsed (object sender, ElapsedEventArgs e) { + //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + //check whether the region is too busy! Especially on sims with LOTS of regions. + //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... if (m_closed) return; bool heuristicsRun = false; @@ -333,15 +341,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; - //Logical Deduction: heuristics are on but haven't been run + //Logical Deduction: heuristics are on but haven't been run } else { - heuristicsPassed = RunHeuristics (); + heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; - if (!heuristicsPassed) + if (!heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; + } doRegionBackup (scene); } } @@ -349,6 +360,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void doRegionBackup (IScene scene) { + if (scene.RegionStatus != RegionStatus.Up) { + //We won't backup a region that isn't operating normally. + m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + return; + } + AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); @@ -454,9 +471,49 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - private bool RunHeuristics () + /* + * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + * */ + private bool RunHeuristics (IScene region) + { + try { + return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); + } catch (Exception e) { + m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + return false; + } + } + + /* + * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunTimeDilationHeuristic (IScene region) { - return true; + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /* + * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunAgentLimitHeuristic (IScene region) + { + string regionName = region.RegionInfo.RegionName; + try { + Scene scene = (Scene)region; + //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); + } catch (InvalidCastException ice) { + m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + return true; + //Non-obstructionist safest answer... + } } private void ExecuteScript (string scriptName, string savePath) -- cgit v1.1 From b3c42e952f24a5c280a3691e1f75912e21c77323 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:54:07 -0500 Subject: Fix small bug with remove region; update settings docs. Our impl of IRegionModuleBase.RemoveRegion didn't remove the scene from the states map. --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 98127b7..7660342 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -71,6 +71,10 @@ using OpenSim.Region.Framework.Scenes; * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. * AutoBackupDir: String. Default: "." (the current directory). * A directory (absolute or relative) where backups should be saved. + * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + * If the time dilation is below this value, don't take a backup right now. + * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + * If the number of agents is greater than this value, don't take a backup right now. * */ namespace OpenSim.Region.OptionalModules.World.AutoBackup @@ -91,7 +95,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { - private readonly IScene m_scene; private bool m_enabled = false; private NamingType m_naming = NamingType.TIME; private Timer m_timer = null; @@ -99,11 +102,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private string m_script = null; private string m_dir = "."; - public AutoBackupModuleState (IScene scene) + public AutoBackupModuleState () { - m_scene = scene; - if (scene == null) - throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); + } public void SetEnabled (bool b) @@ -226,6 +227,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Remove (timer.Interval); timer.Close (); } + states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -238,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (scene); + AutoBackupModuleState st = new AutoBackupModuleState (); states.Add (scene, st); //Read the config settings and set variables. -- cgit v1.1 From 4974a1ce69fb3a1d2937c7de7ba93079a918eb3a Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 12:34:26 -0400 Subject: AutoBackup: Support region-independent settings too. --- .../World/AutoBackup/AutoBackupModule.cs | 232 ++++++++++++++++----- 1 file changed, 175 insertions(+), 57 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7660342..37a2d97 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -49,6 +49,8 @@ using OpenSim.Region.Framework.Scenes; * VERY IMPORTANT: You must create the key name as follows: . * Example: My region is named Foo. * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + * Region-specific settings take precedence. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -121,6 +123,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { return m_timer; } + + public double GetIntervalMinutes () + { + if(m_timer == null) + { + return -1.0; + } + else + { + return m_timer.Interval / 60000.0; + } + } public void SetTimer (Timer t) { @@ -167,6 +181,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_naming = n; } + + public string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; + retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; + return retval; + } } //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. @@ -180,6 +207,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_closed = false; //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); + public AutoBackupModule () { @@ -194,9 +223,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (moduleConfig != null) { m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); if (m_Enabled) { - m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } } + + Timer defTimer = new Timer(720 * 60000); + defaultState.SetTimer(defTimer); + timers.Add (720*60000, defTimer); + defTimer.Elapsed += HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start (); + + AutoBackupModuleState abms = ParseConfig(null, false); + m_log.Debug("[AUTO BACKUP]: Config for default"); + m_log.Debug(abms.ToString()); } void IRegionModuleBase.Close () @@ -238,87 +278,162 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //This really ought not to happen, but just in case, let's pretend it didn't... if (scene == null) return; - - string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (); - states.Add (scene, st); - - //Read the config settings and set variables. + + AutoBackupModuleState abms = ParseConfig(scene, true); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug(abms.ToString()); + } + + AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if(parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; if (config == null) { - //No config settings for any regions, let's just give up. - st.SetEnabled (false); - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + state = defaultState; //defaultState would be disabled too if the section doesn't exist. + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); + if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state + { + state = new AutoBackupModuleState(); + state.SetEnabled (tmpEnabled); } - st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + //If you don't want AutoBackup, we stop. - if (!st.GetEnabled ()) { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; } else { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; + if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) + { + state = new AutoBackupModuleState(); + } + if (timers.ContainsKey (interval)) { - st.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + if(state != null) + state.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); } else { //0 or negative interval == do nothing. - if (interval <= 0.0) { - st.SetEnabled (false); - return; + if (interval <= 0.0 && state != null) { + state.SetEnabled (false); + return state; } Timer tim = new Timer (interval); - st.SetTimer (tim); + state.SetTimer (tim); //Milliseconds -> minutes timers.Add (interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start (); - //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (st.GetTimer ())) { - timerMap[st.GetTimer ()].Add (scene); + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); } else { List scns = new List (1); scns.Add (scene); - timerMap.Add (st.GetTimer (), scns); + timerMap.Add (state.GetTimer (), scns); + } + + bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); + if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) + { + state = new AutoBackupModuleState(); } - st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); + if(state != null) + { + state.SetBusyCheck (tmpBusyCheck); + } //Set file naming algorithm - string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); - if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.TIME); - } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.SEQUENTIAL); - } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.OVERWRITE); + string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.TIME; + } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.SEQUENTIAL; + } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.OVERWRITE; } else { - m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType (NamingType.TIME); + m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); + tmpNamingType = NamingType.TIME; } - st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); + if(state == null && tmpNamingType != defaultState.GetNamingType()) + { + state = new AutoBackupModuleState(); + } - //Let's give the user *one* convenience and auto-mkdir - if (st.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } + if(state != null) + { + state.SetNamingType(tmpNamingType); } + + string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); + if(state == null && tmpScript != defaultState.GetScript()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetScript (tmpScript); + } + + string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); + if(state == null && tmpBackupDir != defaultState.GetBackupDir()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetBackupDir (tmpBackupDir); + //Let's give the user *one* convenience and auto-mkdir + if (state.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); + } + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + return state; } void HandleElapsed (object sender, ElapsedEventArgs e) @@ -336,7 +451,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!timerMap.ContainsKey ((Timer)sender)) { m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach (IScene scene in timerMap[(Timer)sender]) { + + List tmap = timerMap[(Timer)sender]; + if(tmap != null && tmap.Count > 0) + foreach (IScene scene in tmap) { AutoBackupModuleState state = states[scene]; bool heuristics = state.GetBusyCheck (); @@ -345,14 +463,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; //Logical Deduction: heuristics are on but haven't been run } else { heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; } doRegionBackup (scene); @@ -364,16 +482,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { if (scene.RegionStatus != RegionStatus.Up) { //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); return; } AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); if (savePath == null) { - m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } iram.ArchiveRegion (savePath, null); @@ -481,7 +599,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup try { return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); return false; } } @@ -512,7 +630,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); return true; //Non-obstructionist safest answer... } -- cgit v1.1 From 4ad05fb01d038b31738438d02850972e79739f30 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:12:26 -0400 Subject: Fix some NREs on certain paths. --- .../World/AutoBackup/AutoBackupModule.cs | 28 +++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 37a2d97..3d156ca 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -324,6 +324,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //If you don't want AutoBackup, we stop. if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); @@ -346,6 +347,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.SetEnabled (false); return state; } + if(state == null) + state = new AutoBackupModuleState(); Timer tim = new Timer (interval); state.SetTimer (tim); //Milliseconds -> minutes @@ -357,12 +360,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } + } + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); -- cgit v1.1 From 95a6ee0a3793b72deb29fe6e853d59afc2ca631e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:20:46 -0400 Subject: Fix NREs harder. --- .../World/AutoBackup/AutoBackupModule.cs | 39 +++++++++++++--------- 1 file changed, 23 insertions(+), 16 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 3d156ca..e52e9cb 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -225,6 +225,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (m_Enabled) { m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } + else { + m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); + return; + } } Timer defTimer = new Timer(720 * 60000); @@ -360,24 +364,27 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if(state != null) + if(scene != null) { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } } -- cgit v1.1 From 049bce4d003f6854868306c7d98f7b5a501cb047 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 12 Apr 2011 01:14:21 -0400 Subject: Fixup the global defaults config parsing code. --- .../World/AutoBackup/AutoBackupModule.cs | 40 ++++++++++++++-------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e52e9cb..364697b 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -238,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup defTimer.AutoReset = true; defTimer.Start (); - AutoBackupModuleState abms = ParseConfig(null, false); + AutoBackupModuleState abms = ParseConfig(null, true); m_log.Debug("[AUTO BACKUP]: Config for default"); m_log.Debug(abms.ToString()); } @@ -262,16 +262,23 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!m_Enabled) return; - AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); + if(states.ContainsKey(scene)) + { + AutoBackupModuleState abms = states[scene]; + + //Remove this scene out of the timer map list + Timer timer = abms.GetTimer (); + List list = timerMap[timer]; + list.Remove (scene); + + //Shut down the timer if this was the last scene for the timer + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); + } + states.Remove(scene); } - states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -283,9 +290,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (scene == null) return; - AutoBackupModuleState abms = ParseConfig(scene, true); + AutoBackupModuleState abms = ParseConfig(scene, false); m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug(abms.ToString()); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) @@ -322,13 +329,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state { state = new AutoBackupModuleState(); - state.SetEnabled (tmpEnabled); + } + + if(state != null) + { + state.SetEnabled (tmpEnabled); } //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); -- cgit v1.1 From 082fad6dd28513e38120c9d272aeed385de8208f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:29:13 -0400 Subject: Fix most issues raised by justincc: http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 1509 ++++++++++---------- .../World/AutoBackup/AutoBackupModuleState.cs | 109 ++ 2 files changed, 854 insertions(+), 764 deletions(-) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 364697b..a4dbea4 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,764 +1,745 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * 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 OpenSimulator 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.IO; -using System.Timers; -using System.Diagnostics; -using System.Reflection; -using System.Collections.Generic; -using log4net; -using Nini; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Framework.Statistics; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - - -/* - * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: - * In the [Modules] section: - * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. - * VERY IMPORTANT: You must create the key name as follows: . - * Example: My region is named Foo. - * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. - * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. - * Region-specific settings take precedence. - * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. - * This is the only required option for enabling auto-backup; the other options have sane defaults. - * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. - * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). - * The number of minutes between each backup attempt. - * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. - * AutoBackupBusyCheck: True/False. Default: True. - * If True, we will only take an auto-backup if a set of conditions are met. - * These conditions are heuristics to try and avoid taking a backup when the sim is busy. - * AutoBackupScript: String. Default: not specified (disabled). - * File path to an executable script or binary to run when an automatic backup is taken. - * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. - * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! - * argv[1] of the executed file/script will be the file name of the generated OAR. - * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. - * AutoBackupNaming: string. Default: Time. - * One of three strings (case insensitive): - * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. - * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. - * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. - * AutoBackupDir: String. Default: "." (the current directory). - * A directory (absolute or relative) where backups should be saved. - * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. - * If the time dilation is below this value, don't take a backup right now. - * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. - * If the number of agents is greater than this value, don't take a backup right now. - * */ - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - - public enum NamingType - { - TIME, - SEQUENTIAL, - OVERWRITE - } - - public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase - { - - private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); - - //AutoBackupModuleState: Auto-Backup state for one region (scene). - public class AutoBackupModuleState - { - private bool m_enabled = false; - private NamingType m_naming = NamingType.TIME; - private Timer m_timer = null; - private bool m_busycheck = true; - private string m_script = null; - private string m_dir = "."; - - public AutoBackupModuleState () - { - - } - - public void SetEnabled (bool b) - { - m_enabled = b; - } - - public bool GetEnabled () - { - return m_enabled; - } - - public Timer GetTimer () - { - return m_timer; - } - - public double GetIntervalMinutes () - { - if(m_timer == null) - { - return -1.0; - } - else - { - return m_timer.Interval / 60000.0; - } - } - - public void SetTimer (Timer t) - { - m_timer = t; - } - - public bool GetBusyCheck () - { - return m_busycheck; - } - - public void SetBusyCheck (bool b) - { - m_busycheck = b; - } - - - public string GetScript () - { - return m_script; - } - - public void SetScript (string s) - { - m_script = s; - } - - public string GetBackupDir () - { - return m_dir; - } - - public void SetBackupDir (string s) - { - m_dir = s; - } - - public NamingType GetNamingType () - { - return m_naming; - } - - public void SetNamingType (NamingType n) - { - m_naming = n; - } - - public string ToString() - { - string retval = ""; - - retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; - retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; - retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; - retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; - retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; - retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; - return retval; - } - } - - //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary (4); - readonly Dictionary timers = new Dictionary (1); - readonly Dictionary> timerMap = new Dictionary> (1); - private IConfigSource m_configSource = null; - private bool m_Enabled = false; - //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed = false; - //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); - - public AutoBackupModule () - { - - } - - #region IRegionModuleBase implementation - void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) - { - //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) { - m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); - if (m_Enabled) { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); - return; - } - } - - Timer defTimer = new Timer(720 * 60000); - defaultState.SetTimer(defTimer); - timers.Add (720*60000, defTimer); - defTimer.Elapsed += HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start (); - - AutoBackupModuleState abms = ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Config for default"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close () - { - if (!m_Enabled) - return; - - //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers (); - } - - void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) - { - //NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - if(states.ContainsKey(scene)) - { - AutoBackupModuleState abms = states[scene]; - - //Remove this scene out of the timer map list - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - - //Shut down the timer if this was the last scene for the timer - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); - } - states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - //This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - return; - - AutoBackupModuleState abms = ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if(parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - //Read the config settings and set variables. - IConfig config = m_configSource.Configs["AutoBackupModule"]; - if (config == null) { - state = defaultState; //defaultState would be disabled too if the section doesn't exist. - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); - if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetEnabled (tmpEnabled); - } - - //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } else { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; - if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) - { - state = new AutoBackupModuleState(); - } - - if (timers.ContainsKey (interval)) { - if(state != null) - state.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); - } else { - //0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) { - state.SetEnabled (false); - return state; - } - if(state == null) - state = new AutoBackupModuleState(); - Timer tim = new Timer (interval); - state.SetTimer (tim); - //Milliseconds -> minutes - timers.Add (interval, tim); - tim.Elapsed += HandleElapsed; - tim.AutoReset = true; - tim.Start (); - //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); - } - - //Add the current region to the list of regions tied to this timer. - if(scene != null) - { - if(state != null) - { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); - } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); - if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBusyCheck (tmpBusyCheck); - } - - //Set file naming algorithm - string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.TIME; - } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.SEQUENTIAL; - } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.OVERWRITE; - } else { - m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); - tmpNamingType = NamingType.TIME; - } - - if(state == null && tmpNamingType != defaultState.GetNamingType()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetNamingType(tmpNamingType); - } - - string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); - if(state == null && tmpScript != defaultState.GetScript()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetScript (tmpScript); - } - - string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); - if(state == null && tmpBackupDir != defaultState.GetBackupDir()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBackupDir (tmpBackupDir); - //Let's give the user *one* convenience and auto-mkdir - if (state.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } - } - } - - return state; - } - - void HandleElapsed (object sender, ElapsedEventArgs e) - { - //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - //check whether the region is too busy! Especially on sims with LOTS of regions. - //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - // but would allow us to be semantically correct while being easier on perf. - //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (m_closed) - return; - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!timerMap.ContainsKey ((Timer)sender)) { - m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); - } - - List tmap = timerMap[(Timer)sender]; - if(tmap != null && tmap.Count > 0) - foreach (IScene scene in tmap) { - AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck (); - - //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - //Logical Deduction: heuristics are on but haven't been run - } else { - heuristicsPassed = RunHeuristics (scene); - heuristicsRun = true; - if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - } - doRegionBackup (scene); - } - } - } - - void doRegionBackup (IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) { - //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); - return; - } - - AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface (); - string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) { - m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - iram.ArchiveRegion (savePath, null); - ExecuteScript (state.GetScript (), savePath); - } - - string IRegionModuleBase.Name { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface { - get { return null; } - } - - #endregion - #region ISharedRegionModule implementation - void ISharedRegionModule.PostInitialise () - { - //I don't care right now. - } - - #endregion - - //Is this even needed? - public bool IsSharedModule { - get { return true; } - } - - private string BuildOarPath (string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) { - case NamingType.OVERWRITE: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); - return path.FullName; - case NamingType.TIME: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); - return path.FullName; - case NamingType.SEQUENTIAL: - path = new FileInfo (GetNextFile (baseDir, regionName)); - return path.FullName; - default: - m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); - break; - } - - return path.FullName; - } - - //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. - //(Terrible reference to ) - //This format may turn out to be too unwieldy to keep... - //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString () - { - StringWriter sw = new StringWriter (); - sw.Write ("_"); - DateTime now = DateTime.Now; - sw.Write (now.Year); - sw.Write ("y_"); - sw.Write (now.Month); - sw.Write ("M_"); - sw.Write (now.Day); - sw.Write ("d_"); - sw.Write (now.Hour); - sw.Write ("h_"); - sw.Write (now.Minute); - sw.Write ("m_"); - sw.Write (now.Second); - sw.Write ("s"); - sw.Flush (); - string output = sw.ToString (); - sw.Close (); - return output; - } - - //Get the next logical file name - //I really shouldn't put fields here, but for now.... ;) - private string m_dirName = null; - private string m_regionName = null; - private string GetNextFile (string dirName, string regionName) - { - FileInfo uniqueFile = null; - m_dirName = dirName; - m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if (uniqueFile.Exists) { - //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! - //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear (biggestExistingFile); - } - - return uniqueFile.FullName; - } - - /* - * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - * */ - private bool RunHeuristics (IScene region) - { - try { - return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); - } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /* - * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunTimeDilationHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /* - * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunAgentLimitHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - try { - Scene scene = (Scene)region; - //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); - } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); - return true; - //Non-obstructionist safest answer... - } - } - - private void ExecuteScript (string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - return; - - try { - FileInfo fi = new FileInfo (scriptName); - if (fi.Exists) { - ProcessStartInfo psi = new ProcessStartInfo (scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start (psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } catch (Exception e) { - m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) - { - m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers () - { - foreach (Timer t in timerMap.Keys) { - t.Close (); - } - m_closed = true; - } - - /* Find the largest value for which the predicate returns true. - * We use a bisection algorithm (half interval) to make the algorithm scalable. - * The worst-case complexity is about O(log(n)^2) in practice. - * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. - * The number of predicate invocations only hits a few hundred when the maximized value - * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. - * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. - * The Predicate parameter must be a function that accepts a long and returns a bool. - * */ - public long HalfIntervalMaximize (long start, Predicate pred) - { - long prev = start, curr = start, biggest = 0; - - if (start < 0) - throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - - do { - if (pred (curr)) { - if (curr > biggest) { - biggest = curr; - } - prev = curr; - if (curr == 0) { - //Special case because 0 * 2 = 0 :) - curr = 1; - } else { - //Look deeper - curr *= 2; - } - } else { - // We went too far, back off halfway - curr = (curr + prev) / 2; - } - } while (curr - prev > 0); - - return biggest; - } - - public bool FileExistsTest (long num) - { - FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); - return test.Exists; - } - - - //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear (long start) - { - long l = start; - FileInfo retval = null; - do { - retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } while (retval.Exists); - - return retval; - } - } - -} - +#pragma warning disable 1587 +/// +/// Copyright (c) Contributors, http://opensimulator.org/ +/// 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 OpenSimulator 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +/// +/// Config Settings Documentation. +/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: +/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. +/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . +/// Example: My region is named Foo. +/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. +/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. +/// Region-specific settings take precedence. +/// +/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. +/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! +/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. +/// This is the only required option for enabling auto-backup; the other options have sane defaults. +/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. +/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. +/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). +/// The number of minutes between each backup attempt. +/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. +/// AutoBackupBusyCheck: True/False. Default: True. +/// If True, we will only take an auto-backup if a set of conditions are met. +/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. +/// AutoBackupScript: String. Default: not specified (disabled). +/// File path to an executable script or binary to run when an automatic backup is taken. +/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. +/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! +/// argv[1] of the executed file/script will be the file name of the generated OAR. +/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. +/// AutoBackupNaming: string. Default: Time. +/// One of three strings (case insensitive): +/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. +/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. +/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. +/// AutoBackupDir: String. Default: "." (the current directory). +/// A directory (absolute or relative) where backups should be saved. +/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. +/// If the time dilation is below this value, don't take a backup right now. +/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. +/// If the number of agents is greater than this value, don't take a backup right now. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_states = + new Dictionary(1); + + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed; + + private IConfigSource m_configSource; + + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + void IRegionModuleBase.Initialise(IConfigSource source) + { + /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + /// We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + void IRegionModuleBase.AddRegion(Scene scene) + { + /// NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + /// Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + /// Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + /// This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + void ISharedRegionModule.PostInitialise() + { + /// I don't care right now. + } + + #endregion + + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + /// Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + /// defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + /// If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + /// 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + /// Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + /// Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + /// Let's give the user *one* convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + /// check whether the region is too busy! Especially on sims with LOTS of regions. + /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + /// but would allow us to be semantically correct while being easier on perf. + /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + /// Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + /// We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, Guid.NewGuid(), null); + ExecuteScript(state.Script, savePath); + } + + /// This format may turn out to be too unwieldy to keep... + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Sequential numbers, right? Ugh. Almost makes TOO much sense. + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + /// + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + /// Non-obstructionist safest answer... + } + } + + private static void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + /// All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + /// Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs new file mode 100644 index 0000000..1b348af --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -0,0 +1,109 @@ +#pragma warning disable 1587 +/// +/// Copyright (c) Contributors, http://opensimulator.org/ +/// 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 OpenSimulator 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. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + public AutoBackupModuleState() + { + this.Enabled = false; + this.BackupDir = "."; + this.BusyCheck = true; + this.Timer = null; + this.NamingType = NamingType.Time; + this.Script = null; + } + + public bool Enabled + { + get; + set; + } + + public System.Timers.Timer Timer + { + get; + set; + } + + public double IntervalMinutes + { + get + { + if (this.Timer == null) + { + return -1.0; + } + else + { + return this.Timer.Interval / 60000.0; + } + } + } + + public bool BusyCheck + { + get; + set; + } + + public string Script + { + get; + set; + } + + public string BackupDir + { + get; + set; + } + + public NamingType NamingType + { + get; + set; + } + + public new string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n"; + retval += "[AUTO BACKUP]: Script: " + Script + "\n"; + return retval; + } + } +} + -- cgit v1.1 From 440d54a52158057a93d8bd08c5e46290e69d8f3f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:32:08 -0400 Subject: AutoBackup: Removed unneeded imports. --- OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index a4dbea4..7c0a5c6 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -29,7 +29,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Reflection; using System.Timers; -- cgit v1.1 From c82c7e6ed9ecf4858d5afffa635013bd14e90bdf Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 26 Apr 2011 11:42:06 -0400 Subject: Wait for OnOarFileSaved event callback before executing script We want to execute the (optional) user script after I/O is done on the oar. I wasn't aware that ArchiveRegion is asynchronous -- now I am. Should fully resolve comment 0018290 at http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 23 ++++++++++++++-------- .../World/AutoBackup/AutoBackupModuleState.cs | 14 +++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7c0a5c6..bd4893c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -94,18 +94,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); private readonly Dictionary m_states = new Dictionary(1); - private readonly Dictionary> m_timerMap = new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); private bool m_enabled; @@ -528,8 +524,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, Guid.NewGuid(), null); - ExecuteScript(state.Script, savePath); + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + void EventManager_OnOarFileSaved(Guid guid, string message) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } /// This format may turn out to be too unwieldy to keep... diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 1b348af..7fecfa4 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -26,11 +26,17 @@ /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// +using System; +using System.Collections.Generic; + + namespace OpenSim.Region.OptionalModules.World.AutoBackup { /// AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { + private Dictionary m_liveRequests = null; + public AutoBackupModuleState() { this.Enabled = false; @@ -41,6 +47,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.Script = null; } + public Dictionary LiveRequests + { + get { + return this.m_liveRequests ?? + (this.m_liveRequests = new Dictionary(1)); + } + } + public bool Enabled { get; -- cgit v1.1 From 2aab033aaa41f2ebef96e725b76e0153a673b448 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 02:20:50 -0400 Subject: First pass at fixing justincc's feedback v2 ( http://opensimulator.org/mantis/view.php?id=5440 ) Fixing everything here (I think) except the per-region config. That's next. --- .../World/AutoBackup/AutoBackupModule.cs | 1577 +++++++++++--------- .../World/AutoBackup/AutoBackupModuleState.cs | 59 +- 2 files changed, 868 insertions(+), 768 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index bd4893c..4a9615d 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,751 +1,848 @@ -#pragma warning disable 1587 -/// -/// Copyright (c) Contributors, http://opensimulator.org/ -/// 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 OpenSimulator 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Timers; -using System.Text.RegularExpressions; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -/// -/// Config Settings Documentation. -/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: -/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. -/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . -/// Example: My region is named Foo. -/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. -/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. -/// Region-specific settings take precedence. -/// -/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. -/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! -/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. -/// This is the only required option for enabling auto-backup; the other options have sane defaults. -/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. -/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. -/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). -/// The number of minutes between each backup attempt. -/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. -/// AutoBackupBusyCheck: True/False. Default: True. -/// If True, we will only take an auto-backup if a set of conditions are met. -/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. -/// AutoBackupScript: String. Default: not specified (disabled). -/// File path to an executable script or binary to run when an automatic backup is taken. -/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. -/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! -/// argv[1] of the executed file/script will be the file name of the generated OAR. -/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. -/// AutoBackupNaming: string. Default: Time. -/// One of three strings (case insensitive): -/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. -/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. -/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. -/// AutoBackupDir: String. Default: "." (the current directory). -/// A directory (absolute or relative) where backups should be saved. -/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. -/// If the time dilation is below this value, don't take a backup right now. -/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. -/// If the number of agents is greater than this value, don't take a backup right now. -/// - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - public enum NamingType - { - Time, - Sequential, - Overwrite - } - - public class AutoBackupModule : ISharedRegionModule - { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all - private readonly Dictionary m_pendingSaves = new Dictionary(1); - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - private readonly Dictionary m_states = - new Dictionary(1); - private readonly Dictionary> m_timerMap = - new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); - - private bool m_enabled; - +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * 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 OpenSimulator 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// + /// Choose between ways of naming the backup files that are generated. + /// + /// Time: OARs are named by a timestamp. + /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.) + /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made. + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + /// + /// AutoBackupModule: save OAR region backups to disk periodically + /// + /// + /// Config Settings Documentation. + /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: + /// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + /// IMPORTANT: You may optionally specify the key name as follows for a per-region key: [Region Name].[Key Name] + /// Example: My region is named Foo. + /// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + /// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + /// Region-specific settings take precedence. + /// + /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. + /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! + /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + /// This is the only required option for enabling auto-backup; the other options have sane defaults. + /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + /// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. + /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + /// The number of minutes between each backup attempt. + /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + /// AutoBackupBusyCheck: True/False. Default: True. + /// If True, we will only take an auto-backup if a set of conditions are met. + /// These conditions are heuristics to try and avoid taking a backup when the sim is busy. + /// AutoBackupScript: String. Default: not specified (disabled). + /// File path to an executable script or binary to run when an automatic backup is taken. + /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + /// argv[1] of the executed file/script will be the file name of the generated OAR. + /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + /// AutoBackupNaming: string. Default: Time. + /// One of three strings (case insensitive): + /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + /// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + /// AutoBackupDir: String. Default: "." (the current directory). + /// A directory (absolute or relative) where backups should be saved. + /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + /// If the time dilation is below this value, don't take a backup right now. + /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + /// If the number of agents is greater than this value, don't take a backup right now + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all. + /// + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + private readonly Dictionary m_states = + new Dictionary(1); + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed; - - private IConfigSource m_configSource; - - public bool IsSharedModule + /// + private bool m_closed; + + private IConfigSource m_configSource; + + /// + /// Required by framework. + /// + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + /// + /// Identifies the module to the system. + /// + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + /// + /// We don't implement an interface, this is a single-use module. + /// + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + /// + /// Called once in the lifetime of the module at startup. + /// + /// The input config source for OpenSim.ini. + void IRegionModuleBase.Initialise(IConfigSource source) + { + // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + /// + /// Called once at de-init (sim shutting down). + /// + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + // We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// + /// + void IRegionModuleBase.AddRegion(Scene scene) + { + } + + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// + /// The scene (region) to stop performing AutoBackup on. + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + // Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + // Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// + /// The scene to (possibly) perform AutoBackup on. + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + // This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + /// + /// Currently a no-op. + /// + void ISharedRegionModule.PostInitialise() + { + } + + #endregion + + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + // Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + // defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + // If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + // 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + // Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + // Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + // Let's give the user some convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// + /// + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region + // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + // check whether the region is too busy! Especially on sims with LOTS of regions. + // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter. + // Since this is pretty experimental, I haven't decided which alternative makes the most sense. + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + // Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// + /// + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + // We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// + /// + void EventManager_OnOarFileSaved(Guid guid, string message) { - get { return true; } - } - - #region ISharedRegionModule Members - - string IRegionModuleBase.Name - { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface - { - get { return null; } - } - - void IRegionModuleBase.Initialise(IConfigSource source) - { - /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - this.m_configSource = source; - IConfig moduleConfig = source.Configs["AutoBackupModule"]; - if (moduleConfig == null) - { - this.m_enabled = false; - return; - } - else - { - this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); - if (this.m_enabled) - { - m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else - { - return; - } - } - - Timer defTimer = new Timer(43200000); - this.m_defaultState.Timer = defTimer; - this.m_timers.Add(43200000, defTimer); - defTimer.Elapsed += this.HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start(); - - AutoBackupModuleState abms = this.ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Here is the default config:"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close() - { - if (!this.m_enabled) - { - return; - } - - /// We don't want any timers firing while the sim's coming down; strange things may happen. - this.StopAllTimers(); - } - - void IRegionModuleBase.AddRegion(Scene scene) - { - /// NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - if (this.m_states.ContainsKey(scene)) - { - AutoBackupModuleState abms = this.m_states[scene]; - - /// Remove this scene out of the timer map list - Timer timer = abms.Timer; - List list = this.m_timerMap[timer]; - list.Remove(scene); - - /// Shut down the timer if this was the last scene for the timer - if (list.Count == 0) - { - this.m_timerMap.Remove(timer); - this.m_timers.Remove(timer.Interval); - timer.Close(); - } - this.m_states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - /// This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - { - return; - } - - AutoBackupModuleState abms = this.ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - void ISharedRegionModule.PostInitialise() - { - /// I don't care right now. - } - - #endregion - - private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if (parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = this.m_defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - /// Read the config settings and set variables. - IConfig config = this.m_configSource.Configs["AutoBackupModule"]; - if (config == null) - { - /// defaultState would be disabled too if the section doesn't exist. - state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); - if (state == null && tmpEnabled != this.m_defaultState.Enabled) - //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Enabled = tmpEnabled; - } - - /// If you don't want AutoBackup, we stop. - if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - else - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; - if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) - { - state = new AutoBackupModuleState(); - } - - if (this.m_timers.ContainsKey(interval)) - { - if (state != null) - { - state.Timer = this.m_timers[interval]; - } - m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + - sRegionLabel); - } - else - { - /// 0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) - { - state.Enabled = false; - return state; - } - if (state == null) - { - state = new AutoBackupModuleState(); - } - Timer tim = new Timer(interval); - state.Timer = tim; - //Milliseconds -> minutes - this.m_timers.Add(interval, tim); - tim.Elapsed += this.HandleElapsed; - tim.AutoReset = true; - tim.Start(); - } - - /// Add the current region to the list of regions tied to this timer. - if (scene != null) - { - if (state != null) - { - if (this.m_timerMap.ContainsKey(state.Timer)) - { - this.m_timerMap[state.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(state.Timer, scns); - } - } - else - { - if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) - { - this.m_timerMap[this.m_defaultState.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(this.m_defaultState.Timer, scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); - if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BusyCheck = tmpBusyCheck; - } - - /// Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Time; - } - else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Sequential; - } - else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Overwrite; - } - else + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) { - m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + - stmpNamingType); - tmpNamingType = NamingType.Time; + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } - - if (state == null && tmpNamingType != this.m_defaultState.NamingType) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.NamingType = tmpNamingType; - } - - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); - if (state == null && tmpScript != this.m_defaultState.Script) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Script = tmpScript; - } - - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); - if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BackupDir = tmpBackupDir; - /// Let's give the user *one* convenience and auto-mkdir - if (state.BackupDir != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); - if (!dirinfo.Exists) - { - dirinfo.Create(); - } - } - catch (Exception e) - { - m_log.Warn( - "BAD NEWS. You won't be able to save backups to directory " + - state.BackupDir + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", - e); - } - } - } - - return state; - } - - private void HandleElapsed(object sender, ElapsedEventArgs e) - { - /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - /// check whether the region is too busy! Especially on sims with LOTS of regions. - /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - /// but would allow us to be semantically correct while being easier on perf. - /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (this.m_closed) - { - return; - } - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!this.m_timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); - } - - List tmap = this.m_timerMap[(Timer) sender]; - if (tmap != null && tmap.Count > 0) - { - foreach (IScene scene in tmap) - { - AutoBackupModuleState state = this.m_states[scene]; - bool heuristics = state.BusyCheck; - - /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) - { - this.DoRegionBackup(scene); - /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } - else if (heuristicsRun) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - /// Logical Deduction: heuristics are on but haven't been run - } - else - { - heuristicsPassed = this.RunHeuristics(scene); - heuristicsRun = true; - if (!heuristicsPassed) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - } - this.DoRegionBackup(scene); - } - } - } - } - - private void DoRegionBackup(IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) - { - /// We won't backup a region that isn't operating normally. - m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + - " because its status is " + scene.RegionStatus); - return; - } - - AutoBackupModuleState state = this.m_states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, - state.BackupDir, - state.NamingType); - /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) - { - m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - Guid guid = Guid.NewGuid(); - m_pendingSaves.Add(guid, scene); - state.LiveRequests.Add(guid, savePath); - ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); - iram.ArchiveRegion(savePath, guid, null); - } - - void EventManager_OnOarFileSaved(Guid guid, string message) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } - - /// This format may turn out to be too unwieldy to keep... + } + + /// This format may turn out to be too unwieldy to keep... /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - /// Sequential numbers, right? Ugh. Almost makes TOO much sense. - private static string GetTimeString() - { - StringWriter sw = new StringWriter(); - sw.Write("_"); - DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); - return output; - } - - /// - /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - /// - private bool RunHeuristics(IScene region) - { - try - { - return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); - } - catch (Exception e) - { - m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /// + /// Sequential numbers, right? We support those, too! + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunTimeDilationHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= - this.m_configSource.Configs["AutoBackupModule"].GetFloat( - regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /// + /// + /// + /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunAgentLimitHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - try - { - Scene scene = (Scene) region; - /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount() <= - this.m_configSource.Configs["AutoBackupModule"].GetInt( - regionName + ".AutoBackupAgentThreshold", 10); - } - catch (InvalidCastException ice) - { - m_log.Debug( - "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", - ice); - return true; - /// Non-obstructionist safest answer... - } - } - - private static void ExecuteScript(string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - { - return; - } - - try - { - FileInfo fi = new FileInfo(scriptName); - if (fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start(psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } - catch (Exception e) - { - m_log.Warn( - "Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) - { - m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + - " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers() - { - foreach (Timer t in this.m_timerMap.Keys) - { - t.Close(); - } - this.m_closed = true; - } - - private static string GetNextFile(string dirName, string regionName) - { - FileInfo uniqueFile = null; - long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = - new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + - biggestExistingFile + ".oar"); - return uniqueFile.FullName; - } - - private static string BuildOarPath(string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) - { - case NamingType.Overwrite: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); - return path.FullName; - case NamingType.Time: - path = - new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + - GetTimeString() + ".oar"); - return path.FullName; - case NamingType.Sequential: - /// All codepaths in GetNextFile should return a file name ending in .oar - path = new FileInfo(GetNextFile(baseDir, regionName)); - return path.FullName; - default: - m_log.Warn("VERY BAD: Unhandled case element " + naming); - break; - } - - return null; - } - - private static long GetNextOarFileNumber(string dirName, string regionName) - { - long retval = 1; - - DirectoryInfo di = new DirectoryInfo(dirName); - FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); - Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); - - if (fi.LongLength > 0) - { - long subtract = 1L; - bool worked = false; - Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); - - while (!worked && subtract <= fi.LongLength) - { - /// Pick the file with the last natural ordering - string biggestFileName = fi[fi.LongLength - subtract].Name; - MatchCollection matches = reg.Matches(biggestFileName); - long l = 1; - if (matches.Count > 0 && matches[0].Groups.Count > 0) - { - try - { - long.TryParse(matches[0].Groups[1].Value, out l); - retval = l; - worked = true; - } - catch (FormatException fe) - { - m_log.Warn( - "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", - fe); - subtract++; - } - } - else - { - subtract++; - } - } - } - return retval; - } - } -} - - + /// + /// + /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + // Non-obstructionist safest answer... + } + } + + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// + /// + private static void ExecuteScript(string scriptName, string savePath) + { + // Do nothing if there's no script. + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + /// + /// Called if a running script process writes to stderr. + /// + /// + /// + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + /// + /// Quickly stop all timers from firing. + /// + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// + /// + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. + /// + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + // All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// + /// + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + // Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 7fecfa4..2db718c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -1,30 +1,29 @@ -#pragma warning disable 1587 -/// -/// Copyright (c) Contributors, http://opensimulator.org/ -/// 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 OpenSimulator 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. -/// +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * 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 OpenSimulator 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.Collections.Generic; @@ -32,7 +31,11 @@ using System.Collections.Generic; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// If you use this class in any way outside of AutoBackupModule, you should treat the class as opaque. + /// Since it is not part of the framework, you really should not rely upon it outside of the AutoBackupModule implementation. + /// + /// public class AutoBackupModuleState { private Dictionary m_liveRequests = null; -- cgit v1.1 From 0995fedcaca9a921488929ee40f68c71fbba7a70 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 04:32:31 -0400 Subject: AutoBackupModule: Implement per-region settings in Regions.ini. --- .../World/AutoBackup/AutoBackupModule.cs | 291 +++++++++++++-------- 1 file changed, 186 insertions(+), 105 deletions(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4a9615d..ce9a448 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// /// AutoBackupModule: save OAR region backups to disk periodically - /// + /// /// /// Config Settings Documentation. /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: @@ -96,7 +96,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. /// If the number of agents is greater than this value, don't take a backup right now /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all. + /// Also helps if you don't want AutoBackup at all. /// public class AutoBackupModule : ISharedRegionModule { @@ -110,17 +110,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup new Dictionary>(1); private readonly Dictionary m_timers = new Dictionary(1); + private delegate T DefaultGetter(string settingName, T defaultValue); private bool m_enabled; - /// - /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + /// + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! /// private bool m_closed; private IConfigSource m_configSource; - /// - /// Required by framework. + /// + /// Required by framework. /// public bool IsSharedModule { @@ -129,25 +130,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region ISharedRegionModule Members - /// - /// Identifies the module to the system. + /// + /// Identifies the module to the system. /// string IRegionModuleBase.Name { get { return "AutoBackupModule"; } } - /// - /// We don't implement an interface, this is a single-use module. + /// + /// We don't implement an interface, this is a single-use module. /// Type IRegionModuleBase.ReplaceableInterface { get { return null; } } - /// - /// Called once in the lifetime of the module at startup. - /// + /// + /// Called once in the lifetime of the module at startup. + /// /// The input config source for OpenSim.ini. void IRegionModuleBase.Initialise(IConfigSource source) { @@ -184,8 +185,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug(abms.ToString()); } - /// - /// Called once at de-init (sim shutting down). + /// + /// Called once at de-init (sim shutting down). /// void IRegionModuleBase.Close() { @@ -198,17 +199,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.StopAllTimers(); } - /// - /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. - /// + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// /// void IRegionModuleBase.AddRegion(Scene scene) { } - /// - /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. - /// + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// /// The scene (region) to stop performing AutoBackup on. void IRegionModuleBase.RemoveRegion(Scene scene) { @@ -237,10 +238,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Most interesting/complex code paths in AutoBackup begin here. - /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. - /// + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// /// The scene to (possibly) perform AutoBackup on. void IRegionModuleBase.RegionLoaded(Scene scene) { @@ -260,8 +261,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } - /// - /// Currently a no-op. + /// + /// Currently a no-op. /// void ISharedRegionModule.PostInitialise() { @@ -269,12 +270,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #endregion - /// - /// Set up internal state for a given scene. Fairly complex code. - /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. - /// - /// The scene to look at. - /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) { @@ -299,16 +300,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Read the config settings and set variables. + IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null); IConfig config = this.m_configSource.Configs["AutoBackupModule"]; if (config == null) { // defaultState would be disabled too if the section doesn't exist. state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); return state; } - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig); if (state == null && tmpEnabled != this.m_defaultState.Enabled) //Varies from default state { @@ -332,8 +333,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; + this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes, + config, regionConfig) * 60000.0; if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) { state = new AutoBackupModuleState(); @@ -400,8 +401,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); + bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck", + this.m_defaultState.BusyCheck, config, regionConfig); if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) { state = new AutoBackupModuleState(); @@ -413,8 +414,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); + string stmpNamingType = ResolveString("AutoBackupNaming", + this.m_defaultState.NamingType.ToString(), config, regionConfig); NamingType tmpNamingType; if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { @@ -445,8 +446,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.NamingType = tmpNamingType; } - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); + string tmpScript = ResolveString("AutoBackupScript", + this.m_defaultState.Script, config, regionConfig); if (state == null && tmpScript != this.m_defaultState.Script) { state = new AutoBackupModuleState(); @@ -457,7 +458,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.Script = tmpScript; } - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig); if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) { state = new AutoBackupModuleState(); @@ -491,10 +492,90 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return state; } - /// - /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. - /// - /// + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local) + { + if(local != null) + { + return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue)); + } + else + { + return global.GetBoolean(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue)); + } + else + { + return global.GetDouble(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetInt(settingName, global.GetInt(settingName, defaultValue)); + } + else + { + return global.GetInt(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetString(settingName, global.GetString(settingName, defaultValue)); + } + else + { + return global.GetString(settingName, defaultValue); + } + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// /// private void HandleElapsed(object sender, ElapsedEventArgs e) { @@ -554,9 +635,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). - /// + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// /// private void DoRegionBackup(IScene scene) { @@ -585,25 +666,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup iram.ArchiveRegion(savePath, guid, null); } - /// - /// Called by the Event Manager when the OnOarFileSaved event is fired. - /// - /// + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// /// void EventManager_OnOarFileSaved(Guid guid, string message) - { - // Ignore if the OAR save is being done by some other part of the system - if (m_pendingSaves.ContainsKey(guid)) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } + { + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); + } } /// This format may turn out to be too unwieldy to keep... - /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? /// Sequential numbers, right? We support those, too! private static string GetTimeString() { @@ -642,12 +723,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// - /// + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. private bool RunTimeDilationHeuristic(IScene region) { @@ -657,12 +738,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup regionName + ".AutoBackupDilationThreshold", 0.5f); } - /// - /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// - /// + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. private bool RunAgentLimitHeuristic(IScene region) { @@ -685,12 +766,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Run the script or executable specified by the "AutoBackupScript" config setting. - /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. - /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. - /// - /// + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// /// private static void ExecuteScript(string scriptName, string savePath) { @@ -719,10 +800,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Called if a running script process writes to stderr. - /// - /// + /// + /// Called if a running script process writes to stderr. + /// + /// /// private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) { @@ -730,8 +811,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup " is yacking on stderr: " + e.Data); } - /// - /// Quickly stop all timers from firing. + /// + /// Quickly stop all timers from firing. /// private void StopAllTimers() { @@ -742,11 +823,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.m_closed = true; } - /// - /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. - /// - /// - /// + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// /// private static string GetNextFile(string dirName, string regionName) { @@ -760,12 +841,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - /// - /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. - /// - /// Name of the region to save. - /// Absolute or relative path to the directory where the file should reside. - /// The naming scheme for the file name. + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. /// private static string BuildOarPath(string regionName, string baseDir, NamingType naming) { @@ -792,11 +873,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return null; } - /// - /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). - /// - /// - /// + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// /// private static long GetNextOarFileNumber(string dirName, string regionName) { -- cgit v1.1 From 8755a48cde6ee77f421bef07e8b95cf8b68a76ed Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Fri, 6 May 2011 00:34:04 +0100 Subject: fix command display for debugging 'emergency-monitoring' --- OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index db17d8f..bdebbfb 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -107,7 +107,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden scene.AddCommand( this, "emergency-monitoring", - "Go on/off emergency monitoring mode", + "emergency-monitoring", "Go on/off emergency monitoring mode", "Go on/off emergency monitoring mode", EmergencyMonitoring); -- cgit v1.1 From fd44540c023e7df35308a40df9e61d7f9273eba4 Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Fri, 6 May 2011 01:06:28 +0100 Subject: add descriptive explanations for region restart functionality --- OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index bdebbfb..62e6fae 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -111,7 +111,6 @@ namespace OpenSim.Region.CoreModules.UDP.Linden "Go on/off emergency monitoring mode", "Go on/off emergency monitoring mode", EmergencyMonitoring); - } public void RemoveRegion(Scene scene) -- cgit v1.1 From 3d095e84d63e97e88bcb946498eba14de81705b4 Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Fri, 6 May 2011 22:45:03 +0100 Subject: minor: remove mono compiler warnings --- OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 62e6fae..8f8124e 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -189,7 +189,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden int maxNameLength = 18; int maxRegionNameLength = 14; int maxTypeLength = 4; - int totalInfoFieldsLength = maxNameLength + columnPadding + maxRegionNameLength + columnPadding + maxTypeLength + columnPadding; +// int totalInfoFieldsLength = maxNameLength + columnPadding + maxRegionNameLength + columnPadding + maxTypeLength + columnPadding; report.Append(GetColumnEntry("User", maxNameLength, columnPadding)); report.Append(GetColumnEntry("Region", maxRegionNameLength, columnPadding)); -- cgit v1.1 From d4fcba08af080bcc60da490155cc88d3f20e7dda Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Sat, 7 May 2011 01:06:55 +0100 Subject: Add module with "appearance show" command. At the moment, this command just asks the AvatarFactory to perform the existing baked texture check for each avatar in the simulator and returns "OK" or "corrupt". This is for debugging purposes --- .../Avatar/Appearance/AppearanceInfoModule.cs | 122 +++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs (limited to 'OpenSim/Region/OptionalModules') diff --git a/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs new file mode 100644 index 0000000..8589901 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs @@ -0,0 +1,122 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * 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 OpenSimulator 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.Collections.Generic; +using System.Reflection; +using System.Text; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Statistics; +using OpenSim.Region.ClientStack.LindenUDP; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.Avatar.Appearance +{ + /// + /// A module that just holds commands for inspecting avatar appearance. + /// + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AppearanceInfoModule")] + public class AppearanceInfoModule : ISharedRegionModule + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Dictionary m_scenes = new Dictionary(); + protected IAvatarFactory m_avatarFactory; + + public string Name { get { return "Appearance Information Module"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: INITIALIZED MODULE"); + } + + public void PostInitialise() + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: POST INITIALIZED MODULE"); + } + + public void Close() + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: CLOSED MODULE"); + } + + public void AddRegion(Scene scene) + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName); + } + + public void RemoveRegion(Scene scene) + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + + lock (m_scenes) + m_scenes.Remove(scene.RegionInfo.RegionID); + } + + public void RegionLoaded(Scene scene) + { +// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); + + lock (m_scenes) + m_scenes[scene.RegionInfo.RegionID] = scene; + + scene.AddCommand( + this, "appearance show", + "appearance show", + "Show appearance information for each avatar in the simulator. At the moment, ", + ShowAppearanceInfo); + } + + protected void ShowAppearanceInfo(string module, string[] cmd) + { + lock (m_scenes) + { + foreach (Scene scene in m_scenes.Values) + { + scene.ForEachClient( + delegate(IClientAPI client) + { + if (client is LLClientView && !((LLClientView)client).ChildAgentStatus()) + { + bool bakedTextureValid = scene.AvatarFactory.ValidateBakedTextureCache(client); + MainConsole.Instance.OutputFormat( + "{0} baked apperance texture is {1}", client.Name, bakedTextureValid ? "OK" : "corrupt"); + } + }); + } + } + } + } +} \ No newline at end of file -- cgit v1.1