diff options
Diffstat (limited to 'OpenSim/Region/OptionalModules')
-rw-r--r-- | OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1509 | ||||
-rw-r--r-- | OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs | 109 |
2 files changed, 854 insertions, 764 deletions
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 @@ | |||
1 | /* | 1 | #pragma warning disable 1587 |
2 | * Copyright (c) Contributors, http://opensimulator.org/ | 2 | /// |
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | 3 | /// Copyright (c) Contributors, http://opensimulator.org/ |
4 | * | 4 | /// See CONTRIBUTORS.TXT for a full list of copyright holders. |
5 | * Redistribution and use in source and binary forms, with or without | 5 | /// |
6 | * modification, are permitted provided that the following conditions are met: | 6 | /// Redistribution and use in source and binary forms, with or without |
7 | * * Redistributions of source code must retain the above copyright | 7 | /// modification, are permitted provided that the following conditions are met: |
8 | * notice, this list of conditions and the following disclaimer. | 8 | /// * Redistributions of source code must retain the above copyright |
9 | * * Redistributions in binary form must reproduce the above copyright | 9 | /// notice, this list of conditions and the following disclaimer. |
10 | * notice, this list of conditions and the following disclaimer in the | 10 | /// * Redistributions in binary form must reproduce the above copyright |
11 | * documentation and/or other materials provided with the distribution. | 11 | /// notice, this list of conditions and the following disclaimer in the |
12 | * * Neither the name of the OpenSimulator Project nor the | 12 | /// documentation and/or other materials provided with the distribution. |
13 | * names of its contributors may be used to endorse or promote products | 13 | /// * Neither the name of the OpenSimulator Project nor the |
14 | * derived from this software without specific prior written permission. | 14 | /// names of its contributors may be used to endorse or promote products |
15 | * | 15 | /// derived from this software without specific prior written permission. |
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | 16 | /// |
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 17 | /// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 18 | /// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | 19 | /// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 20 | /// DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 21 | /// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 22 | /// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | /// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | /// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | /// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
26 | */ | 26 | /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | 27 | /// | |
28 | using System; | 28 | |
29 | using System.IO; | 29 | using System; |
30 | using System.Timers; | 30 | using System.Collections.Generic; |
31 | using System.Diagnostics; | 31 | using System.Diagnostics; |
32 | using System.Reflection; | 32 | using System.Globalization; |
33 | using System.Collections.Generic; | 33 | using System.IO; |
34 | using log4net; | 34 | using System.Reflection; |
35 | using Nini; | 35 | using System.Timers; |
36 | using Nini.Config; | 36 | using System.Text.RegularExpressions; |
37 | using OpenSim.Framework; | 37 | using log4net; |
38 | using OpenSim.Framework.Statistics; | 38 | using Nini.Config; |
39 | using OpenSim.Region.Framework.Interfaces; | 39 | using OpenSim.Framework; |
40 | using OpenSim.Region.Framework.Scenes; | 40 | using OpenSim.Region.Framework.Interfaces; |
41 | 41 | using OpenSim.Region.Framework.Scenes; | |
42 | 42 | ||
43 | /* | 43 | /// |
44 | * Config Settings Documentation. | 44 | /// Config Settings Documentation. |
45 | * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: | 45 | /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: |
46 | * In the [Modules] section: | 46 | /// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. |
47 | * 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! | 47 | /// IMPORTANT: You may optionally specify the key name as follows for a per-region key: <Region Name>.<Key Name> |
48 | * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. | 48 | /// Example: My region is named Foo. |
49 | * VERY IMPORTANT: You must create the key name as follows: <Region Name>.<Key Name> | 49 | /// 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. |
50 | * Example: My region is named Foo. | 50 | /// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. |
51 | * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. | 51 | /// Region-specific settings take precedence. |
52 | * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. | 52 | /// |
53 | * Region-specific settings take precedence. | 53 | /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. |
54 | * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. | 54 | /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! |
55 | * This is the only required option for enabling auto-backup; the other options have sane defaults. | 55 | /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. |
56 | * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. | 56 | /// This is the only required option for enabling auto-backup; the other options have sane defaults. |
57 | * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). | 57 | /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. |
58 | * The number of minutes between each backup attempt. | 58 | /// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. |
59 | * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. | 59 | /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). |
60 | * AutoBackupBusyCheck: True/False. Default: True. | 60 | /// The number of minutes between each backup attempt. |
61 | * If True, we will only take an auto-backup if a set of conditions are met. | 61 | /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. |
62 | * These conditions are heuristics to try and avoid taking a backup when the sim is busy. | 62 | /// AutoBackupBusyCheck: True/False. Default: True. |
63 | * AutoBackupScript: String. Default: not specified (disabled). | 63 | /// If True, we will only take an auto-backup if a set of conditions are met. |
64 | * File path to an executable script or binary to run when an automatic backup is taken. | 64 | /// These conditions are heuristics to try and avoid taking a backup when the sim is busy. |
65 | * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. | 65 | /// AutoBackupScript: String. Default: not specified (disabled). |
66 | * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! | 66 | /// File path to an executable script or binary to run when an automatic backup is taken. |
67 | * argv[1] of the executed file/script will be the file name of the generated OAR. | 67 | /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. |
68 | * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. | 68 | /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! |
69 | * AutoBackupNaming: string. Default: Time. | 69 | /// argv[1] of the executed file/script will be the file name of the generated OAR. |
70 | * One of three strings (case insensitive): | 70 | /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. |
71 | * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. | 71 | /// AutoBackupNaming: string. Default: Time. |
72 | * "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. | 72 | /// One of three strings (case insensitive): |
73 | * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. | 73 | /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. |
74 | * AutoBackupDir: String. Default: "." (the current directory). | 74 | /// "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. |
75 | * A directory (absolute or relative) where backups should be saved. | 75 | /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. |
76 | * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. | 76 | /// AutoBackupDir: String. Default: "." (the current directory). |
77 | * If the time dilation is below this value, don't take a backup right now. | 77 | /// A directory (absolute or relative) where backups should be saved. |
78 | * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. | 78 | /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. |
79 | * If the number of agents is greater than this value, don't take a backup right now. | 79 | /// If the time dilation is below this value, don't take a backup right now. |
80 | * */ | 80 | /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. |
81 | 81 | /// If the number of agents is greater than this value, don't take a backup right now. | |
82 | namespace OpenSim.Region.OptionalModules.World.AutoBackup | 82 | /// |
83 | { | 83 | |
84 | 84 | namespace OpenSim.Region.OptionalModules.World.AutoBackup | |
85 | public enum NamingType | 85 | { |
86 | { | 86 | public enum NamingType |
87 | TIME, | 87 | { |
88 | SEQUENTIAL, | 88 | Time, |
89 | OVERWRITE | 89 | Sequential, |
90 | } | 90 | Overwrite |
91 | 91 | } | |
92 | public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase | 92 | |
93 | { | 93 | public class AutoBackupModule : ISharedRegionModule |
94 | 94 | { | |
95 | private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); | 95 | private static readonly ILog m_log = |
96 | 96 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | |
97 | //AutoBackupModuleState: Auto-Backup state for one region (scene). | 97 | |
98 | public class AutoBackupModuleState | 98 | /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. |
99 | { | 99 | /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. |
100 | private bool m_enabled = false; | 100 | private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); |
101 | private NamingType m_naming = NamingType.TIME; | 101 | |
102 | private Timer m_timer = null; | 102 | /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. |
103 | private bool m_busycheck = true; | 103 | /// Also helps if you don't want AutoBackup at all |
104 | private string m_script = null; | 104 | private readonly Dictionary<IScene, AutoBackupModuleState> m_states = |
105 | private string m_dir = "."; | 105 | new Dictionary<IScene, AutoBackupModuleState>(1); |
106 | 106 | ||
107 | public AutoBackupModuleState () | 107 | private readonly Dictionary<Timer, List<IScene>> m_timerMap = |
108 | { | 108 | new Dictionary<Timer, List<IScene>>(1); |
109 | 109 | ||
110 | } | 110 | private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1); |
111 | 111 | ||
112 | public void SetEnabled (bool b) | 112 | private bool m_enabled; |
113 | { | 113 | |
114 | m_enabled = b; | 114 | /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! |
115 | } | 115 | private bool m_closed; |
116 | 116 | ||
117 | public bool GetEnabled () | 117 | private IConfigSource m_configSource; |
118 | { | 118 | |
119 | return m_enabled; | 119 | public bool IsSharedModule |
120 | } | 120 | { |
121 | 121 | get { return true; } | |
122 | public Timer GetTimer () | 122 | } |
123 | { | 123 | |
124 | return m_timer; | 124 | #region ISharedRegionModule Members |
125 | } | 125 | |
126 | 126 | string IRegionModuleBase.Name | |
127 | public double GetIntervalMinutes () | 127 | { |
128 | { | 128 | get { return "AutoBackupModule"; } |
129 | if(m_timer == null) | 129 | } |
130 | { | 130 | |
131 | return -1.0; | 131 | Type IRegionModuleBase.ReplaceableInterface |
132 | } | 132 | { |
133 | else | 133 | get { return null; } |
134 | { | 134 | } |
135 | return m_timer.Interval / 60000.0; | 135 | |
136 | } | 136 | void IRegionModuleBase.Initialise(IConfigSource source) |
137 | } | 137 | { |
138 | 138 | /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module | |
139 | public void SetTimer (Timer t) | 139 | this.m_configSource = source; |
140 | { | 140 | IConfig moduleConfig = source.Configs["AutoBackupModule"]; |
141 | m_timer = t; | 141 | if (moduleConfig == null) |
142 | } | 142 | { |
143 | 143 | this.m_enabled = false; | |
144 | public bool GetBusyCheck () | 144 | return; |
145 | { | 145 | } |
146 | return m_busycheck; | 146 | else |
147 | } | 147 | { |
148 | 148 | this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); | |
149 | public void SetBusyCheck (bool b) | 149 | if (this.m_enabled) |
150 | { | 150 | { |
151 | m_busycheck = b; | 151 | m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); |
152 | } | 152 | } |
153 | 153 | else | |
154 | 154 | { | |
155 | public string GetScript () | 155 | return; |
156 | { | 156 | } |
157 | return m_script; | 157 | } |
158 | } | 158 | |
159 | 159 | Timer defTimer = new Timer(43200000); | |
160 | public void SetScript (string s) | 160 | this.m_defaultState.Timer = defTimer; |
161 | { | 161 | this.m_timers.Add(43200000, defTimer); |
162 | m_script = s; | 162 | defTimer.Elapsed += this.HandleElapsed; |
163 | } | 163 | defTimer.AutoReset = true; |
164 | 164 | defTimer.Start(); | |
165 | public string GetBackupDir () | 165 | |
166 | { | 166 | AutoBackupModuleState abms = this.ParseConfig(null, true); |
167 | return m_dir; | 167 | m_log.Debug("[AUTO BACKUP]: Here is the default config:"); |
168 | } | 168 | m_log.Debug(abms.ToString()); |
169 | 169 | } | |
170 | public void SetBackupDir (string s) | 170 | |
171 | { | 171 | void IRegionModuleBase.Close() |
172 | m_dir = s; | 172 | { |
173 | } | 173 | if (!this.m_enabled) |
174 | 174 | { | |
175 | public NamingType GetNamingType () | 175 | return; |
176 | { | 176 | } |
177 | return m_naming; | 177 | |
178 | } | 178 | /// We don't want any timers firing while the sim's coming down; strange things may happen. |
179 | 179 | this.StopAllTimers(); | |
180 | public void SetNamingType (NamingType n) | 180 | } |
181 | { | 181 | |
182 | m_naming = n; | 182 | void IRegionModuleBase.AddRegion(Scene scene) |
183 | } | 183 | { |
184 | 184 | /// NO-OP. Wait for the region to be loaded. | |
185 | public string ToString() | 185 | } |
186 | { | 186 | |
187 | string retval = ""; | 187 | void IRegionModuleBase.RemoveRegion(Scene scene) |
188 | 188 | { | |
189 | retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; | 189 | if (!this.m_enabled) |
190 | retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; | 190 | { |
191 | retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; | 191 | return; |
192 | retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; | 192 | } |
193 | retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; | 193 | |
194 | retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; | 194 | if (this.m_states.ContainsKey(scene)) |
195 | return retval; | 195 | { |
196 | } | 196 | AutoBackupModuleState abms = this.m_states[scene]; |
197 | } | 197 | |
198 | 198 | /// Remove this scene out of the timer map list | |
199 | //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. | 199 | Timer timer = abms.Timer; |
200 | //Also helps if you don't want AutoBackup at all | 200 | List<IScene> list = this.m_timerMap[timer]; |
201 | readonly Dictionary<IScene, AutoBackupModuleState> states = new Dictionary<IScene, AutoBackupModuleState> (4); | 201 | list.Remove(scene); |
202 | readonly Dictionary<double, Timer> timers = new Dictionary<double, Timer> (1); | 202 | |
203 | readonly Dictionary<Timer, List<IScene>> timerMap = new Dictionary<Timer, List<IScene>> (1); | 203 | /// Shut down the timer if this was the last scene for the timer |
204 | private IConfigSource m_configSource = null; | 204 | if (list.Count == 0) |
205 | private bool m_Enabled = false; | 205 | { |
206 | //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! | 206 | this.m_timerMap.Remove(timer); |
207 | private bool m_closed = false; | 207 | this.m_timers.Remove(timer.Interval); |
208 | //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. | 208 | timer.Close(); |
209 | //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. | 209 | } |
210 | readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); | 210 | this.m_states.Remove(scene); |
211 | 211 | } | |
212 | public AutoBackupModule () | 212 | } |
213 | { | 213 | |
214 | 214 | void IRegionModuleBase.RegionLoaded(Scene scene) | |
215 | } | 215 | { |
216 | 216 | if (!this.m_enabled) | |
217 | #region IRegionModuleBase implementation | 217 | { |
218 | void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) | 218 | return; |
219 | { | 219 | } |
220 | //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module | 220 | |
221 | m_configSource = source; | 221 | /// This really ought not to happen, but just in case, let's pretend it didn't... |
222 | IConfig moduleConfig = source.Configs["Modules"]; | 222 | if (scene == null) |
223 | if (moduleConfig != null) { | 223 | { |
224 | m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); | 224 | return; |
225 | if (m_Enabled) { | 225 | } |
226 | m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); | 226 | |
227 | } | 227 | AutoBackupModuleState abms = this.ParseConfig(scene, false); |
228 | else { | 228 | m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); |
229 | m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); | 229 | m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); |
230 | return; | 230 | } |
231 | } | 231 | |
232 | } | 232 | void ISharedRegionModule.PostInitialise() |
233 | 233 | { | |
234 | Timer defTimer = new Timer(720 * 60000); | 234 | /// I don't care right now. |
235 | defaultState.SetTimer(defTimer); | 235 | } |
236 | timers.Add (720*60000, defTimer); | 236 | |
237 | defTimer.Elapsed += HandleElapsed; | 237 | #endregion |
238 | defTimer.AutoReset = true; | 238 | |
239 | defTimer.Start (); | 239 | private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) |
240 | 240 | { | |
241 | AutoBackupModuleState abms = ParseConfig(null, true); | 241 | string sRegionName; |
242 | m_log.Debug("[AUTO BACKUP]: Config for default"); | 242 | string sRegionLabel; |
243 | m_log.Debug(abms.ToString()); | 243 | string prepend; |
244 | } | 244 | AutoBackupModuleState state; |
245 | 245 | ||
246 | void IRegionModuleBase.Close () | 246 | if (parseDefault) |
247 | { | 247 | { |
248 | if (!m_Enabled) | 248 | sRegionName = null; |
249 | return; | 249 | sRegionLabel = "DEFAULT"; |
250 | 250 | prepend = ""; | |
251 | //We don't want any timers firing while the sim's coming down; strange things may happen. | 251 | state = this.m_defaultState; |
252 | StopAllTimers (); | 252 | } |
253 | } | 253 | else |
254 | 254 | { | |
255 | void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) | 255 | sRegionName = scene.RegionInfo.RegionName; |
256 | { | 256 | sRegionLabel = sRegionName; |
257 | //NO-OP. Wait for the region to be loaded. | 257 | prepend = sRegionName + "."; |
258 | } | 258 | state = null; |
259 | 259 | } | |
260 | void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) | 260 | |
261 | { | 261 | /// Read the config settings and set variables. |
262 | if (!m_Enabled) | 262 | IConfig config = this.m_configSource.Configs["AutoBackupModule"]; |
263 | return; | 263 | if (config == null) |
264 | 264 | { | |
265 | if(states.ContainsKey(scene)) | 265 | /// defaultState would be disabled too if the section doesn't exist. |
266 | { | 266 | state = this.m_defaultState; |
267 | AutoBackupModuleState abms = states[scene]; | 267 | m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); |
268 | 268 | return state; | |
269 | //Remove this scene out of the timer map list | 269 | } |
270 | Timer timer = abms.GetTimer (); | 270 | |
271 | List<IScene> list = timerMap[timer]; | 271 | bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); |
272 | list.Remove (scene); | 272 | if (state == null && tmpEnabled != this.m_defaultState.Enabled) |
273 | 273 | //Varies from default state | |
274 | //Shut down the timer if this was the last scene for the timer | 274 | { |
275 | if (list.Count == 0) { | 275 | state = new AutoBackupModuleState(); |
276 | timerMap.Remove (timer); | 276 | } |
277 | timers.Remove (timer.Interval); | 277 | |
278 | timer.Close (); | 278 | if (state != null) |
279 | } | 279 | { |
280 | states.Remove(scene); | 280 | state.Enabled = tmpEnabled; |
281 | } | 281 | } |
282 | } | 282 | |
283 | 283 | /// If you don't want AutoBackup, we stop. | |
284 | void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) | 284 | if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) |
285 | { | 285 | { |
286 | if (!m_Enabled) | 286 | m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); |
287 | return; | 287 | return state; |
288 | 288 | } | |
289 | //This really ought not to happen, but just in case, let's pretend it didn't... | 289 | else |
290 | if (scene == null) | 290 | { |
291 | return; | 291 | m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); |
292 | 292 | } | |
293 | AutoBackupModuleState abms = ParseConfig(scene, false); | 293 | |
294 | m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); | 294 | /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. |
295 | m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); | 295 | double interval = |
296 | } | 296 | config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* |
297 | 297 | 60000.0; | |
298 | AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) | 298 | if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) |
299 | { | 299 | { |
300 | string sRegionName; | 300 | state = new AutoBackupModuleState(); |
301 | string sRegionLabel; | 301 | } |
302 | string prepend; | 302 | |
303 | AutoBackupModuleState state; | 303 | if (this.m_timers.ContainsKey(interval)) |
304 | 304 | { | |
305 | if(parseDefault) | 305 | if (state != null) |
306 | { | 306 | { |
307 | sRegionName = null; | 307 | state.Timer = this.m_timers[interval]; |
308 | sRegionLabel = "DEFAULT"; | 308 | } |
309 | prepend = ""; | 309 | m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + |
310 | state = defaultState; | 310 | sRegionLabel); |
311 | } | 311 | } |
312 | else | 312 | else |
313 | { | 313 | { |
314 | sRegionName = scene.RegionInfo.RegionName; | 314 | /// 0 or negative interval == do nothing. |
315 | sRegionLabel = sRegionName; | 315 | if (interval <= 0.0 && state != null) |
316 | prepend = sRegionName + "."; | 316 | { |
317 | state = null; | 317 | state.Enabled = false; |
318 | } | 318 | return state; |
319 | 319 | } | |
320 | //Read the config settings and set variables. | 320 | if (state == null) |
321 | IConfig config = m_configSource.Configs["AutoBackupModule"]; | 321 | { |
322 | if (config == null) { | 322 | state = new AutoBackupModuleState(); |
323 | state = defaultState; //defaultState would be disabled too if the section doesn't exist. | 323 | } |
324 | m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); | 324 | Timer tim = new Timer(interval); |
325 | return state; | 325 | state.Timer = tim; |
326 | } | 326 | //Milliseconds -> minutes |
327 | 327 | this.m_timers.Add(interval, tim); | |
328 | bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); | 328 | tim.Elapsed += this.HandleElapsed; |
329 | if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state | 329 | tim.AutoReset = true; |
330 | { | 330 | tim.Start(); |
331 | state = new AutoBackupModuleState(); | 331 | } |
332 | } | 332 | |
333 | 333 | /// Add the current region to the list of regions tied to this timer. | |
334 | if(state != null) | 334 | if (scene != null) |
335 | { | 335 | { |
336 | state.SetEnabled (tmpEnabled); | 336 | if (state != null) |
337 | } | 337 | { |
338 | 338 | if (this.m_timerMap.ContainsKey(state.Timer)) | |
339 | //If you don't want AutoBackup, we stop. | 339 | { |
340 | if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { | 340 | this.m_timerMap[state.Timer].Add(scene); |
341 | m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); | 341 | } |
342 | return state; | 342 | else |
343 | } else { | 343 | { |
344 | m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); | 344 | List<IScene> scns = new List<IScene>(1); |
345 | } | 345 | scns.Add(scene); |
346 | 346 | this.m_timerMap.Add(state.Timer, scns); | |
347 | //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. | 347 | } |
348 | double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; | 348 | } |
349 | if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) | 349 | else |
350 | { | 350 | { |
351 | state = new AutoBackupModuleState(); | 351 | if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) |
352 | } | 352 | { |
353 | 353 | this.m_timerMap[this.m_defaultState.Timer].Add(scene); | |
354 | if (timers.ContainsKey (interval)) { | 354 | } |
355 | if(state != null) | 355 | else |
356 | state.SetTimer (timers[interval]); | 356 | { |
357 | m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); | 357 | List<IScene> scns = new List<IScene>(1); |
358 | } else { | 358 | scns.Add(scene); |
359 | //0 or negative interval == do nothing. | 359 | this.m_timerMap.Add(this.m_defaultState.Timer, scns); |
360 | if (interval <= 0.0 && state != null) { | 360 | } |
361 | state.SetEnabled (false); | 361 | } |
362 | return state; | 362 | } |
363 | } | 363 | |
364 | if(state == null) | 364 | bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", |
365 | state = new AutoBackupModuleState(); | 365 | this.m_defaultState.BusyCheck); |
366 | Timer tim = new Timer (interval); | 366 | if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) |
367 | state.SetTimer (tim); | 367 | { |
368 | //Milliseconds -> minutes | 368 | state = new AutoBackupModuleState(); |
369 | timers.Add (interval, tim); | 369 | } |
370 | tim.Elapsed += HandleElapsed; | 370 | |
371 | tim.AutoReset = true; | 371 | if (state != null) |
372 | tim.Start (); | 372 | { |
373 | //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); | 373 | state.BusyCheck = tmpBusyCheck; |
374 | } | 374 | } |
375 | 375 | ||
376 | //Add the current region to the list of regions tied to this timer. | 376 | /// Set file naming algorithm |
377 | if(scene != null) | 377 | string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", |
378 | { | 378 | this.m_defaultState.NamingType.ToString()); |
379 | if(state != null) | 379 | NamingType tmpNamingType; |
380 | { | 380 | if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) |
381 | if (timerMap.ContainsKey (state.GetTimer ())) { | 381 | { |
382 | timerMap[state.GetTimer ()].Add (scene); | 382 | tmpNamingType = NamingType.Time; |
383 | } else { | 383 | } |
384 | List<IScene> scns = new List<IScene> (1); | 384 | else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) |
385 | scns.Add (scene); | 385 | { |
386 | timerMap.Add (state.GetTimer (), scns); | 386 | tmpNamingType = NamingType.Sequential; |
387 | } | 387 | } |
388 | } | 388 | else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) |
389 | else | 389 | { |
390 | { | 390 | tmpNamingType = NamingType.Overwrite; |
391 | if(timerMap.ContainsKey(defaultState.GetTimer())) { | 391 | } |
392 | timerMap[defaultState.GetTimer()].Add(scene); | 392 | else |
393 | } else { | 393 | { |
394 | List<IScene> scns = new List<IScene> (1); | 394 | m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + |
395 | scns.Add(scene); | 395 | stmpNamingType); |
396 | timerMap.Add(defaultState.GetTimer(), scns); | 396 | tmpNamingType = NamingType.Time; |
397 | } | 397 | } |
398 | } | 398 | |
399 | } | 399 | if (state == null && tmpNamingType != this.m_defaultState.NamingType) |
400 | 400 | { | |
401 | bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); | 401 | state = new AutoBackupModuleState(); |
402 | if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) | 402 | } |
403 | { | 403 | |
404 | state = new AutoBackupModuleState(); | 404 | if (state != null) |
405 | } | 405 | { |
406 | 406 | state.NamingType = tmpNamingType; | |
407 | if(state != null) | 407 | } |
408 | { | 408 | |
409 | state.SetBusyCheck (tmpBusyCheck); | 409 | string tmpScript = config.GetString(prepend + "AutoBackupScript", |
410 | } | 410 | this.m_defaultState.Script); |
411 | 411 | if (state == null && tmpScript != this.m_defaultState.Script) | |
412 | //Set file naming algorithm | 412 | { |
413 | string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); | 413 | state = new AutoBackupModuleState(); |
414 | NamingType tmpNamingType; | 414 | } |
415 | if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { | 415 | |
416 | tmpNamingType = NamingType.TIME; | 416 | if (state != null) |
417 | } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { | 417 | { |
418 | tmpNamingType = NamingType.SEQUENTIAL; | 418 | state.Script = tmpScript; |
419 | } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { | 419 | } |
420 | tmpNamingType = NamingType.OVERWRITE; | 420 | |
421 | } else { | 421 | string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); |
422 | m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); | 422 | if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) |
423 | tmpNamingType = NamingType.TIME; | 423 | { |
424 | } | 424 | state = new AutoBackupModuleState(); |
425 | 425 | } | |
426 | if(state == null && tmpNamingType != defaultState.GetNamingType()) | 426 | |
427 | { | 427 | if (state != null) |
428 | state = new AutoBackupModuleState(); | 428 | { |
429 | } | 429 | state.BackupDir = tmpBackupDir; |
430 | 430 | /// Let's give the user *one* convenience and auto-mkdir | |
431 | if(state != null) | 431 | if (state.BackupDir != ".") |
432 | { | 432 | { |
433 | state.SetNamingType(tmpNamingType); | 433 | try |
434 | } | 434 | { |
435 | 435 | DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); | |
436 | string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); | 436 | if (!dirinfo.Exists) |
437 | if(state == null && tmpScript != defaultState.GetScript()) | 437 | { |
438 | { | 438 | dirinfo.Create(); |
439 | state = new AutoBackupModuleState(); | 439 | } |
440 | } | 440 | } |
441 | 441 | catch (Exception e) | |
442 | if(state != null) | 442 | { |
443 | { | 443 | m_log.Warn( |
444 | state.SetScript (tmpScript); | 444 | "BAD NEWS. You won't be able to save backups to directory " + |
445 | } | 445 | state.BackupDir + |
446 | 446 | " because it doesn't exist or there's a permissions issue with it. Here's the exception.", | |
447 | string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); | 447 | e); |
448 | if(state == null && tmpBackupDir != defaultState.GetBackupDir()) | 448 | } |
449 | { | 449 | } |
450 | state = new AutoBackupModuleState(); | 450 | } |
451 | } | 451 | |
452 | 452 | return state; | |
453 | if(state != null) | 453 | } |
454 | { | 454 | |
455 | state.SetBackupDir (tmpBackupDir); | 455 | private void HandleElapsed(object sender, ElapsedEventArgs e) |
456 | //Let's give the user *one* convenience and auto-mkdir | 456 | { |
457 | if (state.GetBackupDir () != ".") { | 457 | /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region |
458 | try { | 458 | /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to |
459 | DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); | 459 | /// check whether the region is too busy! Especially on sims with LOTS of regions. |
460 | if (!dirinfo.Exists) { | 460 | /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, |
461 | dirinfo.Create (); | 461 | /// but would allow us to be semantically correct while being easier on perf. |
462 | } | 462 | /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... |
463 | } catch (Exception e) { | 463 | if (this.m_closed) |
464 | 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); | 464 | { |
465 | } | 465 | return; |
466 | } | 466 | } |
467 | } | 467 | bool heuristicsRun = false; |
468 | 468 | bool heuristicsPassed = false; | |
469 | return state; | 469 | if (!this.m_timerMap.ContainsKey((Timer) sender)) |
470 | } | 470 | { |
471 | 471 | m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); | |
472 | void HandleElapsed (object sender, ElapsedEventArgs e) | 472 | } |
473 | { | 473 | |
474 | //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region | 474 | List<IScene> tmap = this.m_timerMap[(Timer) sender]; |
475 | //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to | 475 | if (tmap != null && tmap.Count > 0) |
476 | //check whether the region is too busy! Especially on sims with LOTS of regions. | 476 | { |
477 | //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, | 477 | foreach (IScene scene in tmap) |
478 | // but would allow us to be semantically correct while being easier on perf. | 478 | { |
479 | //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... | 479 | AutoBackupModuleState state = this.m_states[scene]; |
480 | if (m_closed) | 480 | bool heuristics = state.BusyCheck; |
481 | return; | 481 | |
482 | bool heuristicsRun = false; | 482 | /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. |
483 | bool heuristicsPassed = false; | 483 | if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) |
484 | if (!timerMap.ContainsKey ((Timer)sender)) { | 484 | { |
485 | m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); | 485 | this.DoRegionBackup(scene); |
486 | } | 486 | /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! |
487 | 487 | } | |
488 | List<IScene> tmap = timerMap[(Timer)sender]; | 488 | else if (heuristicsRun) |
489 | if(tmap != null && tmap.Count > 0) | 489 | { |
490 | foreach (IScene scene in tmap) { | 490 | m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + |
491 | AutoBackupModuleState state = states[scene]; | 491 | scene.RegionInfo.RegionName + " right now."); |
492 | bool heuristics = state.GetBusyCheck (); | 492 | continue; |
493 | 493 | /// Logical Deduction: heuristics are on but haven't been run | |
494 | //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. | 494 | } |
495 | if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { | 495 | else |
496 | doRegionBackup (scene); | 496 | { |
497 | //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! | 497 | heuristicsPassed = this.RunHeuristics(scene); |
498 | } else if (heuristics && heuristicsRun && !heuristicsPassed) { | 498 | heuristicsRun = true; |
499 | m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); | 499 | if (!heuristicsPassed) |
500 | continue; | 500 | { |
501 | //Logical Deduction: heuristics are on but haven't been run | 501 | m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + |
502 | } else { | 502 | scene.RegionInfo.RegionName + " right now."); |
503 | heuristicsPassed = RunHeuristics (scene); | 503 | continue; |
504 | heuristicsRun = true; | 504 | } |
505 | if (!heuristicsPassed) { | 505 | this.DoRegionBackup(scene); |
506 | m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); | 506 | } |
507 | continue; | 507 | } |
508 | } | 508 | } |
509 | doRegionBackup (scene); | 509 | } |
510 | } | 510 | |
511 | } | 511 | private void DoRegionBackup(IScene scene) |
512 | } | 512 | { |
513 | 513 | if (scene.RegionStatus != RegionStatus.Up) | |
514 | void doRegionBackup (IScene scene) | 514 | { |
515 | { | 515 | /// We won't backup a region that isn't operating normally. |
516 | if (scene.RegionStatus != RegionStatus.Up) { | 516 | m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + |
517 | //We won't backup a region that isn't operating normally. | 517 | " because its status is " + scene.RegionStatus); |
518 | m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); | 518 | return; |
519 | return; | 519 | } |
520 | } | 520 | |
521 | 521 | AutoBackupModuleState state = this.m_states[scene]; | |
522 | AutoBackupModuleState state = states[scene]; | 522 | IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>(); |
523 | IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule> (); | 523 | string savePath = BuildOarPath(scene.RegionInfo.RegionName, |
524 | string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); | 524 | state.BackupDir, |
525 | //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); | 525 | state.NamingType); |
526 | if (savePath == null) { | 526 | /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); |
527 | m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); | 527 | if (savePath == null) |
528 | return; | 528 | { |
529 | } | 529 | m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); |
530 | iram.ArchiveRegion (savePath, null); | 530 | return; |
531 | ExecuteScript (state.GetScript (), savePath); | 531 | } |
532 | } | 532 | iram.ArchiveRegion(savePath, Guid.NewGuid(), null); |
533 | 533 | ExecuteScript(state.Script, savePath); | |
534 | string IRegionModuleBase.Name { | 534 | } |
535 | get { return "AutoBackupModule"; } | 535 | |
536 | } | 536 | /// This format may turn out to be too unwieldy to keep... |
537 | 537 | /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? | |
538 | Type IRegionModuleBase.ReplaceableInterface { | 538 | /// Sequential numbers, right? Ugh. Almost makes TOO much sense. |
539 | get { return null; } | 539 | private static string GetTimeString() |
540 | } | 540 | { |
541 | 541 | StringWriter sw = new StringWriter(); | |
542 | #endregion | 542 | sw.Write("_"); |
543 | #region ISharedRegionModule implementation | 543 | DateTime now = DateTime.Now; |
544 | void ISharedRegionModule.PostInitialise () | 544 | sw.Write(now.Year); |
545 | { | 545 | sw.Write("y_"); |
546 | //I don't care right now. | 546 | sw.Write(now.Month); |
547 | } | 547 | sw.Write("M_"); |
548 | 548 | sw.Write(now.Day); | |
549 | #endregion | 549 | sw.Write("d_"); |
550 | 550 | sw.Write(now.Hour); | |
551 | //Is this even needed? | 551 | sw.Write("h_"); |
552 | public bool IsSharedModule { | 552 | sw.Write(now.Minute); |
553 | get { return true; } | 553 | sw.Write("m_"); |
554 | } | 554 | sw.Write(now.Second); |
555 | 555 | sw.Write("s"); | |
556 | private string BuildOarPath (string regionName, string baseDir, NamingType naming) | 556 | sw.Flush(); |
557 | { | 557 | string output = sw.ToString(); |
558 | FileInfo path = null; | 558 | sw.Close(); |
559 | switch (naming) { | 559 | return output; |
560 | case NamingType.OVERWRITE: | 560 | } |
561 | path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); | 561 | |
562 | return path.FullName; | 562 | /// |
563 | case NamingType.TIME: | 563 | /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. |
564 | path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); | 564 | /// |
565 | return path.FullName; | 565 | private bool RunHeuristics(IScene region) |
566 | case NamingType.SEQUENTIAL: | 566 | { |
567 | path = new FileInfo (GetNextFile (baseDir, regionName)); | 567 | try |
568 | return path.FullName; | 568 | { |
569 | default: | 569 | return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); |
570 | m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); | 570 | } |
571 | break; | 571 | catch (Exception e) |
572 | } | 572 | { |
573 | 573 | m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); | |
574 | return path.FullName; | 574 | return false; |
575 | } | 575 | } |
576 | 576 | } | |
577 | //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. | 577 | |
578 | //(Terrible reference to <timecube.com>) | 578 | /// |
579 | //This format may turn out to be too unwieldy to keep... | 579 | /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), |
580 | //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? | 580 | /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). |
581 | //Sequential numbers, right? Ugh. Almost makes TOO much sense. | 581 | /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". |
582 | private string GetTimeString () | 582 | /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! |
583 | { | 583 | /// |
584 | StringWriter sw = new StringWriter (); | 584 | private bool RunTimeDilationHeuristic(IScene region) |
585 | sw.Write ("_"); | 585 | { |
586 | DateTime now = DateTime.Now; | 586 | string regionName = region.RegionInfo.RegionName; |
587 | sw.Write (now.Year); | 587 | return region.TimeDilation >= |
588 | sw.Write ("y_"); | 588 | this.m_configSource.Configs["AutoBackupModule"].GetFloat( |
589 | sw.Write (now.Month); | 589 | regionName + ".AutoBackupDilationThreshold", 0.5f); |
590 | sw.Write ("M_"); | 590 | } |
591 | sw.Write (now.Day); | 591 | |
592 | sw.Write ("d_"); | 592 | /// |
593 | sw.Write (now.Hour); | 593 | /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), |
594 | sw.Write ("h_"); | 594 | /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). |
595 | sw.Write (now.Minute); | 595 | /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". |
596 | sw.Write ("m_"); | 596 | /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! |
597 | sw.Write (now.Second); | 597 | /// |
598 | sw.Write ("s"); | 598 | private bool RunAgentLimitHeuristic(IScene region) |
599 | sw.Flush (); | 599 | { |
600 | string output = sw.ToString (); | 600 | string regionName = region.RegionInfo.RegionName; |
601 | sw.Close (); | 601 | try |
602 | return output; | 602 | { |
603 | } | 603 | Scene scene = (Scene) region; |
604 | 604 | /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... | |
605 | //Get the next logical file name | 605 | return scene.GetRootAgentCount() <= |
606 | //I really shouldn't put fields here, but for now.... ;) | 606 | this.m_configSource.Configs["AutoBackupModule"].GetInt( |
607 | private string m_dirName = null; | 607 | regionName + ".AutoBackupAgentThreshold", 10); |
608 | private string m_regionName = null; | 608 | } |
609 | private string GetNextFile (string dirName, string regionName) | 609 | catch (InvalidCastException ice) |
610 | { | 610 | { |
611 | FileInfo uniqueFile = null; | 611 | m_log.Debug( |
612 | m_dirName = dirName; | 612 | "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", |
613 | m_regionName = regionName; | 613 | ice); |
614 | long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); | 614 | return true; |
615 | biggestExistingFile++; | 615 | /// Non-obstructionist safest answer... |
616 | //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. | 616 | } |
617 | uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); | 617 | } |
618 | if (uniqueFile.Exists) { | 618 | |
619 | //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! | 619 | private static void ExecuteScript(string scriptName, string savePath) |
620 | //Now you get to pay the performance cost :) | 620 | { |
621 | uniqueFile = UniqueFileSearchLinear (biggestExistingFile); | 621 | //Fast path out |
622 | } | 622 | if (scriptName == null || scriptName.Length <= 0) |
623 | 623 | { | |
624 | return uniqueFile.FullName; | 624 | return; |
625 | } | 625 | } |
626 | 626 | ||
627 | /* | 627 | try |
628 | * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. | 628 | { |
629 | * */ | 629 | FileInfo fi = new FileInfo(scriptName); |
630 | private bool RunHeuristics (IScene region) | 630 | if (fi.Exists) |
631 | { | 631 | { |
632 | try { | 632 | ProcessStartInfo psi = new ProcessStartInfo(scriptName); |
633 | return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); | 633 | psi.Arguments = savePath; |
634 | } catch (Exception e) { | 634 | psi.CreateNoWindow = true; |
635 | m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); | 635 | Process proc = Process.Start(psi); |
636 | return false; | 636 | proc.ErrorDataReceived += HandleProcErrorDataReceived; |
637 | } | 637 | } |
638 | } | 638 | } |
639 | 639 | catch (Exception e) | |
640 | /* | 640 | { |
641 | * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), | 641 | m_log.Warn( |
642 | * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). | 642 | "Exception encountered when trying to run script for oar backup " + savePath, e); |
643 | * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". | 643 | } |
644 | * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! | 644 | } |
645 | * */ | 645 | |
646 | private bool RunTimeDilationHeuristic (IScene region) | 646 | private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) |
647 | { | 647 | { |
648 | string regionName = region.RegionInfo.RegionName; | 648 | m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + |
649 | return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); | 649 | " is yacking on stderr: " + e.Data); |
650 | } | 650 | } |
651 | 651 | ||
652 | /* | 652 | private void StopAllTimers() |
653 | * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), | 653 | { |
654 | * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). | 654 | foreach (Timer t in this.m_timerMap.Keys) |
655 | * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". | 655 | { |
656 | * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! | 656 | t.Close(); |
657 | * */ | 657 | } |
658 | private bool RunAgentLimitHeuristic (IScene region) | 658 | this.m_closed = true; |
659 | { | 659 | } |
660 | string regionName = region.RegionInfo.RegionName; | 660 | |
661 | try { | 661 | private static string GetNextFile(string dirName, string regionName) |
662 | Scene scene = (Scene)region; | 662 | { |
663 | //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... | 663 | FileInfo uniqueFile = null; |
664 | return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); | 664 | long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); |
665 | } catch (InvalidCastException ice) { | 665 | biggestExistingFile++; |
666 | m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); | 666 | //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. |
667 | return true; | 667 | uniqueFile = |
668 | //Non-obstructionist safest answer... | 668 | new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + |
669 | } | 669 | biggestExistingFile + ".oar"); |
670 | } | 670 | return uniqueFile.FullName; |
671 | 671 | } | |
672 | private void ExecuteScript (string scriptName, string savePath) | 672 | |
673 | { | 673 | private static string BuildOarPath(string regionName, string baseDir, NamingType naming) |
674 | //Fast path out | 674 | { |
675 | if (scriptName == null || scriptName.Length <= 0) | 675 | FileInfo path = null; |
676 | return; | 676 | switch (naming) |
677 | 677 | { | |
678 | try { | 678 | case NamingType.Overwrite: |
679 | FileInfo fi = new FileInfo (scriptName); | 679 | path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); |
680 | if (fi.Exists) { | 680 | return path.FullName; |
681 | ProcessStartInfo psi = new ProcessStartInfo (scriptName); | 681 | case NamingType.Time: |
682 | psi.Arguments = savePath; | 682 | path = |
683 | psi.CreateNoWindow = true; | 683 | new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + |
684 | Process proc = Process.Start (psi); | 684 | GetTimeString() + ".oar"); |
685 | proc.ErrorDataReceived += HandleProcErrorDataReceived; | 685 | return path.FullName; |
686 | } | 686 | case NamingType.Sequential: |
687 | } catch (Exception e) { | 687 | /// All codepaths in GetNextFile should return a file name ending in .oar |
688 | m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); | 688 | path = new FileInfo(GetNextFile(baseDir, regionName)); |
689 | } | 689 | return path.FullName; |
690 | } | 690 | default: |
691 | 691 | m_log.Warn("VERY BAD: Unhandled case element " + naming); | |
692 | void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) | 692 | break; |
693 | { | 693 | } |
694 | m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); | 694 | |
695 | } | 695 | return null; |
696 | 696 | } | |
697 | private void StopAllTimers () | 697 | |
698 | { | 698 | private static long GetNextOarFileNumber(string dirName, string regionName) |
699 | foreach (Timer t in timerMap.Keys) { | 699 | { |
700 | t.Close (); | 700 | long retval = 1; |
701 | } | 701 | |
702 | m_closed = true; | 702 | DirectoryInfo di = new DirectoryInfo(dirName); |
703 | } | 703 | FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); |
704 | 704 | Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); | |
705 | /* Find the largest value for which the predicate returns true. | 705 | |
706 | * We use a bisection algorithm (half interval) to make the algorithm scalable. | 706 | if (fi.LongLength > 0) |
707 | * The worst-case complexity is about O(log(n)^2) in practice. | 707 | { |
708 | * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. | 708 | long subtract = 1L; |
709 | * The number of predicate invocations only hits a few hundred when the maximized value | 709 | bool worked = false; |
710 | * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. | 710 | Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); |
711 | * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. | 711 | |
712 | * The Predicate<long> parameter must be a function that accepts a long and returns a bool. | 712 | while (!worked && subtract <= fi.LongLength) |
713 | * */ | 713 | { |
714 | public long HalfIntervalMaximize (long start, Predicate<long> pred) | 714 | /// Pick the file with the last natural ordering |
715 | { | 715 | string biggestFileName = fi[fi.LongLength - subtract].Name; |
716 | long prev = start, curr = start, biggest = 0; | 716 | MatchCollection matches = reg.Matches(biggestFileName); |
717 | 717 | long l = 1; | |
718 | if (start < 0) | 718 | if (matches.Count > 0 && matches[0].Groups.Count > 0) |
719 | throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); | 719 | { |
720 | 720 | try | |
721 | do { | 721 | { |
722 | if (pred (curr)) { | 722 | long.TryParse(matches[0].Groups[1].Value, out l); |
723 | if (curr > biggest) { | 723 | retval = l; |
724 | biggest = curr; | 724 | worked = true; |
725 | } | 725 | } |
726 | prev = curr; | 726 | catch (FormatException fe) |
727 | if (curr == 0) { | 727 | { |
728 | //Special case because 0 * 2 = 0 :) | 728 | m_log.Warn( |
729 | curr = 1; | 729 | "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", |
730 | } else { | 730 | fe); |
731 | //Look deeper | 731 | subtract++; |
732 | curr *= 2; | 732 | } |
733 | } | 733 | } |
734 | } else { | 734 | else |
735 | // We went too far, back off halfway | 735 | { |
736 | curr = (curr + prev) / 2; | 736 | subtract++; |
737 | } | 737 | } |
738 | } while (curr - prev > 0); | 738 | } |
739 | 739 | } | |
740 | return biggest; | 740 | return retval; |
741 | } | 741 | } |
742 | 742 | } | |
743 | public bool FileExistsTest (long num) | 743 | } |
744 | { | 744 | |
745 | FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); | 745 | |
746 | return test.Exists; | ||
747 | } | ||
748 | |||
749 | |||
750 | //Very slow, hence why we try the HalfIntervalMaximize first! | ||
751 | public FileInfo UniqueFileSearchLinear (long start) | ||
752 | { | ||
753 | long l = start; | ||
754 | FileInfo retval = null; | ||
755 | do { | ||
756 | retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); | ||
757 | } while (retval.Exists); | ||
758 | |||
759 | return retval; | ||
760 | } | ||
761 | } | ||
762 | |||
763 | } | ||
764 | |||
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 @@ | |||
1 | #pragma warning disable 1587 | ||
2 | /// | ||
3 | /// Copyright (c) Contributors, http://opensimulator.org/ | ||
4 | /// See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
5 | /// | ||
6 | /// Redistribution and use in source and binary forms, with or without | ||
7 | /// modification, are permitted provided that the following conditions are met: | ||
8 | /// * Redistributions of source code must retain the above copyright | ||
9 | /// notice, this list of conditions and the following disclaimer. | ||
10 | /// * Redistributions in binary form must reproduce the above copyright | ||
11 | /// notice, this list of conditions and the following disclaimer in the | ||
12 | /// documentation and/or other materials provided with the distribution. | ||
13 | /// * Neither the name of the OpenSimulator Project nor the | ||
14 | /// names of its contributors may be used to endorse or promote products | ||
15 | /// derived from this software without specific prior written permission. | ||
16 | /// | ||
17 | /// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
18 | /// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
19 | /// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
20 | /// DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
21 | /// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
22 | /// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
23 | /// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
24 | /// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
25 | /// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
26 | /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
27 | /// | ||
28 | |||
29 | namespace OpenSim.Region.OptionalModules.World.AutoBackup | ||
30 | { | ||
31 | /// AutoBackupModuleState: Auto-Backup state for one region (scene). | ||
32 | public class AutoBackupModuleState | ||
33 | { | ||
34 | public AutoBackupModuleState() | ||
35 | { | ||
36 | this.Enabled = false; | ||
37 | this.BackupDir = "."; | ||
38 | this.BusyCheck = true; | ||
39 | this.Timer = null; | ||
40 | this.NamingType = NamingType.Time; | ||
41 | this.Script = null; | ||
42 | } | ||
43 | |||
44 | public bool Enabled | ||
45 | { | ||
46 | get; | ||
47 | set; | ||
48 | } | ||
49 | |||
50 | public System.Timers.Timer Timer | ||
51 | { | ||
52 | get; | ||
53 | set; | ||
54 | } | ||
55 | |||
56 | public double IntervalMinutes | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | if (this.Timer == null) | ||
61 | { | ||
62 | return -1.0; | ||
63 | } | ||
64 | else | ||
65 | { | ||
66 | return this.Timer.Interval / 60000.0; | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | public bool BusyCheck | ||
72 | { | ||
73 | get; | ||
74 | set; | ||
75 | } | ||
76 | |||
77 | public string Script | ||
78 | { | ||
79 | get; | ||
80 | set; | ||
81 | } | ||
82 | |||
83 | public string BackupDir | ||
84 | { | ||
85 | get; | ||
86 | set; | ||
87 | } | ||
88 | |||
89 | public NamingType NamingType | ||
90 | { | ||
91 | get; | ||
92 | set; | ||
93 | } | ||
94 | |||
95 | public new string ToString() | ||
96 | { | ||
97 | string retval = ""; | ||
98 | |||
99 | retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n"; | ||
100 | retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n"; | ||
101 | retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n"; | ||
102 | retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n"; | ||
103 | retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n"; | ||
104 | retval += "[AUTO BACKUP]: Script: " + Script + "\n"; | ||
105 | return retval; | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | |||