You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
5.1 KiB

  1. using Ionic.Zlib;
  2. using System;
  3. using System.IO;
  4. using System.Runtime.InteropServices;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. #if NET3
  8. using Net3_Proxy;
  9. using Path = Net3_Proxy.Path;
  10. #endif
  11. namespace IPA.Logging.Printers
  12. {
  13. /// <summary>
  14. /// A <see cref="LogPrinter"/> abstract class that provides the utilities to write to a GZip file.
  15. /// </summary>
  16. public abstract class GZFilePrinter : LogPrinter, IDisposable
  17. {
  18. [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  19. private static extern bool CreateHardLink(
  20. string lpFileName,
  21. string lpExistingFileName,
  22. IntPtr lpSecurityAttributes
  23. );
  24. #if NET4
  25. private const RegexOptions reOptions = RegexOptions.Compiled;
  26. #elif NET3 // Needed because Compiled doesn't exist in Unity's .NET 3 runtime
  27. private const RegexOptions reOptions = RegexOptions.None;
  28. #endif
  29. internal static Regex removeControlCodes = new Regex("\x1b\\[\\d+m", reOptions);
  30. private FileInfo fileInfo;
  31. /// <summary>
  32. /// The <see cref="StreamWriter"/> that writes to the GZip file.
  33. /// </summary>
  34. /// <value>the writer to the underlying filestream</value>
  35. protected StreamWriter FileWriter;
  36. private FileStream fstream;
  37. /// <summary>
  38. /// Gets the <see cref="FileInfo"/> for the file to write to.
  39. /// </summary>
  40. /// <returns>the file to write to</returns>
  41. protected abstract FileInfo GetFileInfo();
  42. private const string latestFormat = "_latest{0}";
  43. private void InitLog()
  44. {
  45. try
  46. {
  47. if (fileInfo == null)
  48. { // first init
  49. fileInfo = GetFileInfo();
  50. var ext = fileInfo.Extension;
  51. var symlink = new FileInfo(Path.Combine(fileInfo.DirectoryName ?? throw new InvalidOperationException(), string.Format(latestFormat, ext)));
  52. if (symlink.Exists) symlink.Delete();
  53. foreach (var file in fileInfo.Directory.EnumerateFiles("*.log", SearchOption.TopDirectoryOnly))
  54. {
  55. if (file.Equals(fileInfo)) continue;
  56. if (file.Extension == ".gz") continue;
  57. CompressOldLog(file);
  58. }
  59. fileInfo.Create().Close();
  60. try
  61. {
  62. if (!CreateHardLink(symlink.FullName, fileInfo.FullName, IntPtr.Zero))
  63. {
  64. var error = Marshal.GetLastWin32Error();
  65. Logger.Default.Error($"Hardlink creation failed ({error})");
  66. }
  67. }
  68. catch (Exception e)
  69. {
  70. Logger.Default.Error("Error creating latest hardlink!");
  71. Logger.Default.Error(e);
  72. }
  73. }
  74. }
  75. catch (Exception e)
  76. {
  77. Logger.Default.Error("Error initializing log!");
  78. Logger.Default.Error(e);
  79. }
  80. }
  81. private static async void CompressOldLog(FileInfo file)
  82. {
  83. Logger.Default.Debug($"Compressing log file {file}");
  84. var newFile = new FileInfo(file.FullName + ".gz");
  85. using (var istream = file.OpenRead())
  86. using (var ostream = newFile.Create())
  87. using (var gz = new GZipStream(ostream, CompressionMode.Compress, CompressionLevel.BestCompression, false))
  88. await istream.CopyToAsync(gz);
  89. file.Delete();
  90. }
  91. /// <summary>
  92. /// Called at the start of any print session.
  93. /// </summary>
  94. public sealed override void StartPrint()
  95. {
  96. InitLog();
  97. fstream = fileInfo.Open(FileMode.Append, FileAccess.Write);
  98. FileWriter = new StreamWriter(fstream, new UTF8Encoding(false));
  99. }
  100. /// <summary>
  101. /// Called at the end of any print session.
  102. /// </summary>
  103. public sealed override void EndPrint()
  104. {
  105. FileWriter.Flush();
  106. fstream.Flush();
  107. FileWriter.Dispose();
  108. fstream.Dispose();
  109. FileWriter = null;
  110. fstream = null;
  111. }
  112. /// <inheritdoc />
  113. public void Dispose()
  114. {
  115. Dispose(true);
  116. GC.SuppressFinalize(this);
  117. }
  118. /// <summary>
  119. /// Disposes the file printer.
  120. /// </summary>
  121. /// <param name="disposing">does nothing</param>
  122. protected virtual void Dispose(bool disposing)
  123. {
  124. if (disposing)
  125. {
  126. FileWriter.Flush();
  127. fstream.Flush();
  128. FileWriter.Close();
  129. fstream.Close();
  130. FileWriter.Dispose();
  131. fstream.Dispose();
  132. }
  133. }
  134. }
  135. }