Skip to content

Commit ea62b94

Browse files
CopilotBornToBeRootCopilot
authored
Add SettingsFolderLocation policy for centralized settings path control (#3324)
* Initial plan * Add policy to override SettingsFolder Location Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Add validation for policy-provided SettingsFolderLocation Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Improve exception handling and documentation structure Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Add SecurityException handling and fix documentation formatting Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Fix using statement and clarify path format documentation Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Use consistent example path in documentation Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Address review feedback: validate directory, update docs, add admin indicator Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Update system-wide policies documentation Removed reference to the 'Available Policies' section. * Update SettingsSettingsView.xaml * Chore: Handle user settings location * Feature: UI change settings * Feature: UI * Feature: UI * Feature: Env var for folder * Docs: Add some docs * Chore: Update packages * Update Source/NETworkManager.Settings/SettingsManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Source/NETworkManager.Settings/SettingsManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Source/NETworkManager.Settings/LocalSettingsManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix: Handle LocalSettingsManager.Load() gracefully * Fix exception handling for LocalSettingsManager and path validation Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> * Fix: Minor improvements * Feature: Improve settings load --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8c2865f commit ea62b94

File tree

22 files changed

+1000
-168
lines changed

22 files changed

+1000
-168
lines changed

Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Lines changed: 76 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Source/NETworkManager.Localization/Resources/Strings.resx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,4 +3963,29 @@ If you click Cancel, the profile file will remain unencrypted.</value>
39633963
<data name="SettingManagedByAdministrator" xml:space="preserve">
39643964
<value>This setting is managed by your administrator.</value>
39653965
</data>
3966+
<data name="RestoreDefaultLocation" xml:space="preserve">
3967+
<value>Restore default location</value>
3968+
</data>
3969+
<data name="Restore" xml:space="preserve">
3970+
<value>Restore</value>
3971+
</data>
3972+
<data name="RestoreDefaultLocationQuestion" xml:space="preserve">
3973+
<value>Restore default location?</value>
3974+
</data>
3975+
<data name="RestoreDefaultLocationSettingsMessage" xml:space="preserve">
3976+
<value>The default path is restored and the application is restarted afterwards.
3977+
3978+
You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten.</value>
3979+
</data>
3980+
<data name="ChangeLocationQuestion" xml:space="preserve">
3981+
<value>Change location?</value>
3982+
</data>
3983+
<data name="ChangeLocationSettingsMessage" xml:space="preserve">
3984+
<value>The location is changed and the application is restarted afterwards.
3985+
3986+
You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten.</value>
3987+
</data>
3988+
<data name="EnterValidFolderPath" xml:space="preserve">
3989+
<value>Enter a valid folder path!</value>
3990+
</data>
39663991
</root>

Source/NETworkManager.Models/NETworkManager.Models.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</ItemGroup>
2828
<ItemGroup>
2929
<PackageReference Include="DnsClient" Version="1.8.0" />
30-
<PackageReference Include="IPNetwork2" Version="3.4.851" />
30+
<PackageReference Include="IPNetwork2" Version="3.4.853" />
3131
<PackageReference Include="Lextm.SharpSnmpLib" Version="12.5.7" />
3232
<PackageReference Include="log4net" Version="3.2.0" />
3333
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
@@ -36,7 +36,7 @@
3636
<PackageReference Include="MahApps.Metro.IconPacks.MaterialLight" Version="6.2.1" />
3737
<PackageReference Include="MahApps.Metro.IconPacks.Modern" Version="6.2.1" />
3838
<PackageReference Include="MahApps.Metro.IconPacks.Octicons" Version="6.2.1" />
39-
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
39+
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
4040
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
4141
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
4242
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.5.4" />

Source/NETworkManager.Profiles/ProfileManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,10 @@ private static void Load(ProfileFileInfo profileFileInfo)
616616
{
617617
var loadedProfileUpdated = false;
618618

619-
Log.Info($"Load profile file: {profileFileInfo.Path}");
620-
621619
if (File.Exists(profileFileInfo.Path))
622620
{
621+
Log.Info($"Loading profile file from: {profileFileInfo.Path}");
622+
623623
// Encrypted profile file
624624
if (profileFileInfo.IsEncrypted)
625625
{
@@ -734,6 +734,8 @@ private static void Load(ProfileFileInfo profileFileInfo)
734734

735735
// Notify subscribers that profiles have been loaded/updated
736736
ProfilesUpdated(false);
737+
738+
Log.Info("Profile file loaded successfully.");
737739
}
738740

739741
/// <summary>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.ComponentModel;
2+
using System.Runtime.CompilerServices;
3+
using System.Text.Json.Serialization;
4+
5+
namespace NETworkManager.Settings;
6+
7+
/// <summary>
8+
/// Class contains local settings that are stored outside the main settings file.
9+
/// These settings control where the main settings file is located.
10+
/// </summary>
11+
public class LocalSettingsInfo
12+
{
13+
/// <summary>
14+
/// Occurs when a property value changes.
15+
/// </summary>
16+
/// <remarks>This event is typically used to notify subscribers that a property value has been updated. It
17+
/// is commonly implemented in classes that support data binding or need to signal changes to property
18+
/// values.</remarks>
19+
public event PropertyChangedEventHandler PropertyChanged;
20+
21+
/// <summary>
22+
/// Helper method to raise the <see cref="PropertyChanged" /> event.
23+
/// </summary>
24+
/// <param name="propertyName">Name of the property that changed.</param>
25+
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
26+
{
27+
SettingsChanged = true;
28+
29+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
30+
}
31+
32+
#region Variables
33+
34+
[JsonIgnore] public bool SettingsChanged { get; set; }
35+
36+
/// <summary>
37+
/// Private field for the <see cref="SettingsFolderLocation" /> property."
38+
/// </summary>
39+
private string _settingsFolderLocation;
40+
41+
/// <summary>
42+
/// Location of the folder where the local settings file is stored.
43+
/// This can be changed by the user to move the settings file to a different location.
44+
/// </summary>
45+
public string SettingsFolderLocation
46+
{
47+
get => _settingsFolderLocation;
48+
set
49+
{
50+
if (_settingsFolderLocation == value)
51+
return;
52+
53+
_settingsFolderLocation = value;
54+
OnPropertyChanged();
55+
}
56+
}
57+
#endregion
58+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using log4net;
2+
using System;
3+
using System.IO;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
7+
namespace NETworkManager.Settings;
8+
9+
/// <summary>
10+
/// Manages local application settings that are stored outside the main settings file.
11+
/// This is used for settings that control where the main settings file is located.
12+
/// </summary>
13+
public static class LocalSettingsManager
14+
{
15+
#region Variables
16+
17+
/// <summary>
18+
/// Logger for logging.
19+
/// </summary>
20+
private static readonly ILog Log = LogManager.GetLogger(typeof(LocalSettingsManager));
21+
22+
/// <summary>
23+
/// Settings file name.
24+
/// </summary>
25+
private static string SettingsFileName => "Settings.json";
26+
27+
/// <summary>
28+
/// Settings that are currently loaded.
29+
/// </summary>
30+
public static LocalSettingsInfo Current { get; private set; }
31+
32+
/// <summary>
33+
/// JSON serializer options for consistent serialization/deserialization.
34+
/// </summary>
35+
private static readonly JsonSerializerOptions JsonOptions = new()
36+
{
37+
WriteIndented = true,
38+
PropertyNameCaseInsensitive = true,
39+
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
40+
Converters = { new JsonStringEnumConverter() }
41+
};
42+
#endregion
43+
44+
#region Methods
45+
46+
/// <summary>
47+
/// Method to get the path of the settings folder.
48+
/// </summary>
49+
/// <returns>Path to the settings folder.</returns>
50+
private static string GetSettingsFolderLocation()
51+
{
52+
return Path.Combine(
53+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
54+
AssemblyManager.Current.Name);
55+
}
56+
57+
/// <summary>
58+
/// Method to get the settings file path
59+
/// </summary>
60+
/// <returns>Settings file path.</returns>
61+
private static string GetSettingsFilePath()
62+
{
63+
return Path.Combine(
64+
GetSettingsFolderLocation(),
65+
SettingsFileName);
66+
}
67+
68+
/// <summary>
69+
/// Initialize new settings (<see cref="SettingsInfo" />) and save them (to a file).
70+
/// </summary>
71+
private static void Initialize()
72+
{
73+
Log.Info("Initializing new local settings.");
74+
75+
Current = new LocalSettingsInfo();
76+
77+
Save();
78+
}
79+
80+
/// <summary>
81+
/// Method to load the settings from a file.
82+
/// </summary>
83+
public static void Load()
84+
{
85+
var filePath = GetSettingsFilePath();
86+
87+
if (File.Exists(filePath))
88+
{
89+
try
90+
{
91+
Log.Info($"Loading local settings from: {filePath}");
92+
93+
var jsonString = File.ReadAllText(filePath);
94+
95+
// Treat empty or JSON "null" as "no settings" instead of crashing
96+
if (string.IsNullOrWhiteSpace(jsonString))
97+
{
98+
Log.Info("Local settings file is empty, initializing new local settings.");
99+
}
100+
else
101+
{
102+
Current = JsonSerializer.Deserialize<LocalSettingsInfo>(jsonString, JsonOptions) ?? new LocalSettingsInfo();
103+
104+
Log.Info("Local settings loaded successfully.");
105+
106+
// Reset change tracking
107+
Current.SettingsChanged = false;
108+
109+
return;
110+
}
111+
}
112+
catch (Exception ex)
113+
{
114+
Log.Error($"Failed to load local settings from: {filePath}", ex);
115+
}
116+
}
117+
118+
// Initialize new local settings if file does not exist or loading failed
119+
Initialize();
120+
}
121+
122+
/// <summary>
123+
/// Method to save the current settings to a file.
124+
/// </summary>
125+
public static void Save()
126+
{
127+
// Create the directory if it does not exist
128+
Directory.CreateDirectory(GetSettingsFolderLocation());
129+
130+
// Serialize to file
131+
var filePath = GetSettingsFilePath();
132+
133+
var jsonString = JsonSerializer.Serialize(Current, JsonOptions);
134+
File.WriteAllText(filePath, jsonString);
135+
136+
Log.Info($"Local settings saved to {filePath}");
137+
138+
// Reset change tracking
139+
Current.SettingsChanged = false;
140+
}
141+
#endregion
142+
}

0 commit comments

Comments
 (0)