AutoBackupModule.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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. using System;
  28. using System.Collections.Generic;
  29. using System.Diagnostics;
  30. using System.IO;
  31. using System.Reflection;
  32. using System.Timers;
  33. using System.Text.RegularExpressions;
  34. using log4net;
  35. using Mono.Addins;
  36. using Nini.Config;
  37. using OpenSim.Framework;
  38. using OpenSim.Region.Framework.Interfaces;
  39. using OpenSim.Region.Framework.Scenes;
  40. namespace OpenSim.Region.OptionalModules.World.AutoBackup
  41. {
  42. /// <summary>
  43. /// Choose between ways of naming the backup files that are generated.
  44. /// </summary>
  45. /// <remarks>Time: OARs are named by a timestamp.
  46. /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.)
  47. /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made.</remarks>
  48. public enum NamingType
  49. {
  50. Time,
  51. Sequential,
  52. Overwrite
  53. }
  54. ///<summary>
  55. /// AutoBackupModule: save OAR region backups to disk periodically
  56. /// </summary>
  57. /// <remarks>
  58. /// Config Settings Documentation.
  59. /// Configuration setting can be specified in two places: OpenSim.ini and/or Regions.ini.
  60. ///
  61. /// OpenSim.ini only settings section [AutoBackupModule]
  62. /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module.
  63. /// if false module is disable and all rest is ignored
  64. /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
  65. /// The number of minutes between each backup attempt.
  66. /// AutoBackupDir: String. Default: "." (the current directory).
  67. /// A directory (absolute or relative) where backups should be saved.
  68. /// AutoBackupKeepFilesForDays remove files older than this number of days. 0 disables
  69. ///
  70. /// Next can be set on OpenSim.ini, as default, and or per region in Regions.ini
  71. /// Region-specific settings take precedence.
  72. ///
  73. /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
  74. /// controls backup per region, with default optionaly set on OpenSim.ini
  75. /// AutoBackupSkipAssets
  76. /// 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
  77. /// AutoBackupKeepFilesForDays
  78. /// 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
  79. /// AutoBackupScript: String. Default: not specified (disabled).
  80. /// File path to an executable script or binary to run when an automatic backup is taken.
  81. /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
  82. /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
  83. /// argv[1] of the executed file/script will be the file name of the generated OAR.
  84. /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
  85. /// AutoBackupNaming: string. Default: Time.
  86. /// One of three strings (case insensitive):
  87. /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
  88. /// "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.
  89. /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
  90. /// </remarks>
  91. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")]
  92. public class AutoBackupModule : ISharedRegionModule
  93. {
  94. private static readonly ILog m_log =
  95. LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  96. private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
  97. private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
  98. new Dictionary<IScene, AutoBackupModuleState>(1);
  99. private delegate T DefaultGetter<T>(string settingName, T defaultValue);
  100. private bool m_enabled;
  101. private ICommandConsole m_console;
  102. private List<Scene> m_Scenes = new List<Scene> ();
  103. private Timer m_masterTimer;
  104. private bool m_busy;
  105. private int m_KeepFilesForDays = -1;
  106. private string m_backupDir;
  107. private bool m_doneFirst;
  108. private double m_baseInterval;
  109. private IConfigSource m_configSource;
  110. /// <summary>
  111. /// Required by framework.
  112. /// </summary>
  113. public bool IsSharedModule
  114. {
  115. get { return true; }
  116. }
  117. #region ISharedRegionModule Members
  118. /// <summary>
  119. /// Identifies the module to the system.
  120. /// </summary>
  121. public string Name
  122. {
  123. get { return "AutoBackupModule"; }
  124. }
  125. /// <summary>
  126. /// We don't implement an interface, this is a single-use module.
  127. /// </summary>
  128. public Type ReplaceableInterface
  129. {
  130. get { return null; }
  131. }
  132. /// <summary>
  133. /// Called once in the lifetime of the module at startup.
  134. /// </summary>
  135. /// <param name="source">The input config source for OpenSim.ini.</param>
  136. public void Initialise(IConfigSource source)
  137. {
  138. // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
  139. m_configSource = source;
  140. IConfig moduleConfig = source.Configs["AutoBackupModule"];
  141. if (moduleConfig == null)
  142. {
  143. m_enabled = false;
  144. return;
  145. }
  146. m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
  147. if(!m_enabled)
  148. return;
  149. ParseDefaultConfig(moduleConfig);
  150. if(!m_enabled)
  151. return;
  152. m_log.Debug("[AUTO BACKUP]: Default config:");
  153. m_log.Debug(m_defaultState.ToString());
  154. m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
  155. m_masterTimer = new Timer();
  156. m_masterTimer.Interval = m_baseInterval;
  157. m_masterTimer.Elapsed += HandleElapsed;
  158. m_masterTimer.AutoReset = false;
  159. m_console = MainConsole.Instance;
  160. m_console.Commands.AddCommand (
  161. "AutoBackup", true, "dooarbackup",
  162. "dooarbackup <regionName> | ALL",
  163. "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);
  164. m_busy = true;
  165. }
  166. /// <summary>
  167. /// Called once at de-init (sim shutting down).
  168. /// </summary>
  169. public void Close()
  170. {
  171. if (!m_enabled)
  172. return;
  173. // We don't want any timers firing while the sim's coming down; strange things may happen.
  174. m_masterTimer.Dispose();
  175. }
  176. /// <summary>
  177. /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded.
  178. /// </summary>
  179. /// <param name="scene"></param>
  180. public void AddRegion (Scene scene)
  181. {
  182. if (!m_enabled)
  183. return;
  184. lock (m_Scenes)
  185. m_Scenes.Add (scene);
  186. }
  187. /// <summary>
  188. /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene.
  189. /// </summary>
  190. /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param>
  191. public void RemoveRegion(Scene scene)
  192. {
  193. if (m_enabled)
  194. return;
  195. lock(m_Scenes)
  196. {
  197. if (m_states.ContainsKey(scene))
  198. m_states.Remove(scene);
  199. m_Scenes.Remove(scene);
  200. }
  201. }
  202. /// <summary>
  203. /// Most interesting/complex code paths in AutoBackup begin here.
  204. /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc.
  205. /// </summary>
  206. /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param>
  207. public void RegionLoaded(Scene scene)
  208. {
  209. if (!m_enabled)
  210. return;
  211. // This really ought not to happen, but just in case, let's pretend it didn't...
  212. if (scene == null)
  213. return;
  214. AutoBackupModuleState abms = ParseConfig(scene);
  215. if(abms == null)
  216. {
  217. m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
  218. m_log.Debug("DEFAULT");
  219. abms = new AutoBackupModuleState(m_defaultState);
  220. }
  221. else
  222. {
  223. m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
  224. m_log.Debug(abms.ToString());
  225. }
  226. m_states.Add(scene, abms);
  227. m_busy = false;
  228. m_masterTimer.Start();
  229. }
  230. /// <summary>
  231. /// Currently a no-op.
  232. /// </summary>
  233. public void PostInitialise()
  234. {
  235. }
  236. #endregion
  237. private void DoBackup (string module, string[] args)
  238. {
  239. if (!m_enabled)
  240. return;
  241. if (args.Length != 2)
  242. {
  243. MainConsole.Instance.Output("Usage: dooarbackup <regionname>");
  244. return;
  245. }
  246. if(m_busy)
  247. {
  248. MainConsole.Instance.Output("Already doing a backup, please try later");
  249. return;
  250. }
  251. m_masterTimer.Stop();
  252. m_busy = true;
  253. bool found = false;
  254. string name = args [1];
  255. Scene[] scenes;
  256. lock (m_Scenes)
  257. scenes = m_Scenes.ToArray();
  258. if(scenes == null)
  259. return;
  260. Scene s;
  261. try
  262. {
  263. if(name == "ALL")
  264. {
  265. for(int i = 0; i < scenes.Length; i++)
  266. {
  267. s = scenes[i];
  268. DoRegionBackup(s);
  269. if (!m_enabled)
  270. return;
  271. }
  272. return;
  273. }
  274. for(int i = 0; i < scenes.Length; i++)
  275. {
  276. s = scenes[i];
  277. if (s.Name == name)
  278. {
  279. found = true;
  280. DoRegionBackup(s);
  281. break;
  282. }
  283. }
  284. }
  285. catch { }
  286. finally
  287. {
  288. if (m_enabled)
  289. m_masterTimer.Start();
  290. m_busy = false;
  291. }
  292. if (!found)
  293. MainConsole.Instance.Output("No such region {0}. Nothing to backup", null, name);
  294. }
  295. private void ParseDefaultConfig(IConfig config)
  296. {
  297. m_backupDir = ".";
  298. string backupDir = config.GetString("AutoBackupDir", ".");
  299. if (backupDir != ".")
  300. {
  301. try
  302. {
  303. DirectoryInfo dirinfo = new DirectoryInfo(backupDir);
  304. if (!dirinfo.Exists)
  305. dirinfo.Create();
  306. }
  307. catch (Exception e)
  308. {
  309. m_enabled = false;
  310. m_log.WarnFormat("[AUTO BACKUP]: Error accessing backup folder {0}. Module disabled. {1}",
  311. backupDir, e);
  312. return;
  313. }
  314. }
  315. m_backupDir = backupDir;
  316. double interval = config.GetDouble("AutoBackupInterval", 720);
  317. interval *= 60000.0;
  318. m_baseInterval = interval;
  319. // How long to keep backup files in days, 0 Disables this feature
  320. m_KeepFilesForDays = config.GetInt("AutoBackupKeepFilesForDays",m_KeepFilesForDays);
  321. m_defaultState.Enabled = config.GetBoolean("AutoBackup", m_defaultState.Enabled);
  322. m_defaultState.SkipAssets = config.GetBoolean("AutoBackupSkipAssets",m_defaultState.SkipAssets);
  323. // Set file naming algorithm
  324. string stmpNamingType = config.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
  325. NamingType tmpNamingType;
  326. if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
  327. tmpNamingType = NamingType.Time;
  328. else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
  329. tmpNamingType = NamingType.Sequential;
  330. else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
  331. tmpNamingType = NamingType.Overwrite;
  332. else
  333. {
  334. m_log.Warn("Unknown naming type specified for Default");
  335. tmpNamingType = NamingType.Time;
  336. }
  337. m_defaultState.NamingType = tmpNamingType;
  338. m_defaultState.Script = config.GetString("AutoBackupScript", m_defaultState.Script);
  339. }
  340. /// <summary>
  341. /// Set up internal state for a given scene. Fairly complex code.
  342. /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene.
  343. /// </summary>
  344. /// <param name="scene">The scene to look at.</param>
  345. /// <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>
  346. /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
  347. private AutoBackupModuleState ParseConfig(IScene scene)
  348. {
  349. if(scene == null)
  350. return null;
  351. string sRegionName;
  352. AutoBackupModuleState state = null;
  353. sRegionName = scene.RegionInfo.RegionName;
  354. // Read the config settings and set variables.
  355. IConfig regionConfig = scene.Config.Configs[sRegionName];
  356. if (regionConfig == null)
  357. return null;
  358. state = new AutoBackupModuleState();
  359. state.Enabled = regionConfig.GetBoolean("AutoBackup", m_defaultState.Enabled);
  360. // Included Option To Skip Assets
  361. state.SkipAssets = regionConfig.GetBoolean("AutoBackupSkipAssets", m_defaultState.SkipAssets);
  362. // Set file naming algorithm
  363. string stmpNamingType = regionConfig.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
  364. NamingType tmpNamingType;
  365. if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
  366. tmpNamingType = NamingType.Time;
  367. else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
  368. tmpNamingType = NamingType.Sequential;
  369. else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
  370. tmpNamingType = NamingType.Overwrite;
  371. else
  372. {
  373. m_log.Warn("Unknown naming type specified for region " + sRegionName + ": " +
  374. stmpNamingType);
  375. tmpNamingType = NamingType.Time;
  376. }
  377. m_defaultState.NamingType = tmpNamingType;
  378. state.Script = regionConfig.GetString("AutoBackupScript", m_defaultState.Script);
  379. return state;
  380. }
  381. /// <summary>
  382. /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
  383. /// </summary>
  384. /// <param name="sender"></param>
  385. /// <param name="e"></param>
  386. private void HandleElapsed(object sender, ElapsedEventArgs e)
  387. {
  388. if (!m_enabled || m_busy)
  389. return;
  390. m_busy = true;
  391. if(m_doneFirst && m_KeepFilesForDays > 0)
  392. RemoveOldFiles();
  393. foreach (IScene scene in m_Scenes)
  394. {
  395. if (!m_enabled)
  396. return;
  397. DoRegionBackup(scene);
  398. }
  399. if (m_enabled)
  400. {
  401. m_masterTimer.Start();
  402. m_busy = false;
  403. }
  404. m_doneFirst = true;
  405. }
  406. /// <summary>
  407. /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable).
  408. /// </summary>
  409. /// <param name="scene"></param>
  410. private void DoRegionBackup(IScene scene)
  411. {
  412. if (!scene.Ready)
  413. {
  414. // We won't backup a region that isn't operating normally.
  415. m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
  416. " because its status is " + scene.RegionStatus);
  417. return;
  418. }
  419. m_busy = true;
  420. AutoBackupModuleState state;
  421. if(!m_states.TryGetValue(scene, out state))
  422. return;
  423. if(state == null || !state.Enabled)
  424. return;
  425. IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
  426. if(iram == null)
  427. return;
  428. string savePath = BuildOarPath(scene.RegionInfo.RegionName,
  429. m_backupDir,
  430. state.NamingType);
  431. if (savePath == null)
  432. {
  433. m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
  434. return;
  435. }
  436. Guid guid = Guid.NewGuid();
  437. m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName);
  438. // Must pass options, even if dictionary is empty!
  439. Dictionary<string, object> options = new Dictionary<string, object>();
  440. if (state.SkipAssets)
  441. options["noassets"] = true;
  442. iram.ArchiveRegion(savePath, guid, options);
  443. ExecuteScript(state.Script, savePath);
  444. }
  445. // For the given state, remove backup files older than the states KeepFilesForDays property
  446. private void RemoveOldFiles()
  447. {
  448. string[] files;
  449. try
  450. {
  451. files = Directory.GetFiles(m_backupDir, "*.oar");
  452. }
  453. catch (Exception Ex)
  454. {
  455. m_log.Error("[AUTO BACKUP]: Error reading backup folder " + m_backupDir + ": " + Ex.Message);
  456. return;
  457. }
  458. DateTime CuttOffDate = DateTime.Now.AddDays(-m_KeepFilesForDays);
  459. foreach (string file in files)
  460. {
  461. try
  462. {
  463. FileInfo fi = new FileInfo(file);
  464. if (fi.CreationTime < CuttOffDate)
  465. fi.Delete();
  466. }
  467. catch (Exception Ex)
  468. {
  469. m_log.Error("[AUTO BACKUP]: Error deleting old backup file '" + file + "': " + Ex.Message);
  470. }
  471. }
  472. }
  473. /// <summary>This format may turn out to be too unwieldy to keep...
  474. /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
  475. /// Sequential numbers, right? We support those, too!</summary>
  476. private static string GetTimeString()
  477. {
  478. StringWriter sw = new StringWriter();
  479. sw.Write("_");
  480. DateTime now = DateTime.Now;
  481. sw.Write(now.Year);
  482. sw.Write("y_");
  483. sw.Write(now.Month);
  484. sw.Write("M_");
  485. sw.Write(now.Day);
  486. sw.Write("d_");
  487. sw.Write(now.Hour);
  488. sw.Write("h_");
  489. sw.Write(now.Minute);
  490. sw.Write("m_");
  491. sw.Write(now.Second);
  492. sw.Write("s");
  493. sw.Flush();
  494. string output = sw.ToString();
  495. sw.Close();
  496. return output;
  497. }
  498. /// <summary>
  499. /// Run the script or executable specified by the "AutoBackupScript" config setting.
  500. /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
  501. /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions.
  502. /// </summary>
  503. /// <param name="scriptName"></param>
  504. /// <param name="savePath"></param>
  505. private static void ExecuteScript(string scriptName, string savePath)
  506. {
  507. // Do nothing if there's no script.
  508. if (scriptName == null || scriptName.Length <= 0)
  509. {
  510. return;
  511. }
  512. try
  513. {
  514. FileInfo fi = new FileInfo(scriptName);
  515. if (fi.Exists)
  516. {
  517. ProcessStartInfo psi = new ProcessStartInfo(scriptName);
  518. psi.Arguments = savePath;
  519. psi.CreateNoWindow = true;
  520. Process proc = Process.Start(psi);
  521. proc.ErrorDataReceived += HandleProcErrorDataReceived;
  522. }
  523. }
  524. catch (Exception e)
  525. {
  526. m_log.Warn(
  527. "Exception encountered when trying to run script for oar backup " + savePath, e);
  528. }
  529. }
  530. /// <summary>
  531. /// Called if a running script process writes to stderr.
  532. /// </summary>
  533. /// <param name="sender"></param>
  534. /// <param name="e"></param>
  535. private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
  536. {
  537. m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
  538. " is yacking on stderr: " + e.Data);
  539. }
  540. /// <summary>
  541. /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
  542. /// </summary>
  543. /// <param name="dirName"></param>
  544. /// <param name="regionName"></param>
  545. /// <returns></returns>
  546. private static string GetNextFile(string dirName, string regionName)
  547. {
  548. FileInfo uniqueFile = null;
  549. long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
  550. biggestExistingFile++;
  551. // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
  552. uniqueFile =
  553. new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
  554. biggestExistingFile + ".oar");
  555. return uniqueFile.FullName;
  556. }
  557. /// <summary>
  558. /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants.
  559. /// </summary>
  560. /// <param name="regionName">Name of the region to save.</param>
  561. /// <param name="baseDir">Absolute or relative path to the directory where the file should reside.</param>
  562. /// <param name="naming">The naming scheme for the file name.</param>
  563. /// <returns></returns>
  564. private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
  565. {
  566. FileInfo path = null;
  567. switch (naming)
  568. {
  569. case NamingType.Overwrite:
  570. path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
  571. return path.FullName;
  572. case NamingType.Time:
  573. path =
  574. new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
  575. GetTimeString() + ".oar");
  576. return path.FullName;
  577. case NamingType.Sequential:
  578. // All codepaths in GetNextFile should return a file name ending in .oar
  579. path = new FileInfo(GetNextFile(baseDir, regionName));
  580. return path.FullName;
  581. default:
  582. m_log.Warn("VERY BAD: Unhandled case element " + naming);
  583. break;
  584. }
  585. return null;
  586. }
  587. /// <summary>
  588. /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile).
  589. /// </summary>
  590. /// <param name="dirName"></param>
  591. /// <param name="regionName"></param>
  592. /// <returns></returns>
  593. private static long GetNextOarFileNumber(string dirName, string regionName)
  594. {
  595. long retval = 1;
  596. DirectoryInfo di = new DirectoryInfo(dirName);
  597. FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
  598. Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
  599. if (fi.LongLength > 0)
  600. {
  601. long subtract = 1L;
  602. bool worked = false;
  603. Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
  604. while (!worked && subtract <= fi.LongLength)
  605. {
  606. // Pick the file with the last natural ordering
  607. string biggestFileName = fi[fi.LongLength - subtract].Name;
  608. MatchCollection matches = reg.Matches(biggestFileName);
  609. long l = 1;
  610. if (matches.Count > 0 && matches[0].Groups.Count > 0)
  611. {
  612. try
  613. {
  614. long.TryParse(matches[0].Groups[1].Value, out l);
  615. retval = l;
  616. worked = true;
  617. }
  618. catch (FormatException fe)
  619. {
  620. m_log.Warn(
  621. "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
  622. fe);
  623. subtract++;
  624. }
  625. }
  626. else
  627. {
  628. subtract++;
  629. }
  630. }
  631. }
  632. return retval;
  633. }
  634. }
  635. }