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