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(-) 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