aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs791
1 files changed, 237 insertions, 554 deletions
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
index ceb3332..a14d819 100644
--- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
@@ -59,70 +59,58 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
59 /// </summary> 59 /// </summary>
60 /// <remarks> 60 /// <remarks>
61 /// Config Settings Documentation. 61 /// Config Settings Documentation.
62 /// Each configuration setting can be specified in two places: OpenSim.ini or Regions.ini. 62 /// Configuration setting can be specified in two places: OpenSim.ini and/or Regions.ini.
63 /// If specified in Regions.ini, the settings should be within the region's section name. 63 ///
64 /// If specified in OpenSim.ini, the settings should be within the [AutoBackupModule] section. 64 /// OpenSim.ini only settings section [AutoBackupModule]
65 /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module.
66 /// if false module is disable and all rest is ignored
67 /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
68 /// The number of minutes between each backup attempt.
69 /// AutoBackupDir: String. Default: "." (the current directory).
70 /// A directory (absolute or relative) where backups should be saved.
71 /// AutoBackupKeepFilesForDays remove files older than this number of days. 0 disables
72 ///
73 /// Next can be set on OpenSim.ini, as default, and or per region in Regions.ini
65 /// Region-specific settings take precedence. 74 /// Region-specific settings take precedence.
66 /// 75 ///
67 /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. 76 /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
68 /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! 77 /// controls backup per region, with default optionaly set on OpenSim.ini
69 /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. 78
70 /// This is the only required option for enabling auto-backup; the other options have sane defaults.
71 /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
72 /// If False globally (the default), only regions that specifically override it in Regions.ini will get AutoBackup functionality.
73 /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
74 /// The number of minutes between each backup attempt.
75 /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
76 /// AutoBackupBusyCheck: True/False. Default: True.
77 /// If True, we will only take an auto-backup if a set of conditions are met.
78 /// These conditions are heuristics to try and avoid taking a backup when the sim is busy.
79 /// AutoBackupSkipAssets 79 /// AutoBackupSkipAssets
80 /// If true, assets are not saved to the oar file. Considerably reduces impact on simulator when backing up. Intended for when assets db is backed up separately 80 /// If true, assets are not saved to the oar file. Considerably reduces impact on simulator when backing up. Intended for when assets db is backed up separately
81 /// AutoBackupKeepFilesForDays 81 /// AutoBackupKeepFilesForDays
82 /// Backup files older than this value (in days) are deleted during the current backup process, 0 will disable this and keep all backup files indefinitely 82 /// Backup files older than this value (in days) are deleted during the current backup process, 0 will disable this and keep all backup files indefinitely
83 /// AutoBackupScript: String. Default: not specified (disabled). 83 /// AutoBackupScript: String. Default: not specified (disabled).
84 /// File path to an executable script or binary to run when an automatic backup is taken. 84 /// File path to an executable script or binary to run when an automatic backup is taken.
85 /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. 85 /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
86 /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! 86 /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
87 /// argv[1] of the executed file/script will be the file name of the generated OAR. 87 /// argv[1] of the executed file/script will be the file name of the generated OAR.
88 /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. 88 /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
89 /// AutoBackupNaming: string. Default: Time. 89 /// AutoBackupNaming: string. Default: Time.
90 /// One of three strings (case insensitive): 90 /// One of three strings (case insensitive):
91 /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. 91 /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
92 /// "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. 92 /// "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.
93 /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. 93 /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
94 /// AutoBackupDir: String. Default: "." (the current directory).
95 /// A directory (absolute or relative) where backups should be saved.
96 /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
97 /// If the time dilation is below this value, don't take a backup right now.
98 /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
99 /// If the number of agents is greater than this value, don't take a backup right now
100 /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
101 /// Also helps if you don't want AutoBackup at all.
102 /// </remarks> 94 /// </remarks>
103 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")] 95 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")]
104 public class AutoBackupModule : ISharedRegionModule 96 public class AutoBackupModule : ISharedRegionModule
105 { 97 {
106 private static readonly ILog m_log = 98 private static readonly ILog m_log =
107 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 99 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
108 private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
109 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); 100 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
110 private readonly Dictionary<IScene, AutoBackupModuleState> m_states = 101 private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
111 new Dictionary<IScene, AutoBackupModuleState>(1); 102 new Dictionary<IScene, AutoBackupModuleState>(1);
112 private readonly Dictionary<Timer, List<IScene>> m_timerMap =
113 new Dictionary<Timer, List<IScene>>(1);
114 private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
115 103
116 private delegate T DefaultGetter<T>(string settingName, T defaultValue); 104 private delegate T DefaultGetter<T>(string settingName, T defaultValue);
117 private bool m_enabled; 105 private bool m_enabled;
118 private ICommandConsole m_console; 106 private ICommandConsole m_console;
119 private List<Scene> m_Scenes = new List<Scene> (); 107 private List<Scene> m_Scenes = new List<Scene> ();
120 108 private Timer m_masterTimer;
121 109 private bool m_busy;
122 /// <summary> 110 private int m_KeepFilesForDays = -1;
123 /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! 111 private string m_backupDir;
124 /// </summary> 112 private bool m_doneFirst;
125 private bool m_closed; 113 private double m_baseInterval;
126 114
127 private IConfigSource m_configSource; 115 private IConfigSource m_configSource;
128 116
@@ -159,36 +147,38 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
159 void IRegionModuleBase.Initialise(IConfigSource source) 147 void IRegionModuleBase.Initialise(IConfigSource source)
160 { 148 {
161 // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module 149 // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
162 this.m_configSource = source; 150 m_configSource = source;
163 IConfig moduleConfig = source.Configs["AutoBackupModule"]; 151 IConfig moduleConfig = source.Configs["AutoBackupModule"];
164 if (moduleConfig == null) 152 if (moduleConfig == null)
165 { 153 {
166 this.m_enabled = false; 154 m_enabled = false;
167 return; 155 return;
168 } 156 }
169 else
170 {
171 this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
172 if (this.m_enabled)
173 {
174 m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
175 }
176 else
177 {
178 return;
179 }
180 }
181 157
182 Timer defTimer = new Timer(43200000); 158 m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
183 this.m_defaultState.Timer = defTimer; 159 if(!m_enabled)
184 this.m_timers.Add(43200000, defTimer); 160 return;
185 defTimer.Elapsed += this.HandleElapsed; 161
186 defTimer.AutoReset = true; 162 ParseDefaultConfig(moduleConfig);
187 defTimer.Start(); 163 if(!m_enabled)
164 return;
165
166 m_log.Debug("[AUTO BACKUP]: Default config:");
167 m_log.Debug(m_defaultState.ToString());
168
169 m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
170 m_masterTimer = new Timer();
171 m_masterTimer.Interval = m_baseInterval;
172 m_masterTimer.Elapsed += HandleElapsed;
173 m_masterTimer.AutoReset = false;
174
175 m_console = MainConsole.Instance;
188 176
189 AutoBackupModuleState abms = this.ParseConfig(null, true); 177 m_console.Commands.AddCommand (
190 m_log.Debug("[AUTO BACKUP]: Here is the default config:"); 178 "AutoBackup", true, "dooarbackup",
191 m_log.Debug(abms.ToString()); 179 "dooarbackup <regionName> | ALL",
180 "saves the single region <regionName> to a oar or ALL regions in instance to oars, using same settings as AutoBackup. Note it restarts time interval", DoBackup);
181 m_busy = true;
192 } 182 }
193 183
194 /// <summary> 184 /// <summary>
@@ -196,13 +186,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
196 /// </summary> 186 /// </summary>
197 void IRegionModuleBase.Close() 187 void IRegionModuleBase.Close()
198 { 188 {
199 if (!this.m_enabled) 189 if (!m_enabled)
200 {
201 return; 190 return;
202 }
203 191
204 // We don't want any timers firing while the sim's coming down; strange things may happen. 192 // We don't want any timers firing while the sim's coming down; strange things may happen.
205 this.StopAllTimers(); 193 m_masterTimer.Dispose();
206 } 194 }
207 195
208 /// <summary> 196 /// <summary>
@@ -211,18 +199,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
211 /// <param name="scene"></param> 199 /// <param name="scene"></param>
212 void IRegionModuleBase.AddRegion (Scene scene) 200 void IRegionModuleBase.AddRegion (Scene scene)
213 { 201 {
214 if (!this.m_enabled) { 202 if (!m_enabled)
215 return; 203 return;
216 }
217 lock (m_Scenes) {
218 m_Scenes.Add (scene);
219 }
220 m_console = MainConsole.Instance;
221 204
222 m_console.Commands.AddCommand ( 205 lock (m_Scenes)
223 "AutoBackup", false, "dobackup", 206 m_Scenes.Add (scene);
224 "dobackup",
225 "do backup.", DoBackup);
226 } 207 }
227 208
228 /// <summary> 209 /// <summary>
@@ -231,28 +212,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
231 /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param> 212 /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param>
232 void IRegionModuleBase.RemoveRegion(Scene scene) 213 void IRegionModuleBase.RemoveRegion(Scene scene)
233 { 214 {
234 if (!this.m_enabled) 215 if (m_enabled)
235 {
236 return; 216 return;
237 }
238 m_Scenes.Remove (scene);
239 if (this.m_states.ContainsKey(scene))
240 {
241 AutoBackupModuleState abms = this.m_states[scene];
242 217
243 // Remove this scene out of the timer map list 218 lock(m_Scenes)
244 Timer timer = abms.Timer; 219 {
245 List<IScene> list = this.m_timerMap[timer]; 220 if (m_states.ContainsKey(scene))
246 list.Remove(scene); 221 m_states.Remove(scene);
247 222 m_Scenes.Remove(scene);
248 // Shut down the timer if this was the last scene for the timer
249 if (list.Count == 0)
250 {
251 this.m_timerMap.Remove(timer);
252 this.m_timers.Remove(timer.Interval);
253 timer.Close();
254 }
255 this.m_states.Remove(scene);
256 } 223 }
257 } 224 }
258 225
@@ -263,22 +230,29 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
263 /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param> 230 /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param>
264 void IRegionModuleBase.RegionLoaded(Scene scene) 231 void IRegionModuleBase.RegionLoaded(Scene scene)
265 { 232 {
266 if (!this.m_enabled) 233 if (!m_enabled)
267 {
268 return; 234 return;
269 }
270 235
271 // This really ought not to happen, but just in case, let's pretend it didn't... 236 // This really ought not to happen, but just in case, let's pretend it didn't...
272 if (scene == null) 237 if (scene == null)
273 {
274 return; 238 return;
275 }
276 239
277 AutoBackupModuleState abms = this.ParseConfig(scene, false); 240 AutoBackupModuleState abms = ParseConfig(scene);
278 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); 241 if(abms == null)
279 m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); 242 {
243 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
244 m_log.Debug("DEFAULT");
245 abms = new AutoBackupModuleState(m_defaultState);
246 }
247 else
248 {
249 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
250 m_log.Debug(abms.ToString());
251 }
280 252
281 m_states.Add(scene, abms); 253 m_states.Add(scene, abms);
254 m_busy = false;
255 m_masterTimer.Start();
282 } 256 }
283 257
284 /// <summary> 258 /// <summary>
@@ -292,356 +266,174 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
292 266
293 private void DoBackup (string module, string[] args) 267 private void DoBackup (string module, string[] args)
294 { 268 {
295 if (args.Length != 2) { 269 if (!m_enabled)
296 MainConsole.Instance.OutputFormat ("Usage: dobackup <regionname>");
297 return; 270 return;
298 }
299 bool found = false;
300 string name = args [1];
301 lock (m_Scenes) {
302 foreach (Scene s in m_Scenes) {
303 string test = s.Name.ToString ();
304 if (test == name) {
305 found = true;
306 DoRegionBackup (s);
307 }
308 }
309 if (!found) {
310 MainConsole.Instance.OutputFormat ("No such region {0}. Nothing to backup", name);
311 }
312 }
313 }
314 271
315 /// <summary> 272 if (args.Length != 2)
316 /// Set up internal state for a given scene. Fairly complex code.
317 /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene.
318 /// </summary>
319 /// <param name="scene">The scene to look at.</param>
320 /// <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>
321 /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
322 private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault)
323 {
324 string sRegionName;
325 string sRegionLabel;
326// string prepend;
327 AutoBackupModuleState state;
328
329 if (parseDefault)
330 {
331 sRegionName = null;
332 sRegionLabel = "DEFAULT";
333// prepend = "";
334 state = this.m_defaultState;
335 }
336 else
337 {
338 sRegionName = scene.RegionInfo.RegionName;
339 sRegionLabel = sRegionName;
340// prepend = sRegionName + ".";
341 state = null;
342 }
343
344 // Read the config settings and set variables.
345 IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null);
346 IConfig config = this.m_configSource.Configs["AutoBackupModule"];
347 if (config == null)
348 { 273 {
349 // defaultState would be disabled too if the section doesn't exist. 274 MainConsole.Instance.OutputFormat ("Usage: dooarbackup <regionname>");
350 state = this.m_defaultState; 275 return;
351 return state;
352 }
353
354 bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig);
355 if (state == null && tmpEnabled != this.m_defaultState.Enabled)
356 //Varies from default state
357 {
358 state = new AutoBackupModuleState();
359 } 276 }
360 277
361 if (state != null) 278 if(m_busy)
362 { 279 {
363 state.Enabled = tmpEnabled; 280 MainConsole.Instance.OutputFormat ("Already doing a backup, please try later");
281 return;
364 } 282 }
365 283
366 // If you don't want AutoBackup, we stop. 284 m_masterTimer.Stop();
367 if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) 285 m_busy = true;
368 {
369 return state;
370 }
371 else
372 {
373 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
374 }
375 286
376 // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. 287 bool found = false;
377 double interval = 288 string name = args [1];
378 this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes, 289 Scene[] scenes;
379 config, regionConfig) * 60000.0; 290 lock (m_Scenes)
380 if (state == null && interval != this.m_defaultState.IntervalMinutes * 60000.0) 291 scenes = m_Scenes.ToArray();
381 {
382 state = new AutoBackupModuleState();
383 }
384 292
385 if (this.m_timers.ContainsKey(interval)) 293 if(scenes == null)
386 { 294 return;
387 if (state != null)
388 {
389 state.Timer = this.m_timers[interval];
390 }
391 m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " +
392 sRegionLabel);
393 }
394 else
395 {
396 // 0 or negative interval == do nothing.
397 if (interval <= 0.0 && state != null)
398 {
399 state.Enabled = false;
400 return state;
401 }
402 if (state == null)
403 {
404 state = new AutoBackupModuleState();
405 }
406 Timer tim = new Timer(interval);
407 state.Timer = tim;
408 //Milliseconds -> minutes
409 this.m_timers.Add(interval, tim);
410 tim.Elapsed += this.HandleElapsed;
411 tim.AutoReset = true;
412 tim.Start();
413 }
414 295
415 // Add the current region to the list of regions tied to this timer. 296 Scene s;
416 if (scene != null) 297 try
417 { 298 {
418 if (state != null) 299 if(name == "ALL")
419 { 300 {
420 if (this.m_timerMap.ContainsKey(state.Timer)) 301 for(int i = 0; i < scenes.Length; i++)
421 { 302 {
422 this.m_timerMap[state.Timer].Add(scene); 303 s = scenes[i];
423 } 304 DoRegionBackup(s);
424 else 305 if (!m_enabled)
425 { 306 return;
426 List<IScene> scns = new List<IScene>(1);
427 scns.Add(scene);
428 this.m_timerMap.Add(state.Timer, scns);
429 } 307 }
308 return;
430 } 309 }
431 else 310
311 for(int i = 0; i < scenes.Length; i++)
432 { 312 {
433 if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) 313 s = scenes[i];
314 if (s.Name == name)
434 { 315 {
435 this.m_timerMap[this.m_defaultState.Timer].Add(scene); 316 found = true;
436 } 317 DoRegionBackup(s);
437 else 318 break;
438 {
439 List<IScene> scns = new List<IScene>(1);
440 scns.Add(scene);
441 this.m_timerMap.Add(this.m_defaultState.Timer, scns);
442 } 319 }
443 } 320 }
444 } 321 }
445 322 catch { }
446 bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck", 323 finally
447 this.m_defaultState.BusyCheck, config, regionConfig);
448 if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
449 { 324 {
450 state = new AutoBackupModuleState(); 325 if (m_enabled)
451 } 326 m_masterTimer.Start();
327 m_busy = false;
328 }
329 if (!found)
330 MainConsole.Instance.OutputFormat ("No such region {0}. Nothing to backup", name);
331 }
452 332
453 if (state != null) 333 private void ParseDefaultConfig(IConfig config)
454 { 334 {
455 state.BusyCheck = tmpBusyCheck;
456 }
457 335
458 // Included Option To Skip Assets 336 m_backupDir = ".";
459 bool tmpSkipAssets = ResolveBoolean("AutoBackupSkipAssets", 337 string backupDir = config.GetString("AutoBackupDir", ".");
460 this.m_defaultState.SkipAssets, config, regionConfig); 338 if (backupDir != ".")
461 if (state == null && tmpSkipAssets != this.m_defaultState.SkipAssets)
462 { 339 {
463 state = new AutoBackupModuleState(); 340 try
341 {
342 DirectoryInfo dirinfo = new DirectoryInfo(backupDir);
343 if (!dirinfo.Exists)
344 dirinfo.Create();
345 }
346 catch (Exception e)
347 {
348 m_enabled = false;
349 m_log.WarnFormat("[AUTO BACKUP]: Error accessing backup folder {0}. Module disabled. {1}",
350 backupDir, e);
351 return;
352 }
464 } 353 }
354 m_backupDir = backupDir;
465 355
466 if (state != null) 356 double interval = config.GetDouble("AutoBackupInterval", 720);
467 { 357 interval *= 60000.0;
468 state.SkipAssets = tmpSkipAssets; 358 m_baseInterval = interval;
469 }
470 359
471 // How long to keep backup files in days, 0 Disables this feature 360 // How long to keep backup files in days, 0 Disables this feature
472 int tmpKeepFilesForDays = ResolveInt("AutoBackupKeepFilesForDays", 361 m_KeepFilesForDays = config.GetInt("AutoBackupKeepFilesForDays",m_KeepFilesForDays);
473 this.m_defaultState.KeepFilesForDays, config, regionConfig);
474 if (state == null && tmpKeepFilesForDays != this.m_defaultState.KeepFilesForDays)
475 {
476 state = new AutoBackupModuleState();
477 }
478 362
479 if (state != null) 363 m_defaultState.Enabled = config.GetBoolean("AutoBackup", m_defaultState.Enabled);
480 { 364
481 state.KeepFilesForDays = tmpKeepFilesForDays; 365 m_defaultState.SkipAssets = config.GetBoolean("AutoBackupSkipAssets",m_defaultState.SkipAssets);
482 }
483 366
484 // Set file naming algorithm 367 // Set file naming algorithm
485 string stmpNamingType = ResolveString("AutoBackupNaming", 368 string stmpNamingType = config.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
486 this.m_defaultState.NamingType.ToString(), config, regionConfig);
487 NamingType tmpNamingType; 369 NamingType tmpNamingType;
488 if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) 370 if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
489 {
490 tmpNamingType = NamingType.Time; 371 tmpNamingType = NamingType.Time;
491 }
492 else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) 372 else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
493 {
494 tmpNamingType = NamingType.Sequential; 373 tmpNamingType = NamingType.Sequential;
495 }
496 else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) 374 else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
497 {
498 tmpNamingType = NamingType.Overwrite; 375 tmpNamingType = NamingType.Overwrite;
499 }
500 else 376 else
501 { 377 {
502 m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + 378 m_log.Warn("Unknown naming type specified for Default");
503 stmpNamingType);
504 tmpNamingType = NamingType.Time; 379 tmpNamingType = NamingType.Time;
505 } 380 }
381 m_defaultState.NamingType = tmpNamingType;
506 382
507 if (state == null && tmpNamingType != this.m_defaultState.NamingType) 383 m_defaultState.Script = config.GetString("AutoBackupScript", m_defaultState.Script);
508 {
509 state = new AutoBackupModuleState();
510 }
511 384
512 if (state != null) 385 }
513 {
514 state.NamingType = tmpNamingType;
515 }
516 386
517 string tmpScript = ResolveString("AutoBackupScript", 387 /// <summary>
518 this.m_defaultState.Script, config, regionConfig); 388 /// Set up internal state for a given scene. Fairly complex code.
519 if (state == null && tmpScript != this.m_defaultState.Script) 389 /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene.
520 { 390 /// </summary>
521 state = new AutoBackupModuleState(); 391 /// <param name="scene">The scene to look at.</param>
522 } 392 /// <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>
393 /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
394 private AutoBackupModuleState ParseConfig(IScene scene)
395 {
396 if(scene == null)
397 return null;
523 398
524 if (state != null) 399 string sRegionName;
525 { 400 AutoBackupModuleState state = null;
526 state.Script = tmpScript;
527 }
528 401
529 string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig); 402 sRegionName = scene.RegionInfo.RegionName;
530 if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
531 {
532 state = new AutoBackupModuleState();
533 }
534 403
535 if (state != null) 404 // Read the config settings and set variables.
536 { 405 IConfig regionConfig = scene.Config.Configs[sRegionName];
537 state.BackupDir = tmpBackupDir; 406 if (regionConfig == null)
538 // Let's give the user some convenience and auto-mkdir 407 return null;
539 if (state.BackupDir != ".")
540 {
541 try
542 {
543 DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir);
544 if (!dirinfo.Exists)
545 {
546 dirinfo.Create();
547 }
548 }
549 catch (Exception e)
550 {
551 m_log.Warn(
552 "[AUTO BACKUP]: BAD NEWS. You won't be able to save backups to directory " +
553 state.BackupDir +
554 " because it doesn't exist or there's a permissions issue with it. Here's the exception.",
555 e);
556 }
557 }
558 }
559 408
560 if(state == null) 409 state = new AutoBackupModuleState();
561 return m_defaultState;
562 410
563 return state; 411 state.Enabled = regionConfig.GetBoolean("AutoBackup", m_defaultState.Enabled);
564 }
565 412
566 /// <summary> 413 // Included Option To Skip Assets
567 /// Helper function for ParseConfig. 414 state.SkipAssets = regionConfig.GetBoolean("AutoBackupSkipAssets", m_defaultState.SkipAssets);
568 /// </summary>
569 /// <param name="settingName"></param>
570 /// <param name="defaultValue"></param>
571 /// <param name="global"></param>
572 /// <param name="local"></param>
573 /// <returns></returns>
574 private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local)
575 {
576 if(local != null)
577 {
578 return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue));
579 }
580 else
581 {
582 return global.GetBoolean(settingName, defaultValue);
583 }
584 }
585 415
586 /// <summary> 416 // Set file naming algorithm
587 /// Helper function for ParseConfig. 417 string stmpNamingType = regionConfig.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
588 /// </summary> 418 NamingType tmpNamingType;
589 /// <param name="settingName"></param> 419 if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
590 /// <param name="defaultValue"></param> 420 tmpNamingType = NamingType.Time;
591 /// <param name="global"></param> 421 else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
592 /// <param name="local"></param> 422 tmpNamingType = NamingType.Sequential;
593 /// <returns></returns> 423 else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
594 private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local) 424 tmpNamingType = NamingType.Overwrite;
595 {
596 if (local != null)
597 {
598 return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue));
599 }
600 else 425 else
601 { 426 {
602 return global.GetDouble(settingName, defaultValue); 427 m_log.Warn("Unknown naming type specified for region " + sRegionName + ": " +
428 stmpNamingType);
429 tmpNamingType = NamingType.Time;
603 } 430 }
604 } 431 m_defaultState.NamingType = tmpNamingType;
605 432
606 /// <summary> 433 state.Script = regionConfig.GetString("AutoBackupScript", m_defaultState.Script);
607 /// Helper function for ParseConfig. 434 return state;
608 /// </summary>
609 /// <param name="settingName"></param>
610 /// <param name="defaultValue"></param>
611 /// <param name="global"></param>
612 /// <param name="local"></param>
613 /// <returns></returns>
614 private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local)
615 {
616 if (local != null)
617 {
618 return local.GetInt(settingName, global.GetInt(settingName, defaultValue));
619 }
620 else
621 {
622 return global.GetInt(settingName, defaultValue);
623 }
624 } 435 }
625 436
626 /// <summary>
627 /// Helper function for ParseConfig.
628 /// </summary>
629 /// <param name="settingName"></param>
630 /// <param name="defaultValue"></param>
631 /// <param name="global"></param>
632 /// <param name="local"></param>
633 /// <returns></returns>
634 private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local)
635 {
636 if (local != null)
637 {
638 return local.GetString(settingName, global.GetString(settingName, defaultValue));
639 }
640 else
641 {
642 return global.GetString(settingName, defaultValue);
643 }
644 }
645 437
646 /// <summary> 438 /// <summary>
647 /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. 439 /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
@@ -650,63 +442,27 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
650 /// <param name="e"></param> 442 /// <param name="e"></param>
651 private void HandleElapsed(object sender, ElapsedEventArgs e) 443 private void HandleElapsed(object sender, ElapsedEventArgs e)
652 { 444 {
653 // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region 445 if (!m_enabled || m_busy)
654 // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
655 // check whether the region is too busy! Especially on sims with LOTS of regions.
656 // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
657 // but would allow us to be semantically correct while being easier on perf.
658 // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
659 // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter.
660 // Since this is pretty experimental, I haven't decided which alternative makes the most sense.
661 if (this.m_closed)
662 {
663 return; 446 return;
664 } 447
665 bool heuristicsRun = false; 448 m_busy = true;
666 bool heuristicsPassed = false; 449 if(m_doneFirst && m_KeepFilesForDays > 0)
667 if (!this.m_timerMap.ContainsKey((Timer) sender)) 450 RemoveOldFiles();
451
452 foreach (IScene scene in m_Scenes)
668 { 453 {
669 m_log.Debug("[AUTO BACKUP]: Code-up error: timerMap doesn't contain timer " + sender); 454 if (!m_enabled)
455 return;
456 DoRegionBackup(scene);
670 } 457 }
671 458
672 List<IScene> tmap = this.m_timerMap[(Timer) sender]; 459 if (m_enabled)
673 if (tmap != null && tmap.Count > 0)
674 { 460 {
675 foreach (IScene scene in tmap) 461 m_masterTimer.Start();
676 { 462 m_busy = false;
677 AutoBackupModuleState state = this.m_states[scene];
678 bool heuristics = state.BusyCheck;
679
680 // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
681 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
682 {
683 this.DoRegionBackup(scene);
684 // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
685 }
686 else if (heuristicsRun)
687 {
688 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
689 scene.RegionInfo.RegionName + " right now.");
690 continue;
691 // Logical Deduction: heuristics are on but haven't been run
692 }
693 else
694 {
695 heuristicsPassed = this.RunHeuristics(scene);
696 heuristicsRun = true;
697 if (!heuristicsPassed)
698 {
699 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
700 scene.RegionInfo.RegionName + " right now.");
701 continue;
702 }
703 this.DoRegionBackup(scene);
704 }
705
706 // Remove Old Backups
707 this.RemoveOldFiles(state);
708 }
709 } 463 }
464
465 m_doneFirst = true;
710 } 466 }
711 467
712 /// <summary> 468 /// <summary>
@@ -723,21 +479,29 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
723 return; 479 return;
724 } 480 }
725 481
726 AutoBackupModuleState state = this.m_states[scene]; 482 m_busy = true;
483
484 AutoBackupModuleState state;
485 if(!m_states.TryGetValue(scene, out state))
486 return;
487
488 if(state == null || !state.Enabled)
489 return;
490
727 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>(); 491 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
492 if(iram == null)
493 return;
494
728 string savePath = BuildOarPath(scene.RegionInfo.RegionName, 495 string savePath = BuildOarPath(scene.RegionInfo.RegionName,
729 state.BackupDir, 496 m_backupDir,
730 state.NamingType); 497 state.NamingType);
731 if (savePath == null) 498 if (savePath == null)
732 { 499 {
733 m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); 500 m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
734 return; 501 return;
735 } 502 }
736 Guid guid = Guid.NewGuid();
737 m_pendingSaves.Add(guid, scene);
738 state.LiveRequests.Add(guid, savePath);
739 ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved);
740 503
504 Guid guid = Guid.NewGuid();
741 m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName); 505 m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName);
742 506
743 // Must pass options, even if dictionary is empty! 507 // Must pass options, even if dictionary is empty!
@@ -747,47 +511,37 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
747 options["noassets"] = true; 511 options["noassets"] = true;
748 512
749 iram.ArchiveRegion(savePath, guid, options); 513 iram.ArchiveRegion(savePath, guid, options);
514 ExecuteScript(state.Script, savePath);
750 } 515 }
751 516
752 // For the given state, remove backup files older than the states KeepFilesForDays property 517 // For the given state, remove backup files older than the states KeepFilesForDays property
753 private void RemoveOldFiles(AutoBackupModuleState state) 518 private void RemoveOldFiles()
754 { 519 {
755 // 0 Means Disabled, Keep Files Indefinitely 520 string[] files;
756 if (state.KeepFilesForDays > 0) 521 try
757 { 522 {
758 string[] files = Directory.GetFiles(state.BackupDir, "*.oar"); 523 files = Directory.GetFiles(m_backupDir, "*.oar");
759 DateTime CuttOffDate = DateTime.Now.AddDays(0 - state.KeepFilesForDays); 524 }
760 525 catch (Exception Ex)
761 foreach (string file in files) 526 {
762 { 527 m_log.Error("[AUTO BACKUP]: Error reading backup folder " + m_backupDir + ": " + Ex.Message);
763 try 528 return;
764 {
765 FileInfo fi = new FileInfo(file);
766 if (fi.CreationTime < CuttOffDate)
767 fi.Delete();
768 }
769 catch (Exception Ex)
770 {
771 m_log.Error("[AUTO BACKUP]: Error deleting old backup file '" + file + "': " + Ex.Message);
772 }
773 }
774 } 529 }
775 }
776 530
777 /// <summary> 531 DateTime CuttOffDate = DateTime.Now.AddDays(-m_KeepFilesForDays);
778 /// Called by the Event Manager when the OnOarFileSaved event is fired. 532
779 /// </summary> 533 foreach (string file in files)
780 /// <param name="guid"></param>
781 /// <param name="message"></param>
782 void EventManager_OnOarFileSaved(Guid guid, string message)
783 {
784 // Ignore if the OAR save is being done by some other part of the system
785 if (m_pendingSaves.ContainsKey(guid))
786 { 534 {
787 AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; 535 try
788 ExecuteScript(abms.Script, abms.LiveRequests[guid]); 536 {
789 m_pendingSaves.Remove(guid); 537 FileInfo fi = new FileInfo(file);
790 abms.LiveRequests.Remove(guid); 538 if (fi.CreationTime < CuttOffDate)
539 fi.Delete();
540 }
541 catch (Exception Ex)
542 {
543 m_log.Error("[AUTO BACKUP]: Error deleting old backup file '" + file + "': " + Ex.Message);
544 }
791 } 545 }
792 } 546 }
793 547
@@ -817,63 +571,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
817 return output; 571 return output;
818 } 572 }
819 573
820 /// <summary>Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.</summary>
821 private bool RunHeuristics(IScene region)
822 {
823 try
824 {
825 return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
826 }
827 catch (Exception e)
828 {
829 m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
830 return false;
831 }
832 }
833
834 /// <summary>
835 /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
836 /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
837 /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
838 /// </summary>
839 /// <param name="region"></param>
840 /// <returns>Returns true if we're not too busy; false means we've got worse time dilation than the threshold.</returns>
841 private bool RunTimeDilationHeuristic(IScene region)
842 {
843 string regionName = region.RegionInfo.RegionName;
844 return region.TimeDilation >=
845 this.m_configSource.Configs["AutoBackupModule"].GetFloat(
846 regionName + ".AutoBackupDilationThreshold", 0.5f);
847 }
848
849 /// <summary>
850 /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
851 /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
852 /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
853 /// </summary>
854 /// <param name="region"></param>
855 /// <returns>Returns true if we're not too busy; false means we've got more agents on the sim than the threshold.</returns>
856 private bool RunAgentLimitHeuristic(IScene region)
857 {
858 string regionName = region.RegionInfo.RegionName;
859 try
860 {
861 Scene scene = (Scene) region;
862 // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
863 return scene.GetRootAgentCount() <=
864 this.m_configSource.Configs["AutoBackupModule"].GetInt(
865 regionName + ".AutoBackupAgentThreshold", 10);
866 }
867 catch (InvalidCastException ice)
868 {
869 m_log.Debug(
870 "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
871 ice);
872 return true;
873 // Non-obstructionist safest answer...
874 }
875 }
876
877 /// <summary> 574 /// <summary>
878 /// Run the script or executable specified by the "AutoBackupScript" config setting. 575 /// Run the script or executable specified by the "AutoBackupScript" config setting.
879 /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. 576 /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
@@ -920,18 +617,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
920 } 617 }
921 618
922 /// <summary> 619 /// <summary>
923 /// Quickly stop all timers from firing.
924 /// </summary>
925 private void StopAllTimers()
926 {
927 foreach (Timer t in this.m_timerMap.Keys)
928 {
929 t.Close();
930 }
931 this.m_closed = true;
932 }
933
934 /// <summary>
935 /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. 620 /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
936 /// </summary> 621 /// </summary>
937 /// <param name="dirName"></param> 622 /// <param name="dirName"></param>
@@ -1033,5 +718,3 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
1033 } 718 }
1034 } 719 }
1035} 720}
1036
1037