Skip to content

Commit 13e66b2

Browse files
authored
Merge pull request #532 from LogExperts/515-the-trusted-pluginsjson-is-written-to-and-loaded-from-appdatalogexpert-in-portable-mode
515 the trusted pluginsjson is written to and loaded from appdatalogexpert in portable mode
2 parents b97762e + 717ff8c commit 13e66b2

23 files changed

Lines changed: 1810 additions & 204 deletions

File tree

src/LogExpert.Configuration/ConfigManager.cs

Lines changed: 335 additions & 13 deletions
Large diffs are not rendered by default.

src/LogExpert.Core/Classes/Filter/FilterStarter.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public FilterStarter (ColumnizerCallback callback, int minThreads)
4545
ThreadCount = Environment.ProcessorCount * 4;
4646
ThreadCount = minThreads;
4747
ThreadPool.GetMinThreads(out _, out var completion);
48-
ThreadPool.SetMinThreads(minThreads, completion);
48+
_ = ThreadPool.SetMinThreads(minThreads, completion);
4949
ThreadPool.GetMaxThreads(out _, out _);
5050
}
5151

@@ -65,7 +65,7 @@ public FilterStarter (ColumnizerCallback callback, int minThreads)
6565

6666
#region Public methods
6767

68-
public async void DoFilter (FilterParams filterParams, int startLine, int maxCount, ProgressCallback progressCallback)
68+
public async Task DoFilter (FilterParams filterParams, int startLine, int maxCount, ProgressCallback progressCallback)
6969
{
7070
FilterResultLines.Clear();
7171
LastFilterLinesList.Clear();
@@ -85,8 +85,11 @@ public async void DoFilter (FilterParams filterParams, int startLine, int maxCou
8585
}
8686

8787
var workStartLine = startLine;
88+
8889
_progressLineCount = 0;
8990
_progressCallback = progressCallback;
91+
92+
var tasks = new List<Task>();
9093
while (workStartLine < startLine + maxCount)
9194
{
9295
if (workStartLine + interval > maxCount)
@@ -100,11 +103,14 @@ public async void DoFilter (FilterParams filterParams, int startLine, int maxCou
100103

101104
_logger.Info(CultureInfo.InvariantCulture, "FilterStarter starts worker for line {0}, lineCount {1}", workStartLine, interval);
102105

103-
var filter = await Task.Run(() => DoWork(filterParams, workStartLine, interval, ThreadProgressCallback)).ConfigureAwait(false);
104-
FilterDoneCallback(filter);
106+
var capturedStartLine = workStartLine;
107+
var capturedInterval = interval;
108+
109+
tasks.Add(Task.Run(() => DoWork(filterParams, capturedStartLine, capturedInterval, ThreadProgressCallback)));
105110
workStartLine += interval;
106111
}
107112

113+
await Task.WhenAll(tasks).ConfigureAwait(false);
108114
MergeResults();
109115
}
110116

@@ -162,14 +168,6 @@ private Filter DoWork (FilterParams filterParams, int startLine, int maxCount, P
162168
return filter;
163169
}
164170

165-
private void FilterDoneCallback (Filter filter)
166-
{
167-
lock (_filterReadyList)
168-
{
169-
_filterReadyList.Add(filter);
170-
}
171-
}
172-
173171
private void MergeResults ()
174172
{
175173
_logger.Info(CultureInfo.InvariantCulture, "Merging filter results.");

src/LogExpert.Core/Classes/Persister/Persister.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using LogExpert.Core.Classes.JsonConverters;
44
using LogExpert.Core.Config;
5+
using LogExpert.Core.Interface;
56

67
using Newtonsoft.Json;
78

@@ -47,13 +48,13 @@ public static class Persister
4748
/// <param name="preferences">The user preferences that determine the save location and other settings. This parameter cannot be <see
4849
/// langword="null"/>.</param>
4950
/// <returns>The full path of the file where the persistence data was saved.</returns>
50-
public static string SavePersistenceData (string logFileName, PersistenceData persistenceData, Preferences preferences, string applicationStartupPath)
51+
public static string SavePersistenceData (string logFileName, PersistenceData persistenceData, Preferences preferences, string sessionBaseDirectory)
5152
{
5253
ArgumentNullException.ThrowIfNull(preferences);
5354
ArgumentNullException.ThrowIfNull(persistenceData);
54-
ArgumentException.ThrowIfNullOrWhiteSpace(applicationStartupPath);
55+
ArgumentException.ThrowIfNullOrWhiteSpace(sessionBaseDirectory);
5556

56-
var fileName = persistenceData.SessionFileName ?? BuildPersisterFileName(logFileName, preferences, applicationStartupPath);
57+
var fileName = persistenceData.SessionFileName ?? BuildPersisterFileName(logFileName, preferences, sessionBaseDirectory);
5758

5859
if (preferences.SaveLocation == SessionSaveLocation.SameDir)
5960
{
@@ -84,12 +85,12 @@ public static string SavePersistenceDataWithFixedName (string persistenceFileNam
8485
/// <param name="logFileName">The name of the log file to load persistence data from. This value cannot be null.</param>
8586
/// <param name="preferences">The preferences used to determine the file path and loading behaviour. This value cannot be null.</param>
8687
/// <returns>The loaded <see cref="PersistenceData"/> object containing the persistence information.</returns>
87-
public static PersistenceData LoadPersistenceData (string logFileName, Preferences preferences, string applicationStartupPath)
88+
public static PersistenceData LoadPersistenceData (string logFileName, Preferences preferences, string sessionBaseDirectory)
8889
{
8990
ArgumentNullException.ThrowIfNull(preferences);
90-
ArgumentNullException.ThrowIfNull(applicationStartupPath);
91+
ArgumentNullException.ThrowIfNull(sessionBaseDirectory);
9192

92-
var fileName = BuildPersisterFileName(logFileName, preferences, applicationStartupPath);
93+
var fileName = BuildPersisterFileName(logFileName, preferences, sessionBaseDirectory);
9394
return LoadInternal(fileName);
9495
}
9596

@@ -99,12 +100,12 @@ public static PersistenceData LoadPersistenceData (string logFileName, Preferenc
99100
/// <param name="logFileName">The name of the log file used to determine the persistence data file.</param>
100101
/// <param name="preferences">The preferences that influence the file name generation. Cannot be <see langword="null"/>.</param>
101102
/// <returns>A <see cref="PersistenceData"/> object containing the loaded data.</returns>
102-
public static PersistenceData LoadPersistenceDataOptionsOnly (string logFileName, Preferences preferences, string applicationStartupPath)
103+
public static PersistenceData LoadPersistenceDataOptionsOnly (string logFileName, Preferences preferences, string sessionBaseDirectory)
103104
{
104105
ArgumentNullException.ThrowIfNull(preferences);
105-
ArgumentNullException.ThrowIfNull(applicationStartupPath);
106+
ArgumentNullException.ThrowIfNull(sessionBaseDirectory);
106107

107-
var fileName = BuildPersisterFileName(logFileName, preferences, applicationStartupPath);
108+
var fileName = BuildPersisterFileName(logFileName, preferences, sessionBaseDirectory);
108109
return LoadInternal(fileName);
109110
}
110111

@@ -138,6 +139,7 @@ public static PersistenceData LoadPersistenceDataFromFixedFile (string persisten
138139
/// <returns>A <see cref="PersistenceData"/> object representing the data loaded from the file.</returns>
139140
public static PersistenceData Load (string fileName)
140141
{
142+
//Dont Call ActiveConfigDir here
141143
return LoadInternal(fileName);
142144
}
143145

@@ -155,7 +157,7 @@ public static PersistenceData Load (string fileName)
155157
/// <param name="preferences">The preferences that determine the save location and directory structure for the persister file.</param>
156158
/// <returns>The full file path of the persister file, including the directory and file name, based on the specified log file
157159
/// name and preferences.</returns>
158-
private static string BuildPersisterFileName (string logFileName, Preferences preferences, string applicationStartupPath)
160+
private static string BuildPersisterFileName (string logFileName, Preferences preferences, string sessionBaseDirectory)
159161
{
160162
string dir;
161163
string file;
@@ -186,7 +188,7 @@ private static string BuildPersisterFileName (string logFileName, Preferences pr
186188
}
187189
case SessionSaveLocation.ApplicationStartupDir:
188190
{
189-
dir = Path.Join(applicationStartupPath, "sessionFiles");
191+
dir = sessionBaseDirectory;
190192
file = dir + Path.DirectorySeparatorChar + BuildSessionFileNameFromPath(logFileName);
191193
break;
192194
}

src/LogExpert.Core/Classes/Persister/ProjectFileValidator.cs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Security;
2+
13
using LogExpert.Core.Interface;
24

35
namespace LogExpert.Core.Classes.Persister;
@@ -30,6 +32,9 @@ public static ProjectValidationResult ValidateProject (ProjectData projectData,
3032

3133
var result = new ProjectValidationResult();
3234

35+
// Cache drive letters once to avoid repeated expensive DriveInfo.GetDrives() calls
36+
var cachedDriveLetters = GetFixedDriveLetters();
37+
3338
foreach (var fileName in projectData.FileNames)
3439
{
3540
var normalizedPath = NormalizeFilePath(fileName);
@@ -55,7 +60,7 @@ public static ProjectValidationResult ValidateProject (ProjectData projectData,
5560
{
5661
result.MissingFiles.Add(fileName);
5762

58-
var alternativePaths = FindAlternativePaths(fileName, projectData.ProjectFilePath);
63+
var alternativePaths = FindAlternativePaths(fileName, projectData.ProjectFilePath, cachedDriveLetters);
5964
result.PossibleAlternatives[fileName] = alternativePaths;
6065
}
6166
}
@@ -99,6 +104,29 @@ private static bool IsUri (string fileName)
99104
!uri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase);
100105
}
101106

107+
/// <summary>
108+
/// Gets the list of fixed drive letters that are ready.
109+
/// Extracted to avoid repeated expensive DriveInfo.GetDrives() calls.
110+
/// </summary>
111+
private static List<char> GetFixedDriveLetters ()
112+
{
113+
try
114+
{
115+
return [.. DriveInfo.GetDrives()
116+
.Where(d => d.IsReady && d.DriveType == DriveType.Fixed)
117+
.Select(d => d.Name[0])];
118+
}
119+
catch(Exception ex) when (
120+
ex is IOException
121+
or UnauthorizedAccessException
122+
or SecurityException
123+
or DriveNotFoundException
124+
or ArgumentNullException)
125+
{
126+
return [];
127+
}
128+
}
129+
102130
/// <summary>
103131
/// Searches for alternative file paths that may correspond to the specified file name, considering common locations
104132
/// such as the project directory, its subdirectories, the user's Documents/LogExpert folder, alternate drive
@@ -112,9 +140,10 @@ private static bool IsUri (string fileName)
112140
/// whitespace.</param>
113141
/// <param name="projectFilePath">The full path to the project file used as a reference for searching related directories. Can be null or empty if
114142
/// project context is not available.</param>
143+
/// <param name="cachedDriveLetters">Pre-computed list of fixed drive letters to avoid repeated DriveInfo.GetDrives() calls.</param>
115144
/// <returns>A list of strings containing the full paths of files found that match the specified file name in alternative
116145
/// locations. The list will be empty if no matching files are found.</returns>
117-
private static List<string> FindAlternativePaths (string fileName, string projectFilePath)
146+
private static List<string> FindAlternativePaths (string fileName, string projectFilePath, List<char> cachedDriveLetters)
118147
{
119148
var alternatives = new List<string>();
120149

@@ -191,15 +220,10 @@ UnauthorizedAccessException or
191220
{
192221
try
193222
{
194-
var driveLetters = DriveInfo.GetDrives()
195-
.Where(d => d.IsReady && d.DriveType == DriveType.Fixed)
196-
.Select(d => d.Name[0])
197-
.ToList();
198-
199223
var originalDrive = Path.GetPathRoot(fileName)?[0];
200224
var pathWithoutDrive = fileName.Length > 3 ? fileName[3..] : string.Empty;
201225

202-
foreach (var drive in driveLetters.Where(drive => drive != originalDrive && !string.IsNullOrEmpty(pathWithoutDrive)))
226+
foreach (var drive in cachedDriveLetters.Where(drive => drive != originalDrive && !string.IsNullOrEmpty(pathWithoutDrive)))
203227
{
204228
var alternatePath = $"{drive}:\\{pathWithoutDrive}";
205229
if (File.Exists(alternatePath) && !alternatives.Contains(alternatePath))

src/LogExpert.Core/Interface/IConfigManager.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,18 @@ public interface IConfigManager
3434
/// Returns the application startup path combined with "portable" subdirectory.
3535
/// When a portableMode.json file exists in this directory, the application runs in portable mode.
3636
/// </remarks>
37+
[Obsolete("Use PortableConfigDir instead. This property is misnamed and may cause confusion. It will be removed in a future version.")]
3738
string PortableModeDir { get; }
3839

40+
/// <summary>
41+
/// Gets the directory path for portable mode settings.
42+
/// </summary>
43+
/// <remarks>
44+
/// Returns the application startup path combined with "portable" subdirectory.
45+
/// When a portableMode.json file exists in this directory, the application runs in portable mode.
46+
/// </remarks>
47+
string PortableConfigDir { get; }
48+
3949
/// <summary>
4050
/// Gets the standard configuration directory path.
4151
/// </summary>
@@ -51,6 +61,20 @@ public interface IConfigManager
5161
/// <value>Returns "portableMode.json"</value>
5262
string PortableModeSettingsFileName { get; }
5363

64+
/// <summary>
65+
/// {ApplicationStartupPath}/configuration/sessions/<br></br>
66+
/// Used for session file storage in portable mode.
67+
/// </summary>
68+
string PortableSessionDir { get; }
69+
70+
/// <summary>
71+
/// Gets the directory path where the current session's data is stored.
72+
/// </summary>
73+
/// <remarks>This property is useful for accessing files or configurations that are specific to the active
74+
/// session. The returned path may vary between sessions and should not be assumed to be persistent across
75+
/// application restarts.</remarks>
76+
string ActiveSessionDir { get; }
77+
5478
/// <summary>
5579
/// Initializes the ConfigManager with application-specific paths and screen information.
5680
/// This method must be called once before accessing Settings or other configuration.
@@ -164,6 +188,26 @@ public interface IConfigManager
164188
/// <remarks>Call this method to remove all entries from the recent files list, typically to reset user
165189
/// history or in response to a privacy-related action. After calling this method, the list of last open files will
166190
/// be empty until new files are opened.</remarks>
167-
168191
void ClearLastOpenFilesList ();
192+
193+
/// <summary>
194+
/// Returns the active configuration directory based on the current mode.
195+
/// In portable mode: {AppDir}/configuration/
196+
/// In normal mode: %APPDATA%/LogExpert/
197+
/// </summary>
198+
string ActiveConfigDir { get; }
199+
200+
/// <summary>
201+
/// Copies configuration files from normal mode location (%APPDATA%/LogExpert/)
202+
/// to the portable configuration directory ({AppDir}/configuration/).
203+
/// Called when portable mode is activated and user confirms copy.
204+
/// </summary>
205+
void CopyConfigToPortable ();
206+
207+
/// <summary>
208+
/// Moves configuration files from the portable directory ({AppDir}/configuration/)
209+
/// back to normal mode locations (%APPDATA%/LogExpert/).
210+
/// Called when portable mode is deactivated and user confirms migration.
211+
/// </summary>
212+
void MoveConfigFromPortable ();
169213
}

0 commit comments

Comments
 (0)