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.

169 lines
5.5 KiB

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