Skip to content
Merged
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
16 changes: 8 additions & 8 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project>
<PropertyGroup>
<Version>1.21.0.0</Version>
<AssemblyVersion>1.21.0.0</AssemblyVersion>
<FileVersion>1.21.0.0</FileVersion>
<InformationalVersion>1.20.0.0</InformationalVersion>
<Version>1.31.0.0</Version>
<AssemblyVersion>1.31.0.0</AssemblyVersion>
<FileVersion>1.31.0.0</FileVersion>
<InformationalVersion>1.31.0.0</InformationalVersion>
<Authors>Hirogen, zarunbal, RandallFlagg, TheNicker</Authors>
<Company>Log Expert</Company>
<Company>LogExperts</Company>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<AssemblyOriginatorKeyFile>..\Solution Items\Key.snk</AssemblyOriginatorKeyFile>
<PublishAot>false</PublishAot>
Expand All @@ -21,8 +21,8 @@
<RepositoryUrl>https://github.com/LogExperts/LogExpert</RepositoryUrl>
<PackageTags>LogExpert, Columnizer, Logging, Windows, Winforms</PackageTags>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/LogExperts/LogExpert/releases/tag/v.1.20.0</PackageReleaseNotes>
<PackageVersion>1.21.0.0</PackageVersion>
<PackageReleaseNotes>https://github.com/LogExperts/LogExpert/releases/tag/v.1.31.0</PackageReleaseNotes>
<PackageVersion>1.31.0.0</PackageVersion>
<Optimize Condition="'$(Configuration)' == 'Release'">true</Optimize>
<Product>LogExpert</Product>
<Copyright>Copyright © LogExpert 2025</Copyright>
Expand Down
18 changes: 14 additions & 4 deletions src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ public RolloverFilenameHandler(ILogFileInfo logFileInfo, MultiFileOptions option
public LinkedList<string> GetNameList(IPluginRegistry pluginRegistry)
{
LinkedList<string> fileList = new();

var fileName = _filenameBuilder.BuildFileName();
var filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;
fileList.AddFirst(filePath);
_ = fileList.AddFirst(filePath);

var found = true;

while (found)
{
found = false;
Expand All @@ -67,11 +70,12 @@ public LinkedList<string> GetNameList(IPluginRegistry pluginRegistry)
filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine
if (FileExists(filePath, pluginRegistry))
{
fileList.AddFirst(filePath);
_ = fileList.AddFirst(filePath);
found = true;
continue;
}
}

// if file with index isn't found or no index is in format pattern, decrement the current date
if (_filenameBuilder.IsDatePattern)
{
Expand All @@ -84,7 +88,7 @@ public LinkedList<string> GetNameList(IPluginRegistry pluginRegistry)
filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine
if (FileExists(filePath, pluginRegistry))
{
fileList.AddFirst(filePath);
_ = fileList.AddFirst(filePath);
found = true;
break;
}
Expand All @@ -104,8 +108,14 @@ public LinkedList<string> GetNameList(IPluginRegistry pluginRegistry)
private bool FileExists(string filePath, IPluginRegistry pluginRegistry)
{
var fs = pluginRegistry.FindFileSystemForUri(filePath);

if(fs == null)
{
return false;
}

var info = fs.GetLogfileInfo(filePath);
return info.FileExists;
return info is not null && info.FileExists;
}

#endregion
Expand Down
8 changes: 4 additions & 4 deletions src/LogExpert.Resources/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/LogExpert.Resources/Resources.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2134,18 +2134,18 @@ LogExpert neu starten, um die Änderungen zu übernehmen?</value>
<value>TabController ist bereits mit einem DockPanel initialisiert</value>
</data>
<data name="SettingsDialog_UI_PortableMode_Title" xml:space="preserve">
<value>Tragbarer Modus</value>
<value>Portable Modus</value>
</data>
<data name="SettingsDialog_UI_PortableMode_CopySettingsQuestion" xml:space="preserve">
<value>Der tragbare Modus konnte nicht aktiviert werden: {0}</value>
<value>Möchten Sie Ihre aktuellen Einstellungen in den Portable-Modus Konfigurationsordner kopieren?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MoveSettingsQuestion" xml:space="preserve">
<value>Möchten Sie Ihre aktuellen Einstellungen in den tragbaren Konfigurationsordner kopieren?</value>
<value>Möchten Sie die tragbaren Einstellungen zurück in den Standardkonfigurationsordner (%APPDATA%\LogExpert) verschieben?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_ActivationError" xml:space="preserve">
<value>Einige Dateien konnten nicht migriert werden: {0}</value>
<value>Der Portable Modus konnte nicht aktiviert werden: {0}</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MigrationError" xml:space="preserve">
<value>Möchten Sie die tragbaren Einstellungen zurück in den Standardkonfigurationsordner (%APPDATA%\LogExpert) verschieben?</value>
<value>Einige Dateien konnten nicht migriert werden: {0}</value>
</data>
</root>
8 changes: 4 additions & 4 deletions src/LogExpert.Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2146,15 +2146,15 @@ Restart LogExpert to apply changes?</value>
<value>Portable Mode</value>
</data>
<data name="SettingsDialog_UI_PortableMode_CopySettingsQuestion" xml:space="preserve">
<value>Failed to activate portable mode: {0}</value>
<value>Do you want to copy your current settings to the portable configuration folder?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MoveSettingsQuestion" xml:space="preserve">
<value>Do you want to copy your current settings to the portable configuration folder?</value>
<value>Do you want to move the portable settings back to the default configuration folder (%APPDATA%\LogExpert)?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_ActivationError" xml:space="preserve">
<value>Some files could not be migrated: {0}</value>
<value>Failed to activate portable mode: {0}</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MigrationError" xml:space="preserve">
<value>Do you want to move the portable settings back to the default configuration folder (%APPDATA%\LogExpert)?</value>
<value>Some files could not be migrated: {0}</value>
</data>
</root>
8 changes: 4 additions & 4 deletions src/LogExpert.Resources/Resources.zh-CN.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2054,15 +2054,15 @@ YY[YY] = 年
<value>便携模式</value>
</data>
<data name="SettingsDialog_UI_PortableMode_CopySettingsQuestion" xml:space="preserve">
<value>无法激活便携模式:{0}</value>
<value>您想将当前设置复制到便携式配置文件夹吗?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MoveSettingsQuestion" xml:space="preserve">
<value>您想将当前设置复制到便携式配置文件夹吗?</value>
<value>您想要将可移植设置移回默认配置文件夹 (%APPDATA%\LogExpert) 吗?</value>
</data>
<data name="SettingsDialog_UI_PortableMode_ActivationError" xml:space="preserve">
<value>某些文件无法迁移:{0}</value>
<value>无法激活便携模式:{0}</value>
</data>
<data name="SettingsDialog_UI_PortableMode_MigrationError" xml:space="preserve">
<value>您想要将可移植设置移回默认配置文件夹 (%APPDATA%\LogExpert) 吗?</value>
<value>某些文件无法迁移:{0}</value>
</data>
</root>
136 changes: 136 additions & 0 deletions src/LogExpert.Tests/RolloverFilenameHandlerNullTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using ColumnizerLib;

using LogExpert.Core.Classes.Log;
using LogExpert.Core.Entities;
using LogExpert.Core.Interface;

using Moq;

using NUnit.Framework;

namespace LogExpert.Tests;

[TestFixture]
internal class RolloverFilenameHandlerNullTests
{
/// <summary>
/// Verifies that GetNameList does not throw when GetLogfileInfo returns null
/// for rollover file candidates. This simulates the SFTP scenario where
/// constructing SftpLogFileInfo fails for non-existent files.
/// </summary>
[Test]
public void GetNameList_WhenGetLogfileInfoReturnsNull_DoesNotThrow ()
{
// Arrange: Create a mock ILogFileInfo for the "base" file
var baseFileInfo = new Mock<ILogFileInfo>();
_ = baseFileInfo.Setup(f => f.FileName).Returns("app.log");
_ = baseFileInfo.Setup(f => f.DirectoryName).Returns("sftp://host/var/log");
_ = baseFileInfo.Setup(f => f.DirectorySeparatorChar).Returns('/');
_ = baseFileInfo.Setup(f => f.FileExists).Returns(true);

// Arrange: Create a mock IFileSystemPlugin that returns null for rollover files
var mockFs = new Mock<IFileSystemPlugin>();
_ = mockFs.Setup(fs => fs.CanHandleUri(It.IsAny<string>())).Returns(true);
// Return null for any GetLogfileInfo call — simulates constructor failure
_ = mockFs.Setup(fs => fs.GetLogfileInfo(It.IsAny<string>())).Returns((ILogFileInfo)null);

// Arrange: Create a mock IPluginRegistry
var mockRegistry = new Mock<IPluginRegistry>();
_ = mockRegistry.Setup(r => r.FindFileSystemForUri(It.IsAny<string>())).Returns(mockFs.Object);

MultiFileOptions options = new()
{
FormatPattern = "*$J(.)",
MaxDayTry = 5
};

RolloverFilenameHandler handler = new(baseFileInfo.Object, options);

// Act & Assert: Should not throw NullReferenceException
LinkedList<string> result = null;
Assert.DoesNotThrow(() => result = handler.GetNameList(mockRegistry.Object));

// The list should contain only the base file
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.First.Value, Does.Contain("app.log"));
}

/// <summary>
/// Verifies that GetNameList does not throw when FindFileSystemForUri returns null.
/// This could happen with an unrecognized URI scheme.
/// </summary>
[Test]
public void GetNameList_WhenFindFileSystemReturnsNull_DoesNotThrow ()
{
// Arrange
var baseFileInfo = new Mock<ILogFileInfo>();
_ = baseFileInfo.Setup(f => f.FileName).Returns("app.log");
_ = baseFileInfo.Setup(f => f.DirectoryName).Returns("custom://host/logs");
_ = baseFileInfo.Setup(f => f.DirectorySeparatorChar).Returns('/');
_ = baseFileInfo.Setup(f => f.FileExists).Returns(true);

var mockRegistry = new Mock<IPluginRegistry>();
_ = mockRegistry.Setup(r => r.FindFileSystemForUri(It.IsAny<string>())).Returns((IFileSystemPlugin)null);

MultiFileOptions options = new()
{
FormatPattern = "*$J(.)",
MaxDayTry = 3
};

RolloverFilenameHandler handler = new(baseFileInfo.Object, options);

// Act & Assert
LinkedList<string> result = null;
Assert.DoesNotThrow(() => result = handler.GetNameList(mockRegistry.Object));

Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.EqualTo(1));
}

/// <summary>
/// Verifies that GetNameList correctly finds rollover files when GetLogfileInfo
/// returns a valid ILogFileInfo with FileExists = true. Ensures the null guard
/// does not break the normal happy path.
/// </summary>
[Test]
public void GetNameList_WhenRolloverFilesExist_ReturnsAllFiles ()
{
// Arrange: base file
var baseFileInfo = new Mock<ILogFileInfo>();
_ = baseFileInfo.Setup(f => f.FileName).Returns("app.log");
_ = baseFileInfo.Setup(f => f.DirectoryName).Returns("/var/log");
_ = baseFileInfo.Setup(f => f.DirectorySeparatorChar).Returns('/');

// Arrange: rollover file .log.1 exists, .log.2 does not
var rollover1Info = new Mock<ILogFileInfo>();
_ = rollover1Info.Setup(f => f.FileExists).Returns(true);

var mockFs = new Mock<IFileSystemPlugin>();
_ = mockFs.Setup(fs => fs.CanHandleUri(It.IsAny<string>())).Returns(true);

// First call (for .log.1) returns a file that exists
// Second call (for .log.2) returns null (simulating constructor failure)
_ = mockFs.SetupSequence(fs => fs.GetLogfileInfo(It.IsAny<string>()))
.Returns(rollover1Info.Object)
.Returns((ILogFileInfo)null);

var mockRegistry = new Mock<IPluginRegistry>();
_ = mockRegistry.Setup(r => r.FindFileSystemForUri(It.IsAny<string>())).Returns(mockFs.Object);

MultiFileOptions options = new()
{
FormatPattern = "*$J(.)",
MaxDayTry = 3
};

RolloverFilenameHandler handler = new(baseFileInfo.Object, options);

// Act
var result = handler.GetNameList(mockRegistry.Object);

// Assert: base file + 1 rollover file
Assert.That(result.Count, Is.EqualTo(2));
}
}
2 changes: 1 addition & 1 deletion src/LogExpert.UI/Controls/LogWindow/LogWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6302,7 +6302,7 @@ public PersistenceData GetPersistenceData ()
TabName = Text,
SessionFileName = SessionFileName,
Columnizer = CurrentColumnizer,
LineCount = _logFileReader.LineCount
LineCount = _logFileReader != null ? _logFileReader.LineCount : 0


};
Expand Down
3 changes: 2 additions & 1 deletion src/LogExpert.UI/Dialogs/SettingsDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,8 @@ private void OnPortableModeCheckedChanged (object sender, EventArgs e)
var markerPath = Path.Join(ConfigManager.PortableConfigDir, ConfigManager.PortableModeSettingsFileName);
if (!File.Exists(markerPath))
{
using (File.Create(markerPath)) { }
using (File.Create(markerPath))
{ }
}

Preferences.PortableMode = true;
Expand Down
Loading