1 /*---------------------------------------------------------------------------* 2 3 Copyright 2010-2012 Nintendo. All rights reserved. 4 5 These coded instructions, statements, and computer programs contain 6 proprietary information of Nintendo of America Inc. and/or Nintendo 7 Company Ltd., and are protected by Federal copyright law. They may 8 not be disclosed to third parties or copied or duplicated in any form, 9 in whole or in part, without the prior written consent of Nintendo. 10 11 *---------------------------------------------------------------------------*/ 12 using System; 13 using System.IO; 14 using System.Diagnostics; 15 using System.Xml; 16 using System.Text; 17 using System.Collections.Generic; 18 19 namespace CafeX 20 { 21 /// <summary> 22 /// This class encapsulates the environment variables required by Cafe 23 /// </summary> 24 internal class EnvVar 25 { 26 private string var_name; 27 private string var_value; 28 private static Dictionary<string, EnvVar> dictionaryOfVariables = new Dictionary<string, EnvVar>(); 29 private static Dictionary<string, string> dictionaryOfSavedValues = new Dictionary<string, string>(); 30 BackUpCurrentEnvironment()31 public static void BackUpCurrentEnvironment() 32 { 33 #if DEBUG 34 Log.WriteLine("BackUpCurrentEnvironment started."); 35 #endif 36 dictionaryOfSavedValues.Clear(); 37 38 foreach (string variableName in dictionaryOfVariables.Keys) 39 { 40 dictionaryOfSavedValues.Add(variableName, dictionaryOfVariables[variableName].value); 41 } 42 #if DEBUG 43 Log.WriteLine("BackUpCurrentEnvironment completed."); 44 #endif 45 } 46 RestoreSavedEnvironment()47 public static void RestoreSavedEnvironment() 48 { 49 #if DEBUG 50 Log.WriteLine("RestoreSavedEnvironment started."); 51 #endif 52 if (dictionaryOfSavedValues.Count == 0) 53 { 54 #if DEBUG 55 Log.WriteLine("RestoreSavedEnvironment ignored. No backup available."); 56 #endif 57 58 // No backup had been made, so there is no way to restore. Just return. 59 return; 60 } 61 62 foreach (string variableName in dictionaryOfVariables.Keys) 63 { 64 // Set the respective envVar instance in the dictionary of variables. 65 if (dictionaryOfSavedValues.ContainsKey(variableName)) 66 { 67 EnvVar temp = dictionaryOfVariables[variableName]; 68 temp.value = dictionaryOfSavedValues[variableName]; 69 } 70 } 71 #if DEBUG 72 Log.WriteLine("RestoreSavedEnvironment completed successfully."); 73 #endif 74 } 75 EnvVar(string name)76 public EnvVar(string name) 77 { 78 if (string.IsNullOrEmpty(name)) 79 { 80 throw new ArgumentNullException("name cannot be null."); 81 } 82 83 var_name = name; 84 var_value = Environment.GetEnvironmentVariable(var_name); 85 // Store the environment variable, so it can be backed-up and restored at will 86 EnvVar.dictionaryOfVariables.Add(name, this); 87 } 88 89 public string value 90 { 91 get 92 { 93 var_value = Environment.GetEnvironmentVariable(var_name); 94 return var_value; 95 } 96 97 set 98 { 99 var_value = value; 100 Environment.SetEnvironmentVariable(var_name, var_value); 101 } 102 } 103 AddToVar(string addition, char delimiter)104 public void AddToVar(string addition, char delimiter) 105 { 106 if (value == null) 107 value = addition; 108 else 109 value = value + delimiter + addition; 110 } 111 RemoveFromVar(string removal, char delimiter)112 public void RemoveFromVar(string removal, char delimiter) 113 { 114 if (string.IsNullOrEmpty(value)) 115 { 116 #if DEBUG 117 Log.WriteLine("RemoveFromVar: removal is empty!"); 118 #endif 119 120 return; 121 } 122 if (string.IsNullOrEmpty(delimiter.ToString())) 123 { 124 #if DEBUG 125 Log.WriteLine("RemoveFromVar: delimiter is null!"); 126 #endif 127 128 return; 129 } 130 131 int position = value.IndexOf(removal); 132 133 if (position >= 0) 134 { 135 int len = removal.Length; 136 if (position > 0) 137 { 138 // There is more text in value than only the one we want to remove. 139 // ex: data1 data2 removal data3 140 // ex: data1 removal. 141 // In those scenarios, we need to remove the delimiter char before, since AddToVar does value = value + delimiter + addition; 142 --position; 143 ++len; 144 } 145 146 value = value.Remove(position, len); 147 } 148 else 149 { 150 #if DEBUG 151 Log.WriteLine("RemoveFromVar: \"" + removal + "\" does not exist in the arg list!"); 152 #endif 153 return; 154 } 155 } 156 157 158 } 159 160 /// <summary> 161 /// This class provides enables CafeX to work in both Windows and Cygwin environments by converting paths to the Windows format. 162 /// </summary> 163 internal static class PathConverter 164 { 165 /// <summary> 166 /// Ensure the provide path is rooted. 167 /// </summary> 168 /// <param name="input">Path to a file.</param> 169 /// <returns>Rooted path.</returns> Root(string input)170 internal static string Root(string input) 171 { 172 if (Path.IsPathRooted(input)) 173 { 174 return input; 175 } 176 else 177 { 178 return Environment.CurrentDirectory + "\\" + input; 179 } 180 } 181 182 /// <summary> 183 /// Convert a Cygwin path to a Windows path. 184 /// If the input is already a valid Windows path, it is returned unchanged. 185 /// </summary> 186 /// <param name="input">Cywin path.</param> 187 /// <returns>Windows equivalent of the provided Cygwin path.</returns> Windowsify(string input)188 internal static string Windowsify(string input) 189 { 190 #if DEBUG 191 Log.WriteLine("Windowsify input = " + input); 192 #endif 193 if (string.IsNullOrEmpty(input)) 194 { 195 return input; 196 } 197 198 int cygdriverIndex = -1; 199 200 cygdriverIndex = input.IndexOf(@"/cygdrive///"); 201 202 if ((cygdriverIndex == 0 || cygdriverIndex == 1 ) && 203 !string.IsNullOrEmpty(CafeX.Program.CYGWIN_PATH.value) ) 204 { 205 // Special treatment 206 input = input.Replace( @"/cygdrive///", Path.Combine(CafeX.Program.CYGWIN_PATH.value, "cygdrive/") ); 207 // it should continue with normal conversion to fix the \\ 208 } 209 210 // Normal conversion -> path starts with /cygdriver/<stuff> or "/cygdriver/<stuff>" 211 cygdriverIndex = input.IndexOf("/cygdrive/"); 212 if (cygdriverIndex == 0 || cygdriverIndex == 1) 213 { 214 char drive_letter = input.ToCharArray()[10 + cygdriverIndex]; 215 input = input.Remove(0, 11 + cygdriverIndex); 216 input = input.Replace('/', '\\'); 217 input = drive_letter + ":" + input; 218 } 219 else 220 { 221 // This should be a path that doesn't start with cygdrive, 222 // so just make sure it has the proper \\ instead of / 223 input = input.Replace('/', '\\'); 224 } 225 226 #if DEBUG 227 Log.WriteLine("Windowsify return = " + input); 228 #endif 229 return input; 230 } 231 } 232 233 /// <summary> 234 /// This class enables external processes to be started and monitored. 235 /// </summary> 236 internal static class ProcessExecutor 237 { 238 /// <summary> 239 /// Creates a process, starts it, wait for its execution and return its exit code. 240 /// </summary> 241 /// <param name="filename">Path to the executable to start on a different process.</param> 242 /// <param name="arguments">Arguments to the executable.</param> 243 /// <returns>The process' exit code.</returns> DoProcess(string filename, string arguments)244 internal static int DoProcess(string filename, string arguments) 245 { 246 #if DEBUG 247 Log.WriteLine("ProcessorExecutor.DoProcess : " + filename + " " + arguments); 248 #endif 249 try 250 { 251 Process p = new Process(); 252 ProcessStartInfo si = new ProcessStartInfo(filename); 253 si.UseShellExecute = false; 254 si.CreateNoWindow = true; 255 si.Arguments = arguments; 256 si.RedirectStandardError = true; 257 si.RedirectStandardOutput = true; 258 p.StartInfo = si; 259 260 p.Start(); 261 262 p.WaitForExit(); 263 int ret = p.ExitCode; 264 p.Close(); 265 p.Dispose(); 266 p = null; 267 268 return ret; 269 } 270 catch (Exception e) 271 { 272 Console.WriteLine("CafeX Error: Exception in process executor"); 273 Console.WriteLine(filename + " " + arguments); 274 Console.WriteLine(e.Message); 275 return -1; 276 } 277 } 278 279 /// <summary> 280 /// Creates and starts a new process, but return immediately the execution to the caller. 281 /// The child's stdout and errout is printed to this process' stdout. 282 /// </summary> 283 /// <param name="filename">Path to the executable to start on a different process.</param> 284 /// <param name="arguments">Arguments to the executable.</param> 285 /// <returns>0 if the process is created, -1 otherwise.</returns> DoProcess_DontWait(string filename, string arguments)286 internal static int DoProcess_DontWait(string filename, string arguments) 287 { 288 #if DEBUG 289 Log.WriteLine("ProcessorExecutor.DoProcess_DontWait : " + filename + " " + arguments); 290 #endif 291 292 try 293 { 294 Process p = new Process(); 295 ProcessStartInfo si = new ProcessStartInfo(filename); 296 si.UseShellExecute = false; 297 si.CreateNoWindow = true; 298 si.Arguments = arguments; 299 si.RedirectStandardError = true; 300 si.RedirectStandardOutput = true; 301 p.StartInfo = si; 302 // Won't wait, but need to connect stdout and stderr to this parent process. 303 p.OutputDataReceived += (sender, args) => Console.WriteLine(args.Data); 304 p.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data); 305 p.Start(); 306 p.BeginOutputReadLine(); 307 p.BeginErrorReadLine(); 308 return 0; 309 } 310 catch (Exception e) 311 { 312 Console.WriteLine("CafeX Error: Exception in process executor"); 313 Console.WriteLine(filename + " " + arguments); 314 Console.WriteLine(e.Message); 315 return -1; 316 } 317 } 318 319 /// <summary> 320 /// Creates a process, starts it, wait for its execution and return its exit code. 321 /// The child's process stdout is captured and returned to the caller in the ouput variable. 322 /// </summary> 323 /// <param name="filename">Path to the executable to start on a different process.</param> 324 /// <param name="arguments">Arguments to the executable.</param> 325 /// <param name="output">Returns the child's process captured output the the caller.</param> 326 /// <returns>Chid's process exit code.</returns> DoProcess_GetOutput(string filename, string arguments, out string output)327 internal static int DoProcess_GetOutput(string filename, string arguments, out string output) 328 { 329 #if DEBUG 330 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments); 331 #endif 332 333 return DoProcess_GetOutput(filename, arguments, false, -1, out output); 334 } 335 336 /// <summary> 337 /// Creates a process, starts it, wait for its execution and return its exit code. 338 /// The child's process stdout is captured and returned to the caller in the ouput variable. 339 /// </summary> 340 /// <param name="filename">Path to the executable to start on a different process.</param> 341 /// <param name="arguments">Arguments to the executable.</param> 342 /// <param name="timeout">How long (in ms) to wait for the process to end. Set to values less than 1 to wait indefinately.</param> 343 /// <param name="output">Returns the child's process captured output the the caller.</param> 344 /// <returns>Chid's process exit code.</returns> DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output)345 internal static int DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output) 346 { 347 #if DEBUG 348 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments); 349 #endif 350 351 try 352 { 353 int ret; 354 bool isDataReceived = !(waitDataReceived); // If we want to wait for data received event (waitDataReceived=true), need to set isDataReceived = false. 355 System.Threading.ManualResetEvent dataReceivedEvent = new System.Threading.ManualResetEvent(false); 356 TimeSpan DATA_RECEIVE_FLUSH_TIMEOUT = new TimeSpan(0, 0, 2); 357 Process p = new Process(); 358 ProcessStartInfo si = new ProcessStartInfo(filename); 359 si.UseShellExecute = false; 360 si.CreateNoWindow = true; 361 si.Arguments = arguments; 362 StringBuilder outputTmp = new StringBuilder(); 363 DataReceivedEventHandler callback = (sender, e) => 364 { 365 if (e != null && e.Data != null) 366 { 367 outputTmp.AppendLine( e.Data ); 368 if (!isDataReceived) 369 { 370 isDataReceived = true; 371 dataReceivedEvent.Set(); 372 } 373 } 374 }; 375 376 si.RedirectStandardError = true; 377 si.RedirectStandardOutput = true; 378 p.StartInfo = si; 379 p.OutputDataReceived += callback; 380 p.ErrorDataReceived += callback; 381 p.Start(); 382 p.BeginOutputReadLine(); 383 p.BeginErrorReadLine(); 384 bool exitedProperly = true; 385 386 if (timeout > 0) 387 { 388 exitedProperly = p.WaitForExit(timeout); 389 if (!exitedProperly) 390 { 391 Console.WriteLine("ERROR: Process started for '" + filename + " " + arguments + "' did not finish in the expected timeout (" + timeout.ToString() + "ms)."); 392 Console.WriteLine("Dumping process std/out std/err until now:\n {0} \n", outputTmp.ToString()); 393 } 394 } 395 else 396 { 397 p.WaitForExit(); 398 } 399 400 if (exitedProperly) 401 { 402 ret = p.ExitCode; 403 } 404 else 405 { 406 ret = -1; 407 } 408 409 // Some output should have been received either in stdout or stderr, so wait 410 if (isDataReceived == false) 411 { 412 dataReceivedEvent.WaitOne(DATA_RECEIVE_FLUSH_TIMEOUT); 413 } 414 415 p.CancelOutputRead(); 416 p.CancelErrorRead(); 417 p.StartInfo.RedirectStandardOutput = false; 418 p.StartInfo.RedirectStandardError = false; 419 p.OutputDataReceived -= callback; 420 p.ErrorDataReceived -= callback; 421 422 p.Close(); 423 p.Dispose(); 424 p = null; 425 426 output = outputTmp.ToString(); 427 428 return ret; 429 } 430 catch (Exception e) 431 { 432 output = string.Empty; 433 Console.WriteLine("CafeX Error: Exception in process executor"); 434 Console.WriteLine(filename + " " + arguments); 435 Console.WriteLine(e.Message); 436 return -1; 437 } 438 } 439 440 /// <summary> 441 /// Creates a process, starts it, wait for its execution and return its exit code. 442 /// The child's process stdout and errout are set to a callback handler provided. 443 /// </summary> 444 /// <param name="filename">Path to the executable to start on a different process.</param> 445 /// <param name="arguments">Arguments to the executable.</param> 446 /// <param name="callback">An instance of DataReceivedEventHandler provided by the caller.</param> 447 /// <returns>Chid's process exit code.</returns> StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion)448 internal static int StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion) 449 { 450 #if DEBUG 451 Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments); 452 #endif 453 454 try 455 { 456 Process p = new Process(); 457 ProcessStartInfo si = new ProcessStartInfo(filename); 458 si.UseShellExecute = false; 459 si.CreateNoWindow = true; 460 si.Arguments = arguments; 461 si.RedirectStandardError = true; 462 si.RedirectStandardOutput = true; 463 p.StartInfo = si; 464 p.OutputDataReceived += callback; 465 p.ErrorDataReceived += callback; 466 467 p.Start(); 468 469 p.BeginOutputReadLine(); 470 p.BeginErrorReadLine(); 471 472 if (waitCompletion) 473 { 474 p.WaitForExit(); 475 int ret = p.ExitCode; 476 p.CancelOutputRead(); 477 p.CancelErrorRead(); 478 p.StartInfo.RedirectStandardOutput = false; 479 p.StartInfo.RedirectStandardError = false; 480 p.OutputDataReceived -= callback; 481 p.ErrorDataReceived -= callback; 482 p.Close(); 483 p.Dispose(); 484 p = null; 485 486 return ret; 487 488 } 489 else 490 { 491 return 0; 492 } 493 494 } 495 catch (Exception e) 496 { 497 Console.WriteLine("CafeX Error: Exception in process executor"); 498 Console.WriteLine(filename + " " + arguments); 499 Console.WriteLine(e.Message); 500 return -1; 501 } 502 } 503 StartProcess_RedirectOutput(string filename, string arguments)504 internal static int StartProcess_RedirectOutput(string filename, string arguments) 505 { 506 #if DEBUG 507 Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments); 508 #endif 509 510 try 511 { 512 Process p = new Process(); 513 ProcessStartInfo si = new ProcessStartInfo(filename); 514 si.UseShellExecute = false; 515 si.CreateNoWindow = true; 516 si.Arguments = arguments; 517 si.RedirectStandardError = true; 518 si.RedirectStandardOutput = true; 519 si.RedirectStandardInput = true; 520 p.StartInfo = si; 521 522 p.Start(); 523 524 p.BeginOutputReadLine(); 525 526 return 0; 527 } 528 catch (Exception e) 529 { 530 Console.WriteLine("CafeX Error: Exception in process executor"); 531 Console.WriteLine(filename + " " + arguments); 532 Console.WriteLine(e.Message); 533 return -1; 534 } 535 } 536 } 537 538 /// <summary> 539 /// This class allows string arrays to be combined into a single string with the delimiter of choice. 540 /// </summary> 541 internal static class StringCombiner 542 { MakeDelimitedString(string[] arr, char delimiter)543 internal static string MakeDelimitedString(string[] arr, char delimiter) 544 { 545 string ret = string.Empty; 546 if (arr != null) 547 { 548 for (int i = 0; i < arr.Length; ++i) 549 { 550 if (i == 0) 551 { 552 ret += arr[i]; 553 } 554 else 555 { 556 ret += delimiter + arr[i]; 557 } 558 } 559 } 560 561 return ret; 562 } 563 } 564 565 /// <summary> 566 /// This class is used to edit xml content files. 567 /// </summary> 568 internal static class XmlHandler 569 { 570 /// <summary> 571 /// Traverses the xml document from the provided root node until it finds the node that matches the provided name. 572 /// </summary> 573 /// <param name="root_name">Root node</param> 574 /// <param name="node_name">Name of the node being searched</param> 575 /// <returns>The node that matches the provided node name, null if it was not found.</returns> GetXmlNodeRecursive(XmlNode root_name, string node_name)576 internal static XmlNode GetXmlNodeRecursive(XmlNode root_name, string node_name) 577 { 578 if (root_name.Name == node_name) 579 return root_name; 580 581 XmlNode ret = null; 582 foreach (XmlNode child in root_name.ChildNodes) 583 { 584 ret = GetXmlNodeRecursive(child, node_name); 585 if (ret != null) 586 break; 587 } 588 589 return ret; 590 } 591 592 /// <summary> 593 /// Check if a node by the provided name exists inside an xml document. 594 /// </summary> 595 /// <param name="file_name">Full path to the xml document.</param> 596 /// <param name="node_name">Name of the node to search.</param> 597 /// <returns>True if the node was found, false otherwise.</returns> NodeExists(string file_name, string node_name)598 internal static bool NodeExists(string file_name, string node_name) 599 { 600 #if DEBUG 601 Log.WriteLine(string.Format("NodeExists: File: {0}, Node: {1}", file_name, node_name)); 602 #endif 603 XmlDocument document = new XmlDocument(); 604 document.Load(file_name); 605 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 606 return (node == null) ? false : true; 607 } 608 609 /// <summary> 610 /// Returns the value of a node from an existing xml document. 611 /// </summary> 612 /// <param name="file_name">Full path to the xml document.</param> 613 /// <param name="node_name">Name of the node to search.</param> 614 /// <returns>String representing the node's value, null if the node was not found.</returns> GetNodeValue(string file_name, string node_name)615 internal static string GetNodeValue(string file_name, string node_name) 616 { 617 #if DEBUG 618 Log.WriteLine("GetNodeValue called. file_name=" + file_name + ", node_name=" + node_name); 619 #endif 620 XmlDocument document = new XmlDocument(); 621 document.Load(file_name); 622 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 623 if (node == null) 624 { 625 Console.WriteLine("cafex xmlhandler getnodevalue failed: Node " + node_name + " does not exist in " + file_name); 626 return null; 627 } 628 #if DEBUG 629 Log.WriteLine("GetNodeValue return = " + node.InnerText); 630 #endif 631 return node.InnerText; 632 } 633 634 /// <summary> 635 /// Updates the value of a node inside an xml document. 636 /// </summary> 637 /// <param name="file_name">Full path to the xml document.</param> 638 /// <param name="node_name">Name of the node to search.</param> 639 /// <param name="new_value">The new value for the node.</param> UpdateNodeValue(string file_name, string node_name, string new_value)640 internal static void UpdateNodeValue(string file_name, string node_name, string new_value) 641 { 642 #if DEBUG 643 Log.WriteLine("UpdateNodeValue called. file_name=" + file_name + ", node_name=" + node_name + ", new_value=" + new_value); 644 #endif 645 XmlDocument document = new XmlDocument(); 646 document.Load(file_name); 647 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 648 if (node == null) 649 { 650 Console.WriteLine("cafex xmlhandler updatenodevalue failed: Node " + node_name + " does not exist in " + file_name); 651 return; 652 } 653 654 // Only update if necessary. 655 if (node.InnerText != new_value) 656 { 657 if (File.Exists(file_name)) 658 { 659 // Strip the read-only attribute so we can overwrite it if it exists 660 File.SetAttributes(file_name, File.GetAttributes(file_name) & ~FileAttributes.ReadOnly); 661 } 662 663 // Before the update, check if the file is not locked for 3 times 664 uint retries = Program.RETRY_ATTEMPS; 665 while (FileUtil.IsFileLocked(file_name) && retries > 0) 666 { 667 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); 668 --retries; 669 } 670 671 node.InnerText = new_value; 672 #if DEBUG 673 Log.WriteLine("updateNodeValue: file: " + file_name + ", node: " + node_name + ", newvalue: " + new_value); 674 #endif 675 XmlWriterSettings settings = new XmlWriterSettings(); 676 settings.NewLineChars = "\n"; 677 settings.Indent = true; 678 settings.Encoding = new UTF8Encoding(false); 679 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 680 { 681 document.WriteTo(writer); 682 writer.Flush(); 683 writer.Close(); 684 } 685 686 File.SetAttributes(file_name, FileAttributes.Normal); 687 #if DEBUG 688 Log.WriteLine("updateNodeValue completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 689 #endif 690 } 691 } 692 693 /// <summary> 694 /// Add an xml node to an xml document. 695 /// </summary> 696 /// <param name="file_name">Full path to the xml document.</param> 697 /// <param name="node_name">Name for the new xml node.</param> 698 /// <param name="value">Node's value.</param> 699 /// <param name="attributes">Array of strings representing the node's attributes.</param> AddNode(string file_name, string node_name, string value, string[] attributes)700 internal static void AddNode(string file_name, string node_name, string value, string[] attributes) 701 { 702 #if DEBUG 703 Log.WriteLine("AddNode called. file_name=" + file_name + ", node_name=" + node_name + ", value=" + value); 704 string attrString = null; 705 foreach (string attribute in attributes) 706 { 707 attrString += attribute + " "; 708 } 709 Log.WriteLine("Attributes = " + attrString); 710 #endif 711 XmlDocument document = new XmlDocument(); 712 document.Load(file_name); 713 XmlElement node = document.CreateElement(node_name); 714 node.InnerText = value; 715 716 for (int i = 0; i < attributes.Length; i += 2) 717 { 718 node.SetAttribute(attributes[i], attributes[i + 1]); 719 } 720 721 document.DocumentElement.AppendChild(node); 722 #if DEBUG 723 Log.WriteLine("AddNode: file: " + file_name + ", node: " + node_name + ", value: " + value); 724 #endif 725 XmlWriterSettings settings = new XmlWriterSettings(); 726 settings.NewLineChars = "\n"; 727 settings.Indent = true; 728 settings.Encoding = new UTF8Encoding(false); 729 // Before the update, check if the file is not locked for 3 times 730 uint retries = Program.RETRY_ATTEMPS; 731 while (FileUtil.IsFileLocked(file_name) && retries > 0) 732 { 733 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s 734 --retries; 735 } 736 737 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 738 { 739 document.WriteTo(writer); 740 writer.Flush(); 741 writer.Close(); 742 } 743 744 File.SetAttributes(file_name, FileAttributes.Normal); 745 #if DEBUG 746 Log.WriteLine("AddNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 747 #endif 748 } 749 750 /// <summary> 751 /// Remove a node from an xml document. 752 /// </summary> 753 /// <param name="file_name">Full path to the xml document.</param> 754 /// <param name="node_name">Name for the node to search.</param> RemoveNode(string file_name, string node_name)755 internal static void RemoveNode(string file_name, string node_name) 756 { 757 #if DEBUG 758 Log.WriteLine("RemoveNode called. file_name=" + file_name + ", node_name=" + node_name); 759 #endif 760 XmlDocument document = new XmlDocument(); 761 document.Load(file_name); 762 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 763 if (node == null) 764 { 765 return; 766 } 767 #if DEBUG 768 Log.WriteLine("RemoveNode: file: " + file_name + ", node: " + node_name); 769 #endif 770 document.DocumentElement.RemoveChild(node); 771 772 XmlWriterSettings settings = new XmlWriterSettings(); 773 settings.NewLineChars = "\n"; 774 settings.Indent = true; 775 settings.Encoding = new UTF8Encoding(false); 776 // Before the update, check if the file is not locked for 3 times 777 uint retries = Program.RETRY_ATTEMPS; 778 while (FileUtil.IsFileLocked(file_name) && retries > 0) 779 { 780 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s 781 --retries; 782 } 783 784 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 785 { 786 document.WriteTo(writer); 787 writer.Flush(); 788 writer.Close(); 789 } 790 791 File.SetAttributes(file_name, FileAttributes.Normal); 792 #if DEBUG 793 Log.WriteLine("RemoveNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 794 #endif 795 } 796 UpdateNodeValueByPath(string file_name, string xpath, string new_value)797 internal static void UpdateNodeValueByPath(string file_name, string xpath, string new_value) 798 { 799 #if DEBUG 800 Log.WriteLine("UpdateNodeValueByPath called. file_name=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value); 801 #endif 802 803 XmlDocument document = new XmlDocument(); 804 document.Load(file_name); 805 806 try 807 { 808 XmlNode target = document.SelectSingleNode(xpath); 809 810 if (target == null) 811 { 812 Console.WriteLine("cafex xmlhandler UpdateNodeValueByPath failed: Node specified by path " + xpath + " does not exist in " + file_name); 813 return; 814 } 815 816 if (target.InnerText != new_value) 817 { 818 uint retries = Program.RETRY_ATTEMPS; 819 while (FileUtil.IsFileLocked(file_name) && retries > 0) 820 { 821 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); 822 --retries; 823 } 824 825 target.InnerText = new_value; 826 #if DEBUG 827 Log.WriteLine("UpdateNodeValueByPath: file=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value); 828 #endif 829 XmlWriterSettings settings = new XmlWriterSettings(); 830 settings.NewLineChars = "\n"; 831 settings.Indent = true; 832 settings.Encoding = new UTF8Encoding(false); 833 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 834 { 835 document.WriteTo(writer); 836 writer.Flush(); 837 writer.Close(); 838 } 839 840 File.SetAttributes(file_name, FileAttributes.Normal); 841 #if DEBUG 842 Log.WriteLine("UpdateNodeValueByPath completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 843 #endif 844 } 845 } 846 catch (System.Xml.XPath.XPathException) 847 { 848 Console.WriteLine("UpdateNodeValueByPath contained an invalid xpath parameter! xpath={0}\nNode will not be updated!",xpath); 849 return; 850 } 851 } 852 } 853 854 /// <summary> 855 /// This class is a wrapper on HEX conversion functions used by CafeX. 856 /// </summary> 857 internal static class HexHandler 858 { 859 /// <summary> 860 /// Transform the input string into its integer representation. 861 /// Strings that are invalid representations of integers will cause exceptions. 862 /// </summary> 863 /// <param name="input">String representing a number.</param> 864 /// <returns>The integer that represents the provided string.</returns> GetNumberFromString(string input)865 internal static uint GetNumberFromString(string input) 866 { 867 #if DEBUG 868 Log.WriteLine("GetNumberFromString called. input=" + input); 869 #endif 870 if (input.StartsWith("0x")) 871 { 872 return Convert.ToUInt32(input, 16); 873 } 874 else 875 { 876 foreach (char c in input) 877 { 878 if ((c >= 'a' || c >= 'A') && 879 (c <= 'f' || c <= 'F')) 880 { 881 return Convert.ToUInt32(input, 16); 882 } 883 } 884 885 return Convert.ToUInt32(input); 886 } 887 } 888 889 /// <summary> 890 /// Transform the input string into its long representation. 891 /// Strings that are invalid representations of longs will cause exceptions. 892 /// </summary> 893 /// <param name="input">String representing a number.</param> 894 /// <returns>The long that represents the provided string.</returns> GetLongNumberFromString(string input)895 internal static long GetLongNumberFromString(string input) 896 { 897 #if DEBUG 898 Log.WriteLine("GetLongNumberFromString called. input=" + input); 899 #endif 900 901 if (input.StartsWith("0x")) 902 { 903 return Convert.ToInt64(input, 16); 904 } 905 else 906 { 907 foreach (char c in input) 908 { 909 if ((c >= 'a' || c >= 'A') && 910 (c <= 'f' || c <= 'F')) 911 { 912 return Convert.ToInt64(input, 16); 913 } 914 } 915 return Convert.ToInt64(input); 916 } 917 } 918 } 919 920 /// <summary> 921 /// Utilities for file verification. 922 /// </summary> 923 internal static class FileUtil 924 { 925 private static string cafex_system_wide_mtx_name = "Global\\cafex_system_wide_mtx"; 926 private static DateTime entryAssemblyTs; 927 private static bool bRunningFromCygwin = false; 928 private static string assemblyFileName; 929 930 931 /// <summary> 932 /// Initializes one-shot only utility variables 933 /// </summary> GatherEnvironment()934 internal static void GatherEnvironment() 935 { 936 string cygcheck; 937 assemblyFileName = System.Reflection.Assembly.GetExecutingAssembly().Location; 938 entryAssemblyTs = File.GetLastWriteTimeUtc(assemblyFileName); 939 cygcheck = Environment.GetEnvironmentVariable("CYGCHECK"); 940 if ( !(string.IsNullOrEmpty(cygcheck)) ) 941 { 942 if( cygcheck.StartsWith("cygwin" ) ) 943 { 944 bRunningFromCygwin = true; 945 } 946 } 947 } 948 949 950 /// <summary> 951 /// Determine whether we are running from within cygwin 952 /// </summary> 953 internal static bool RunningFromCygwin 954 { 955 get { return bRunningFromCygwin; } 956 } 957 958 /// <summary> 959 /// Check if a file is locked for R/W and/or delete. 960 /// </summary> 961 /// <param name="filename">Full path to the file.</param> 962 /// <returns>True if the file is locked, false otherwise.</returns> IsFileLocked(string filename)963 internal static bool IsFileLocked(string filename) 964 { 965 FileStream stream = null; 966 bool result = false; 967 968 try 969 { 970 FileInfo file = new FileInfo(filename); 971 stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None); 972 result = false; 973 } 974 catch (IOException) 975 { 976 //the file is unavailable because it is: 977 //a) still being written to 978 //b) or being processed by another thread (maybe held by PCFS) 979 //c) or does not exist (has already been processed) 980 #if DEBUG 981 // Collect debug information to help troubleshoot who has this file handle 982 try 983 { 984 Process handleTool = new Process(); 985 handleTool.StartInfo.FileName = "handle.exe"; 986 handleTool.StartInfo.Arguments = filename + " /accepteula"; 987 handleTool.StartInfo.UseShellExecute = false; 988 handleTool.StartInfo.RedirectStandardOutput = true; 989 handleTool.Start(); 990 handleTool.WaitForExit(); 991 string handleOutput = handleTool.StandardOutput.ReadToEnd(); 992 Log.WriteLine("############## FILE LOCKED WARNING REPORT ##############"); 993 Log.WriteLine("Filename = " + filename); 994 Log.WriteLine("Handles opened:"); 995 Log.WriteLine(handleOutput); 996 Log.WriteLine("############## FILE LOCKED END OF REPORT ##############"); 997 998 999 } 1000 catch (Exception) 1001 { 1002 Log.WriteLine("Unable to get handle information on file " + filename); 1003 } 1004 finally 1005 #endif 1006 { 1007 result = true; 1008 } 1009 } 1010 finally 1011 { 1012 if (stream != null) 1013 { 1014 stream.Close(); 1015 } 1016 } 1017 1018 return result; 1019 1020 } 1021 1022 1023 1024 /// <summary> 1025 /// Expand an executable to the temp folder from a resource location 1026 /// </summary> 1027 /// <param name="payload">Executable bytes from resource</param> 1028 /// <param name="filename">File to be written to disk</param> 1029 /// <returns>Full path to temporary file written to disk.</returns> ExpandExecutable(byte[] payload, string filename)1030 internal static string ExpandExecutable(byte[] payload, string filename) 1031 { 1032 System.Threading.Mutex mtx = null; 1033 string full_tmp_path; 1034 1035 full_tmp_path = Environment.GetEnvironmentVariable("CAFE_TEMP"); 1036 1037 if( string.IsNullOrEmpty(full_tmp_path) ) 1038 { 1039 full_tmp_path = Environment.GetEnvironmentVariable("TEMP"); 1040 if( string.IsNullOrEmpty(full_tmp_path) ) 1041 { 1042 full_tmp_path = Environment.GetEnvironmentVariable("TMP"); 1043 if ( string.IsNullOrEmpty(full_tmp_path) ) 1044 { 1045 full_tmp_path = Path.GetDirectoryName(assemblyFileName); 1046 } 1047 } 1048 } 1049 1050 1051 full_tmp_path = PathConverter.Windowsify(full_tmp_path) + "\\" + filename; 1052 1053 // the kinds of things that can prevent this from happening are all fatal 1054 // errors, don't try to catch anything 1055 mtx = new System.Threading.Mutex(false, cafex_system_wide_mtx_name); 1056 1057 mtx.WaitOne(); 1058 1059 // from this point on, we must release the mutex no matter what happens! 1060 1061 try 1062 { 1063 DateTime tempExeTs; 1064 bool doOverwrite = false; 1065 try 1066 { 1067 tempExeTs = File.GetLastWriteTimeUtc(full_tmp_path); 1068 1069 // if the internal resource is newer than the file on disk 1070 if (entryAssemblyTs >= tempExeTs) 1071 { 1072 doOverwrite = true; 1073 } 1074 // else, we will use the file that's there, as it is newer than we are 1075 } 1076 catch (FileNotFoundException) 1077 { 1078 // file's not there - we will overwrite it 1079 doOverwrite = true; 1080 } 1081 1082 if (doOverwrite) 1083 { 1084 try 1085 { 1086 File.WriteAllBytes(full_tmp_path, payload); 1087 } 1088 catch( System.IO.DirectoryNotFoundException ) 1089 { 1090 try 1091 { 1092 Directory.CreateDirectory( Path.GetDirectoryName(full_tmp_path) ); 1093 } 1094 catch 1095 { 1096 } 1097 File.WriteAllBytes(full_tmp_path, payload); 1098 } 1099 1100 } 1101 } 1102 finally 1103 { 1104 mtx.ReleaseMutex(); 1105 } 1106 1107 return full_tmp_path; 1108 1109 } 1110 1111 /// <summary> 1112 /// Check if the provide string is a valid file name. 1113 /// </summary> 1114 /// <param name="filename">String to check.</param> 1115 /// <returns>True if it is a valid file name, false otherwise.</returns> IsValidFile(string filename, bool checkExistence)1116 internal static bool IsValidFile(string filename, bool checkExistence) 1117 { 1118 #if DEBUG 1119 Log.WriteLine("IsValidFile called. filename=" + filename + ", checkExistence=" + checkExistence.ToString()); 1120 #endif 1121 if (string.IsNullOrEmpty(filename)) 1122 { 1123 return false; 1124 } 1125 1126 // Convert to windows, if it was a cygwin path 1127 filename = PathConverter.Windowsify(filename); 1128 bool returnVal = false; 1129 try 1130 { 1131 FileInfo file = new FileInfo(filename); 1132 // A path without a filename resolves to a valid FileInfo instance 1133 // so we need to check if there is a file at the end of the path. 1134 if (Path.GetFileName(file.FullName).Length == 0) 1135 { 1136 // There is no filename, only a valid path! 1137 return false; 1138 } 1139 1140 if (!checkExistence) 1141 { 1142 returnVal = true; 1143 } 1144 else 1145 { 1146 returnVal = file.Exists; 1147 } 1148 } 1149 catch (ArgumentException) 1150 { 1151 } 1152 catch (System.IO.PathTooLongException) 1153 { 1154 } 1155 catch (NotSupportedException) 1156 { 1157 } 1158 1159 #if DEBUG 1160 Log.WriteLine("IsValidFile return " + returnVal.ToString()); 1161 #endif 1162 return returnVal; 1163 } 1164 AddQuotes(ref string fileNameOrListWithSpaces)1165 internal static void AddQuotes(ref string fileNameOrListWithSpaces) 1166 { 1167 #if DEBUG 1168 Log.WriteLine("AddQuotes called. " + "fileNameOrListWithSpaces=" + fileNameOrListWithSpaces); 1169 #endif 1170 if (string.IsNullOrEmpty(fileNameOrListWithSpaces)) 1171 { 1172 return; 1173 } 1174 1175 if (fileNameOrListWithSpaces.StartsWith("\"") && fileNameOrListWithSpaces.EndsWith("\"")) 1176 { 1177 return; 1178 } 1179 1180 fileNameOrListWithSpaces = "\"" + fileNameOrListWithSpaces + "\""; 1181 } 1182 1183 1184 /// <summary> 1185 /// Copy a directory from one location to another with optional recursion. 1186 /// </summary> 1187 /// <param name="sourceDirName">Source folder name.</param> 1188 /// <param name="destDirName">Destination folder name.</param> 1189 /// <param name="copySubDirs">Flag to copy all of the sub-folders recurively.</param> DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)1190 internal static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) 1191 { 1192 #if DEBUG 1193 Console.WriteLine("FileUtil::DirectoryCopy: Copying '{0}' => '{1}'{2}", sourceDirName, destDirName, copySubDirs ? " recursively" : ""); 1194 #endif 1195 // Get the subdirectories for the specified directory. 1196 DirectoryInfo dir = new DirectoryInfo(sourceDirName); 1197 DirectoryInfo[] dirs = dir.GetDirectories(); 1198 1199 if (!dir.Exists) 1200 { 1201 throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName); 1202 } 1203 1204 // If the destination directory doesn't exist, create it. 1205 if (!Directory.Exists(destDirName)) 1206 { 1207 Directory.CreateDirectory(destDirName); 1208 } 1209 1210 // Get the files in the directory and copy them to the new location. 1211 FileInfo[] files = dir.GetFiles(); 1212 foreach (FileInfo file in files) 1213 { 1214 string temppath = Path.Combine(destDirName, file.Name); 1215 file.CopyTo(temppath, false); 1216 } 1217 1218 // If copying subdirectories, copy them and their contents to new location. 1219 if (copySubDirs) 1220 { 1221 foreach (DirectoryInfo subdir in dirs) 1222 { 1223 string temppath = Path.Combine(destDirName, subdir.Name); 1224 DirectoryCopy(subdir.FullName, temppath, copySubDirs); 1225 } 1226 } 1227 } 1228 1229 /// <summary> 1230 /// Deletes the content of a directory with optional recursion 1231 /// </summary> 1232 /// <param name="dirName">Directory folder name.</param> 1233 /// <param name="deleteSubDirs">Flag to remove subdirectories recursively.</param> DirectoryDelete(string dirName, bool deleteSubDirs)1234 internal static void DirectoryDelete(string dirName, bool deleteSubDirs) 1235 { 1236 #if DEBUG 1237 Console.WriteLine("FileUtil::DirectoryDelete: Deleting '{0}'{1}", dirName, deleteSubDirs ? " recursively" : ""); 1238 #endif 1239 // Delete all of the files in the directory first 1240 foreach (string srcEntry in Directory.GetFiles(dirName)) 1241 { 1242 FileDelete(srcEntry); 1243 } 1244 1245 if (deleteSubDirs) 1246 { 1247 foreach (string srcEntry in Directory.GetDirectories(dirName)) 1248 { 1249 DirectoryDelete(srcEntry, deleteSubDirs); 1250 } 1251 1252 #if DEBUG 1253 Console.WriteLine("FileUtil::DirectoryDelete: Deleting '{0}'", dirName); 1254 #endif 1255 Directory.Delete(dirName); 1256 } 1257 } 1258 FileDelete(string fileName)1259 internal static void FileDelete(string fileName) 1260 { 1261 #if DEBUG 1262 Console.WriteLine("FileUtil::FileDelete: Deleting '{0}'", fileName); 1263 #endif 1264 if (File.Exists(fileName)) 1265 { 1266 // Strip any readonly, hidden and system attributes 1267 FileInfo fileInfo = new FileInfo(fileName); 1268 fileInfo.Attributes = FileAttributes.Normal; 1269 1270 // Now delete the file 1271 File.Delete(fileName); 1272 } 1273 } 1274 } 1275 1276 /// <summary> 1277 /// Log to the stdout and debug. This is only used in debug builds. 1278 /// </summary> 1279 internal static class Log 1280 { 1281 internal static StreamWriter sw = null; 1282 OpenLogFile(string logFilePath)1283 internal static bool OpenLogFile(string logFilePath) 1284 { 1285 try 1286 { 1287 if (sw == null) 1288 { 1289 string path = Environment.ExpandEnvironmentVariables(logFilePath); 1290 path = PathConverter.Windowsify(path); 1291 string directory = path.Substring(0, path.LastIndexOf('\\')); 1292 if (!Directory.Exists(directory)) 1293 { 1294 Directory.CreateDirectory(directory); 1295 } 1296 sw = new StreamWriter(path); 1297 } 1298 } 1299 catch (Exception ex) 1300 { 1301 Console.WriteLine("Failed to open the log file. InnerException: {0}", ex.Message); 1302 sw = null; 1303 } 1304 return (sw != null); 1305 } WriteLine(string message)1306 internal static void WriteLine(string message) 1307 { 1308 message = string.Format("[ {0} - DEBUG: {1} ] {2}", System.Diagnostics.Process.GetCurrentProcess().Id, DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff"), message); 1309 Console.WriteLine(message); 1310 Debug.WriteLine(message); 1311 if (sw != null) 1312 { 1313 sw.WriteLine(message); 1314 } 1315 } 1316 WriteLine(string format, object arg0)1317 internal static void WriteLine(string format, object arg0) 1318 { 1319 WriteLine(string.Format(format, arg0)); 1320 } 1321 WriteLine(string format, object arg0, object arg1)1322 internal static void WriteLine(string format, object arg0, object arg1) 1323 { 1324 WriteLine(string.Format(format, arg0, arg1)); 1325 } 1326 WriteLine(string format, object arg0, object arg1, object arg2)1327 internal static void WriteLine(string format, object arg0, object arg1, object arg2) 1328 { 1329 WriteLine(string.Format(format, arg0, arg1, arg2)); 1330 } 1331 WriteLineRaw(string message)1332 internal static void WriteLineRaw(string message) 1333 { 1334 Console.WriteLine(message); 1335 Debug.WriteLine(message); 1336 if (sw != null) 1337 { 1338 sw.WriteLine(message); 1339 } 1340 } 1341 WriteLineRaw(string format, object arg0)1342 internal static void WriteLineRaw(string format, object arg0) 1343 { 1344 WriteLineRaw(string.Format(format, arg0)); 1345 } 1346 WriteLineRaw(string format, object arg0, object arg1)1347 internal static void WriteLineRaw(string format, object arg0, object arg1) 1348 { 1349 WriteLineRaw(string.Format(format, arg0, arg1)); 1350 } 1351 WriteLineRaw(string format, object arg0, object arg1, object arg2)1352 internal static void WriteLineRaw(string format, object arg0, object arg1, object arg2) 1353 { 1354 WriteLineRaw(string.Format(format, arg0, arg1, arg2)); 1355 } 1356 CloseLogFile()1357 internal static void CloseLogFile() 1358 { 1359 if (sw != null) 1360 { 1361 sw.Close(); 1362 } 1363 } 1364 } 1365 1366 /// <summary> 1367 /// This class provides tools for argument validation. 1368 /// </summary> 1369 internal static class ArgumentChecks 1370 { IsArgumentValidSetting(string[] args, int index)1371 internal static bool IsArgumentValidSetting(string[] args, int index) 1372 { 1373 #if DEBUG 1374 Log.WriteLine("IsArgumentValidSetting called. Checking index=" + index.ToString()); 1375 string argString = null; 1376 for (int counter = 0; counter < args.Length; ++counter) 1377 { 1378 argString += "{" + counter.ToString() + "}" + args[counter] + " "; 1379 } 1380 Log.WriteLine("arguments=" + argString); 1381 #endif 1382 bool returnVal = false; 1383 // If the argument doesn't exist, it is not valid. 1384 if (!ArgumentExists(args, index)) 1385 { 1386 returnVal = false; 1387 } 1388 else 1389 { 1390 // If the argument starts with '-' it is another option, not a setting. 1391 if (!args[index].StartsWith("-")) 1392 { 1393 returnVal = true; 1394 } 1395 else 1396 { 1397 returnVal = false; 1398 } 1399 } 1400 1401 #if DEBUG 1402 Log.WriteLine("IsArgumentValidSetting return " + returnVal.ToString()); 1403 #endif 1404 return returnVal; 1405 } 1406 1407 /// <summary> 1408 /// Check if the provided index exists in the argument list. 1409 /// </summary> 1410 /// <param name="args">Array of string representing an argument list.</param> 1411 /// <param name="index">The index of the argument that will be checked.</param> 1412 /// <returns>True if the argument at the provide index exists, false otherwise.</returns> ArgumentExists(string[] args, int index)1413 internal static bool ArgumentExists(string[] args, int index) 1414 { 1415 if (args == null || args.Length == 0 || 1416 index < 0 || index > (args.Length - 1)) 1417 { 1418 return false; 1419 } 1420 else 1421 { 1422 return true; 1423 } 1424 } 1425 } 1426 1427 internal class PerfTimer 1428 { 1429 private System.Diagnostics.Stopwatch _clock; 1430 private string _name; 1431 1432 public string Name 1433 { 1434 get { return _name;} 1435 } 1436 PerfTimer(string name)1437 public PerfTimer(string name) 1438 { 1439 _name = name; 1440 _clock = new Stopwatch(); 1441 } 1442 Start()1443 public void Start() 1444 { 1445 Log.WriteLine("[TIMER]: Starting PerfTimer " + _name); 1446 _clock.Start(); 1447 } 1448 Report()1449 public void Report() 1450 { 1451 Log.WriteLine("[TIMER]: PerfTimer " + _name + " current time is " + _clock.ElapsedMilliseconds + " ms."); 1452 } 1453 Stop()1454 public void Stop() 1455 { 1456 _clock.Stop(); 1457 Log.WriteLine("[TIMER]: PerfTimer " + _name + " stopped."); 1458 this.Report(); 1459 } 1460 Reset()1461 public void Reset() 1462 { 1463 _clock.Reset(); 1464 Log.WriteLine("[TIMER]: PerfTimer " + _name + " reset."); 1465 } 1466 } 1467 }