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.cs751
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs123
-rw-r--r--bin/OpenSimDefaults.ini4
4 files changed, 879 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..bd4893c
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
@@ -0,0 +1,751 @@
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 /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
98 /// Also helps if you don't want AutoBackup at all
99 private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
100 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
101 private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
102 new Dictionary<IScene, AutoBackupModuleState>(1);
103 private readonly Dictionary<Timer, List<IScene>> m_timerMap =
104 new Dictionary<Timer, List<IScene>>(1);
105 private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
106
107 private bool m_enabled;
108
109 /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
110 private bool m_closed;
111
112 private IConfigSource m_configSource;
113
114 public bool IsSharedModule
115 {
116 get { return true; }
117 }
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 {
389 m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
390 stmpNamingType);
391 tmpNamingType = NamingType.Time;
392 }
393
394 if (state == null && tmpNamingType != this.m_defaultState.NamingType)
395 {
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?
544 /// Sequential numbers, right? Ugh. Almost makes TOO much sense.
545 private static string GetTimeString()
546 {
547 StringWriter sw = new StringWriter();
548 sw.Write("_");
549 DateTime now = DateTime.Now;
550 sw.Write(now.Year);
551 sw.Write("y_");
552 sw.Write(now.Month);
553 sw.Write("M_");
554 sw.Write(now.Day);
555 sw.Write("d_");
556 sw.Write(now.Hour);
557 sw.Write("h_");
558 sw.Write(now.Minute);
559 sw.Write("m_");
560 sw.Write(now.Second);
561 sw.Write("s");
562 sw.Flush();
563 string output = sw.ToString();
564 sw.Close();
565 return output;
566 }
567
568 ///
569 /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.
570 ///
571 private bool RunHeuristics(IScene region)
572 {
573 try
574 {
575 return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
576 }
577 catch (Exception e)
578 {
579 m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
580 return false;
581 }
582 }
583
584 ///
585 /// 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).
587 /// 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!
589 ///
590 private bool RunTimeDilationHeuristic(IScene region)
591 {
592 string regionName = region.RegionInfo.RegionName;
593 return region.TimeDilation >=
594 this.m_configSource.Configs["AutoBackupModule"].GetFloat(
595 regionName + ".AutoBackupDilationThreshold", 0.5f);
596 }
597
598 ///
599 /// 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).
601 /// 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!
603 ///
604 private bool RunAgentLimitHeuristic(IScene region)
605 {
606 string regionName = region.RegionInfo.RegionName;
607 try
608 {
609 Scene scene = (Scene) region;
610 /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
611 return scene.GetRootAgentCount() <=
612 this.m_configSource.Configs["AutoBackupModule"].GetInt(
613 regionName + ".AutoBackupAgentThreshold", 10);
614 }
615 catch (InvalidCastException ice)
616 {
617 m_log.Debug(
618 "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
619 ice);
620 return true;
621 /// Non-obstructionist safest answer...
622 }
623 }
624
625 private static void ExecuteScript(string scriptName, string savePath)
626 {
627 //Fast path out
628 if (scriptName == null || scriptName.Length <= 0)
629 {
630 return;
631 }
632
633 try
634 {
635 FileInfo fi = new FileInfo(scriptName);
636 if (fi.Exists)
637 {
638 ProcessStartInfo psi = new ProcessStartInfo(scriptName);
639 psi.Arguments = savePath;
640 psi.CreateNoWindow = true;
641 Process proc = Process.Start(psi);
642 proc.ErrorDataReceived += HandleProcErrorDataReceived;
643 }
644 }
645 catch (Exception e)
646 {
647 m_log.Warn(
648 "Exception encountered when trying to run script for oar backup " + savePath, e);
649 }
650 }
651
652 private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
653 {
654 m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
655 " is yacking on stderr: " + e.Data);
656 }
657
658 private void StopAllTimers()
659 {
660 foreach (Timer t in this.m_timerMap.Keys)
661 {
662 t.Close();
663 }
664 this.m_closed = true;
665 }
666
667 private static string GetNextFile(string dirName, string regionName)
668 {
669 FileInfo uniqueFile = null;
670 long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
671 biggestExistingFile++;
672 //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
673 uniqueFile =
674 new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
675 biggestExistingFile + ".oar");
676 return uniqueFile.FullName;
677 }
678
679 private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
680 {
681 FileInfo path = null;
682 switch (naming)
683 {
684 case NamingType.Overwrite:
685 path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
686 return path.FullName;
687 case NamingType.Time:
688 path =
689 new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
690 GetTimeString() + ".oar");
691 return path.FullName;
692 case NamingType.Sequential:
693 /// All codepaths in GetNextFile should return a file name ending in .oar
694 path = new FileInfo(GetNextFile(baseDir, regionName));
695 return path.FullName;
696 default:
697 m_log.Warn("VERY BAD: Unhandled case element " + naming);
698 break;
699 }
700
701 return null;
702 }
703
704 private static long GetNextOarFileNumber(string dirName, string regionName)
705 {
706 long retval = 1;
707
708 DirectoryInfo di = new DirectoryInfo(dirName);
709 FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
710 Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
711
712 if (fi.LongLength > 0)
713 {
714 long subtract = 1L;
715 bool worked = false;
716 Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
717
718 while (!worked && subtract <= fi.LongLength)
719 {
720 /// Pick the file with the last natural ordering
721 string biggestFileName = fi[fi.LongLength - subtract].Name;
722 MatchCollection matches = reg.Matches(biggestFileName);
723 long l = 1;
724 if (matches.Count > 0 && matches[0].Groups.Count > 0)
725 {
726 try
727 {
728 long.TryParse(matches[0].Groups[1].Value, out l);
729 retval = l;
730 worked = true;
731 }
732 catch (FormatException fe)
733 {
734 m_log.Warn(
735 "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
736 fe);
737 subtract++;
738 }
739 }
740 else
741 {
742 subtract++;
743 }
744 }
745 }
746 return retval;
747 }
748 }
749}
750
751
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs
new file mode 100644
index 0000000..7fecfa4
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs
@@ -0,0 +1,123 @@
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;
31
32
33namespace OpenSim.Region.OptionalModules.World.AutoBackup
34{
35 /// AutoBackupModuleState: Auto-Backup state for one region (scene).
36 public class AutoBackupModuleState
37 {
38 private Dictionary<Guid, string> m_liveRequests = null;
39
40 public AutoBackupModuleState()
41 {
42 this.Enabled = false;
43 this.BackupDir = ".";
44 this.BusyCheck = true;
45 this.Timer = null;
46 this.NamingType = NamingType.Time;
47 this.Script = null;
48 }
49
50 public Dictionary<Guid, string> LiveRequests
51 {
52 get {
53 return this.m_liveRequests ??
54 (this.m_liveRequests = new Dictionary<Guid, string>(1));
55 }
56 }
57
58 public bool Enabled
59 {
60 get;
61 set;
62 }
63
64 public System.Timers.Timer Timer
65 {
66 get;
67 set;
68 }
69
70 public double IntervalMinutes
71 {
72 get
73 {
74 if (this.Timer == null)
75 {
76 return -1.0;
77 }
78 else
79 {
80 return this.Timer.Interval / 60000.0;
81 }
82 }
83 }
84
85 public bool BusyCheck
86 {
87 get;
88 set;
89 }
90
91 public string Script
92 {
93 get;
94 set;
95 }
96
97 public string BackupDir
98 {
99 get;
100 set;
101 }
102
103 public NamingType NamingType
104 {
105 get;
106 set;
107 }
108
109 public new string ToString()
110 {
111 string retval = "";
112
113 retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n";
114 retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n";
115 retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n";
116 retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n";
117 retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n";
118 retval += "[AUTO BACKUP]: Script: " + Script + "\n";
119 return retval;
120 }
121 }
122}
123
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"