Skip to content

Comments

Add profile file location management with policy support and rename folder location properties#3340

Open
Copilot wants to merge 2 commits intomainfrom
copilot/add-profile-file-location-ui
Open

Add profile file location management with policy support and rename folder location properties#3340
Copilot wants to merge 2 commits intomainfrom
copilot/add-profile-file-location-ui

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Changes proposed in this pull request

Implements the same folder location management for Profiles that PR #3324 added for Settings: user-selectable location via UI, admin-enforced location via system-wide policy, with validation, restart-on-change, and graceful fallback.

Also renames policy/local-settings properties to follow the Section_SettingName convention consistently.

Profile location management (mirrors Settings implementation):

  • ProfileManager.GetProfilesFolderLocation() now resolves with priority: Policy → Custom user path → Default/Portable
  • Added GetDefaultProfilesFolderLocation(), GetPortableProfilesFolderLocation(), ValidateProfilesFolderPath() with full error handling
  • SettingsInfo.Profiles_CustomProfilesFolderLocation stores user-selected path (in main settings, not LocalSettingsInfo — settings file is already loaded when profile location is needed)
  • PolicyInfo.Profiles_FolderLocation for admin-enforced path via config.json

UI (SettingsProfilesView):

  • Replaced read-only textbox with editable textbox + browse button + drag-drop
  • Change / Restore Default Location buttons with confirmation dialog + app restart
  • Shield icon + "managed by administrator" message when policy-controlled
  • Portable version info message + disabled controls when applicable

Property renames:

  • SettingsFolderLocationSettings_FolderLocation (PolicyInfo, LocalSettingsInfo, all references)
  • ProfilesFolderLocationProfiles_FolderLocation (PolicyInfo, all references)

config.json example:

{
  "Update_CheckForUpdatesAtStartup": false,
  "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings",
  "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles"
}

Related issue(s)

Based on PR #3324

Copilot generated summary

Copilot summary

This PR adds profile file location management matching the existing settings folder location feature from PR #3324. The ProfileManager.GetProfilesFolderLocation() method now implements a three-tier priority system (policy override → custom user path → default/portable fallback) with comprehensive path validation. The Settings > Profiles UI is updated with an editable location textbox, browse/drag-drop support, change/restore buttons, and admin policy indicators. Policy properties are renamed from SettingsFolderLocation/ProfilesFolderLocation to Settings_FolderLocation/Profiles_FolderLocation for naming consistency. Documentation updated across profiles.md, settings.md, system-wide-policies.md, and the changelog.

To-Do

Contributing

By submitting this pull request, I confirm the following:


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…older location properties

Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Copilot AI changed the title [WIP] Add profile file location change functionality in UI Add profile file location management with policy support and rename folder location properties Feb 20, 2026
Copilot AI requested a review from BornToBeRoot February 20, 2026 22:56
@BornToBeRoot BornToBeRoot marked this pull request as ready for review February 20, 2026 22:57
Copilot AI review requested due to automatic review settings February 20, 2026 22:57
@github-actions github-actions bot added this to the next-release milestone Feb 20, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds profile folder location management (user-configurable with restart + admin policy override) analogous to the existing settings folder location feature, and renames policy/local-settings JSON properties to consistently follow the Section_SettingName convention.

Changes:

  • Implement policy/custom/default resolution + validation for the profiles folder path.
  • Update the Profiles settings UI to allow editing/browsing/drag-drop, with policy/portable-mode disabling and restart-on-change flows.
  • Rename policy/local-settings JSON property names (SettingsFolderLocationSettings_FolderLocation, ProfilesFolderLocationProfiles_FolderLocation) and update docs/examples accordingly.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
Website/docs/system-wide-policies.md Updates policy example JSON key for settings folder location.
Website/docs/settings/settings.md Updates documented policy property name for settings folder location.
Website/docs/settings/profiles.md Documents new profiles folder location system-wide policy with examples.
Website/docs/changelog/next-release.md Adds changelog entries for new profiles folder location capability/policy.
Source/NETworkManager/Views/SettingsProfilesView.xaml.cs Adds drag/drop handlers for the profiles location textbox.
Source/NETworkManager/Views/SettingsProfilesView.xaml Reworks the Profiles location UI (editable path, browse button, policy/portable indicators, change/restore actions).
Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs Updates references to renamed settings policy/local-settings property.
Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs Adds Profiles location state + commands for browse/change/restore with restart flow.
Source/NETworkManager.Settings/config.json.example Updates example config to new key names and adds profiles folder location key.
Source/NETworkManager.Settings/SettingsManager.cs Renames settings folder location policy/local-settings references + log messages.
Source/NETworkManager.Settings/SettingsInfo.cs Adds persisted user setting for custom profiles folder location.
Source/NETworkManager.Settings/PolicyManager.cs Logs new policy property names (settings + profiles).
Source/NETworkManager.Settings/PolicyInfo.cs Renames settings policy JSON key and adds profiles policy JSON key.
Source/NETworkManager.Settings/LocalSettingsInfo.cs Renames local-settings property for settings folder location.
Source/NETworkManager.Profiles/ProfileManager.cs Implements policy/custom/default resolution + validation for profiles folder location.
Source/NETworkManager.Localization/Resources/Strings.resx Adds localized strings for profiles location change/restore confirmation dialogs.
Source/NETworkManager.Localization/Resources/Strings.Designer.cs Updates designer for the new localization keys.
Files not reviewed (1)
  • Source/NETworkManager.Localization/Resources/Strings.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


var files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files != null)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

files[0] is accessed without checking files.Length > 0. This can throw if the drop data contains an empty array. Consider guarding for null/empty and, if a file is dropped, using its directory (or rejecting files) so the location textbox doesn't get set to a file path.

Suggested change
if (files != null)
if (files is { Length: > 0 })

Copilot uses AI. Check for mistakes.

private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PreviewDragOver always sets Copy even when the dragged data isn't a file/folder drop. This makes the UI indicate a valid drop when it isn't. Consider setting Effects=None unless e.Data.GetDataPresent(DataFormats.FileDrop) is true.

Suggested change
e.Effects = DragDropEffects.Copy;
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;

Copilot uses AI. Check for mistakes.
<UserControl.Resources>
<converters:BooleanToVisibilityCollapsedConverter x:Key="BooleanToVisibilityCollapsedConverter" />
<converters:BooleanReverseToVisibilityCollapsedConverter x:Key="BooleanReverseToVisibilityCollapsedConverter" />
<converters:BooleanReverseConverter x:Key="BooleanReverseConverter" />
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BooleanReverseConverter is declared in resources but not referenced anywhere in this XAML. Consider removing it to avoid unused resources (or reference it if it’s intended to be used).

Suggested change
<converters:BooleanReverseConverter x:Key="BooleanReverseConverter" />

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +18
[JsonPropertyName("Settings_FolderLocation")]
public string? Settings_FolderLocation { get; set; }

[JsonPropertyName("Profiles_FolderLocation")]
public string? Profiles_FolderLocation { get; set; }
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming the JSON key to Settings_FolderLocation is a breaking change for existing config.json deployments that still use SettingsFolderLocation (it won’t deserialize into this property). If backward compatibility is desired, consider keeping an alias property for the old key (marked [Obsolete]) and mapping it to the new property during/after deserialization.

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 56
/// <summary>
/// Location of the folder where the local settings file is stored.
/// This can be changed by the user to move the settings file to a different location.
/// </summary>
public string SettingsFolderLocation
public string Settings_FolderLocation
{
get => _settingsFolderLocation;
get => _settings_FolderLocation;
set
{
if (_settingsFolderLocation == value)
if (_settings_FolderLocation == value)
return;

_settingsFolderLocation = value;
_settings_FolderLocation = value;
OnPropertyChanged();
}
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming this property to Settings_FolderLocation changes the serialized local settings JSON field name as well, so existing users’ local settings files containing SettingsFolderLocation won’t be picked up and their custom location will silently reset. Consider adding a backward-compatible deserialization path (e.g., an [JsonPropertyName("SettingsFolderLocation")] legacy property that populates Settings_FolderLocation, or a migration step in LocalSettingsManager.Load()).

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 52
Currently supported policies include (more will be added in future releases):
- Check for updates at startup (see [Update > Check for updates at startup](../settings/update.md#check-for-updates-at-startup) documentation for more information)
- Settings folder location (see [Settings > Location](../settings/settings.md#location) documentation for more information)
- Profiles folder location (see [Profiles > Location](../settings/profiles.md#location) documentation for more information)

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changelog entry adds the new profiles folder policy, but it doesn’t mention that the policy property names were renamed (e.g., SettingsFolderLocationSettings_FolderLocation). Without an explicit note, existing admin config.json files may silently stop applying. Consider adding a brief breaking-change/migration note here (and/or linking the PR).

Copilot uses AI. Check for mistakes.
Comment on lines 30 to 44
public string Location
{
get => _location;
set
{
if (value == _location)
return;

if (!_isLoading)
IsLocationChanged = !string.Equals(value, ProfileManager.GetProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase);

_location = value;
OnPropertyChanged();
}
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsDefaultLocation is only set once in LoadSettings() and never updated when Location changes. This can leave the “Restore default location” button stuck hidden/shown after the user edits/browses a new path. Consider recalculating IsDefaultLocation (and possibly IsLocationChanged) whenever Location is updated (outside _isLoading).

Copilot uses AI. Check for mistakes.
/// <summary>
/// Gets the command that initiates the action to change the location.
/// </summary>
public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChangeLocationCommand invokes ChangeLocationAction().ConfigureAwait(false) but never awaits it. With the current RelayCommand (sync Action<object>), this becomes fire-and-forget and any exceptions in the async flow can go unobserved. Consider using an async lambda (async _ => await ...) or an AsyncRelayCommand-style implementation so the task is awaited/handled.

Suggested change
public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
public ICommand ChangeLocationCommand => new RelayCommand(async _ => await ChangeLocationAction().ConfigureAwait(false));

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +257
/// </summary>
public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false));

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestoreDefaultLocationCommand invokes RestoreDefaultLocationActionAsync().ConfigureAwait(false) but never awaits it. This is fire-and-forget with potential unobserved exceptions (and ConfigureAwait(false) has no effect unless awaited). Consider wiring the command to an awaited async lambda or using an async command abstraction.

Copilot uses AI. Check for mistakes.

/// <summary>
/// Private field for the <see cref="SettingsFolderLocation" /> property."
/// Private field for the <see cref="Settings_FolderLocation" /> property."
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s an extra trailing quote in the XML doc comment (property."), which looks like a typo.

Suggested change
/// Private field for the <see cref="Settings_FolderLocation" /> property."
/// Private field for the <see cref="Settings_FolderLocation" /> property.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants