aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region')
-rw-r--r--OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml1
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs613
2 files changed, 614 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
index 5eea286..8691343 100644
--- a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
+++ b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
@@ -13,5 +13,6 @@
13 <RegionModule id="Concierge" type="OpenSim.Region.OptionalModules.Avatar.Concierge.ConciergeModule" /> 13 <RegionModule id="Concierge" type="OpenSim.Region.OptionalModules.Avatar.Concierge.ConciergeModule" />
14 <RegionModule id="VivoxVoice" type="OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice.VivoxVoiceModule" /> 14 <RegionModule id="VivoxVoice" type="OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice.VivoxVoiceModule" />
15 <RegionModule id="WorldViewModule" type="OpenSim.Region.OptionalModules.World.WorldView.WorldViewModule" /> 15 <RegionModule id="WorldViewModule" type="OpenSim.Region.OptionalModules.World.WorldView.WorldViewModule" />
16 <RegionModule id="AutoBackupModule" type="OpenSim.Region.OptionalModules.World.AutoBackup.AutoBackupModule" />
16 </Extension> 17 </Extension>
17</Addin> 18</Addin>
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
new file mode 100644
index 0000000..7660342
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
@@ -0,0 +1,613 @@
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
28using System;
29using System.IO;
30using System.Timers;
31using System.Diagnostics;
32using System.Reflection;
33using System.Collections.Generic;
34using log4net;
35using Nini;
36using Nini.Config;
37using OpenSim.Framework;
38using OpenSim.Framework.Statistics;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42
43/*
44 * Config Settings Documentation.
45 * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options:
46 * In the [Modules] section:
47 * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini!
48 * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section.
49 * VERY IMPORTANT: You must create the key name as follows: <Region Name>.<Key Name>
50 * Example: My region is named Foo.
51 * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini.
52 * AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
53 * This is the only required option for enabling auto-backup; the other options have sane defaults.
54 * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
55 * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
56 * The number of minutes between each backup attempt.
57 * If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
58 * AutoBackupBusyCheck: True/False. Default: True.
59 * If True, we will only take an auto-backup if a set of conditions are met.
60 * These conditions are heuristics to try and avoid taking a backup when the sim is busy.
61 * AutoBackupScript: String. Default: not specified (disabled).
62 * File path to an executable script or binary to run when an automatic backup is taken.
63 * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
64 * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
65 * argv[1] of the executed file/script will be the file name of the generated OAR.
66 * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
67 * AutoBackupNaming: string. Default: Time.
68 * One of three strings (case insensitive):
69 * "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
70 * "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.
71 * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
72 * AutoBackupDir: String. Default: "." (the current directory).
73 * A directory (absolute or relative) where backups should be saved.
74 * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
75 * If the time dilation is below this value, don't take a backup right now.
76 * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
77 * If the number of agents is greater than this value, don't take a backup right now.
78 * */
79
80namespace OpenSim.Region.OptionalModules.World.AutoBackup
81{
82
83 public enum NamingType
84 {
85 TIME,
86 SEQUENTIAL,
87 OVERWRITE
88 }
89
90 public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase
91 {
92
93 private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType);
94
95 //AutoBackupModuleState: Auto-Backup state for one region (scene).
96 public class AutoBackupModuleState
97 {
98 private bool m_enabled = false;
99 private NamingType m_naming = NamingType.TIME;
100 private Timer m_timer = null;
101 private bool m_busycheck = true;
102 private string m_script = null;
103 private string m_dir = ".";
104
105 public AutoBackupModuleState ()
106 {
107
108 }
109
110 public void SetEnabled (bool b)
111 {
112 m_enabled = b;
113 }
114
115 public bool GetEnabled ()
116 {
117 return m_enabled;
118 }
119
120 public Timer GetTimer ()
121 {
122 return m_timer;
123 }
124
125 public void SetTimer (Timer t)
126 {
127 m_timer = t;
128 }
129
130 public bool GetBusyCheck ()
131 {
132 return m_busycheck;
133 }
134
135 public void SetBusyCheck (bool b)
136 {
137 m_busycheck = b;
138 }
139
140
141 public string GetScript ()
142 {
143 return m_script;
144 }
145
146 public void SetScript (string s)
147 {
148 m_script = s;
149 }
150
151 public string GetBackupDir ()
152 {
153 return m_dir;
154 }
155
156 public void SetBackupDir (string s)
157 {
158 m_dir = s;
159 }
160
161 public NamingType GetNamingType ()
162 {
163 return m_naming;
164 }
165
166 public void SetNamingType (NamingType n)
167 {
168 m_naming = n;
169 }
170 }
171
172 //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
173 //Also helps if you don't want AutoBackup at all
174 readonly Dictionary<IScene, AutoBackupModuleState> states = new Dictionary<IScene, AutoBackupModuleState> (4);
175 readonly Dictionary<double, Timer> timers = new Dictionary<double, Timer> (1);
176 readonly Dictionary<Timer, List<IScene>> timerMap = new Dictionary<Timer, List<IScene>> (1);
177 private IConfigSource m_configSource = null;
178 private bool m_Enabled = false;
179 //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
180 private bool m_closed = false;
181 //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP.
182 //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down.
183 public AutoBackupModule ()
184 {
185
186 }
187
188 #region IRegionModuleBase implementation
189 void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source)
190 {
191 //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
192 m_configSource = source;
193 IConfig moduleConfig = source.Configs["Modules"];
194 if (moduleConfig != null) {
195 m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false);
196 if (m_Enabled) {
197 m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled");
198 }
199 }
200 }
201
202 void IRegionModuleBase.Close ()
203 {
204 if (!m_Enabled)
205 return;
206
207 //We don't want any timers firing while the sim's coming down; strange things may happen.
208 StopAllTimers ();
209 }
210
211 void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene)
212 {
213 //NO-OP. Wait for the region to be loaded.
214 }
215
216 void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene)
217 {
218 if (!m_Enabled)
219 return;
220
221 AutoBackupModuleState abms = states[scene];
222 Timer timer = abms.GetTimer ();
223 List<IScene> list = timerMap[timer];
224 list.Remove (scene);
225 if (list.Count == 0) {
226 timerMap.Remove (timer);
227 timers.Remove (timer.Interval);
228 timer.Close ();
229 }
230 states.Remove(scene);
231 }
232
233 void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene)
234 {
235 if (!m_Enabled)
236 return;
237
238 //This really ought not to happen, but just in case, let's pretend it didn't...
239 if (scene == null)
240 return;
241
242 string sRegionName = scene.RegionInfo.RegionName;
243 AutoBackupModuleState st = new AutoBackupModuleState ();
244 states.Add (scene, st);
245
246 //Read the config settings and set variables.
247 IConfig config = m_configSource.Configs["AutoBackupModule"];
248 if (config == null) {
249 //No config settings for any regions, let's just give up.
250 st.SetEnabled (false);
251 m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled.");
252 return;
253 }
254 st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false));
255 //If you don't want AutoBackup, we stop.
256 if (!st.GetEnabled ()) {
257 m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled.");
258 return;
259 } else {
260 m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED.");
261 }
262
263 //Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
264 double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000;
265 if (timers.ContainsKey (interval)) {
266 st.SetTimer (timers[interval]);
267 m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName);
268 } else {
269 //0 or negative interval == do nothing.
270 if (interval <= 0.0) {
271 st.SetEnabled (false);
272 return;
273 }
274 Timer tim = new Timer (interval);
275 st.SetTimer (tim);
276 //Milliseconds -> minutes
277 timers.Add (interval, tim);
278 tim.Elapsed += HandleElapsed;
279 tim.AutoReset = true;
280 tim.Start ();
281 //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName);
282 }
283
284 //Add the current region to the list of regions tied to this timer.
285 if (timerMap.ContainsKey (st.GetTimer ())) {
286 timerMap[st.GetTimer ()].Add (scene);
287 } else {
288 List<IScene> scns = new List<IScene> (1);
289 scns.Add (scene);
290 timerMap.Add (st.GetTimer (), scns);
291 }
292
293 st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true));
294
295 //Set file naming algorithm
296 string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time");
297 if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) {
298 st.SetNamingType (NamingType.TIME);
299 } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) {
300 st.SetNamingType (NamingType.SEQUENTIAL);
301 } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) {
302 st.SetNamingType (NamingType.OVERWRITE);
303 } else {
304 m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype);
305 st.SetNamingType (NamingType.TIME);
306 }
307
308 st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null));
309 st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", "."));
310
311 //Let's give the user *one* convenience and auto-mkdir
312 if (st.GetBackupDir () != ".") {
313 try {
314 DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ());
315 if (!dirinfo.Exists) {
316 dirinfo.Create ();
317 }
318 } catch (Exception e) {
319 m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e);
320 }
321 }
322 }
323
324 void HandleElapsed (object sender, ElapsedEventArgs e)
325 {
326 //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region
327 //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
328 //check whether the region is too busy! Especially on sims with LOTS of regions.
329 //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
330 // but would allow us to be semantically correct while being easier on perf.
331 //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
332 if (m_closed)
333 return;
334 bool heuristicsRun = false;
335 bool heuristicsPassed = false;
336 if (!timerMap.ContainsKey ((Timer)sender)) {
337 m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ());
338 }
339 foreach (IScene scene in timerMap[(Timer)sender]) {
340 AutoBackupModuleState state = states[scene];
341 bool heuristics = state.GetBusyCheck ();
342
343 //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
344 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) {
345 doRegionBackup (scene);
346 //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
347 } else if (heuristics && heuristicsRun && !heuristicsPassed) {
348 m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now.");
349 continue;
350 //Logical Deduction: heuristics are on but haven't been run
351 } else {
352 heuristicsPassed = RunHeuristics (scene);
353 heuristicsRun = true;
354 if (!heuristicsPassed) {
355 m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now.");
356 continue;
357 }
358 doRegionBackup (scene);
359 }
360 }
361 }
362
363 void doRegionBackup (IScene scene)
364 {
365 if (scene.RegionStatus != RegionStatus.Up) {
366 //We won't backup a region that isn't operating normally.
367 m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ());
368 return;
369 }
370
371 AutoBackupModuleState state = states[scene];
372 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule> ();
373 string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ());
374 //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath);
375 if (savePath == null) {
376 m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed");
377 return;
378 }
379 iram.ArchiveRegion (savePath, null);
380 ExecuteScript (state.GetScript (), savePath);
381 }
382
383 string IRegionModuleBase.Name {
384 get { return "AutoBackupModule"; }
385 }
386
387 Type IRegionModuleBase.ReplaceableInterface {
388 get { return null; }
389 }
390
391 #endregion
392 #region ISharedRegionModule implementation
393 void ISharedRegionModule.PostInitialise ()
394 {
395 //I don't care right now.
396 }
397
398 #endregion
399
400 //Is this even needed?
401 public bool IsSharedModule {
402 get { return true; }
403 }
404
405 private string BuildOarPath (string regionName, string baseDir, NamingType naming)
406 {
407 FileInfo path = null;
408 switch (naming) {
409 case NamingType.OVERWRITE:
410 path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName);
411 return path.FullName;
412 case NamingType.TIME:
413 path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar");
414 return path.FullName;
415 case NamingType.SEQUENTIAL:
416 path = new FileInfo (GetNextFile (baseDir, regionName));
417 return path.FullName;
418 default:
419 m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ());
420 break;
421 }
422
423 return path.FullName;
424 }
425
426 //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God.
427 //(Terrible reference to <timecube.com>)
428 //This format may turn out to be too unwieldy to keep...
429 //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
430 //Sequential numbers, right? Ugh. Almost makes TOO much sense.
431 private string GetTimeString ()
432 {
433 StringWriter sw = new StringWriter ();
434 sw.Write ("_");
435 DateTime now = DateTime.Now;
436 sw.Write (now.Year);
437 sw.Write ("y_");
438 sw.Write (now.Month);
439 sw.Write ("M_");
440 sw.Write (now.Day);
441 sw.Write ("d_");
442 sw.Write (now.Hour);
443 sw.Write ("h_");
444 sw.Write (now.Minute);
445 sw.Write ("m_");
446 sw.Write (now.Second);
447 sw.Write ("s");
448 sw.Flush ();
449 string output = sw.ToString ();
450 sw.Close ();
451 return output;
452 }
453
454 //Get the next logical file name
455 //I really shouldn't put fields here, but for now.... ;)
456 private string m_dirName = null;
457 private string m_regionName = null;
458 private string GetNextFile (string dirName, string regionName)
459 {
460 FileInfo uniqueFile = null;
461 m_dirName = dirName;
462 m_regionName = regionName;
463 long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest);
464 biggestExistingFile++;
465 //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
466 uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar");
467 if (uniqueFile.Exists) {
468 //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file!
469 //Now you get to pay the performance cost :)
470 uniqueFile = UniqueFileSearchLinear (biggestExistingFile);
471 }
472
473 return uniqueFile.FullName;
474 }
475
476 /*
477 * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.
478 * */
479 private bool RunHeuristics (IScene region)
480 {
481 try {
482 return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region);
483 } catch (Exception e) {
484 m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e);
485 return false;
486 }
487 }
488
489 /*
490 * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
491 * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
492 * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
493 * Return value of "true" ==> not too busy. Return value of "false" ==> too busy!
494 * */
495 private bool RunTimeDilationHeuristic (IScene region)
496 {
497 string regionName = region.RegionInfo.RegionName;
498 return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f);
499 }
500
501 /*
502 * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
503 * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
504 * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
505 * Return value of "true" ==> not too busy. Return value of "false" ==> too busy!
506 * */
507 private bool RunAgentLimitHeuristic (IScene region)
508 {
509 string regionName = region.RegionInfo.RegionName;
510 try {
511 Scene scene = (Scene)region;
512 //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
513 return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10);
514 } catch (InvalidCastException ice) {
515 m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!");
516 return true;
517 //Non-obstructionist safest answer...
518 }
519 }
520
521 private void ExecuteScript (string scriptName, string savePath)
522 {
523 //Fast path out
524 if (scriptName == null || scriptName.Length <= 0)
525 return;
526
527 try {
528 FileInfo fi = new FileInfo (scriptName);
529 if (fi.Exists) {
530 ProcessStartInfo psi = new ProcessStartInfo (scriptName);
531 psi.Arguments = savePath;
532 psi.CreateNoWindow = true;
533 Process proc = Process.Start (psi);
534 proc.ErrorDataReceived += HandleProcErrorDataReceived;
535 }
536 } catch (Exception e) {
537 m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e);
538 }
539 }
540
541 void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e)
542 {
543 m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data);
544 }
545
546 private void StopAllTimers ()
547 {
548 foreach (Timer t in timerMap.Keys) {
549 t.Close ();
550 }
551 m_closed = true;
552 }
553
554 /* Find the largest value for which the predicate returns true.
555 * We use a bisection algorithm (half interval) to make the algorithm scalable.
556 * The worst-case complexity is about O(log(n)^2) in practice.
557 * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search.
558 * The number of predicate invocations only hits a few hundred when the maximized value
559 * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times.
560 * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway.
561 * The Predicate<long> parameter must be a function that accepts a long and returns a bool.
562 * */
563 public long HalfIntervalMaximize (long start, Predicate<long> pred)
564 {
565 long prev = start, curr = start, biggest = 0;
566
567 if (start < 0)
568 throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative");
569
570 do {
571 if (pred (curr)) {
572 if (curr > biggest) {
573 biggest = curr;
574 }
575 prev = curr;
576 if (curr == 0) {
577 //Special case because 0 * 2 = 0 :)
578 curr = 1;
579 } else {
580 //Look deeper
581 curr *= 2;
582 }
583 } else {
584 // We went too far, back off halfway
585 curr = (curr + prev) / 2;
586 }
587 } while (curr - prev > 0);
588
589 return biggest;
590 }
591
592 public bool FileExistsTest (long num)
593 {
594 FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar");
595 return test.Exists;
596 }
597
598
599 //Very slow, hence why we try the HalfIntervalMaximize first!
600 public FileInfo UniqueFileSearchLinear (long start)
601 {
602 long l = start;
603 FileInfo retval = null;
604 do {
605 retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar");
606 } while (retval.Exists);
607
608 return retval;
609 }
610 }
611
612}
613