From 6211da7797135bf1b2fcf4752d2f325f87a227bd Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Mon, 19 Oct 2026 11:02:23 +0200 Subject: [PATCH 1/8] =?UTF-8?q?Fix=20MultiFile=20=E2=80=9CMax=20days?= =?UTF-8?q?=E2=80=9D=20filtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added the file’s UTC last-write timestamp to ILogFileInfo. - Implemented timestamp retrieval for: - Local files - SFTP files - Applied the Max days limit when MultiFile companion files are discovered. - The file explicitly opened by the user is always retained. - MaxDayTry = 0 preserves the previous unlimited behavior. - Filesystem plugins that cannot provide a timestamp retain the previous behavior and their files are not excluded. --- src/ColumnizerLib/ILogFileInfo.cs | 8 ++- .../Classes/Log/RolloverFilenameHandler.cs | 18 +++-- .../Entities/MultifileOptions.cs | 6 +- src/LogExpert.Tests/LogExpert.Tests.csproj | 3 + src/LogExpert.Tests/RolloverHandlerTest.cs | 56 +++++++++++++++- .../LogfileReaderMultiFileFlagTests.cs | 67 ++++++++++++++++++- src/LogExpert.Tests/TestData/app.log.2 | 1 + src/PluginRegistry/FileSystem/LogFileInfo.cs | 11 ++- src/SftpFileSystemx64/SftpLogFileInfo.cs | 22 +++++- 9 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 src/LogExpert.Tests/TestData/app.log.2 diff --git a/src/ColumnizerLib/ILogFileInfo.cs b/src/ColumnizerLib/ILogFileInfo.cs index 6a10b530c..b2d3052e5 100644 --- a/src/ColumnizerLib/ILogFileInfo.cs +++ b/src/ColumnizerLib/ILogFileInfo.cs @@ -51,6 +51,12 @@ public interface ILogFileInfo /// bool FileExists { get; } + /// + /// Gets the time when the file was last written, in UTC, or when the file system plugin + /// cannot provide it. + /// + DateTime? LastWriteTimeUtc => null; + /// /// The interval (in ms) LogExpert should check for file changes. The property is checked by LogExpert repeatedly in the loop which /// checks for file changes. So you can adjust the poll interval as needed. E.g. you can lower the interval when many changes occur and @@ -81,4 +87,4 @@ public interface ILogFileInfo bool FileHasChanged(); #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs index 1c4fe52c9..3b16aab49 100644 --- a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs +++ b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs @@ -69,7 +69,7 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) _filenameBuilder.Index += 1; fileName = _filenameBuilder.BuildFileName(); filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine - if (FileExists(filePath, pluginRegistry)) + if (FileExistsWithinAgeLimit(filePath, pluginRegistry)) { _ = fileList.AddFirst(filePath); found = true; @@ -87,7 +87,7 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) _filenameBuilder.DecrementDate(); fileName = _filenameBuilder.BuildFileName(); filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine - if (FileExists(filePath, pluginRegistry)) + if (FileExistsWithinAgeLimit(filePath, pluginRegistry)) { _ = fileList.AddFirst(filePath); found = true; @@ -106,7 +106,7 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) #region Private Methods - private bool FileExists(string filePath, IPluginRegistry pluginRegistry) + private bool FileExistsWithinAgeLimit(string filePath, IPluginRegistry pluginRegistry) { var fs = pluginRegistry.FindFileSystemForUri(filePath); @@ -116,8 +116,16 @@ private bool FileExists(string filePath, IPluginRegistry pluginRegistry) } var info = fs.GetLogfileInfo(filePath); - return info is not null && info.FileExists; + if (info is null || !info.FileExists) + { + return false; + } + + // Zero keeps the historical unlimited behavior. Files without timestamp support are retained. + return _options.MaxDayTry <= 0 + || info.LastWriteTimeUtc is not { } lastWriteTimeUtc + || lastWriteTimeUtc >= DateTime.UtcNow.AddDays(-_options.MaxDayTry); } #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Core/Entities/MultifileOptions.cs b/src/LogExpert.Core/Entities/MultifileOptions.cs index b9ab6d782..a1b0c16d3 100644 --- a/src/LogExpert.Core/Entities/MultifileOptions.cs +++ b/src/LogExpert.Core/Entities/MultifileOptions.cs @@ -5,9 +5,13 @@ public class MultiFileOptions { #region Properties + /// + /// Gets or sets the maximum age, in days, of companion files loaded into a MultiFile. For date-based patterns, + /// this also limits how many missing calendar days are searched. Zero disables the file-age limit. + /// public int MaxDayTry { get; set; } = 3; public string FormatPattern { get; set; } = "*$J(.)"; #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Tests/LogExpert.Tests.csproj b/src/LogExpert.Tests/LogExpert.Tests.csproj index 1f64b7ffe..b78e92ee0 100644 --- a/src/LogExpert.Tests/LogExpert.Tests.csproj +++ b/src/LogExpert.Tests/LogExpert.Tests.csproj @@ -111,6 +111,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/LogExpert.Tests/RolloverHandlerTest.cs b/src/LogExpert.Tests/RolloverHandlerTest.cs index 1f5e13b17..20f683172 100644 --- a/src/LogExpert.Tests/RolloverHandlerTest.cs +++ b/src/LogExpert.Tests/RolloverHandlerTest.cs @@ -91,4 +91,58 @@ public void TestFilenameListWithDate (string format, int retries) Cleanup(); } -} \ No newline at end of file + + [Test] + [TestCase("*$J(.)", "app.log.1", "app.log.2")] + [TestCase("*$J(.).log", "app.1.log", "app.2.log")] + public void GetNameList_ExcludesIndexedFilesOlderThanMaxDays ( + string formatPattern, + string recentFileName, + string oldFileName) + { + var directory = Directory.CreateDirectory(TEST_DIR_NAME); + TestDirectory = directory; + var activeFile = CreateFile(directory, "app.log"); + var recentFile = CreateFile(directory, recentFileName); + var oldFile = CreateFile(directory, oldFileName); + File.SetLastWriteTimeUtc(activeFile, DateTime.UtcNow); + File.SetLastWriteTimeUtc(recentFile, DateTime.UtcNow.AddDays(-2)); + File.SetLastWriteTimeUtc(oldFile, DateTime.UtcNow.AddDays(-4)); + + MultiFileOptions options = new() + { + FormatPattern = formatPattern, + MaxDayTry = 3 + }; + ILogFileInfo info = new LogFileInfo(new Uri(activeFile)); + RolloverFilenameHandler handler = new(info, options); + + var fileList = handler.GetNameList(PluginRegistry.PluginRegistry.Instance); + + Assert.That(fileList, Is.EqualTo(new[] { recentFile, activeFile })); + Cleanup(); + } + + [Test] + public void GetNameList_WithZeroMaxDays_DoesNotFilterByAge () + { + var directory = Directory.CreateDirectory(TEST_DIR_NAME); + TestDirectory = directory; + var activeFile = CreateFile(directory, "app.log"); + var oldFile = CreateFile(directory, "app.log.1"); + File.SetLastWriteTimeUtc(oldFile, DateTime.UtcNow.AddYears(-1)); + + MultiFileOptions options = new() + { + FormatPattern = "*$J(.)", + MaxDayTry = 0 + }; + ILogFileInfo info = new LogFileInfo(new Uri(activeFile)); + RolloverFilenameHandler handler = new(info, options); + + var fileList = handler.GetNameList(PluginRegistry.PluginRegistry.Instance); + + Assert.That(fileList, Is.EqualTo(new[] { oldFile, activeFile })); + Cleanup(); + } +} diff --git a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs index 00e36f731..c1a681f4f 100644 --- a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -26,6 +26,8 @@ public void SetUp () File.Copy(Path.Join(_testDataDirectory, "app.log"), _logFile); File.Copy(Path.Join(_testDataDirectory, "app.log.1"), _logFile + ".1"); + File.SetLastWriteTimeUtc(_logFile, DateTime.UtcNow); + File.SetLastWriteTimeUtc(_logFile + ".1", DateTime.UtcNow); _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); } @@ -68,12 +70,39 @@ public void SingleFileCtor_MultiFileTrue_ExpandsRollover () }); } + [Test] + public void SingleFileCtor_MultiFileTrue_ExcludesIndexAfterExtensionFilesOlderThanMaxDays () + { + var oldRolloverFile = _logFile + ".2"; + File.Copy(Path.Combine(_testDataDirectory, "app.log.2"), oldRolloverFile); + var now = DateTime.UtcNow; + File.SetLastWriteTimeUtc(_logFile, now); + File.SetLastWriteTimeUtc(_logFile + ".1", now.AddDays(-2)); + File.SetLastWriteTimeUtc(oldRolloverFile, now.AddDays(-4)); + MultiFileOptions options = new() + { + FormatPattern = "*$J(.)", + MaxDayTry = 3 + }; + using var reader = CreateSingleFileReader(multiFile: true, options); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.True); + Assert.That(reader.GetLogFileInfoList().Select(file => Path.GetFileName(file.FullName)), + Is.EqualTo(new[] { "app.log.1", "app.log" })); + }); + } + [Test] public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () { var options = new MultiFileOptions { FormatPattern = "*$J(.).log" }; - File.Copy(Path.Combine(_testDataDirectory, "app.1.log"), Path.Combine(_testDirectory, "app.1.log")); - File.Copy(Path.Combine(_testDataDirectory, "app.2.log"), Path.Combine(_testDirectory, "app.2.log")); + var (firstRolloverFile, secondRolloverFile) = CopyIndexBeforeExtensionFiles(); + File.SetLastWriteTimeUtc(firstRolloverFile, DateTime.UtcNow); + File.SetLastWriteTimeUtc(secondRolloverFile, DateTime.UtcNow); using var reader = CreateSingleFileReader(multiFile: true, options); reader.ReadFiles(); @@ -86,6 +115,31 @@ public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () }); } + [Test] + public void SingleFileCtor_MultiFileTrue_ExcludesIndexBeforeExtensionFilesOlderThanMaxDays () + { + var options = new MultiFileOptions + { + FormatPattern = "*$J(.).log", + MaxDayTry = 3 + }; + var (firstRolloverFile, secondRolloverFile) = CopyIndexBeforeExtensionFiles(); + var now = DateTime.UtcNow; + File.SetLastWriteTimeUtc(_logFile, now); + File.SetLastWriteTimeUtc(firstRolloverFile, now.AddDays(-2)); + File.SetLastWriteTimeUtc(secondRolloverFile, now.AddDays(-4)); + using var reader = CreateSingleFileReader(multiFile: true, options); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.True); + Assert.That(reader.GetLogFileInfoList().Select(file => Path.GetFileName(file.FullName)), + Is.EqualTo(new[] { "app.1.log", "app.log" })); + }); + } + [Test] public void MultiFileCtor_AlwaysMultiFile () { @@ -124,4 +178,13 @@ private LogfileReader CreateSingleFileReader (bool multiFile, MultiFileOptions? progressReporter: NullProgressReporter.Instance); } + private (string FirstRolloverFile, string SecondRolloverFile) CopyIndexBeforeExtensionFiles () + { + var firstRolloverFile = Path.Combine(_testDirectory, "app.1.log"); + var secondRolloverFile = Path.Combine(_testDirectory, "app.2.log"); + File.Copy(Path.Combine(_testDataDirectory, "app.1.log"), firstRolloverFile); + File.Copy(Path.Combine(_testDataDirectory, "app.2.log"), secondRolloverFile); + return (firstRolloverFile, secondRolloverFile); + } + } diff --git a/src/LogExpert.Tests/TestData/app.log.2 b/src/LogExpert.Tests/TestData/app.log.2 new file mode 100644 index 000000000..501086237 --- /dev/null +++ b/src/LogExpert.Tests/TestData/app.log.2 @@ -0,0 +1 @@ +app.log.2 diff --git a/src/PluginRegistry/FileSystem/LogFileInfo.cs b/src/PluginRegistry/FileSystem/LogFileInfo.cs index bfcdbe003..cd6727753 100644 --- a/src/PluginRegistry/FileSystem/LogFileInfo.cs +++ b/src/PluginRegistry/FileSystem/LogFileInfo.cs @@ -88,6 +88,15 @@ public bool FileExists } } + public DateTime? LastWriteTimeUtc + { + get + { + fInfo.Refresh(); + return fInfo.Exists ? fInfo.LastWriteTimeUtc : null; + } + } + //TODO this should be set from outside once public int PollInterval => PluginRegistry.PollingInterval; @@ -174,4 +183,4 @@ public override string ToString () } #endregion -} \ No newline at end of file +} diff --git a/src/SftpFileSystemx64/SftpLogFileInfo.cs b/src/SftpFileSystemx64/SftpLogFileInfo.cs index 6cf5cca7c..585913645 100644 --- a/src/SftpFileSystemx64/SftpLogFileInfo.cs +++ b/src/SftpFileSystemx64/SftpLogFileInfo.cs @@ -235,6 +235,26 @@ public string FileName public string FullName => Uri.ToString(); + public DateTime? LastWriteTimeUtc + { + get + { + try + { + var file = (SftpFile)_sftp.Get(_remoteFileName); + return file.Attributes.LastWriteTimeUtc; + } + catch (Exception e) when (e is SshException or + SftpPathNotFoundException or + SftpPermissionDeniedException or + ArgumentNullException) + { + _logger.LogError(e.Message); + return null; + } + } + } + public long Length { get @@ -305,4 +325,4 @@ public Stream OpenStream () } #endregion -} \ No newline at end of file +} From 963aaa52ec7986e191185a1129a1597a8e04516a Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Mon, 19 Oct 2026 11:15:42 +0200 Subject: [PATCH 2/8] Hint added EN, DE, CNs --- src/LogExpert.Resources/Resources.Designer.cs | 9 +++++++++ src/LogExpert.Resources/Resources.de.resx | 3 +++ src/LogExpert.Resources/Resources.resx | 3 +++ src/LogExpert.Resources/Resources.zh-CN.resx | 5 ++++- .../Dialogs/MultiFileMaskDialog.Designer.cs | 14 +++++++++++++- src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs | 3 ++- 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index c9a2efa58..c5cb10175 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -4442,6 +4442,15 @@ public static string MultiFileMaskDialog_UI_Label_MaxDays { return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDays", resourceCulture); } } + + /// + /// Looks up a localized string similar to 0 = Unlimited number of days. + /// + public static string MultiFileMaskDialog_UI_Label_MaxDaysHint { + get { + return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDaysHint", resourceCulture); + } + } /// /// Looks up a localized string similar to MultiFile settings for:. diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index 2ef41f1cb..882157a39 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -1679,6 +1679,9 @@ Ein ausgewähltes Tool erscheint in der Iconbar. Alle anderen verfügbaren Tools Max Tage: + + 0 = Unbegrenzte Tage + Muster syntax: diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index 79a319f5b..7891f537b 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -1742,6 +1742,9 @@ Checked tools will appear in the icon bar. All other tools are available in the Max days: + + 0 = Unlimited number of days + Pattern syntax: diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index d8026b569..cf4bd7c05 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -1548,6 +1548,9 @@ 最大天数: + + 0 = 天数不受限制 + 模式语法: @@ -2261,4 +2264,4 @@ YY[YY] = 年 无法应用隐藏规则,显示所有行:{0} - \ No newline at end of file + diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs index 2d95ab396..ecd580420 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs @@ -33,6 +33,7 @@ private void InitializeComponent() this.labelFileName = new System.Windows.Forms.Label(); this.labelFileNamePattern = new System.Windows.Forms.Label(); this.upDownMaxDays = new System.Windows.Forms.NumericUpDown(); + this.labelMaxDaysHint = new System.Windows.Forms.Label(); this.fileNamePatternTextBox = new System.Windows.Forms.TextBox(); this.labelMaxDays = new System.Windows.Forms.Label(); this.Settings = new System.Windows.Forms.GroupBox(); @@ -86,7 +87,16 @@ private void InitializeComponent() 0, 0, 0}); - // + // + // labelMaxDaysHint + // + this.labelMaxDaysHint.AutoSize = true; + this.labelMaxDaysHint.Location = new System.Drawing.Point(146, 57); + this.labelMaxDaysHint.Name = "labelMaxDaysHint"; + this.labelMaxDaysHint.Size = new System.Drawing.Size(202, 20); + this.labelMaxDaysHint.TabIndex = 6; + this.labelMaxDaysHint.Text = "0 = Unlimited number of days"; + // // fileNamePatternTextBox // this.fileNamePatternTextBox.Location = new System.Drawing.Point(149, 25); @@ -110,6 +120,7 @@ private void InitializeComponent() this.Settings.Controls.Add(this.labelFileNamePattern); this.Settings.Controls.Add(this.labelMaxDays); this.Settings.Controls.Add(this.upDownMaxDays); + this.Settings.Controls.Add(this.labelMaxDaysHint); this.Settings.Controls.Add(this.fileNamePatternTextBox); this.Settings.Location = new System.Drawing.Point(15, 39); this.Settings.Name = "Settings"; @@ -182,6 +193,7 @@ private void InitializeComponent() private System.Windows.Forms.Label labelFileName; private System.Windows.Forms.Label labelFileNamePattern; private System.Windows.Forms.NumericUpDown upDownMaxDays; + private System.Windows.Forms.Label labelMaxDaysHint; private System.Windows.Forms.TextBox fileNamePatternTextBox; private System.Windows.Forms.Label labelMaxDays; private System.Windows.Forms.GroupBox Settings; diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs index ef81346a5..054e2a2d6 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs @@ -34,6 +34,7 @@ private void ApplyResources () labelMultiSettingsFor.Text = Resources.MultiFileMaskDialog_UI_Label_SettingsFor; labelFileNamePattern.Text = Resources.MultiFileMaskDialog_UI_Label_FileNamePattern; labelMaxDays.Text = Resources.MultiFileMaskDialog_UI_Label_MaxDays; + labelMaxDaysHint.Text = Resources.MultiFileMaskDialog_UI_Label_MaxDaysHint; syntaxHelpLabel.Text = Resources.MultiFileMaskDialog_UI_Label_SyntaxHelp; buttonOk.Text = Resources.LogExpert_Common_UI_Button_OK; buttonCancel.Text = Resources.LogExpert_Common_UI_Button_Cancel; @@ -66,4 +67,4 @@ private void OnMultiFileMaskDialogLoad (object sender, EventArgs e) } #endregion -} \ No newline at end of file +} From a7dd5f9fe0034f6003306b809d3681a740f0c7f5 Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Mon, 19 Oct 2026 14:56:41 +0200 Subject: [PATCH 3/8] Fixed update as per request --- src/ColumnizerLib/ColumnizerLib.csproj | 1 + src/ColumnizerLib/ILogFileInfo.cs | 2 +- .../Classes/Log/RolloverFilenameHandler.cs | 47 ++++- .../Classes/Persister/PersistenceData.cs | 2 + .../Classes/Persister/PersisterXML.cs | 12 ++ .../Classes/Persister/SessionFileComposer.cs | 2 + .../Classes/Persister/SessionSnapshot.cs | 2 + .../Entities/MultifileOptions.cs | 10 +- .../PersisterTests.cs | 3 + .../PersisterXmlMalformedTests.cs | 1 + .../SessionFileComposerTests.cs | 26 +++ src/LogExpert.Resources/Resources.Designer.cs | 63 +++++- src/LogExpert.Resources/Resources.de.resx | 33 ++- src/LogExpert.Resources/Resources.resx | 33 ++- src/LogExpert.Resources/Resources.zh-CN.resx | 33 ++- .../PreferencesMultiFileOptionsTests.cs | 48 +++++ src/LogExpert.Tests/LogExpert.Tests.csproj | 3 - .../RolloverFilenameHandlerAgeTests.cs | 194 ++++++++++++++++++ src/LogExpert.Tests/RolloverHandlerTest.cs | 56 +---- .../LogfileReaderMultiFileFlagTests.cs | 67 +----- src/LogExpert.Tests/TestData/app.log.2 | 1 - .../Dialogs/MultiFileAgeSettingsTests.cs | 107 ++++++++++ .../Controls/LogWindow/LogWindow.cs | 10 +- .../Dialogs/MultiFileMaskDialog.Designer.cs | 113 ++++++---- .../Dialogs/MultiFileMaskDialog.cs | 18 +- .../Dialogs/SettingsDialog.Designer.cs | 69 +++++-- src/LogExpert.UI/Dialogs/SettingsDialog.cs | 6 +- src/PluginRegistry/FileSystem/LogFileInfo.cs | 2 +- src/SftpFileSystemx64/SftpLogFileInfo.cs | 63 +++--- 29 files changed, 759 insertions(+), 268 deletions(-) create mode 100644 src/LogExpert.Tests/ConfigManagerTests/PreferencesMultiFileOptionsTests.cs create mode 100644 src/LogExpert.Tests/RolloverFilenameHandlerAgeTests.cs delete mode 100644 src/LogExpert.Tests/TestData/app.log.2 create mode 100644 src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs diff --git a/src/ColumnizerLib/ColumnizerLib.csproj b/src/ColumnizerLib/ColumnizerLib.csproj index df508ab62..54252f118 100644 --- a/src/ColumnizerLib/ColumnizerLib.csproj +++ b/src/ColumnizerLib/ColumnizerLib.csproj @@ -8,6 +8,7 @@ ColumnizerLib LogExpert.ColumnizerLib + 1.22.0 diff --git a/src/ColumnizerLib/ILogFileInfo.cs b/src/ColumnizerLib/ILogFileInfo.cs index b2d3052e5..ed5a36309 100644 --- a/src/ColumnizerLib/ILogFileInfo.cs +++ b/src/ColumnizerLib/ILogFileInfo.cs @@ -87,4 +87,4 @@ public interface ILogFileInfo bool FileHasChanged(); #endregion -} +} \ No newline at end of file diff --git a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs index 3b16aab49..a7cefec28 100644 --- a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs +++ b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs @@ -21,6 +21,7 @@ public class RolloverFilenameHandler private readonly RolloverFilenameBuilder _filenameBuilder; private readonly ILogFileInfo _logFileInfo; private readonly MultiFileOptions _options; + private readonly TimeProvider _timeProvider; #endregion @@ -31,10 +32,12 @@ public class RolloverFilenameHandler /// /// The complete path of the logfile /// Multifile option (e.g. format pattern) - public RolloverFilenameHandler(ILogFileInfo logFileInfo, MultiFileOptions options) + /// Time source used to calculate the file-age cutoff. + public RolloverFilenameHandler(ILogFileInfo logFileInfo, MultiFileOptions options, TimeProvider timeProvider = null) { _options = options; _logFileInfo = logFileInfo; + _timeProvider = timeProvider ?? TimeProvider.System; _filenameBuilder = new RolloverFilenameBuilder(_options.FormatPattern); _filenameBuilder.SetFileName(logFileInfo.FileName); } @@ -69,12 +72,18 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) _filenameBuilder.Index += 1; fileName = _filenameBuilder.BuildFileName(); filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine - if (FileExistsWithinAgeLimit(filePath, pluginRegistry)) + var result = GetCandidateResult(filePath, pluginRegistry); + if (result == CandidateResult.Include) { _ = fileList.AddFirst(filePath); found = true; continue; } + + if (result == CandidateResult.TooOld) + { + return fileList; + } } // if file with index isn't found or no index is in format pattern, decrement the current date @@ -87,13 +96,19 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) _filenameBuilder.DecrementDate(); fileName = _filenameBuilder.BuildFileName(); filePath = _logFileInfo.DirectoryName + _logFileInfo.DirectorySeparatorChar + fileName;//TODO: Change to Directory.Combine - if (FileExistsWithinAgeLimit(filePath, pluginRegistry)) + var result = GetCandidateResult(filePath, pluginRegistry); + if (result == CandidateResult.Include) { _ = fileList.AddFirst(filePath); found = true; break; } + if (result == CandidateResult.TooOld) + { + return fileList; + } + tryCounter++; } } @@ -106,26 +121,36 @@ public LinkedList GetNameList(IPluginRegistry pluginRegistry) #region Private Methods - private bool FileExistsWithinAgeLimit(string filePath, IPluginRegistry pluginRegistry) + private CandidateResult GetCandidateResult(string filePath, IPluginRegistry pluginRegistry) { var fs = pluginRegistry.FindFileSystemForUri(filePath); if(fs == null) { - return false; + return CandidateResult.Missing; } var info = fs.GetLogfileInfo(filePath); if (info is null || !info.FileExists) { - return false; + return CandidateResult.Missing; } - // Zero keeps the historical unlimited behavior. Files without timestamp support are retained. - return _options.MaxDayTry <= 0 - || info.LastWriteTimeUtc is not { } lastWriteTimeUtc - || lastWriteTimeUtc >= DateTime.UtcNow.AddDays(-_options.MaxDayTry); + if (_options.MaxFileAgeDays <= 0 || info.LastWriteTimeUtc is not { } lastWriteTimeUtc) + { + return CandidateResult.Include; + } + + var cutoffUtc = _timeProvider.GetUtcNow().UtcDateTime.AddDays(-_options.MaxFileAgeDays); + return lastWriteTimeUtc >= cutoffUtc ? CandidateResult.Include : CandidateResult.TooOld; + } + + private enum CandidateResult + { + Missing, + Include, + TooOld } #endregion -} +} \ No newline at end of file diff --git a/src/LogExpert.Core/Classes/Persister/PersistenceData.cs b/src/LogExpert.Core/Classes/Persister/PersistenceData.cs index 37e80e377..fb72943c0 100644 --- a/src/LogExpert.Core/Classes/Persister/PersistenceData.cs +++ b/src/LogExpert.Core/Classes/Persister/PersistenceData.cs @@ -71,6 +71,8 @@ public class PersistenceData public int MultiFileMaxDays { get; set; } + public int MultiFileMaxFileAgeDays { get; set; } + public List MultiFileNames { get; set; } = []; public string MultiFilePattern { get; set; } diff --git a/src/LogExpert.Core/Classes/Persister/PersisterXML.cs b/src/LogExpert.Core/Classes/Persister/PersisterXML.cs index f27285a72..a236e19b8 100644 --- a/src/LogExpert.Core/Classes/Persister/PersisterXML.cs +++ b/src/LogExpert.Core/Classes/Persister/PersisterXML.cs @@ -521,6 +521,18 @@ FormatException or persistenceData.MultiFileMaxDays = 0; } + value = GetOptionsAttribute(optionsNode, "multifile", "maxFileAgeDays"); + try + { + persistenceData.MultiFileMaxFileAgeDays = value != null ? int.Parse(value, CultureInfo.InvariantCulture) : 0; + } + catch (Exception ex) when (ex is ArgumentNullException or + FormatException or + OverflowException) + { + persistenceData.MultiFileMaxFileAgeDays = 0; + } + XmlNode multiFileNode = optionsNode.SelectSingleNode("multifile"); if (multiFileNode != null) { diff --git a/src/LogExpert.Core/Classes/Persister/SessionFileComposer.cs b/src/LogExpert.Core/Classes/Persister/SessionFileComposer.cs index 490aa8c83..5482075e2 100644 --- a/src/LogExpert.Core/Classes/Persister/SessionFileComposer.cs +++ b/src/LogExpert.Core/Classes/Persister/SessionFileComposer.cs @@ -36,6 +36,7 @@ public static PersistenceData Compose (SessionSnapshot snapshot) FilterSaveListVisible = snapshot.FilterSaveListVisible, MultiFile = snapshot.MultiFile, MultiFileMaxDays = snapshot.MultiFileMaxDays, + MultiFileMaxFileAgeDays = snapshot.MultiFileMaxFileAgeDays, MultiFilePattern = snapshot.MultiFilePattern, TabName = snapshot.TabName, HighlightGroupName = snapshot.HighlightGroupName, @@ -83,6 +84,7 @@ public static SessionSnapshot Decompose (PersistenceData persistenceData) FilterSaveListVisible = persistenceData.FilterSaveListVisible, MultiFile = persistenceData.MultiFile, MultiFileMaxDays = persistenceData.MultiFileMaxDays, + MultiFileMaxFileAgeDays = persistenceData.MultiFileMaxFileAgeDays, MultiFilePattern = persistenceData.MultiFilePattern, TabName = persistenceData.TabName, HighlightGroupName = persistenceData.HighlightGroupName, diff --git a/src/LogExpert.Core/Classes/Persister/SessionSnapshot.cs b/src/LogExpert.Core/Classes/Persister/SessionSnapshot.cs index bec804861..462309012 100644 --- a/src/LogExpert.Core/Classes/Persister/SessionSnapshot.cs +++ b/src/LogExpert.Core/Classes/Persister/SessionSnapshot.cs @@ -43,6 +43,8 @@ public class SessionSnapshot public int MultiFileMaxDays { get; set; } + public int MultiFileMaxFileAgeDays { get; set; } + public string MultiFilePattern { get; set; } public string TabName { get; set; } diff --git a/src/LogExpert.Core/Entities/MultifileOptions.cs b/src/LogExpert.Core/Entities/MultifileOptions.cs index a1b0c16d3..e83d1362f 100644 --- a/src/LogExpert.Core/Entities/MultifileOptions.cs +++ b/src/LogExpert.Core/Entities/MultifileOptions.cs @@ -6,12 +6,16 @@ public class MultiFileOptions #region Properties /// - /// Gets or sets the maximum age, in days, of companion files loaded into a MultiFile. For date-based patterns, - /// this also limits how many missing calendar days are searched. Zero disables the file-age limit. + /// Gets or sets how many consecutive days without a file may be skipped when walking a date pattern. /// public int MaxDayTry { get; set; } = 3; + /// + /// Gets or sets the maximum age, in days, of rollover files loaded into a MultiFile. Zero disables the limit. + /// + public int MaxFileAgeDays { get; set; } + public string FormatPattern { get; set; } = "*$J(.)"; #endregion -} +} \ No newline at end of file diff --git a/src/LogExpert.Persister.Tests/PersisterTests.cs b/src/LogExpert.Persister.Tests/PersisterTests.cs index 52e0dcb78..b07be02cd 100644 --- a/src/LogExpert.Persister.Tests/PersisterTests.cs +++ b/src/LogExpert.Persister.Tests/PersisterTests.cs @@ -357,6 +357,7 @@ public void RoundTrip_WithApplicationStartupDir_PreservesAllData () MultiFile = true, MultiFilePattern = "test*.log", MultiFileMaxDays = 7, + MultiFileMaxFileAgeDays = 5, LineCount = 1000, CellSelectMode = true }; @@ -377,6 +378,8 @@ public void RoundTrip_WithApplicationStartupDir_PreservesAllData () Assert.That(loadedData.MultiFile, Is.EqualTo(originalData.MultiFile), "MultiFile should match"); Assert.That(loadedData.MultiFilePattern, Is.EqualTo(originalData.MultiFilePattern), "MultiFilePattern should match"); Assert.That(loadedData.MultiFileMaxDays, Is.EqualTo(originalData.MultiFileMaxDays), "MultiFileMaxDays should match"); + Assert.That(loadedData.MultiFileMaxFileAgeDays, Is.EqualTo(originalData.MultiFileMaxFileAgeDays), + "MultiFileMaxFileAgeDays should match"); Assert.That(loadedData.LineCount, Is.EqualTo(originalData.LineCount), "LineCount should match"); Assert.That(loadedData.CellSelectMode, Is.EqualTo(originalData.CellSelectMode), "CellSelectMode should match"); } diff --git a/src/LogExpert.Persister.Tests/PersisterXmlMalformedTests.cs b/src/LogExpert.Persister.Tests/PersisterXmlMalformedTests.cs index ee6aaba95..b4546e5f7 100644 --- a/src/LogExpert.Persister.Tests/PersisterXmlMalformedTests.cs +++ b/src/LogExpert.Persister.Tests/PersisterXmlMalformedTests.cs @@ -44,6 +44,7 @@ public void Load_FileElementWithoutOptions_ReturnsDefaults () Assert.That(data.MultiFile, Is.False); Assert.That(data.MultiFilePattern, Is.Null); Assert.That(data.MultiFileMaxDays, Is.Zero); + Assert.That(data.MultiFileMaxFileAgeDays, Is.Zero); Assert.That(data.FilterVisible, Is.False); // A missing means "nothing was persisted", so every option keeps its diff --git a/src/LogExpert.Persister.Tests/SessionFileComposerTests.cs b/src/LogExpert.Persister.Tests/SessionFileComposerTests.cs index b1c97af82..f820d7165 100644 --- a/src/LogExpert.Persister.Tests/SessionFileComposerTests.cs +++ b/src/LogExpert.Persister.Tests/SessionFileComposerTests.cs @@ -172,6 +172,7 @@ private static SessionSnapshot CreateFullSnapshot (int filterTabDepth = 2) FilterSaveListVisible = true, MultiFile = true, MultiFileMaxDays = 7, + MultiFileMaxFileAgeDays = 5, MultiFilePattern = "app*.log", TabName = "My Tab", HighlightGroupName = "Errors", @@ -247,6 +248,7 @@ private static PersistenceData CreateCarriedPersistenceData (int filterTabDepth) FilterSaveListVisible = true, MultiFile = true, MultiFileMaxDays = 7, + MultiFileMaxFileAgeDays = 5, MultiFilePattern = "app*.log", TabName = "My Tab", HighlightGroupName = "Errors", @@ -422,6 +424,28 @@ public void DiskTrip_ComposePersistLoadDecompose_ReturnsEqualSnapshot () Assert.That(SerializeForComparison(roundTripped), Is.EqualTo(SerializeForComparison(snapshot))); } + [Test] + public void Decompose_LegacyJsonWithoutMaxFileAgeDays_DefaultsToOff () + { + var sessionFileName = Path.Join(_testDirectory, "legacy-json.lxp"); + File.WriteAllText(sessionFileName, """ + { + "MultiFile": true, + "MultiFileMaxDays": 7, + "MultiFilePattern": "*$J(.)" + } + """); + + var loaded = Core.Classes.Persister.Persister.Load(sessionFileName); + var snapshot = SessionFileComposer.Decompose(loaded); + + Assert.Multiple(() => + { + Assert.That(loaded.MultiFileMaxFileAgeDays, Is.Zero); + Assert.That(snapshot.MultiFileMaxFileAgeDays, Is.Zero); + }); + } + #endregion #region Window filter (issue #666) @@ -490,6 +514,8 @@ public void Decompose_LegacyXmlSessionFileOnDisk_TakesTheFirstFilterAsTheWindowF Assert.That(snapshot.FilterParams.SearchText, Is.EqualTo("the first global filter")); Assert.That(snapshot.FilterParams.SpreadBefore, Is.EqualTo(1), "the restored entry must be the first one, whole — the second carries SpreadBefore 2"); + Assert.That(snapshot.MultiFileMaxFileAgeDays, Is.Zero, + "legacy XML Session Files without the new field must keep age filtering disabled"); Assert.That(snapshot.FilterParams.Color, Is.EqualTo(Color.Black), "Color is the field the legacy payload writes as \"Black\" — losing it means the entry was not really read"); } diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index c5cb10175..7109f7a3f 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -4435,20 +4435,29 @@ public static string MultiFileMaskDialog_UI_Label_FileNamePattern { } /// - /// Looks up a localized string similar to Max days:. + /// Looks up a localized string similar to Max day gap:. /// - public static string MultiFileMaskDialog_UI_Label_MaxDays { + public static string MultiFileMaskDialog_UI_Label_MaxDayGap { get { - return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDays", resourceCulture); + return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDayGap", resourceCulture); } } /// - /// Looks up a localized string similar to 0 = Unlimited number of days. + /// Looks up a localized string similar to Max file age (days):. /// - public static string MultiFileMaskDialog_UI_Label_MaxDaysHint { + public static string MultiFileMaskDialog_UI_Label_MaxFileAgeDays { get { - return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDaysHint", resourceCulture); + return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxFileAgeDays", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 0 = Off. + /// + public static string MultiFileMaskDialog_UI_Label_MaxFileAgeDaysHint { + get { + return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxFileAgeDaysHint", resourceCulture); } } @@ -6193,11 +6202,47 @@ public static string SettingsDialog_UI_Label_labelLinesPerBlock { } /// - /// Looks up a localized string similar to Max days:. + /// Looks up a localized string similar to Max day gap:. + /// + public static string SettingsDialog_UI_Label_labelMaxDayGap { + get { + return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxDayGap", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max file age (days):. + /// + public static string SettingsDialog_UI_Label_labelMaxFileAgeDays { + get { + return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxFileAgeDays", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 0 = Off. + /// + public static string SettingsDialog_UI_Label_labelMaxFileAgeDaysHint { + get { + return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxFileAgeDaysHint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to For $D patterns: how many consecutive days without a log file to skip before giving up.. + /// + public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays { + get { + return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum age of rollover files to load, in days. Set to 0 to disable the age limit.. /// - public static string SettingsDialog_UI_Label_labelMaxDays { + public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays { get { - return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxDays", resourceCulture); + return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays", resourceCulture); } } diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index 882157a39..c27cab565 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -721,8 +721,20 @@ Damit die anderen Fenster beim Auswählen einer Zeile (Mausklick oder Pfeiltaste Pattern: - - Maximale Tage: + + Max. Tageslücke: + + + Max. Dateialter (Tage): + + + 0 = Aus + + + Bei $D-Mustern: Anzahl aufeinanderfolgender Tage ohne Protokolldatei, die vor dem Abbruch übersprungen werden. + + + Maximales Alter der zu ladenden Rollover-Dateien in Tagen. 0 deaktiviert die Altersgrenze. Argumente: @@ -1676,11 +1688,14 @@ Ein ausgewähltes Tool erscheint in der Iconbar. Alle anderen verfügbaren Tools Dateinamemuster: - - Max Tage: + + Max. Tageslücke: + + + Max. Dateialter (Tage): - - 0 = Unbegrenzte Tage + + 0 = Aus Muster syntax: @@ -1699,7 +1714,11 @@ Beispiele: DD = Tag MM = Monat YY[YY] = Jahr -Alle anderen Zeichen werden benutzt wie sie angegeben sind +Alle anderen Zeichen werden benutzt wie sie angegeben sind + +MultiFile-Grenzen: +Max. Tageslücke = Bei $D-Mustern aufeinanderfolgende Tage ohne Protokolldatei, die vor dem Abbruch übersprungen werden +Max. Dateialter (Tage) = Älteste zu ladende Rollover-Dateien in Tagen; 0 deaktiviert die Altersgrenze Spalten diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index 7891f537b..20dce13fc 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -719,8 +719,20 @@ Checked tools will appear in the icon bar. All other tools are available in the Arguments: - - Max days: + + Max day gap: + + + Max file age (days): + + + 0 = Off + + + For $D patterns: how many consecutive days without a log file to skip before giving up. + + + Maximum age of rollover files to load, in days. Set to 0 to disable the age limit. Pattern: @@ -1739,11 +1751,14 @@ Checked tools will appear in the icon bar. All other tools are available in the File name pattern: - - Max days: + + Max day gap: + + + Max file age (days): - - 0 = Unlimited number of days + + 0 = Off Pattern syntax: @@ -1762,7 +1777,11 @@ Examples: DD = day MM = month YY[YY] = year -all other chars will be used as given +all other chars will be used as given + +MultiFile limits: +Max day gap = For $D patterns, consecutive days without a log file to skip before giving up +Max file age (days) = Oldest rollover files to load; 0 disables the age limit Columns diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index cf4bd7c05..e3bffad1e 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -616,8 +616,20 @@ 参数: - - 最大天数: + + 最大日期间隔: + + + 最大文件年龄(天): + + + 0 = 关闭 + + + 对于 $D 模式:放弃搜索前可跳过的连续无日志文件天数。 + + + 要加载的滚动日志文件的最大天数。设置为 0 可关闭文件期限限制。 模式: @@ -1545,11 +1557,14 @@ 文件名模式: - - 最大天数: + + 最大日期间隔: + + + 最大文件年龄(天): - - 0 = 天数不受限制 + + 0 = 关闭 模式语法: @@ -1568,7 +1583,11 @@ $J(<前缀>) = 类似 $J,但在非零时添加 <前缀> DD = 日 MM = 月 YY[YY] = 年 -其它字符将按原样使用 +其它字符将按原样使用 + +多文件限制: +最大日期间隔 = 对于 $D 模式,放弃搜索前可跳过的连续无日志文件天数 +最大文件年龄(天) = 要加载的最旧滚动日志文件天数;0 表示关闭期限限制 列 diff --git a/src/LogExpert.Tests/ConfigManagerTests/PreferencesMultiFileOptionsTests.cs b/src/LogExpert.Tests/ConfigManagerTests/PreferencesMultiFileOptionsTests.cs new file mode 100644 index 000000000..c56b796a5 --- /dev/null +++ b/src/LogExpert.Tests/ConfigManagerTests/PreferencesMultiFileOptionsTests.cs @@ -0,0 +1,48 @@ +using LogExpert.Core.Config; +using LogExpert.Core.Entities; + +using Newtonsoft.Json; + +using NUnit.Framework; + +namespace LogExpert.Tests.ConfigManagerTests; + +[TestFixture] +public class PreferencesMultiFileOptionsTests +{ + [Test] + public void Deserialize_LegacyJsonWithoutMaxFileAgeDays_DefaultsToOff () + { + const string legacyJson = """ + { + "MultiFileOptions": { + "FormatPattern": "*$J(.)", + "MaxDayTry": 7 + } + } + """; + + var preferences = JsonConvert.DeserializeObject(legacyJson); + + Assert.That(preferences, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(preferences!.MultiFileOptions.MaxDayTry, Is.EqualTo(7)); + Assert.That(preferences.MultiFileOptions.MaxFileAgeDays, Is.Zero); + }); + } + + [Test] + public void RoundTrip_PreservesMaxFileAgeDays () + { + var preferences = new Preferences + { + MultiFileOptions = new MultiFileOptions { MaxFileAgeDays = 14 } + }; + + var restored = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(preferences)); + + Assert.That(restored, Is.Not.Null); + Assert.That(restored!.MultiFileOptions.MaxFileAgeDays, Is.EqualTo(14)); + } +} diff --git a/src/LogExpert.Tests/LogExpert.Tests.csproj b/src/LogExpert.Tests/LogExpert.Tests.csproj index b78e92ee0..1f64b7ffe 100644 --- a/src/LogExpert.Tests/LogExpert.Tests.csproj +++ b/src/LogExpert.Tests/LogExpert.Tests.csproj @@ -111,9 +111,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/src/LogExpert.Tests/RolloverFilenameHandlerAgeTests.cs b/src/LogExpert.Tests/RolloverFilenameHandlerAgeTests.cs new file mode 100644 index 000000000..b3a3036b5 --- /dev/null +++ b/src/LogExpert.Tests/RolloverFilenameHandlerAgeTests.cs @@ -0,0 +1,194 @@ +using ColumnizerLib; + +using LogExpert.Core.Classes.Log; +using LogExpert.Core.Entities; +using LogExpert.Core.Interfaces; + +using Moq; + +using NUnit.Framework; + +namespace LogExpert.Tests; + +[TestFixture] +internal sealed class RolloverFilenameHandlerAgeTests +{ + private const string LOG_DIRECTORY = "/logs"; + private static readonly DateTimeOffset _now = new(2026, 10, 19, 12, 0, 0, TimeSpan.Zero); + + [TestCase("*$J(.)", "app.log.1", "app.log.2")] + [TestCase("*$J(.).log", "app.1.log", "app.2.log")] + public void GetNameList_MaxFileAge_StopsAtFirstOldIndexedFile ( + string pattern, + string recentFileName, + string oldFileName) + { + var context = CreateContext( + "app.log", + new MultiFileOptions { FormatPattern = pattern, MaxFileAgeDays = 5 }, + (recentFileName, _now.AddDays(-2).UtcDateTime), + (oldFileName, _now.AddDays(-10).UtcDateTime)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.That(result, Is.EqualTo(new[] { GetPath(recentFileName), GetPath("app.log") })); + } + + [Test] + public void GetNameList_ZeroMaxFileAge_LoadsAllIndexedFiles () + { + var context = CreateContext( + "app.log", + new MultiFileOptions { FormatPattern = "*$J(.)", MaxFileAgeDays = 0 }, + ("app.log.1", _now.AddDays(-10).UtcDateTime), + ("app.log.2", _now.AddDays(-20).UtcDateTime)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.That(result, Is.EqualTo(new[] { GetPath("app.log.2"), GetPath("app.log.1"), GetPath("app.log") })); + } + + [Test] + public void GetNameList_FirstRolloverIsOld_StopsBeforeNewerHigherIndex () + { + var context = CreateContext( + "app.log", + new MultiFileOptions { FormatPattern = "*$J(.)", MaxFileAgeDays = 5 }, + ("app.log.1", _now.AddDays(-10).UtcDateTime), + ("app.log.2", _now.AddDays(-1).UtcDateTime)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(new[] { GetPath("app.log") })); + Assert.That(context.RequestedPaths, Does.Not.Contain(GetPath("app.log.2"))); + }); + } + + [Test] + public void GetNameList_OpenedFileIsOld_StillIncludesOpenedFile () + { + var context = CreateContext( + "app.log", + new MultiFileOptions { FormatPattern = "*$J(.)", MaxFileAgeDays = 5 }, + activeLastWriteTimeUtc: _now.AddDays(-10).UtcDateTime); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.That(result, Is.EqualTo(new[] { GetPath("app.log") })); + } + + [Test] + public void GetNameList_DatePattern_SkipsConfiguredGapAndStopsAtFirstOldFile () + { + var context = CreateContext( + "app_2026-10-19.log", + new MultiFileOptions + { + FormatPattern = "*$D(yyyy-MM-dd).log", + MaxDayTry = 3, + MaxFileAgeDays = 5 + }, + ("app_2026-10-17.log", _now.AddDays(-2).UtcDateTime), + ("app_2026-10-16.log", _now.AddDays(-10).UtcDateTime)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.That(result, Is.EqualTo(new[] { GetPath("app_2026-10-17.log"), GetPath("app_2026-10-19.log") })); + } + + [Test] + public void GetNameList_UnknownLastWriteTime_IncludesFile () + { + var context = CreateContext( + "app.log", + new MultiFileOptions { FormatPattern = "*$J(.)", MaxFileAgeDays = 5 }, + ("app.log.1", null)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.That(result, Is.EqualTo(new[] { GetPath("app.log.1"), GetPath("app.log") })); + } + + [Test] + public void GetNameList_MixedPattern_OldIndexedFileStopsBeforeDateFallback () + { + var previousDateFile = "app_2026-10-18.log"; + var context = CreateContext( + "app_2026-10-19.log", + new MultiFileOptions + { + FormatPattern = "*$D(yyyy-MM-dd)$J(.).log", + MaxDayTry = 3, + MaxFileAgeDays = 5 + }, + ("app_2026-10-19.1.log", _now.AddDays(-10).UtcDateTime), + (previousDateFile, _now.AddDays(-1).UtcDateTime)); + + var result = context.Handler.GetNameList(context.Registry); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(new[] { GetPath("app_2026-10-19.log") })); + Assert.That(context.RequestedPaths, Does.Not.Contain(GetPath(previousDateFile))); + }); + } + + private static TestContext CreateContext ( + string activeFileName, + MultiFileOptions options, + params (string FileName, DateTime? LastWriteTimeUtc)[] rolloverFiles) + { + return CreateContext(activeFileName, options, _now.UtcDateTime, rolloverFiles); + } + + private static TestContext CreateContext ( + string activeFileName, + MultiFileOptions options, + DateTime? activeLastWriteTimeUtc, + params (string FileName, DateTime? LastWriteTimeUtc)[] rolloverFiles) + { + var activeFile = CreateFileInfo(activeFileName, activeLastWriteTimeUtc); + var files = rolloverFiles.ToDictionary(file => GetPath(file.FileName), + file => CreateFileInfo(file.FileName, file.LastWriteTimeUtc)); + List requestedPaths = []; + + var fileSystem = new Mock(); + _ = fileSystem.Setup(fs => fs.GetLogfileInfo(It.IsAny())) + .Returns((string path) => + { + requestedPaths.Add(path); + return files.GetValueOrDefault(path)?.Object; + }); + + var registry = new Mock(); + _ = registry.Setup(r => r.FindFileSystemForUri(It.IsAny())).Returns(fileSystem.Object); + + RolloverFilenameHandler handler = new(activeFile.Object, options, new FixedTimeProvider(_now)); + return new TestContext(handler, registry.Object, requestedPaths); + } + + private static Mock CreateFileInfo (string fileName, DateTime? lastWriteTimeUtc) + { + var fileInfo = new Mock(); + _ = fileInfo.SetupGet(file => file.FileName).Returns(fileName); + _ = fileInfo.SetupGet(file => file.DirectoryName).Returns(LOG_DIRECTORY); + _ = fileInfo.SetupGet(file => file.DirectorySeparatorChar).Returns('/'); + _ = fileInfo.SetupGet(file => file.FileExists).Returns(true); + _ = fileInfo.SetupGet(file => file.LastWriteTimeUtc).Returns(lastWriteTimeUtc); + return fileInfo; + } + + private static string GetPath (string fileName) => $"{LOG_DIRECTORY}/{fileName}"; + + private sealed record TestContext ( + RolloverFilenameHandler Handler, + IPluginRegistry Registry, + List RequestedPaths); + + private sealed class FixedTimeProvider (DateTimeOffset utcNow) : TimeProvider + { + public override DateTimeOffset GetUtcNow () => utcNow; + } +} diff --git a/src/LogExpert.Tests/RolloverHandlerTest.cs b/src/LogExpert.Tests/RolloverHandlerTest.cs index 20f683172..1f5e13b17 100644 --- a/src/LogExpert.Tests/RolloverHandlerTest.cs +++ b/src/LogExpert.Tests/RolloverHandlerTest.cs @@ -91,58 +91,4 @@ public void TestFilenameListWithDate (string format, int retries) Cleanup(); } - - [Test] - [TestCase("*$J(.)", "app.log.1", "app.log.2")] - [TestCase("*$J(.).log", "app.1.log", "app.2.log")] - public void GetNameList_ExcludesIndexedFilesOlderThanMaxDays ( - string formatPattern, - string recentFileName, - string oldFileName) - { - var directory = Directory.CreateDirectory(TEST_DIR_NAME); - TestDirectory = directory; - var activeFile = CreateFile(directory, "app.log"); - var recentFile = CreateFile(directory, recentFileName); - var oldFile = CreateFile(directory, oldFileName); - File.SetLastWriteTimeUtc(activeFile, DateTime.UtcNow); - File.SetLastWriteTimeUtc(recentFile, DateTime.UtcNow.AddDays(-2)); - File.SetLastWriteTimeUtc(oldFile, DateTime.UtcNow.AddDays(-4)); - - MultiFileOptions options = new() - { - FormatPattern = formatPattern, - MaxDayTry = 3 - }; - ILogFileInfo info = new LogFileInfo(new Uri(activeFile)); - RolloverFilenameHandler handler = new(info, options); - - var fileList = handler.GetNameList(PluginRegistry.PluginRegistry.Instance); - - Assert.That(fileList, Is.EqualTo(new[] { recentFile, activeFile })); - Cleanup(); - } - - [Test] - public void GetNameList_WithZeroMaxDays_DoesNotFilterByAge () - { - var directory = Directory.CreateDirectory(TEST_DIR_NAME); - TestDirectory = directory; - var activeFile = CreateFile(directory, "app.log"); - var oldFile = CreateFile(directory, "app.log.1"); - File.SetLastWriteTimeUtc(oldFile, DateTime.UtcNow.AddYears(-1)); - - MultiFileOptions options = new() - { - FormatPattern = "*$J(.)", - MaxDayTry = 0 - }; - ILogFileInfo info = new LogFileInfo(new Uri(activeFile)); - RolloverFilenameHandler handler = new(info, options); - - var fileList = handler.GetNameList(PluginRegistry.PluginRegistry.Instance); - - Assert.That(fileList, Is.EqualTo(new[] { oldFile, activeFile })); - Cleanup(); - } -} +} \ No newline at end of file diff --git a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs index c1a681f4f..00e36f731 100644 --- a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -26,8 +26,6 @@ public void SetUp () File.Copy(Path.Join(_testDataDirectory, "app.log"), _logFile); File.Copy(Path.Join(_testDataDirectory, "app.log.1"), _logFile + ".1"); - File.SetLastWriteTimeUtc(_logFile, DateTime.UtcNow); - File.SetLastWriteTimeUtc(_logFile + ".1", DateTime.UtcNow); _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); } @@ -70,39 +68,12 @@ public void SingleFileCtor_MultiFileTrue_ExpandsRollover () }); } - [Test] - public void SingleFileCtor_MultiFileTrue_ExcludesIndexAfterExtensionFilesOlderThanMaxDays () - { - var oldRolloverFile = _logFile + ".2"; - File.Copy(Path.Combine(_testDataDirectory, "app.log.2"), oldRolloverFile); - var now = DateTime.UtcNow; - File.SetLastWriteTimeUtc(_logFile, now); - File.SetLastWriteTimeUtc(_logFile + ".1", now.AddDays(-2)); - File.SetLastWriteTimeUtc(oldRolloverFile, now.AddDays(-4)); - MultiFileOptions options = new() - { - FormatPattern = "*$J(.)", - MaxDayTry = 3 - }; - using var reader = CreateSingleFileReader(multiFile: true, options); - - reader.ReadFiles(); - - Assert.Multiple(() => - { - Assert.That(reader.IsMultiFile, Is.True); - Assert.That(reader.GetLogFileInfoList().Select(file => Path.GetFileName(file.FullName)), - Is.EqualTo(new[] { "app.log.1", "app.log" })); - }); - } - [Test] public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () { var options = new MultiFileOptions { FormatPattern = "*$J(.).log" }; - var (firstRolloverFile, secondRolloverFile) = CopyIndexBeforeExtensionFiles(); - File.SetLastWriteTimeUtc(firstRolloverFile, DateTime.UtcNow); - File.SetLastWriteTimeUtc(secondRolloverFile, DateTime.UtcNow); + File.Copy(Path.Combine(_testDataDirectory, "app.1.log"), Path.Combine(_testDirectory, "app.1.log")); + File.Copy(Path.Combine(_testDataDirectory, "app.2.log"), Path.Combine(_testDirectory, "app.2.log")); using var reader = CreateSingleFileReader(multiFile: true, options); reader.ReadFiles(); @@ -115,31 +86,6 @@ public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () }); } - [Test] - public void SingleFileCtor_MultiFileTrue_ExcludesIndexBeforeExtensionFilesOlderThanMaxDays () - { - var options = new MultiFileOptions - { - FormatPattern = "*$J(.).log", - MaxDayTry = 3 - }; - var (firstRolloverFile, secondRolloverFile) = CopyIndexBeforeExtensionFiles(); - var now = DateTime.UtcNow; - File.SetLastWriteTimeUtc(_logFile, now); - File.SetLastWriteTimeUtc(firstRolloverFile, now.AddDays(-2)); - File.SetLastWriteTimeUtc(secondRolloverFile, now.AddDays(-4)); - using var reader = CreateSingleFileReader(multiFile: true, options); - - reader.ReadFiles(); - - Assert.Multiple(() => - { - Assert.That(reader.IsMultiFile, Is.True); - Assert.That(reader.GetLogFileInfoList().Select(file => Path.GetFileName(file.FullName)), - Is.EqualTo(new[] { "app.1.log", "app.log" })); - }); - } - [Test] public void MultiFileCtor_AlwaysMultiFile () { @@ -178,13 +124,4 @@ private LogfileReader CreateSingleFileReader (bool multiFile, MultiFileOptions? progressReporter: NullProgressReporter.Instance); } - private (string FirstRolloverFile, string SecondRolloverFile) CopyIndexBeforeExtensionFiles () - { - var firstRolloverFile = Path.Combine(_testDirectory, "app.1.log"); - var secondRolloverFile = Path.Combine(_testDirectory, "app.2.log"); - File.Copy(Path.Combine(_testDataDirectory, "app.1.log"), firstRolloverFile); - File.Copy(Path.Combine(_testDataDirectory, "app.2.log"), secondRolloverFile); - return (firstRolloverFile, secondRolloverFile); - } - } diff --git a/src/LogExpert.Tests/TestData/app.log.2 b/src/LogExpert.Tests/TestData/app.log.2 deleted file mode 100644 index 501086237..000000000 --- a/src/LogExpert.Tests/TestData/app.log.2 +++ /dev/null @@ -1 +0,0 @@ -app.log.2 diff --git a/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs b/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs new file mode 100644 index 000000000..a0a503774 --- /dev/null +++ b/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs @@ -0,0 +1,107 @@ +using System.Reflection; + +using LogExpert.Core.Config; +using LogExpert.Core.Entities; +using LogExpert.Core.Interfaces; +using LogExpert.Dialogs; +using LogExpert.UI.Dialogs; + +using Moq; + +using NUnit.Framework; + +using UIStrings = LogExpert.Resources; + +namespace LogExpert.UI.Tests.Dialogs; + +[TestFixture] +[Apartment(ApartmentState.STA)] +public class MultiFileAgeSettingsTests +{ + [Test] + public void MultiFileMaskDialog_LoadsAndSavesBothLimits () + { + using var dialog = new MultiFileMaskDialog("app.log") + { + FileNamePattern = "*$J(.)", + MaxDayGap = 3, + MaxFileAgeDays = 14 + }; + + InvokePrivate(dialog, "OnMultiFileMaskDialogLoad"); + + var maxDayGap = Find(dialog, "upDownMaxDayGap"); + var maxFileAgeDays = Find(dialog, "upDownMaxFileAgeDays"); + Assert.Multiple(() => + { + Assert.That(maxDayGap.Value, Is.EqualTo(3)); + Assert.That(maxFileAgeDays.Value, Is.EqualTo(14)); + Assert.That(Find public const int MAX_FILE_AGE_DAYS = 3650; - /// - /// Gets or sets how many consecutive days without a file may be skipped when walking a date pattern. - /// public int MaxDayTry { get; set; } = 3; /// @@ -24,4 +21,4 @@ public class MultiFileOptions public string FormatPattern { get; set; } = "*$J(.)"; #endregion -} +} \ No newline at end of file diff --git a/src/LogExpert.UI.Tests/Controls/LogWindowCoordinatorIntegrationTests.cs b/src/LogExpert.UI.Tests/Controls/LogWindowCoordinatorIntegrationTests.cs index 946ae85b0..3cdae3697 100644 --- a/src/LogExpert.UI.Tests/Controls/LogWindowCoordinatorIntegrationTests.cs +++ b/src/LogExpert.UI.Tests/Controls/LogWindowCoordinatorIntegrationTests.cs @@ -178,4 +178,4 @@ public void Dispose_UnsubscribesFromHighlightSettingsChanged () _logWindow = null; // prevent double-dispose in TearDown } -} +} \ No newline at end of file diff --git a/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs b/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs index d188bb6a7..dfee68483 100644 --- a/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs +++ b/src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs @@ -89,6 +89,7 @@ public void SettingsDialog_LoadsAndSavesGlobalMaxFileAge () { Assert.That(maxDayGap.Value, Is.EqualTo(3)); Assert.That(maxFileAgeDays.Value, Is.EqualTo(14)); + Assert.That(maxFileAgeDays.Maximum, Is.EqualTo(MultiFileOptions.MAX_FILE_AGE_DAYS)); }); maxDayGap.Value = 7; diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs index f3365b324..b221e86e7 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs @@ -111,7 +111,7 @@ private void InitializeComponent() this.labelMaxFileAgeDaysHint.Size = new System.Drawing.Size(54, 20); this.labelMaxFileAgeDaysHint.TabIndex = 7; this.labelMaxFileAgeDaysHint.Text = "0 = Off"; - // + // // fileNamePatternTextBox // this.fileNamePatternTextBox.Location = new System.Drawing.Point(149, 25); diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs index 0529ca0d4..4244c7631 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs @@ -75,4 +75,4 @@ private void OnMultiFileMaskDialogLoad (object sender, EventArgs e) } #endregion -} +} \ No newline at end of file diff --git a/src/LogExpert.UI/Dialogs/SettingsDialog.cs b/src/LogExpert.UI/Dialogs/SettingsDialog.cs index ea4bc94c6..8904f1d24 100644 --- a/src/LogExpert.UI/Dialogs/SettingsDialog.cs +++ b/src/LogExpert.UI/Dialogs/SettingsDialog.cs @@ -1643,4 +1643,4 @@ private void UpdateSampleAndPreview () } #endregion -} +} \ No newline at end of file diff --git a/src/SftpFileSystemx64/SftpLogFileInfo.cs b/src/SftpFileSystemx64/SftpLogFileInfo.cs index 18a2edb6c..b4b613fb0 100644 --- a/src/SftpFileSystemx64/SftpLogFileInfo.cs +++ b/src/SftpFileSystemx64/SftpLogFileInfo.cs @@ -333,4 +333,4 @@ e is SftpPermissionDeniedException || } #endregion -} +} \ No newline at end of file From 5608b4e77d1ce2206f1e1c9dc92e35a47b870ecc Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Fri, 30 Oct 2026 09:20:11 +0100 Subject: [PATCH 6/8] TabIndex update --- .../Dialogs/MultiFileMaskDialog.Designer.cs | 14 +++++++------- .../Dialogs/SettingsDialog.Designer.cs | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs index b221e86e7..0c7b42191 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs @@ -101,7 +101,7 @@ private void InitializeComponent() 0}); this.upDownMaxFileAgeDays.Name = "upDownMaxFileAgeDays"; this.upDownMaxFileAgeDays.Size = new System.Drawing.Size(49, 26); - this.upDownMaxFileAgeDays.TabIndex = 4; + this.upDownMaxFileAgeDays.TabIndex = 7; // // labelMaxFileAgeDaysHint // @@ -109,7 +109,7 @@ private void InitializeComponent() this.labelMaxFileAgeDaysHint.Location = new System.Drawing.Point(225, 87); this.labelMaxFileAgeDaysHint.Name = "labelMaxFileAgeDaysHint"; this.labelMaxFileAgeDaysHint.Size = new System.Drawing.Size(54, 20); - this.labelMaxFileAgeDaysHint.TabIndex = 7; + this.labelMaxFileAgeDaysHint.TabIndex = 8; this.labelMaxFileAgeDaysHint.Text = "0 = Off"; // // fileNamePatternTextBox @@ -117,7 +117,7 @@ private void InitializeComponent() this.fileNamePatternTextBox.Location = new System.Drawing.Point(149, 25); this.fileNamePatternTextBox.Name = "fileNamePatternTextBox"; this.fileNamePatternTextBox.Size = new System.Drawing.Size(247, 26); - this.fileNamePatternTextBox.TabIndex = 2; + this.fileNamePatternTextBox.TabIndex = 4; // // labelMaxDayGap // @@ -151,7 +151,7 @@ private void InitializeComponent() this.Settings.Location = new System.Drawing.Point(15, 39); this.Settings.Name = "Settings"; this.Settings.Size = new System.Drawing.Size(402, 128); - this.Settings.TabIndex = 6; + this.Settings.TabIndex = 9; this.Settings.TabStop = false; // // syntaxHelpLabel @@ -159,7 +159,7 @@ private void InitializeComponent() this.syntaxHelpLabel.Location = new System.Drawing.Point(15, 170); this.syntaxHelpLabel.Name = "syntaxHelpLabel"; this.syntaxHelpLabel.Size = new System.Drawing.Size(402, 310); - this.syntaxHelpLabel.TabIndex = 7; + this.syntaxHelpLabel.TabIndex = 10; this.syntaxHelpLabel.Text = "Syntax Help Label"; // // buttonOk @@ -169,7 +169,7 @@ private void InitializeComponent() this.buttonOk.Location = new System.Drawing.Point(261, 493); this.buttonOk.Name = "buttonOk"; this.buttonOk.Size = new System.Drawing.Size(75, 23); - this.buttonOk.TabIndex = 8; + this.buttonOk.TabIndex = 11; this.buttonOk.Text = "OK"; this.buttonOk.UseVisualStyleBackColor = true; this.buttonOk.Click += new System.EventHandler(this.OnButtonOKClick); @@ -181,7 +181,7 @@ private void InitializeComponent() this.buttonCancel.Location = new System.Drawing.Point(342, 493); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(75, 23); - this.buttonCancel.TabIndex = 9; + this.buttonCancel.TabIndex = 12; this.buttonCancel.Text = "Cancel"; this.buttonCancel.UseVisualStyleBackColor = true; // diff --git a/src/LogExpert.UI/Dialogs/SettingsDialog.Designer.cs b/src/LogExpert.UI/Dialogs/SettingsDialog.Designer.cs index 700032a24..a404a21a9 100644 --- a/src/LogExpert.UI/Dialogs/SettingsDialog.Designer.cs +++ b/src/LogExpert.UI/Dialogs/SettingsDialog.Designer.cs @@ -1571,7 +1571,7 @@ private void InitializeComponent () labelMaxFileAgeDays.Margin = new Padding(4, 0, 4, 0); labelMaxFileAgeDays.Name = "labelMaxFileAgeDays"; labelMaxFileAgeDays.Size = new Size(102, 15); - labelMaxFileAgeDays.TabIndex = 6; + labelMaxFileAgeDays.TabIndex = 4; labelMaxFileAgeDays.Text = "Max file age (days):"; // // labelMaxFileAgeDaysHint @@ -1581,7 +1581,7 @@ private void InitializeComponent () labelMaxFileAgeDaysHint.Margin = new Padding(4, 0, 4, 0); labelMaxFileAgeDaysHint.Name = "labelMaxFileAgeDaysHint"; labelMaxFileAgeDaysHint.Size = new Size(43, 15); - labelMaxFileAgeDaysHint.TabIndex = 7; + labelMaxFileAgeDaysHint.TabIndex = 6; labelMaxFileAgeDaysHint.Text = "0 = Off"; // // labelPattern @@ -1613,7 +1613,7 @@ private void InitializeComponent () upDownMultifileMaxFileAgeDays.Name = "upDownMultifileMaxFileAgeDays"; helpProvider.SetShowHelp(upDownMultifileMaxFileAgeDays, false); upDownMultifileMaxFileAgeDays.Size = new Size(92, 23); - upDownMultifileMaxFileAgeDays.TabIndex = 2; + upDownMultifileMaxFileAgeDays.TabIndex = 5; // // textBoxMultifilePattern // From 71f52f9be606dc969d84e2fc28726030cecb8aba Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Fri, 30 Oct 2026 09:42:06 +0100 Subject: [PATCH 7/8] Optional clamp cleanup --- .../Classes/Log/RolloverFilenameHandler.cs | 2 +- src/LogExpert.Core/Entities/MultifileOptions.cs | 10 ++++++++++ src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs | 2 +- src/LogExpert.UI/Dialogs/SettingsDialog.cs | 3 +-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs index 0fefdc7f2..ba9e5fbaa 100644 --- a/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs +++ b/src/LogExpert.Core/Classes/Log/RolloverFilenameHandler.cs @@ -136,7 +136,7 @@ private CandidateResult GetCandidateResult(string filePath, IPluginRegistry plug return CandidateResult.Missing; } - var maxFileAgeDays = Math.Clamp(_options.MaxFileAgeDays, 0, MultiFileOptions.MAX_FILE_AGE_DAYS); + var maxFileAgeDays = _options.ClampedMaxFileAgeDays; if (maxFileAgeDays == 0 || info.LastWriteTimeUtc is not { } lastWriteTimeUtc) { return CandidateResult.Include; diff --git a/src/LogExpert.Core/Entities/MultifileOptions.cs b/src/LogExpert.Core/Entities/MultifileOptions.cs index 83c494e91..f74a4f8ec 100644 --- a/src/LogExpert.Core/Entities/MultifileOptions.cs +++ b/src/LogExpert.Core/Entities/MultifileOptions.cs @@ -18,6 +18,16 @@ public class MultiFileOptions /// public int MaxFileAgeDays { get; set; } + /// + /// Gets the maximum file age constrained to the range supported by the UI and rollover reader. + /// + public int ClampedMaxFileAgeDays => ClampMaxFileAgeDays(MaxFileAgeDays); + + public static int ClampMaxFileAgeDays (int value) + { + return Math.Clamp(value, 0, MAX_FILE_AGE_DAYS); + } + public string FormatPattern { get; set; } = "*$J(.)"; #endregion diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs index 4244c7631..2f7bb24e2 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs @@ -71,7 +71,7 @@ private void OnMultiFileMaskDialogLoad (object sender, EventArgs e) { fileNamePatternTextBox.Text = FileNamePattern; upDownMaxDayGap.Value = MaxDayGap; - upDownMaxFileAgeDays.Value = Math.Clamp(MaxFileAgeDays, 0, MultiFileOptions.MAX_FILE_AGE_DAYS); + upDownMaxFileAgeDays.Value = MultiFileOptions.ClampMaxFileAgeDays(MaxFileAgeDays); } #endregion diff --git a/src/LogExpert.UI/Dialogs/SettingsDialog.cs b/src/LogExpert.UI/Dialogs/SettingsDialog.cs index 8904f1d24..ebaac8a36 100644 --- a/src/LogExpert.UI/Dialogs/SettingsDialog.cs +++ b/src/LogExpert.UI/Dialogs/SettingsDialog.cs @@ -648,8 +648,7 @@ private void FillMultifileSettings () textBoxMultifilePattern.Text = Preferences.MultiFileOptions.FormatPattern; upDownMultifileDays.Value = Preferences.MultiFileOptions.MaxDayTry; - upDownMultifileMaxFileAgeDays.Value = Math.Clamp(Preferences.MultiFileOptions.MaxFileAgeDays, 0, - MultiFileOptions.MAX_FILE_AGE_DAYS); + upDownMultifileMaxFileAgeDays.Value = Preferences.MultiFileOptions.ClampedMaxFileAgeDays; } private void GetToolListBoxData () From 3733ee98d5218ff12aab411d78190b4b39e7481b Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Fri, 30 Oct 2026 09:58:04 +0100 Subject: [PATCH 8/8] Optional regeneration of resource accessors - Resources.Designer.cs --- src/LogExpert.Resources/Resources.Designer.cs | 368 +++++++++--------- 1 file changed, 188 insertions(+), 180 deletions(-) diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 41c80b789..625847afc 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace LogExpert { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -416,7 +416,7 @@ public static string Columnizer_SnapshotUnavailable { return ResourceManager.GetString("Columnizer_SnapshotUnavailable", resourceCulture); } } - + /// /// Looks up a localized string similar to Timestamp selector. /// @@ -1081,6 +1081,123 @@ public static string HighlightDialog_UI_Button_SelectPlugin { } } + /// + /// Looks up a localized string similar to Create a new highlight item (opens the editor dialog). + /// + public static string HighlightDialog_UI_Button_ToolTip_btnAdd { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnAdd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy the selected highlight into a new one (opens the editor dialog). + /// + public static string HighlightDialog_UI_Button_ToolTip_btnCopy { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnCopy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy the current highlight group into a new one. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnCopyGroup { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnCopyGroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete the current highlight. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnDelete { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnDelete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete the current highlight group. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnDeleteGroup { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnDeleteGroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit the selected highlight in the editor dialog. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnEdit { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnEdit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export highlight groups. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnExportGroup { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnExportGroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Import highlight groups. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnImportGroup { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnImportGroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move the current highlight one position down. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnMoveDown { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveDown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move the current highlight group one position down. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnMoveGroupDown { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveGroupDown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move the current highlight group one position up. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnMoveGroupUp { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveGroupUp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move the current highlight one position up. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnMoveUp { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveUp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create a new empty highlight group. + /// + public static string HighlightDialog_UI_Button_ToolTip_btnNewGroup { + get { + return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnNewGroup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Change selection highlight color.... /// @@ -1189,6 +1306,15 @@ public static string HighlightDialog_UI_CheckBox_WordMatch { } } + /// + /// Looks up a localized string similar to Choose a group to create different highlight settings. Type in a name to change the name of a group.. + /// + public static string HighlightDialog_UI_ComboBox_ToolTip_comboBoxGroups { + get { + return ResourceManager.GetString("HighlightDialog_UI_ComboBox_ToolTip_comboBoxGroups", resourceCulture); + } + } + /// /// Looks up a localized string similar to [Default]. /// @@ -4208,7 +4334,7 @@ public static string MarkerBar_Bookmarks { return ResourceManager.GetString("MarkerBar_Bookmarks", resourceCulture); } } - + /// /// Looks up a localized string similar to Clear search. /// @@ -4217,7 +4343,7 @@ public static string MarkerBar_ClearSearch { return ResourceManager.GetString("MarkerBar_ClearSearch", resourceCulture); } } - + /// /// Looks up a localized string similar to Discovering markers…. /// @@ -4226,7 +4352,7 @@ public static string MarkerBar_Discovering { return ResourceManager.GetString("MarkerBar_Discovering", resourceCulture); } } - + /// /// Looks up a localized string similar to Filter hits. /// @@ -4235,7 +4361,7 @@ public static string MarkerBar_FilterHits { return ResourceManager.GetString("MarkerBar_FilterHits", resourceCulture); } } - + /// /// Looks up a localized string similar to Highlights. /// @@ -4244,7 +4370,7 @@ public static string MarkerBar_Highlights { return ResourceManager.GetString("MarkerBar_Highlights", resourceCulture); } } - + /// /// Looks up a localized string similar to Could not read log line {0} while discovering markers.. /// @@ -4253,7 +4379,7 @@ public static string MarkerBar_LineUnavailable { return ResourceManager.GetString("MarkerBar_LineUnavailable", resourceCulture); } } - + /// /// Looks up a localized string similar to Marker discovery failed: {0}. /// @@ -4262,7 +4388,7 @@ public static string MarkerBar_ScanFailed { return ResourceManager.GetString("MarkerBar_ScanFailed", resourceCulture); } } - + /// /// Looks up a localized string similar to Search hits. /// @@ -4271,7 +4397,7 @@ public static string MarkerBar_SearchHits { return ResourceManager.GetString("MarkerBar_SearchHits", resourceCulture); } } - + /// /// Looks up a localized string similar to Marker bar. /// @@ -4280,7 +4406,7 @@ public static string MarkerBar_Title { return ResourceManager.GetString("MarkerBar_Title", resourceCulture); } } - + /// /// Looks up a localized string similar to {0}: lines {1}–{2}, {3} matching lines. /// @@ -4289,7 +4415,7 @@ public static string MarkerBar_ToolTip { return ResourceManager.GetString("MarkerBar_ToolTip", resourceCulture); } } - + /// /// Looks up a localized string similar to Close existing tabs. /// @@ -4442,7 +4568,7 @@ public static string MultiFileMaskDialog_UI_Label_MaxDayGap { return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxDayGap", resourceCulture); } } - + /// /// Looks up a localized string similar to Max file age (days):. /// @@ -4451,7 +4577,7 @@ public static string MultiFileMaskDialog_UI_Label_MaxFileAgeDays { return ResourceManager.GetString("MultiFileMaskDialog_UI_Label_MaxFileAgeDays", resourceCulture); } } - + /// /// Looks up a localized string similar to 0 = Off. /// @@ -4474,16 +4600,24 @@ public static string MultiFileMaskDialog_UI_Label_SettingsFor { /// Looks up a localized string similar to Pattern syntax: /// ///* = any characters (wildcard) - ///$D(&lt;date&gt;) = Date pattern + ///$D(<date>) = Date pattern ///$I = File index number ///$J = File index number, hidden when zero - ///$J(&lt;prefix&gt;) = Like $J, but adding &lt;prefix&gt; when non-zero + ///$J(<prefix>) = Like $J, but adding <prefix> when non-zero /// - ///&lt;date&gt;: + ///Examples: + ///*$J(.) → app.log, app.log.1, app.log.2 + ///*$J(.).log → app.log, app.1.log, app.2.log + /// + ///<date>: ///DD = day ///MM = month ///YY[YY] = year - ///all other chars will be used as given. + ///all other chars will be used as given + /// + ///MultiFile limits: + ///Max day gap = For $D patterns, consecutive days without a log file to skip before giving up + ///Max file age [rest of string was truncated]";. /// public static string MultiFileMaskDialog_UI_Label_SyntaxHelp { get { @@ -4582,7 +4716,7 @@ public static string ParamRequesterDialog_UI_Label_ValueForParameter { } /// - /// Looks up a localized string similar to &Value for "{0}":. + /// Looks up a localized string similar to &Value for "{0}":. /// public static string ParamRequesterDialog_UI_Label_ValueForParameterNamed { get { @@ -5660,7 +5794,7 @@ public static string SettingsDialog_UI_CheckBox_checkBoxShowBookmarkMarkers { return ResourceManager.GetString("SettingsDialog_UI_CheckBox_checkBoxShowBookmarkMarkers", resourceCulture); } } - + /// /// Looks up a localized string similar to Show Error Message?. /// @@ -5678,7 +5812,7 @@ public static string SettingsDialog_UI_CheckBox_checkBoxShowFilterMarkers { return ResourceManager.GetString("SettingsDialog_UI_CheckBox_checkBoxShowFilterMarkers", resourceCulture); } } - + /// /// Looks up a localized string similar to Show highlight markers. /// @@ -5687,7 +5821,7 @@ public static string SettingsDialog_UI_CheckBox_checkBoxShowHighlightMarkers { return ResourceManager.GetString("SettingsDialog_UI_CheckBox_checkBoxShowHighlightMarkers", resourceCulture); } } - + /// /// Looks up a localized string similar to Show marker bar. /// @@ -5696,7 +5830,7 @@ public static string SettingsDialog_UI_CheckBox_checkBoxShowMarkerBar { return ResourceManager.GetString("SettingsDialog_UI_CheckBox_checkBoxShowMarkerBar", resourceCulture); } } - + /// /// Looks up a localized string similar to Show search markers. /// @@ -5705,7 +5839,7 @@ public static string SettingsDialog_UI_CheckBox_checkBoxShowSearchMarkers { return ResourceManager.GetString("SettingsDialog_UI_CheckBox_checkBoxShowSearchMarkers", resourceCulture); } } - + /// /// Looks up a localized string similar to Allow only 1 Instance. /// @@ -6218,7 +6352,16 @@ public static string SettingsDialog_UI_Label_labelMaxDayGap { return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxDayGap", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Maximum Display Length (restart required). + /// + public static string SettingsDialog_UI_Label_labelMaxDisplayLength { + get { + return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxDisplayLength", resourceCulture); + } + } + /// /// Looks up a localized string similar to Max file age (days):. /// @@ -6227,7 +6370,7 @@ public static string SettingsDialog_UI_Label_labelMaxFileAgeDays { return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxFileAgeDays", resourceCulture); } } - + /// /// Looks up a localized string similar to 0 = Off. /// @@ -6236,24 +6379,6 @@ public static string SettingsDialog_UI_Label_labelMaxFileAgeDaysHint { return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxFileAgeDaysHint", resourceCulture); } } - - /// - /// Looks up a localized string similar to For $D patterns: how many consecutive days without a log file to skip before giving up.. - /// - public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays { - get { - return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Maximum age of rollover files to load, in days. Set to 0 to disable the age limit.. - /// - public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays { - get { - return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays", resourceCulture); - } - } /// /// Looks up a localized string similar to Maximum filter entries. @@ -6264,15 +6389,6 @@ public static string SettingsDialog_UI_Label_labelMaximumFilterEntries { } } - /// - /// Looks up a localized string similar to Maximum Display Length (restart required). - /// - public static string SettingsDialog_UI_Label_labelMaxDisplayLength { - get { - return ResourceManager.GetString("SettingsDialog_UI_Label_labelMaxDisplayLength", resourceCulture); - } - } - /// /// Looks up a localized string similar to Maximum filter entries displayed. /// @@ -6382,6 +6498,24 @@ public static string SettingsDialog_UI_Label_labelWorkingDir { } } + /// + /// Looks up a localized string similar to For $D patterns: how many consecutive days without a log file to skip before giving up.. + /// + public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays { + get { + return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileDays", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum age of rollover files to load, in days. Set to 0 to disable the age limit.. + /// + public static string SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays { + get { + return ResourceManager.GetString("SettingsDialog_UI_NumericUpDown_ToolTip_upDownMultifileMaxFileAgeDays", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to activate portable mode: {0}. /// @@ -6669,7 +6803,7 @@ public static string SettingsDialog_UI_TabPage_tabPageMarkerBar { return ResourceManager.GetString("SettingsDialog_UI_TabPage_tabPageMarkerBar", resourceCulture); } } - + /// /// Looks up a localized string similar to Memory/CPU. /// @@ -6864,131 +6998,5 @@ public static string ToolArgsDialog_UI_Title { return ResourceManager.GetString("ToolArgsDialog_UI_Title", resourceCulture); } } - - /// - /// Looks up a localized string similar to Create a new highlight item (opens the editor dialog). - /// - public static string HighlightDialog_UI_Button_ToolTip_btnAdd { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnAdd", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Copy the selected highlight into a new one (opens the editor dialog). - /// - public static string HighlightDialog_UI_Button_ToolTip_btnCopy { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnCopy", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Edit the selected highlight in the editor dialog. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnEdit { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnEdit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Delete the current highlight. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnDelete { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnDelete", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Move the current highlight one position up. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnMoveUp { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveUp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Move the current highlight one position down. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnMoveDown { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveDown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Export highlight groups. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnExportGroup { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnExportGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Import highlight groups. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnImportGroup { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnImportGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Move the current highlight group one position down. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnMoveGroupDown { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveGroupDown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Move the current highlight group one position up. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnMoveGroupUp { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnMoveGroupUp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Copy the current highlight group into a new one. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnCopyGroup { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnCopyGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Delete the current highlight group. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnDeleteGroup { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnDeleteGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Create a new empty highlight group. - /// - public static string HighlightDialog_UI_Button_ToolTip_btnNewGroup { - get { - return ResourceManager.GetString("HighlightDialog_UI_Button_ToolTip_btnNewGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Choose a group to create different highlight settings. Type in a name to change the name of a group.. - /// - public static string HighlightDialog_UI_ComboBox_ToolTip_comboBoxGroups { - get { - return ResourceManager.GetString("HighlightDialog_UI_ComboBox_ToolTip_comboBoxGroups", resourceCulture); - } - } } }