Skip to content

Commit 337cac1

Browse files
authored
Merge pull request #500 from LogExperts/448-allow-only-1-instance-setting-does-nothing
448 allow only 1 instance setting does nothing
2 parents 7fba6b4 + ca40072 commit 337cac1

12 files changed

Lines changed: 987 additions & 75 deletions

File tree

src/LogExpert.Configuration/ConfigManager.cs

Lines changed: 115 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ public Settings Settings
8383
{
8484
get
8585
{
86-
field ??= Load();
87-
88-
return field;
86+
_settings ??= Load();
87+
return _settings;
8988
}
9089
}
9190

@@ -196,7 +195,7 @@ public ImportResult Import (FileInfo fileInfo, ExportImportFlags importFlags)
196195
// Handle any critical errors from loading
197196
if (loadResult.CriticalFailure)
198197
{
199-
return ImportResult.Failed("Import Failed", $"Import file is invalid or corrupted:\n\n{loadResult.CriticalMessage}\n\nImport cancelled.");
198+
return ImportResult.Failed("Import Failed", $"Import file is invalid or corrupted:\n\n{loadResult.CriticalMessage}\n\nImport canceled.");
200199
}
201200

202201
importedSettings = loadResult.Settings;
@@ -205,10 +204,10 @@ public ImportResult Import (FileInfo fileInfo, ExportImportFlags importFlags)
205204
JsonSerializationException)
206205
{
207206
_logger.Error($"Import file is invalid or corrupted: {ex}");
208-
return ImportResult.Failed("Import Failed", $"Import file is invalid or corrupted:\n\n{ex.Message}\n\nImport cancelled.");
207+
return ImportResult.Failed("Import Failed", $"Import file is invalid or corrupted:\n\n{ex.Message}\n\nImport canceled.");
209208
}
210209

211-
if (SettingsAreEmptyOrDefault(importedSettings))
210+
if (SettingsAreEmptyOrDefault(importedSettings, importFlags))
212211
{
213212
_logger.Warn("Import file appears to contain empty or default settings");
214213

@@ -228,8 +227,8 @@ public ImportResult Import (FileInfo fileInfo, ExportImportFlags importFlags)
228227
$"History={importedSettings.FileHistoryList?.Count ?? 0}, " +
229228
$"Highlights={importedSettings.Preferences?.HighlightGroupList?.Count ?? 0}");
230229

231-
// Proceed with import
232-
Instance._settings = Instance.Import(Instance._settings, fileInfo, importFlags);
230+
// Proceed with import - Use Settings property to ensure _settings is initialized
231+
_settings = Instance.Import(Instance.Settings, fileInfo, importFlags);
233232
Save(SettingsFlags.All);
234233

235234
_logger.Info("Import completed successfully");
@@ -502,6 +501,7 @@ UnauthorizedAccessException or
502501
/// <summary>
503502
/// Initialize settings with required default values
504503
/// </summary>
504+
505505
private static Settings InitializeSettings (Settings settings)
506506
{
507507
settings.Preferences ??= new Preferences();
@@ -841,20 +841,43 @@ private Settings Import (Settings currentSettings, FileInfo fileInfo, ExportImpo
841841
Settings ownSettings = ObjectClone.Clone(currentSettings);
842842
Settings newSettings;
843843

844-
// at first check for 'Other' as this are the most options.
844+
// Check for 'All' flag first - import everything
845+
if (flags.HasFlag(ExportImportFlags.All))
846+
{
847+
// For All, start with imported settings and selectively keep some current data if KeepExisting is set
848+
newSettings = ObjectClone.Clone(importSettings);
849+
850+
if (flags.HasFlag(ExportImportFlags.KeepExisting))
851+
{
852+
// Merge with existing settings
853+
newSettings.FilterList = ReplaceOrKeepExisting(flags, ownSettings.FilterList, importSettings.FilterList);
854+
newSettings.FileHistoryList = ReplaceOrKeepExisting(flags, ownSettings.FileHistoryList, importSettings.FileHistoryList);
855+
newSettings.SearchHistoryList = ReplaceOrKeepExisting(flags, ownSettings.SearchHistoryList, importSettings.SearchHistoryList);
856+
newSettings.FilterHistoryList = ReplaceOrKeepExisting(flags, ownSettings.FilterHistoryList, importSettings.FilterHistoryList);
857+
newSettings.FilterRangeHistoryList = ReplaceOrKeepExisting(flags, ownSettings.FilterRangeHistoryList, importSettings.FilterRangeHistoryList);
858+
859+
newSettings.Preferences.HighlightGroupList = ReplaceOrKeepExisting(flags, ownSettings.Preferences.HighlightGroupList, importSettings.Preferences.HighlightGroupList);
860+
newSettings.Preferences.ColumnizerMaskList = ReplaceOrKeepExisting(flags, ownSettings.Preferences.ColumnizerMaskList, importSettings.Preferences.ColumnizerMaskList);
861+
newSettings.Preferences.HighlightMaskList = ReplaceOrKeepExisting(flags, ownSettings.Preferences.HighlightMaskList, importSettings.Preferences.HighlightMaskList);
862+
newSettings.Preferences.ToolEntries = ReplaceOrKeepExisting(flags, ownSettings.Preferences.ToolEntries, importSettings.Preferences.ToolEntries);
863+
}
864+
865+
return newSettings;
866+
}
867+
868+
// For partial imports, start with current settings and selectively update
869+
newSettings = ownSettings;
870+
871+
// Check for 'Other' as this covers most preference options
845872
if ((flags & ExportImportFlags.Other) == ExportImportFlags.Other)
846873
{
847-
newSettings = ownSettings;
848874
newSettings.Preferences = ObjectClone.Clone(importSettings.Preferences);
875+
// Preserve specific lists that have their own flags
849876
newSettings.Preferences.ColumnizerMaskList = ownSettings.Preferences.ColumnizerMaskList;
850877
newSettings.Preferences.HighlightMaskList = ownSettings.Preferences.HighlightMaskList;
851878
newSettings.Preferences.HighlightGroupList = ownSettings.Preferences.HighlightGroupList;
852879
newSettings.Preferences.ToolEntries = ownSettings.Preferences.ToolEntries;
853880
}
854-
else
855-
{
856-
newSettings = ownSettings;
857-
}
858881

859882
if ((flags & ExportImportFlags.ColumnizerMasks) == ExportImportFlags.ColumnizerMasks)
860883
{
@@ -909,12 +932,14 @@ private static void SetBoundsWithinVirtualScreen (Settings settings)
909932
}
910933

911934
/// <summary>
912-
/// Checks if settings object appears to be empty or default.
913-
/// This helps detect corrupted or uninitialized settings files.
935+
/// Checks if settings object appears to be empty or default, considering the import flags.
936+
/// For full imports, all sections are checked. For partial imports, only relevant sections are validated.
937+
/// This helps detect corrupted files while allowing legitimate partial imports.
914938
/// </summary>
915939
/// <param name="settings">Settings object to validate</param>
916-
/// <returns>True if settings appear empty/default, false if they contain user data</returns>
917-
private static bool SettingsAreEmptyOrDefault (Settings settings)
940+
/// <param name="importFlags">Flags indicating which sections are being imported</param>
941+
/// <returns>True if the relevant settings sections appear empty/default, false if they contain user data</returns>
942+
private static bool SettingsAreEmptyOrDefault (Settings settings, ExportImportFlags importFlags)
918943
{
919944
if (settings == null)
920945
{
@@ -926,17 +951,76 @@ private static bool SettingsAreEmptyOrDefault (Settings settings)
926951
return true;
927952
}
928953

929-
var filterCount = settings.FilterList?.Count ?? 0;
930-
var historyCount = settings.FileHistoryList?.Count ?? 0;
931-
var searchHistoryCount = settings.SearchHistoryList?.Count ?? 0;
932-
var highlightCount = settings.Preferences.HighlightGroupList?.Count ?? 0;
933-
var columnizerMaskCount = settings.Preferences.ColumnizerMaskList?.Count ?? 0;
954+
// For full imports or when no specific flags are set, check all sections
955+
if (importFlags is ExportImportFlags.All or ExportImportFlags.None)
956+
{
957+
var filterCount = settings.FilterList?.Count ?? 0;
958+
var historyCount = settings.FileHistoryList?.Count ?? 0;
959+
var searchHistoryCount = settings.SearchHistoryList?.Count ?? 0;
960+
var highlightCount = settings.Preferences.HighlightGroupList?.Count ?? 0;
961+
var columnizerMaskCount = settings.Preferences.ColumnizerMaskList?.Count ?? 0;
962+
963+
return filterCount == 0 &&
964+
historyCount == 0 &&
965+
searchHistoryCount == 0 &&
966+
highlightCount == 0 &&
967+
columnizerMaskCount == 0;
968+
}
969+
970+
// For partial imports, check only the sections being imported
971+
// At least one relevant section must have data for the import to be valid
972+
bool hasAnyRelevantData = false;
973+
974+
// Check HighlightSettings flag
975+
if (importFlags.HasFlag(ExportImportFlags.HighlightSettings))
976+
{
977+
var highlightCount = settings.Preferences.HighlightGroupList?.Count ?? 0;
978+
if (highlightCount > 0)
979+
{
980+
hasAnyRelevantData = true;
981+
}
982+
}
983+
984+
// Check ColumnizerMasks flag
985+
if (importFlags.HasFlag(ExportImportFlags.ColumnizerMasks))
986+
{
987+
var columnizerMaskCount = settings.Preferences.ColumnizerMaskList?.Count ?? 0;
988+
if (columnizerMaskCount > 0)
989+
{
990+
hasAnyRelevantData = true;
991+
}
992+
}
993+
994+
// Check HighlightMasks flag
995+
if (importFlags.HasFlag(ExportImportFlags.HighlightMasks))
996+
{
997+
var highlightMaskCount = settings.Preferences.HighlightMaskList?.Count ?? 0;
998+
if (highlightMaskCount > 0)
999+
{
1000+
hasAnyRelevantData = true;
1001+
}
1002+
}
1003+
1004+
// Check ToolEntries flag
1005+
if (importFlags.HasFlag(ExportImportFlags.ToolEntries))
1006+
{
1007+
var toolEntriesCount = settings.Preferences.ToolEntries?.Count ?? 0;
1008+
if (toolEntriesCount > 0)
1009+
{
1010+
hasAnyRelevantData = true;
1011+
}
1012+
}
1013+
1014+
// Check Other flag (preferences/settings that don't fall into specific categories)
1015+
if (importFlags.HasFlag(ExportImportFlags.Other))
1016+
{
1017+
// For 'Other', we consider the settings valid if Preferences object exists
1018+
// This covers font settings, colors, and other preference data
1019+
hasAnyRelevantData = true;
1020+
}
9341021

935-
return filterCount == 0 &&
936-
historyCount == 0 &&
937-
searchHistoryCount == 0 &&
938-
highlightCount == 0 &&
939-
columnizerMaskCount == 0;
1022+
// Return true (isEmpty) if no relevant data was found in any checked section
1023+
return !hasAnyRelevantData;
9401024
}
9411025

9421026
/// <summary>
@@ -959,11 +1043,12 @@ private bool ValidateSettings (Settings settings)
9591043
return false;
9601044
}
9611045

962-
if (SettingsAreEmptyOrDefault(settings))
1046+
// For save operations, always validate all sections (use ExportImportFlags.All)
1047+
if (SettingsAreEmptyOrDefault(settings, ExportImportFlags.All))
9631048
{
9641049
_logger.Warn("Settings appear to be empty - this may indicate data loss");
9651050

966-
if (_settings != null && !SettingsAreEmptyOrDefault(_settings))
1051+
if (_settings != null && !SettingsAreEmptyOrDefault(_settings, ExportImportFlags.All))
9671052
{
9681053
_logger.Warn($"Previous settings: " +
9691054
$"Filters={_settings.FilterList?.Count ?? 0}, " +

src/LogExpert.Core/Classes/ObjectClone.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
using System.IO;
2-
using System.Text.Json;
1+
using Newtonsoft.Json;
32

43
namespace LogExpert.Core.Classes;
54

65
public static class ObjectClone
76
{
87
#region Public methods
98

10-
public static T Clone<T>(T RealObject)
9+
/// <summary>
10+
/// Creates a deep clone of an object using JSON serialization.
11+
/// Uses Newtonsoft.Json to ensure proper handling of complex types like System.Drawing.Color.
12+
/// </summary>
13+
/// <typeparam name="T">Type of object to clone</typeparam>
14+
/// <param name="realObject">Object to clone</param>
15+
/// <returns>Deep clone of the object</returns>
16+
public static T Clone<T> (T realObject)
1117
{
12-
using MemoryStream objectStream = new();
13-
14-
JsonSerializer.Serialize(objectStream, RealObject);
15-
objectStream.Seek(0, SeekOrigin.Begin);
16-
return JsonSerializer.Deserialize<T>(objectStream);
18+
var json = JsonConvert.SerializeObject(realObject);
19+
return JsonConvert.DeserializeObject<T>(json);
1720
}
1821

1922
#endregion

src/LogExpert.Core/Config/Preferences.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ public class Preferences
1212

1313
public bool PortableMode { get; set; }
1414

15+
/// <summary>
16+
/// OBSOLETE: This setting is no longer used. It was originally intended to show an error dialog when "Allow Only One Instance" was enabled,
17+
/// but this behavior was incorrect (showed dialog on success instead of failure). The feature now works silently on success and only shows
18+
/// a warning on IPC failure. This property is kept for backward compatibility with old settings files but is no longer used or saved.
19+
/// Will be removed in a future version.
20+
/// </summary>
21+
[Obsolete("This setting is no longer used and will be removed in a future version. The 'Allow Only One Instance' feature now works silently.")]
22+
[System.Text.Json.Serialization.JsonIgnore]
23+
[Newtonsoft.Json.JsonIgnore]
1524
public bool ShowErrorMessageAllowOnlyOneInstances { get; set; }
1625

1726
/// <summary>

src/LogExpert.Core/Interface/ILogExpertProxy.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ public interface ILogExpertProxy
2929
/// <param name="logWin"></param>
3030
void WindowClosed(ILogTabWindow logWin);
3131

32+
/// <summary>
33+
/// Notifies the proxy that a window has been activated by the user.
34+
/// Used to track which window should receive new files when "Allow Only One Instance" is enabled.
35+
/// </summary>
36+
/// <param name="window">The window that was activated</param>
37+
void NotifyWindowActivated(ILogTabWindow window);
38+
3239
int GetLogWindowCount();
3340

3441
#endregion

0 commit comments

Comments
 (0)