Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/LogExpert.Core/Classes/Log/LogfileReader.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -190,7 +190,11 @@ private LogfileReader (
{
try
{
_mmfReader = new MemoryMappedFileReader(_watchedILogFileInfo.FullName, EncodingOptions.Encoding ?? Encoding.Default);
using var stream = new FileStream(_watchedILogFileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var (preambleLength, detectedEncoding) = PositionAwareStreamReaderBase.DetectPreambleLength(stream);
var encoding = PositionAwareStreamReaderBase.DetermineEncoding(EncodingOptions, detectedEncoding);

_mmfReader = new MemoryMappedFileReader(_watchedILogFileInfo.FullName, encoding, preambleLength);
}
catch (IOException)
{
Expand Down
5 changes: 3 additions & 2 deletions src/LogExpert.Core/Classes/Log/MemoryMappedFileReader.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,10 @@ namespace LogExpert.Core.Classes.Log;
/// Reads log lines via memory-mapped file access. Builds a line-offset index on load.
/// Supports tail mode by re-mapping when the file grows.
/// </summary>
internal sealed class MemoryMappedFileReader (string filePath, Encoding encoding) : IDisposable
internal sealed class MemoryMappedFileReader (string filePath, Encoding encoding, int preambleLength) : IDisposable
{
private readonly Encoding _encoding = encoding;
private readonly int _preambleLength = preambleLength;
private readonly LineOffsetIndex _lineIndex = new();
private MemoryMappedFile _mmf;
private MemoryMappedViewAccessor _accessor;
Expand All@@ -33,7 +34,7 @@ public void BuildIndex (long startOffset = 0)
if (startOffset == 0)
{
_lineIndex.Clear();
_lineIndex.Add(0); // first line starts at offset 0
_lineIndex.Add(_preambleLength); // Skip the BOM, when present, just like StreamReader.
}

fs.Position = startOffset;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -165,9 +165,9 @@ protected void MovePosition (int offset)

public static Encoding DetermineEncoding (EncodingOptions options, Encoding detectedEncoding)
{
return options?.Encoding != null
? options.Encoding
: detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default;
// An explicit/persisted encoding also represents a manual Encoding-menu choice.
// Without one, use the file BOM before the configured and machine defaults.
return options?.Encoding ?? detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default;
}

/// <summary>
Expand Down
13 changes: 13 additions & 0 deletions src/LogExpert.Tests/Dialogs/SettingsDialogPortableModeTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
using System.Text;

using LogExpert;
using LogExpert.Core.Config;
using LogExpert.Core.Interfaces;
using LogExpert.Dialogs;
Expand All@@ -19,6 +22,16 @@ namespace LogExpert.Tests.Dialogs;
[TestFixture]
public class SettingsDialogPortableModeTests
{
[Test]
public void AvailableEncodings_ContainsWindows1250 ()
{
Program.RegisterEncodingProvider();

var encodings = SettingsDialog.GetAvailableEncodings();

Assert.That(encodings.Select(encoding => encoding.CodePage), Does.Contain(1250));
}

private string _testDataPath = null!;
private string _portableConfigDir = null!;

Expand Down
11 changes: 11 additions & 0 deletions src/LogExpert.Tests/IPC/OneInstanceIpcTests.cs
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
using System.Text;

using LogExpert;
using LogExpert.Core.Classes.IPC;

using Newtonsoft.Json;
Expand All@@ -13,6 +16,14 @@ namespace LogExpert.Tests.IPC;
[TestFixture]
public class OneInstanceIpcTests
{
[Test]
public void RegisterEncodingProvider_MakesWindows1252Available ()
{
Program.RegisterEncodingProvider();

Assert.That(Encoding.GetEncoding(1252).CodePage, Is.EqualTo(1252));
}

#region IPC Message Type Tests

[Test]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ public class LogfileReaderBlockAllocationTests
[SetUp]
public void Setup ()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
_tempFile = Path.GetTempFileName();
_ = PluginRegistry.PluginRegistry.Create(Path.GetDirectoryName(_tempFile)!, 500);
}
Expand DownExpand Up@@ -229,6 +230,60 @@ public void ReadFiles_MultipleBufferRotations_AllLinesCorrect ()
}
}

[Test]
public void ReadFiles_BomlessFile_UsesConfiguredDefaultEncoding ()
{
var configuredEncoding = Encoding.GetEncoding(1252);
File.WriteAllText(_tempFile, "Euro: €\n", configuredEncoding);

using var reader = CreateReader(new EncodingOptions { DefaultEncoding = configuredEncoding });
reader.ReadFiles();

Assert.That(reader.CurrentEncoding.CodePage, Is.EqualTo(configuredEncoding.CodePage));
Assert.That(reader.GetLogLineMemory(0)?.FullLine.Span.ToString(), Is.EqualTo("Euro: €"));
}

[Test]
public void ReadFiles_Bom_OverridesConfiguredDefaultEncoding ()
{
var configuredEncoding = Encoding.GetEncoding(1252);
File.WriteAllText(_tempFile, "Euro: €\n", Encoding.UTF8);

using var reader = CreateReader(new EncodingOptions { DefaultEncoding = configuredEncoding });
reader.ReadFiles();

Assert.That(reader.CurrentEncoding.WebName, Is.EqualTo(Encoding.UTF8.WebName));
Assert.That(reader.GetLogLineMemory(0)?.FullLine.Span.ToString(), Is.EqualTo("Euro: €"));
}

[Test]
public void ReadFiles_ExplicitEncoding_OverridesBom ()
{
var explicitEncoding = Encoding.GetEncoding(1252);
File.WriteAllText(_tempFile, "ASCII text\n", Encoding.UTF8);

using var reader = CreateReader(new EncodingOptions { Encoding = explicitEncoding });
reader.ReadFiles();

Assert.That(reader.CurrentEncoding.CodePage, Is.EqualTo(explicitEncoding.CodePage));
Assert.That(reader.GetLogLineMemory(0)?.FullLine.Span.ToString(), Is.EqualTo("ASCII text"));
}

private LogfileReader CreateReader (EncodingOptions encodingOptions)
{
return new LogfileReader(
_tempFile,
encodingOptions,
multiFile: false,
bufferCount: 100,
linesPerBuffer: 500,
new MultiFileOptions(),
ReaderType.System,
PluginRegistry.PluginRegistry.Instance,
maximumLineLength: 500,
progressReporter: Core.Classes.Log.ProgressReporters.NullProgressReporter.Instance);
}

private static void VerifyLine (LogfileReader reader, int lineNum, int expectedIndex)
{
var line = reader.GetLogLineMemory(lineNum);
Expand Down
33 changes: 23 additions & 10 deletions src/LogExpert.UI/Dialogs/SettingsDialog.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,8 +86,6 @@ private SettingsDialog (Preferences prefs, LogTabWindow logTabWin)

LoadResources();

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

ResumeLayout();
}

Expand DownExpand Up@@ -693,23 +691,38 @@ private void DisplayCurrentIcon ()
/// </summary>
/// <remarks>
/// This method clears any existing items in the combo box and adds a selection of common encodings, including
/// ASCII, Default (UTF-8), ISO-8859-1, UTF-8, Unicode, and Windows-1252. The value member of the combo box is set
/// ASCII, Default (UTF-8), ISO-8859-1, UTF-8, Unicode, Windows-1250, and Windows-1252. The value member of the combo box is set
/// to a specific header name defined in the resources.
/// </remarks>
private void FillEncodingList ()
{
comboBoxEncoding.Items.Clear();

_ = comboBoxEncoding.Items.Add(Encoding.ASCII);
_ = comboBoxEncoding.Items.Add(Encoding.Default);
_ = comboBoxEncoding.Items.Add(Encoding.GetEncoding("iso-8859-1"));
_ = comboBoxEncoding.Items.Add(Encoding.UTF8);
_ = comboBoxEncoding.Items.Add(Encoding.Unicode);
_ = comboBoxEncoding.Items.Add(CodePagesEncodingProvider.Instance.GetEncoding(1252));
foreach (var encoding in GetAvailableEncodings())
{
_ = comboBoxEncoding.Items.Add(encoding);
}

comboBoxEncoding.ValueMember = Resources.SettingsDialog_UI_ComboBox_Encoding_ValueMember_HeaderName;
}

/// <summary>
/// Gets the encodings offered in the Preferences dropdown.
/// </summary>
internal static IReadOnlyList<Encoding> GetAvailableEncodings ()
{
return
[
Encoding.ASCII,
Encoding.Default,
Encoding.GetEncoding("iso-8859-1"),
Encoding.UTF8,
Encoding.Unicode,
Encoding.GetEncoding(1250),
Encoding.GetEncoding(1252)
];
}

/// <summary>
/// Populates the language selection list with available language options.
/// </summary>
Expand DownExpand Up@@ -1622,4 +1635,4 @@ private void UpdateSampleAndPreview ()
}

#endregion
}
}
22 changes: 16 additions & 6 deletions src/LogExpert/Program.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,8 @@ private static void Main (string[] args)
Application.EnableVisualStyles();
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

RegisterEncodingProvider();

// Register the plugin assembly resolver early so that settings deserialization
// can find plugin types (e.g., CsvColumnizer) before PluginRegistry.Create() runs.
PluginRegistry.PluginRegistry.RegisterAssemblyResolver();
Expand DownExpand Up@@ -247,8 +249,8 @@ or ArgumentNullException
}
}

[SupportedOSPlatform("windows")]
private static void SetDarkMode ()
[SupportedOSPlatform("windows")]
private static void SetDarkMode ()
{
var darkModeEnabled = ConfigManager.Instance.Settings.Preferences.DarkMode;
if (darkModeEnabled)
Expand All@@ -259,9 +261,17 @@ private static void SetDarkMode ()
{
Application.SetColorMode(SystemColorMode.System);
}
}

[SupportedOSPlatform("windows")]
}

/// <summary>
/// Makes legacy Windows code pages configured in preferences available before settings are read.
/// </summary>
internal static void RegisterEncodingProvider ()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}

[SupportedOSPlatform("windows")]
private static void SetCulture ()
{
var defaultCulture = CultureInfo.GetCultureInfo(ConfigManager.Instance.Settings.Preferences.DefaultLanguage ?? "en-US");
Expand DownExpand Up@@ -485,4 +495,4 @@ private static void CurrentDomain_UnhandledException (object sender, UnhandledEx
}

#endregion
}
}
Loading