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