OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
AutoBackupModule.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.Diagnostics;
31 using System.IO;
32 using System.Reflection;
33 using System.Timers;
34 using System.Text.RegularExpressions;
35 using log4net;
36 using Mono.Addins;
37 using Nini.Config;
38 using OpenSim.Framework;
39 using OpenSim.Region.Framework.Interfaces;
40 using OpenSim.Region.Framework.Scenes;
41 
42 namespace OpenSim.Region.OptionalModules.World.AutoBackup
43 {
50  public enum NamingType
51  {
52  Time,
53  Sequential,
54  Overwrite
55  }
56 
103  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")]
105  {
106  private static readonly ILog m_log =
107  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();
110  private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
111  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 
116  private delegate T DefaultGetter<T>(string settingName, T defaultValue);
117  private bool m_enabled;
118  private ICommandConsole m_console;
119  private List<Scene> m_Scenes = new List<Scene> ();
120 
121 
125  private bool m_closed;
126 
127  private IConfigSource m_configSource;
128 
132  public bool IsSharedModule
133  {
134  get { return true; }
135  }
136 
137  #region ISharedRegionModule Members
138 
142  string IRegionModuleBase.Name
143  {
144  get { return "AutoBackupModule"; }
145  }
146 
150  Type IRegionModuleBase.ReplaceableInterface
151  {
152  get { return null; }
153  }
154 
159  void IRegionModuleBase.Initialise(IConfigSource source)
160  {
161  // 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;
163  IConfig moduleConfig = source.Configs["AutoBackupModule"];
164  if (moduleConfig == null)
165  {
166  this.m_enabled = false;
167  return;
168  }
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 
182  Timer defTimer = new Timer(43200000);
183  this.m_defaultState.Timer = defTimer;
184  this.m_timers.Add(43200000, defTimer);
185  defTimer.Elapsed += this.HandleElapsed;
186  defTimer.AutoReset = true;
187  defTimer.Start();
188 
189  AutoBackupModuleState abms = this.ParseConfig(null, true);
190  m_log.Debug("[AUTO BACKUP]: Here is the default config:");
191  m_log.Debug(abms.ToString());
192  }
193 
197  void IRegionModuleBase.Close()
198  {
199  if (!this.m_enabled)
200  {
201  return;
202  }
203 
204  // We don't want any timers firing while the sim's coming down; strange things may happen.
205  this.StopAllTimers();
206  }
207 
212  void IRegionModuleBase.AddRegion (Scene scene)
213  {
214  if (!this.m_enabled) {
215  return;
216  }
217  lock (m_Scenes) {
218  m_Scenes.Add (scene);
219  }
220  m_console = MainConsole.Instance;
221 
222  m_console.Commands.AddCommand (
223  "AutoBackup", false, "dobackup",
224  "dobackup",
225  "do backup.", DoBackup);
226  }
227 
232  void IRegionModuleBase.RemoveRegion(Scene scene)
233  {
234  if (!this.m_enabled)
235  {
236  return;
237  }
238  m_Scenes.Remove (scene);
239  if (this.m_states.ContainsKey(scene))
240  {
241  AutoBackupModuleState abms = this.m_states[scene];
242 
243  // Remove this scene out of the timer map list
245  List<IScene> list = this.m_timerMap[timer];
246  list.Remove(scene);
247 
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  }
257  }
258 
264  void IRegionModuleBase.RegionLoaded(Scene scene)
265  {
266  if (!this.m_enabled)
267  {
268  return;
269  }
270 
271  // This really ought not to happen, but just in case, let's pretend it didn't...
272  if (scene == null)
273  {
274  return;
275  }
276 
277  AutoBackupModuleState abms = this.ParseConfig(scene, false);
278  m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
279  m_log.Debug((abms == null ? "DEFAULT" : abms.ToString()));
280 
281  m_states.Add(scene, abms);
282  }
283 
287  void ISharedRegionModule.PostInitialise()
288  {
289  }
290 
291  #endregion
292 
293  private void DoBackup (string module, string[] args)
294  {
295  if (args.Length != 2) {
296  MainConsole.Instance.OutputFormat ("Usage: dobackup <regionname>");
297  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 
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  {
349  // defaultState would be disabled too if the section doesn't exist.
350  state = this.m_defaultState;
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  }
360 
361  if (state != null)
362  {
363  state.Enabled = tmpEnabled;
364  }
365 
366  // If you don't want AutoBackup, we stop.
367  if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled))
368  {
369  return state;
370  }
371  else
372  {
373  m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
374  }
375 
376  // Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
377  double interval =
378  this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes,
379  config, regionConfig) * 60000.0;
380  if (state == null && interval != this.m_defaultState.IntervalMinutes * 60000.0)
381  {
382  state = new AutoBackupModuleState();
383  }
384 
385  if (this.m_timers.ContainsKey(interval))
386  {
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 
415  // Add the current region to the list of regions tied to this timer.
416  if (scene != null)
417  {
418  if (state != null)
419  {
420  if (this.m_timerMap.ContainsKey(state.Timer))
421  {
422  this.m_timerMap[state.Timer].Add(scene);
423  }
424  else
425  {
426  List<IScene> scns = new List<IScene>(1);
427  scns.Add(scene);
428  this.m_timerMap.Add(state.Timer, scns);
429  }
430  }
431  else
432  {
433  if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer))
434  {
435  this.m_timerMap[this.m_defaultState.Timer].Add(scene);
436  }
437  else
438  {
439  List<IScene> scns = new List<IScene>(1);
440  scns.Add(scene);
441  this.m_timerMap.Add(this.m_defaultState.Timer, scns);
442  }
443  }
444  }
445 
446  bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck",
447  this.m_defaultState.BusyCheck, config, regionConfig);
448  if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
449  {
450  state = new AutoBackupModuleState();
451  }
452 
453  if (state != null)
454  {
455  state.BusyCheck = tmpBusyCheck;
456  }
457 
458  // Included Option To Skip Assets
459  bool tmpSkipAssets = ResolveBoolean("AutoBackupSkipAssets",
460  this.m_defaultState.SkipAssets, config, regionConfig);
461  if (state == null && tmpSkipAssets != this.m_defaultState.SkipAssets)
462  {
463  state = new AutoBackupModuleState();
464  }
465 
466  if (state != null)
467  {
468  state.SkipAssets = tmpSkipAssets;
469  }
470 
471  // How long to keep backup files in days, 0 Disables this feature
472  int tmpKeepFilesForDays = ResolveInt("AutoBackupKeepFilesForDays",
473  this.m_defaultState.KeepFilesForDays, config, regionConfig);
474  if (state == null && tmpKeepFilesForDays != this.m_defaultState.KeepFilesForDays)
475  {
476  state = new AutoBackupModuleState();
477  }
478 
479  if (state != null)
480  {
481  state.KeepFilesForDays = tmpKeepFilesForDays;
482  }
483 
484  // Set file naming algorithm
485  string stmpNamingType = ResolveString("AutoBackupNaming",
486  this.m_defaultState.NamingType.ToString(), config, regionConfig);
487  NamingType tmpNamingType;
488  if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
489  {
490  tmpNamingType = NamingType.Time;
491  }
492  else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
493  {
494  tmpNamingType = NamingType.Sequential;
495  }
496  else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
497  {
498  tmpNamingType = NamingType.Overwrite;
499  }
500  else
501  {
502  m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
503  stmpNamingType);
504  tmpNamingType = NamingType.Time;
505  }
506 
507  if (state == null && tmpNamingType != this.m_defaultState.NamingType)
508  {
509  state = new AutoBackupModuleState();
510  }
511 
512  if (state != null)
513  {
514  state.NamingType = tmpNamingType;
515  }
516 
517  string tmpScript = ResolveString("AutoBackupScript",
518  this.m_defaultState.Script, config, regionConfig);
519  if (state == null && tmpScript != this.m_defaultState.Script)
520  {
521  state = new AutoBackupModuleState();
522  }
523 
524  if (state != null)
525  {
526  state.Script = tmpScript;
527  }
528 
529  string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig);
530  if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
531  {
532  state = new AutoBackupModuleState();
533  }
534 
535  if (state != null)
536  {
537  state.BackupDir = tmpBackupDir;
538  // Let's give the user some convenience and auto-mkdir
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 
560  if(state == null)
561  return m_defaultState;
562 
563  return state;
564  }
565 
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 
594  private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local)
595  {
596  if (local != null)
597  {
598  return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue));
599  }
600  else
601  {
602  return global.GetDouble(settingName, defaultValue);
603  }
604  }
605 
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  }
625 
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 
651  private void HandleElapsed(object sender, ElapsedEventArgs e)
652  {
653  // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region
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;
664  }
665  bool heuristicsRun = false;
666  bool heuristicsPassed = false;
667  if (!this.m_timerMap.ContainsKey((Timer) sender))
668  {
669  m_log.Debug("[AUTO BACKUP]: Code-up error: timerMap doesn't contain timer " + sender);
670  }
671 
672  List<IScene> tmap = this.m_timerMap[(Timer) sender];
673  if (tmap != null && tmap.Count > 0)
674  {
675  foreach (IScene scene in tmap)
676  {
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  }
710  }
711 
716  private void DoRegionBackup(IScene scene)
717  {
718  if (!scene.Ready)
719  {
720  // We won't backup a region that isn't operating normally.
721  m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
722  " because its status is " + scene.RegionStatus);
723  return;
724  }
725 
726  AutoBackupModuleState state = this.m_states[scene];
727  IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
728  string savePath = BuildOarPath(scene.RegionInfo.RegionName,
729  state.BackupDir,
730  state.NamingType);
731  if (savePath == null)
732  {
733  m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
734  return;
735  }
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 
741  m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName);
742 
743  // Must pass options, even if dictionary is empty!
744  Dictionary<string, object> options = new Dictionary<string, object>();
745 
746  if (state.SkipAssets)
747  options["noassets"] = true;
748 
749  iram.ArchiveRegion(savePath, guid, options);
750  }
751 
752  // For the given state, remove backup files older than the states KeepFilesForDays property
753  private void RemoveOldFiles(AutoBackupModuleState state)
754  {
755  // 0 Means Disabled, Keep Files Indefinitely
756  if (state.KeepFilesForDays > 0)
757  {
758  string[] files = Directory.GetFiles(state.BackupDir, "*.oar");
759  DateTime CuttOffDate = DateTime.Now.AddDays(0 - state.KeepFilesForDays);
760 
761  foreach (string file in files)
762  {
763  try
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  }
775  }
776 
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  {
787  AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])];
788  ExecuteScript(abms.Script, abms.LiveRequests[guid]);
789  m_pendingSaves.Remove(guid);
790  abms.LiveRequests.Remove(guid);
791  }
792  }
793 
797  private static string GetTimeString()
798  {
799  StringWriter sw = new StringWriter();
800  sw.Write("_");
801  DateTime now = DateTime.Now;
802  sw.Write(now.Year);
803  sw.Write("y_");
804  sw.Write(now.Month);
805  sw.Write("M_");
806  sw.Write(now.Day);
807  sw.Write("d_");
808  sw.Write(now.Hour);
809  sw.Write("h_");
810  sw.Write(now.Minute);
811  sw.Write("m_");
812  sw.Write(now.Second);
813  sw.Write("s");
814  sw.Flush();
815  string output = sw.ToString();
816  sw.Close();
817  return output;
818  }
819 
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 
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 
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 
884  private static void ExecuteScript(string scriptName, string savePath)
885  {
886  // Do nothing if there's no script.
887  if (scriptName == null || scriptName.Length <= 0)
888  {
889  return;
890  }
891 
892  try
893  {
894  FileInfo fi = new FileInfo(scriptName);
895  if (fi.Exists)
896  {
897  ProcessStartInfo psi = new ProcessStartInfo(scriptName);
898  psi.Arguments = savePath;
899  psi.CreateNoWindow = true;
900  Process proc = Process.Start(psi);
901  proc.ErrorDataReceived += HandleProcErrorDataReceived;
902  }
903  }
904  catch (Exception e)
905  {
906  m_log.Warn(
907  "Exception encountered when trying to run script for oar backup " + savePath, e);
908  }
909  }
910 
916  private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
917  {
918  m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
919  " is yacking on stderr: " + e.Data);
920  }
921 
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 
940  private static string GetNextFile(string dirName, string regionName)
941  {
942  FileInfo uniqueFile = null;
943  long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
944  biggestExistingFile++;
945  // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
946  uniqueFile =
947  new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
948  biggestExistingFile + ".oar");
949  return uniqueFile.FullName;
950  }
951 
959  private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
960  {
961  FileInfo path = null;
962  switch (naming)
963  {
964  case NamingType.Overwrite:
965  path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
966  return path.FullName;
967  case NamingType.Time:
968  path =
969  new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
970  GetTimeString() + ".oar");
971  return path.FullName;
972  case NamingType.Sequential:
973  // All codepaths in GetNextFile should return a file name ending in .oar
974  path = new FileInfo(GetNextFile(baseDir, regionName));
975  return path.FullName;
976  default:
977  m_log.Warn("VERY BAD: Unhandled case element " + naming);
978  break;
979  }
980 
981  return null;
982  }
983 
990  private static long GetNextOarFileNumber(string dirName, string regionName)
991  {
992  long retval = 1;
993 
994  DirectoryInfo di = new DirectoryInfo(dirName);
995  FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
996  Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
997 
998  if (fi.LongLength > 0)
999  {
1000  long subtract = 1L;
1001  bool worked = false;
1002  Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
1003 
1004  while (!worked && subtract <= fi.LongLength)
1005  {
1006  // Pick the file with the last natural ordering
1007  string biggestFileName = fi[fi.LongLength - subtract].Name;
1008  MatchCollection matches = reg.Matches(biggestFileName);
1009  long l = 1;
1010  if (matches.Count > 0 && matches[0].Groups.Count > 0)
1011  {
1012  try
1013  {
1014  long.TryParse(matches[0].Groups[1].Value, out l);
1015  retval = l;
1016  worked = true;
1017  }
1018  catch (FormatException fe)
1019  {
1020  m_log.Warn(
1021  "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
1022  fe);
1023  subtract++;
1024  }
1025  }
1026  else
1027  {
1028  subtract++;
1029  }
1030  }
1031  }
1032  return retval;
1033  }
1034  }
1035 }
1036 
1037 
RegionInfo RegionInfo
Definition: IScene.cs:64
AutoBackupModuleState: Auto-Backup state for one region (scene). If you use this class in any way out...
bool Ready
Is this region ready for use?
Definition: IScene.cs:80
System.Timers.Timer Timer
AutoBackupModule: save OAR region backups to disk periodically
NamingType
Choose between ways of naming the backup files that are generated.
Interactive OpenSim region server
Definition: OpenSim.cs:55