diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index fbd0f19ec1..b57b12bff7 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -2220,6 +2220,15 @@ public static string Country { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Create daily backup ähnelt. + /// + public static string CreateDailyBackup { + get { + return ResourceManager.GetString("CreateDailyBackup", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Credential ähnelt. /// @@ -4450,6 +4459,15 @@ public static string HelpMessage_ExperimentalFeatures { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Number of backups that are retained before the oldest one is deleted. ähnelt. + /// + public static string HelpMessage_MaximumNumberOfBackups { + get { + return ResourceManager.GetString("HelpMessage_MaximumNumberOfBackups", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Application that is displayed at startup. ähnelt. /// @@ -5783,6 +5801,15 @@ public static string MaximumHops { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Maximum number of backups ähnelt. + /// + public static string MaximumNumberOfBackups { + get { + return ResourceManager.GetString("MaximumNumberOfBackups", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Maximum number ({0}) of hops/router reached! ähnelt. /// diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index 28ccf1c004..3dd4967eaf 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -3951,4 +3951,13 @@ If you click Cancel, the profile file will remain unencrypted. Could not parse "{0}". + + Maximum number of backups + + + Create daily backup + + + Number of backups that are retained before the oldest one is deleted. + \ No newline at end of file diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs index d7662f6704..7cd8c34038 100644 --- a/Source/NETworkManager.Models/Network/NetworkInterface.cs +++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs @@ -527,7 +527,6 @@ private static void RemoveIPAddressFromNetworkInterface(NetworkInterfaceConfig c #endregion - #region Events /// diff --git a/Source/NETworkManager.Profiles/GroupInfo.cs b/Source/NETworkManager.Profiles/GroupInfo.cs index 4b39c78043..2598a48d05 100644 --- a/Source/NETworkManager.Profiles/GroupInfo.cs +++ b/Source/NETworkManager.Profiles/GroupInfo.cs @@ -311,4 +311,4 @@ public GroupInfo(GroupInfo group) : this(group.Name) [XmlIgnore] public SecureString SNMP_Auth { get; set; } public SNMPV3PrivacyProvider SNMP_PrivacyProvider { get; set; } = GlobalStaticConfiguration.SNMP_PrivacyProvider; [XmlIgnore] public SecureString SNMP_Priv { get; set; } -} \ No newline at end of file +} diff --git a/Source/NETworkManager.Profiles/GroupInfoSerializable.cs b/Source/NETworkManager.Profiles/GroupInfoSerializable.cs index 2ef946500c..01e9a03aa1 100644 --- a/Source/NETworkManager.Profiles/GroupInfoSerializable.cs +++ b/Source/NETworkManager.Profiles/GroupInfoSerializable.cs @@ -10,6 +10,7 @@ public GroupInfoSerializable() public GroupInfoSerializable(GroupInfo profileGroup) : base(profileGroup) { + } /// diff --git a/Source/NETworkManager.Profiles/ProfileFileData.cs b/Source/NETworkManager.Profiles/ProfileFileData.cs new file mode 100644 index 0000000000..fd85c9e62c --- /dev/null +++ b/Source/NETworkManager.Profiles/ProfileFileData.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; + +namespace NETworkManager.Profiles; + +/// +/// Represents the data structure for a profile file, including versioning, backup information, and groups of profiles. +/// +/// This class supports property change notification through the +/// interface, allowing consumers to track changes to its properties. The property +/// indicates whether the data has been modified since it was last saved, but is not persisted when serializing the +/// object. Use this class to manage and persist user profile data, including handling schema migrations via the property. +public class ProfileFileData : INotifyPropertyChanged +{ + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Helper method to raise the event and mark data as changed. + /// + /// Name of the property that changed. + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + ProfilesChanged = true; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Indicates if the profile file data has been modified and needs to be saved. + /// This is not serialized to the file. + /// + [JsonIgnore] + public bool ProfilesChanged { get; set; } + + private int _version = 1; + + /// + /// Schema version for handling future migrations. + /// + public int Version + { + get => _version; + set + { + if (value == _version) + return; + + _version = value; + OnPropertyChanged(); + } + } + + private DateTime? _lastBackup; + + /// + /// Date of the last backup (used for daily backup tracking). + /// + public DateTime? LastBackup + { + get => _lastBackup; + set + { + if (value == _lastBackup) + return; + + _lastBackup = value; + OnPropertyChanged(); + } + } + + /// + /// Working copy of groups containing profiles (runtime objects with SecureString passwords). + /// This is the single source of truth for profile data in memory. + /// Not serialized directly - use for JSON serialization. + /// + [JsonIgnore] + public List Groups { get; set; } = []; + + /// + /// Serializable representation of groups for JSON persistence. + /// Gets: Converts working Groups to serializable format. + /// Sets: Converts deserialized data back to working Groups. + /// + [JsonPropertyName("Groups")] + public List GroupsSerializable + { + get => [.. Groups.Select(g => new GroupInfoSerializable(g) + { + Profiles = [.. g.Profiles.Where(p => !p.IsDynamic).Select(p => new ProfileInfoSerializable(p) + { + RemoteDesktop_Password = p.RemoteDesktop_Password != null + ? Utilities.SecureStringHelper.ConvertToString(p.RemoteDesktop_Password) + : string.Empty, + RemoteDesktop_GatewayServerPassword = p.RemoteDesktop_GatewayServerPassword != null + ? Utilities.SecureStringHelper.ConvertToString(p.RemoteDesktop_GatewayServerPassword) + : string.Empty, + SNMP_Community = p.SNMP_Community != null + ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Community) + : string.Empty, + SNMP_Auth = p.SNMP_Auth != null + ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Auth) + : string.Empty, + SNMP_Priv = p.SNMP_Priv != null + ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Priv) + : string.Empty + })], + RemoteDesktop_Password = g.RemoteDesktop_Password != null + ? Utilities.SecureStringHelper.ConvertToString(g.RemoteDesktop_Password) + : string.Empty, + RemoteDesktop_GatewayServerPassword = g.RemoteDesktop_GatewayServerPassword != null + ? Utilities.SecureStringHelper.ConvertToString(g.RemoteDesktop_GatewayServerPassword) + : string.Empty, + SNMP_Community = g.SNMP_Community != null + ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Community) + : string.Empty, + SNMP_Auth = g.SNMP_Auth != null + ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Auth) + : string.Empty, + SNMP_Priv = g.SNMP_Priv != null + ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Priv) + : string.Empty + }).Where(g => !g.IsDynamic)]; + + set + { + Groups = value?.Select(gs => new GroupInfo(gs) + { + Profiles = [.. (gs.Profiles ?? []).Select(ps => new ProfileInfo(ps) + { + TagsCollection = (ps.TagsCollection == null || ps.TagsCollection.Count == 0) && !string.IsNullOrEmpty(ps.Tags) + ? new Controls.ObservableSetCollection(ps.Tags.Split([';'], StringSplitOptions.RemoveEmptyEntries)) + : ps.TagsCollection, + RemoteDesktop_Password = !string.IsNullOrEmpty(ps.RemoteDesktop_Password) + ? Utilities.SecureStringHelper.ConvertToSecureString(ps.RemoteDesktop_Password) + : null, + RemoteDesktop_GatewayServerPassword = !string.IsNullOrEmpty(ps.RemoteDesktop_GatewayServerPassword) + ? Utilities.SecureStringHelper.ConvertToSecureString(ps.RemoteDesktop_GatewayServerPassword) + : null, + SNMP_Community = !string.IsNullOrEmpty(ps.SNMP_Community) + ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Community) + : null, + SNMP_Auth = !string.IsNullOrEmpty(ps.SNMP_Auth) + ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Auth) + : null, + SNMP_Priv = !string.IsNullOrEmpty(ps.SNMP_Priv) + ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Priv) + : null + })], + RemoteDesktop_Password = !string.IsNullOrEmpty(gs.RemoteDesktop_Password) + ? Utilities.SecureStringHelper.ConvertToSecureString(gs.RemoteDesktop_Password) + : null, + RemoteDesktop_GatewayServerPassword = !string.IsNullOrEmpty(gs.RemoteDesktop_GatewayServerPassword) + ? Utilities.SecureStringHelper.ConvertToSecureString(gs.RemoteDesktop_GatewayServerPassword) + : null, + SNMP_Community = !string.IsNullOrEmpty(gs.SNMP_Community) + ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Community) + : null, + SNMP_Auth = !string.IsNullOrEmpty(gs.SNMP_Auth) + ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Auth) + : null, + SNMP_Priv = !string.IsNullOrEmpty(gs.SNMP_Priv) + ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Priv) + : null + }).ToList() ?? []; + } + } +} diff --git a/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs b/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs index 16ad3435ab..96e14fbe0f 100644 --- a/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs +++ b/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs @@ -2,12 +2,21 @@ public class ProfileInfoSerializable : ProfileInfo { + /// + /// Initializes a new instance of the class. + /// public ProfileInfoSerializable() { + } + /// + /// Initializes a new instance of the class using the specified profile information. + /// + /// The object that contains the profile data to be serialized. Cannot be null. public ProfileInfoSerializable(ProfileInfo profile) : base(profile) { + } /// diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs index e7cdb73390..29ff30c471 100644 --- a/Source/NETworkManager.Profiles/ProfileManager.cs +++ b/Source/NETworkManager.Profiles/ProfileManager.cs @@ -16,7 +16,7 @@ namespace NETworkManager.Profiles; public static class ProfileManager { - #region Variables + #region Variables private static readonly ILog Log = LogManager.GetLogger(typeof(ProfileManager)); /// @@ -25,14 +25,14 @@ public static class ProfileManager private const string ProfilesFolderName = "Profiles"; /// - /// Default profile name. + /// Profiles backups directory name. /// - private const string ProfilesDefaultFileName = "Default"; + private static string BackupFolderName => "Backups"; /// - /// Profiles backups directory name. + /// Default profile name. /// - private static string BackupFolderName => "Backups"; + private const string ProfilesDefaultFileName = "Default"; /// /// Profile file extension. @@ -92,14 +92,26 @@ private set } /// - /// Currently loaded groups with profiles. + /// Currently loaded profile file data (wrapper containing groups and metadata). + /// This is updated during load/save operations. /// - public static List Groups { get; set; } = []; + private static ProfileFileData _loadedProfileFileData = new(); /// - /// Indicates if profiles have changed. + /// Currently loaded profile file data (wrapper containing groups and metadata). + /// This is updated during load/save operations. /// - public static bool ProfilesChanged { get; set; } + public static ProfileFileData LoadedProfileFileData + { + get => _loadedProfileFileData; + private set + { + if (Equals(value, _loadedProfileFileData)) + return; + + _loadedProfileFileData = value; + } + } #endregion @@ -172,9 +184,9 @@ private static void ProfileMigrationCompleted() /// /// Method to fire the . /// - private static void ProfilesUpdated() + private static void ProfilesUpdated(bool profilesChanged = true) { - ProfilesChanged = true; + LoadedProfileFileData?.ProfilesChanged = profilesChanged; OnProfilesUpdated?.Invoke(null, EventArgs.Empty); } @@ -270,15 +282,21 @@ private static void LoadProfileFiles() /// /// Method to create a profile file. /// - /// + /// Name of the profile file to create. + /// Thrown when profileName is null or empty. public static void CreateEmptyProfileFile(string profileName) { + ArgumentException.ThrowIfNullOrWhiteSpace(profileName); + ProfileFileInfo profileFileInfo = new(profileName, Path.Combine(GetProfilesFolderLocation(), $"{profileName}{ProfileFileExtension}")); Directory.CreateDirectory(GetProfilesFolderLocation()); - SerializeToFile(profileFileInfo.Path, []); + // Create and serialize empty ProfileFileData to new file (without loading it) + var emptyProfileFileData = new ProfileFileData(); + var jsonString = JsonSerializer.Serialize(emptyProfileFileData, JsonOptions); + File.WriteAllText(profileFileInfo.Path, jsonString); ProfileFiles.Add(profileFileInfo); } @@ -288,8 +306,15 @@ public static void CreateEmptyProfileFile(string profileName) /// /// to rename. /// New of the profile file. + /// Thrown when profileFileInfo is null. + /// Thrown when newProfileName is null or empty. public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string newProfileName) { + ArgumentNullException.ThrowIfNull(profileFileInfo); + ArgumentException.ThrowIfNullOrWhiteSpace(newProfileName); + + + // Check if the profile is currently in use var switchProfile = false; if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo)) @@ -298,6 +323,12 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new switchProfile = true; } + // Create backup + Backup(profileFileInfo.Path, + GetProfilesBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path))); + + // Create new profile info with the new name ProfileFileInfo newProfileFileInfo = new(newProfileName, Path.Combine(GetProfilesFolderLocation(), $"{newProfileName}{Path.GetExtension(profileFileInfo.Path)}"), profileFileInfo.IsEncrypted) @@ -306,15 +337,18 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new IsPasswordValid = profileFileInfo.IsPasswordValid }; + // Copy the profile file to the new location File.Copy(profileFileInfo.Path, newProfileFileInfo.Path); ProfileFiles.Add(newProfileFileInfo); + // Switch profile, if it was previously loaded if (switchProfile) { Switch(newProfileFileInfo, false); LoadedProfileFileChanged(LoadedProfileFile, true); } + // Remove the old profile file File.Delete(profileFileInfo.Path); ProfileFiles.Remove(profileFileInfo); } @@ -323,8 +357,12 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new /// Method to delete a profile file. /// /// to delete. + /// Thrown when profileFileInfo is null. public static void DeleteProfileFile(ProfileFileInfo profileFileInfo) { + ArgumentNullException.ThrowIfNull(profileFileInfo); + + // Trigger switch via UI (to get the password if the file is encrypted), if the selected profile file is deleted if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo)) LoadedProfileFileChanged(ProfileFiles.FirstOrDefault(x => !x.Equals(profileFileInfo))); @@ -342,8 +380,12 @@ public static void DeleteProfileFile(ProfileFileInfo profileFileInfo) /// /// which should be encrypted. /// Password to encrypt the profile file. + /// Thrown when profileFileInfo or password is null. public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureString password) { + ArgumentNullException.ThrowIfNull(profileFileInfo); + ArgumentNullException.ThrowIfNull(password); + // Check if the profile is currently in use var switchProfile = false; @@ -353,6 +395,11 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin switchProfile = true; } + // Create backup + Backup(profileFileInfo.Path, + GetProfilesBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path))); + // Create a new profile info with the encryption infos var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name, Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtensionEncrypted), true) @@ -361,18 +408,32 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin IsPasswordValid = true }; - List profiles = Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension - ? DeserializeFromXmlFile(profileFileInfo.Path) - : DeserializeFromFile(profileFileInfo.Path); + // Save current state to prevent corruption + var previousLoadedProfileFileData = LoadedProfileFileData; - // Save the encrypted file - var decryptedBytes = SerializeToByteArray(profiles); - var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes, - SecureStringHelper.ConvertToString(newProfileFileInfo.Password), - GlobalStaticConfiguration.Profile_EncryptionKeySize, - GlobalStaticConfiguration.Profile_EncryptionIterations); + try + { + // Load the existing profile data (temporarily overwrites LoadedProfileFileData) + if (Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension) + DeserializeFromXmlFile(profileFileInfo.Path); + else + DeserializeFromFile(profileFileInfo.Path); - File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes); + // Save the encrypted file + var decryptedBytes = SerializeToByteArray(); + var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes, + SecureStringHelper.ConvertToString(newProfileFileInfo.Password), + GlobalStaticConfiguration.Profile_EncryptionKeySize, + GlobalStaticConfiguration.Profile_EncryptionIterations); + + File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes); + } + finally + { + // Restore previous state if this wasn't the currently loaded profile + if (!switchProfile) + LoadedProfileFileData = previousLoadedProfileFileData; + } // Add the new profile ProfileFiles.Add(newProfileFileInfo); @@ -397,9 +458,14 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin /// which should be changed. /// Password to decrypt the profile file. /// Password to encrypt the profile file. + /// Thrown when profileFileInfo, password, or newPassword is null. public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureString password, SecureString newPassword) { + ArgumentNullException.ThrowIfNull(profileFileInfo); + ArgumentNullException.ThrowIfNull(password); + ArgumentNullException.ThrowIfNull(newPassword); + // Check if the profile is currently in use var switchProfile = false; @@ -409,7 +475,12 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS switchProfile = true; } - // Create a new profile info with the encryption infos + // Create backup + Backup(profileFileInfo.Path, + GetProfilesBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path))); + + // Create new profile info with the encryption infos var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name, Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtensionEncrypted), true) { @@ -417,26 +488,37 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS IsPasswordValid = true }; - // Load and decrypt the profiles from the profile file - var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path); - var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password), - GlobalStaticConfiguration.Profile_EncryptionKeySize, - GlobalStaticConfiguration.Profile_EncryptionIterations); - - List profiles; + // Save current state to prevent corruption + var previousLoadedProfileFileData = LoadedProfileFileData; - profiles = IsXmlContent(decryptedBytes) - ? DeserializeFromXmlByteArray(decryptedBytes) - : DeserializeFromByteArray(decryptedBytes); + try + { + // Load and decrypt the profiles from the profile file (temporarily overwrites LoadedProfileFileData) + var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path); + var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password), + GlobalStaticConfiguration.Profile_EncryptionKeySize, + GlobalStaticConfiguration.Profile_EncryptionIterations); + + if (IsXmlContent(decryptedBytes)) + DeserializeFromXmlByteArray(decryptedBytes); + else + DeserializeFromByteArray(decryptedBytes); - // Save the encrypted file - decryptedBytes = SerializeToByteArray(profiles); - encryptedBytes = CryptoHelper.Encrypt(decryptedBytes, - SecureStringHelper.ConvertToString(newProfileFileInfo.Password), - GlobalStaticConfiguration.Profile_EncryptionKeySize, - GlobalStaticConfiguration.Profile_EncryptionIterations); + // Save the encrypted file with new password + decryptedBytes = SerializeToByteArray(); + encryptedBytes = CryptoHelper.Encrypt(decryptedBytes, + SecureStringHelper.ConvertToString(newProfileFileInfo.Password), + GlobalStaticConfiguration.Profile_EncryptionKeySize, + GlobalStaticConfiguration.Profile_EncryptionIterations); - File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes); + File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes); + } + finally + { + // Restore previous state if this wasn't the currently loaded profile + if (!switchProfile) + LoadedProfileFileData = previousLoadedProfileFileData; + } // Add the new profile ProfileFiles.Add(newProfileFileInfo); @@ -457,8 +539,12 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS /// /// which should be decrypted. /// Password to decrypt the profile file. + /// Thrown when profileFileInfo or password is null. public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureString password) { + ArgumentNullException.ThrowIfNull(profileFileInfo); + ArgumentNullException.ThrowIfNull(password); + // Check if the profile is currently in use var switchProfile = false; @@ -468,21 +554,40 @@ public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureStri switchProfile = true; } - // Create a new profile info + // Create backup + Backup(profileFileInfo.Path, + GetProfilesBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path))); + + // Create new profile info var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name, Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtension)); - // Load and decrypt the profiles from the profile file - var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path); - var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password), - GlobalStaticConfiguration.Profile_EncryptionKeySize, - GlobalStaticConfiguration.Profile_EncryptionIterations); + // Save current state to prevent corruption + var previousLoadedProfileFileData = LoadedProfileFileData; - List profiles = IsXmlContent(decryptedBytes) - ? DeserializeFromXmlByteArray(decryptedBytes) - : DeserializeFromByteArray(decryptedBytes); - // Save the decrypted profiles to the profile file - SerializeToFile(newProfileFileInfo.Path, profiles); + try + { + // Load and decrypt the profiles from the profile file (temporarily overwrites LoadedProfileFileData) + var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path); + var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password), + GlobalStaticConfiguration.Profile_EncryptionKeySize, + GlobalStaticConfiguration.Profile_EncryptionIterations); + + if (IsXmlContent(decryptedBytes)) + DeserializeFromXmlByteArray(decryptedBytes); + else + DeserializeFromByteArray(decryptedBytes); + + // Save the decrypted profiles to the profile file + SerializeToFile(newProfileFileInfo.Path); + } + finally + { + // Restore previous state if this wasn't the currently loaded profile + if (!switchProfile) + LoadedProfileFileData = previousLoadedProfileFileData; + } // Add the new profile ProfileFiles.Add(newProfileFileInfo); @@ -524,8 +629,6 @@ private static void Load(ProfileFileInfo profileFileInfo) GlobalStaticConfiguration.Profile_EncryptionKeySize, GlobalStaticConfiguration.Profile_EncryptionIterations); - List groups; - if (IsXmlContent(decryptedBytes)) { // @@ -535,7 +638,7 @@ private static void Load(ProfileFileInfo profileFileInfo) Log.Info($"Legacy XML profile file detected inside encrypted profile: {profileFileInfo.Path}. Migration in progress..."); // Load from legacy XML byte array - groups = DeserializeFromXmlByteArray(decryptedBytes); + DeserializeFromXmlByteArray(decryptedBytes); // Create a backup of the legacy XML file Backup(profileFileInfo.Path, @@ -543,7 +646,7 @@ private static void Load(ProfileFileInfo profileFileInfo) TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path))); // Save encrypted profile file with new JSON format - var newDecryptedBytes = SerializeToByteArray([.. groups]); + var newDecryptedBytes = SerializeToByteArray(); var newEncryptedBytes = CryptoHelper.Encrypt(newDecryptedBytes, SecureStringHelper.ConvertToString(profileFileInfo.Password), GlobalStaticConfiguration.Profile_EncryptionKeySize, @@ -555,11 +658,9 @@ private static void Load(ProfileFileInfo profileFileInfo) } else { - groups = DeserializeFromByteArray(decryptedBytes); + DeserializeFromByteArray(decryptedBytes); } - AddGroups(groups); - // Password is valid ProfileFiles.FirstOrDefault(x => x.Equals(profileFileInfo))!.IsPasswordValid = true; profileFileInfo.IsPasswordValid = true; @@ -568,8 +669,6 @@ private static void Load(ProfileFileInfo profileFileInfo) // Unencrypted profile file else { - List groups; - if (Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension) { // @@ -578,9 +677,7 @@ private static void Load(ProfileFileInfo profileFileInfo) Log.Info($"Legacy XML profile file detected: {profileFileInfo.Path}. Migration in progress..."); // Load from legacy XML file - groups = DeserializeFromXmlFile(profileFileInfo.Path); - - ProfilesChanged = false; + DeserializeFromXmlFile(profileFileInfo.Path); LoadedProfileFile = profileFileInfo; @@ -594,7 +691,7 @@ private static void Load(ProfileFileInfo profileFileInfo) Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtension)); // Save new JSON file - SerializeToFile(newProfileFileInfo.Path, groups); + SerializeToFile(newProfileFileInfo.Path); // Notify migration started ProfileMigrationStarted(); @@ -619,10 +716,8 @@ private static void Load(ProfileFileInfo profileFileInfo) } else { - groups = DeserializeFromFile(profileFileInfo.Path); + DeserializeFromFile(profileFileInfo.Path); } - - AddGroups(groups); } } else @@ -632,12 +727,13 @@ private static void Load(ProfileFileInfo profileFileInfo) throw new FileNotFoundException($"{profileFileInfo.Path} could not be found!"); } - ProfilesChanged = false; - LoadedProfileFile = profileFileInfo; if (loadedProfileUpdated) LoadedProfileFileChanged(LoadedProfileFile, true); + + // Notify subscribers that profiles have been loaded/updated + ProfilesUpdated(false); } /// @@ -647,7 +743,7 @@ public static void Save() { if (LoadedProfileFile == null) { - Log.Warn("Cannot save profiles because no profile file is loaded. The profile file may be encrypted and not yet unlocked."); + Log.Warn("Cannot save profiles because no profile file is loaded or the profile file is encrypted and not yet unlocked."); return; } @@ -655,13 +751,16 @@ public static void Save() // Ensure the profiles directory exists. Directory.CreateDirectory(GetProfilesFolderLocation()); + // Create backup before modifying + CreateDailyBackupIfNeeded(); + // Write profiles to the profile file (JSON, optionally encrypted). if (LoadedProfileFile.IsEncrypted) { // Only if the password provided earlier was valid... if (LoadedProfileFile.IsPasswordValid) { - var decryptedBytes = SerializeToByteArray([.. Groups]); + var decryptedBytes = SerializeToByteArray(); var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes, SecureStringHelper.ConvertToString(LoadedProfileFile.Password), GlobalStaticConfiguration.Profile_EncryptionKeySize, @@ -672,10 +771,10 @@ public static void Save() } else { - SerializeToFile(LoadedProfileFile.Path, [.. Groups]); + SerializeToFile(LoadedProfileFile.Path); } - ProfilesChanged = false; + LoadedProfileFileData?.ProfilesChanged = false; } /// @@ -684,14 +783,14 @@ public static void Save() /// Save loaded profile file (default is true) public static void Unload(bool saveLoadedProfiles = true) { - if (saveLoadedProfiles && LoadedProfileFile != null && ProfilesChanged) + if (saveLoadedProfiles && LoadedProfileFile != null && LoadedProfileFileData?.ProfilesChanged == true) Save(); LoadedProfileFile = null; + LoadedProfileFileData = new ProfileFileData(); - Groups.Clear(); - - ProfilesUpdated(); + // Don't mark as changed since we just unloaded + ProfilesUpdated(false); } /// @@ -711,159 +810,121 @@ public static void Switch(ProfileFileInfo info, bool saveLoadedProfiles = true) #region Serialize and deserialize /// - /// Method to serialize a list of groups as to a JSON file. + /// Method to serialize profile data to a JSON file. /// /// Path to a JSON file. - /// List of the groups as to serialize. - private static void SerializeToFile(string filePath, List groups) + private static void SerializeToFile(string filePath) { - var jsonString = JsonSerializer.Serialize(SerializeGroup(groups), JsonOptions); + // Ensure LoadedProfileFileData exists + LoadedProfileFileData ??= new ProfileFileData(); - File.WriteAllText(filePath, jsonString); - } - - /// - /// Method to serialize a list of groups as to a byte array. - /// - /// List of the groups as to serialize. - /// Serialized list of groups as as byte array. - private static byte[] SerializeToByteArray(List groups) - { - var jsonString = JsonSerializer.Serialize(SerializeGroup(groups), JsonOptions); + var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions); - return Encoding.UTF8.GetBytes(jsonString); + File.WriteAllText(filePath, jsonString); } /// - /// Method to serialize a list of groups as . + /// Method to serialize profile data to a byte array. /// - /// List of the groups as to serialize. - /// Serialized list of groups as . - private static List SerializeGroup(List groups) + /// Serialized profile data as byte array. + private static byte[] SerializeToByteArray() { - List groupsSerializable = []; + // Ensure LoadedProfileFileData exists + LoadedProfileFileData ??= new ProfileFileData(); - foreach (var group in groups) - { - // Don't save temp groups - if (group.IsDynamic) - continue; + var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions); - var profilesSerializable = (from profile in @group.Profiles - where !profile.IsDynamic - select new ProfileInfoSerializable(profile) - { - RemoteDesktop_Password = profile.RemoteDesktop_Password != null - ? SecureStringHelper.ConvertToString(profile.RemoteDesktop_Password) - : string.Empty, - RemoteDesktop_GatewayServerPassword = profile.RemoteDesktop_GatewayServerPassword != null - ? SecureStringHelper.ConvertToString(profile.RemoteDesktop_GatewayServerPassword) - : string.Empty, - SNMP_Community = profile.SNMP_Community != null - ? SecureStringHelper.ConvertToString(profile.SNMP_Community) - : string.Empty, - SNMP_Auth = profile.SNMP_Auth != null - ? SecureStringHelper.ConvertToString(profile.SNMP_Auth) - : string.Empty, - SNMP_Priv = profile.SNMP_Priv != null - ? SecureStringHelper.ConvertToString(profile.SNMP_Priv) - : string.Empty - }).ToList(); - - groupsSerializable.Add(new GroupInfoSerializable(group) - { - Profiles = profilesSerializable, - RemoteDesktop_Password = group.RemoteDesktop_Password != null - ? SecureStringHelper.ConvertToString(group.RemoteDesktop_Password) - : string.Empty, - RemoteDesktop_GatewayServerPassword = group.RemoteDesktop_GatewayServerPassword != null - ? SecureStringHelper.ConvertToString(group.RemoteDesktop_GatewayServerPassword) - : string.Empty, - SNMP_Community = group.SNMP_Community != null - ? SecureStringHelper.ConvertToString(group.SNMP_Community) - : string.Empty, - SNMP_Auth = - group.SNMP_Auth != null ? SecureStringHelper.ConvertToString(group.SNMP_Auth) : string.Empty, - SNMP_Priv = - group.SNMP_Priv != null ? SecureStringHelper.ConvertToString(group.SNMP_Priv) : string.Empty - }); - } - - return groupsSerializable; + return Encoding.UTF8.GetBytes(jsonString); } /// - /// Method to deserialize a list of groups as from a JSON file. + /// Method to deserialize profile data from a JSON file. /// /// Path to a JSON file. - /// List of groups as . - private static List DeserializeFromFile(string filePath) + private static void DeserializeFromFile(string filePath) { var jsonString = File.ReadAllText(filePath); - return DeserializeFromJson(jsonString); + DeserializeFromJson(jsonString); } /// /// Method to deserialize a list of groups as from a legacy XML file. /// /// Path to an XML file. - /// List of groups as . [Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")] - private static List DeserializeFromXmlFile(string filePath) + private static void DeserializeFromXmlFile(string filePath) { using FileStream fileStream = new(filePath, FileMode.Open); - return DeserializeFromXmlStream(fileStream); + DeserializeFromXmlStream(fileStream); } /// - /// Method to deserialize a list of groups as from a byte array. + /// Method to deserialize profile data from a byte array. /// - /// Serialized list of groups as as byte array. - /// List of groups as . - private static List DeserializeFromByteArray(byte[] data) + /// Serialized profile data as byte array. + private static void DeserializeFromByteArray(byte[] data) { var jsonString = Encoding.UTF8.GetString(data); - return DeserializeFromJson(jsonString); + DeserializeFromJson(jsonString); } /// /// Method to deserialize a list of groups as from a legacy XML byte array. /// /// Serialized list of groups as as XML byte array. - /// List of groups as . [Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")] - private static List DeserializeFromXmlByteArray(byte[] xml) + private static void DeserializeFromXmlByteArray(byte[] xml) { using MemoryStream memoryStream = new(xml); - return DeserializeFromXmlStream(memoryStream); + DeserializeFromXmlStream(memoryStream); } /// - /// Method to deserialize a list of groups as from JSON string. + /// Method to deserialize profile data from JSON string. /// /// JSON string to deserialize. - /// List of groups as . - private static List DeserializeFromJson(string jsonString) + private static void DeserializeFromJson(string jsonString) { + try + { + var profileFileData = JsonSerializer.Deserialize(jsonString, JsonOptions); + + if (profileFileData != null) + { + LoadedProfileFileData = profileFileData; + return; + } + } + catch (JsonException) + { + Log.Info("Failed to deserialize as ProfileFileData, trying legacy format (direct Groups array)..."); + } + + // Fallback: Try to deserialize as legacy format (direct array of GroupInfoSerializable) var groupsSerializable = JsonSerializer.Deserialize>(jsonString, JsonOptions); if (groupsSerializable == null) throw new InvalidOperationException("Failed to deserialize JSON profile file."); - return DeserializeGroup(groupsSerializable); + // Create ProfileFileData wrapper for legacy format + LoadedProfileFileData = new ProfileFileData + { + GroupsSerializable = groupsSerializable + }; + + Log.Info("Successfully loaded profile file in legacy format. It will be migrated to new format on next save."); } /// /// Method to deserialize a list of groups as from an XML stream. /// /// Stream to deserialize. - /// List of groups as . [Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")] - private static List DeserializeFromXmlStream(Stream stream) + private static void DeserializeFromXmlStream(Stream stream) { XmlSerializer xmlSerializer = new(typeof(List)); @@ -872,7 +933,10 @@ private static List DeserializeFromXmlStream(Stream stream) if (groupsSerializable == null) throw new InvalidOperationException("Failed to deserialize XML profile file."); - return DeserializeGroup(groupsSerializable); + LoadedProfileFileData = new ProfileFileData + { + GroupsSerializable = groupsSerializable + }; } /// @@ -900,96 +964,49 @@ private static bool IsXmlContent(byte[] data) } } - /// - /// Method to deserialize a list of groups as . - /// - /// List of serializable groups to deserialize. - /// List of groups as . - private static List DeserializeGroup(List groupsSerializable) - { - if (groupsSerializable == null) - throw new ArgumentNullException(nameof(groupsSerializable)); - - return [.. from groupSerializable in groupsSerializable - let profiles = (groupSerializable.Profiles ?? new List()).Select(profileSerializable => new ProfileInfo(profileSerializable) - { - // Migrate old tags to new tags list - // if TagsList is null or empty and Tags is not null or empty, split Tags by ';' and create a new ObservableSetCollection - // else use the existing TagsList - TagsCollection = (profileSerializable.TagsCollection == null || profileSerializable.TagsCollection.Count == 0) && !string.IsNullOrEmpty(profileSerializable.Tags) ? - new Controls.ObservableSetCollection(profileSerializable.Tags.Split([';'], StringSplitOptions.RemoveEmptyEntries)) : - profileSerializable.TagsCollection, - - // Convert passwords to secure strings - RemoteDesktop_Password = !string.IsNullOrEmpty(profileSerializable.RemoteDesktop_Password) - ? SecureStringHelper.ConvertToSecureString(profileSerializable.RemoteDesktop_Password) - : null, - RemoteDesktop_GatewayServerPassword = - !string.IsNullOrEmpty(profileSerializable.RemoteDesktop_GatewayServerPassword) - ? SecureStringHelper.ConvertToSecureString(profileSerializable - .RemoteDesktop_GatewayServerPassword) - : null, - SNMP_Community = !string.IsNullOrEmpty(profileSerializable.SNMP_Community) - ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Community) - : null, - SNMP_Auth = !string.IsNullOrEmpty(profileSerializable.SNMP_Auth) - ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Auth) - : null, - SNMP_Priv = !string.IsNullOrEmpty(profileSerializable.SNMP_Priv) - ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Priv) - : null - }) - .ToList() - select new GroupInfo(groupSerializable) - { - Profiles = profiles, - - // Convert passwords to secure strings - RemoteDesktop_Password = !string.IsNullOrEmpty(groupSerializable.RemoteDesktop_Password) - ? SecureStringHelper.ConvertToSecureString(groupSerializable.RemoteDesktop_Password) - : null, - RemoteDesktop_GatewayServerPassword = - !string.IsNullOrEmpty(groupSerializable.RemoteDesktop_GatewayServerPassword) - ? SecureStringHelper.ConvertToSecureString( - groupSerializable.RemoteDesktop_GatewayServerPassword) - : null, - SNMP_Community = !string.IsNullOrEmpty(groupSerializable.SNMP_Community) - ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Community) - : null, - SNMP_Auth = !string.IsNullOrEmpty(groupSerializable.SNMP_Auth) - ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Auth) - : null, - SNMP_Priv = !string.IsNullOrEmpty(groupSerializable.SNMP_Priv) - ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Priv) - : null - }]; - } - #endregion #region Add, remove, replace group(s) and more. /// - /// Method to add a list of to the list. + /// Method to add a list of to the loaded profile data. /// /// List of groups as to add. - private static void AddGroups(List groups) + /// Thrown when groups collection is null. + private static void AddGroups(List groups, bool profilesChanged = true) { + ArgumentNullException.ThrowIfNull(groups); + + var skippedCount = 0; foreach (var group in groups) - Groups.Add(group); + { + if (group is null) + { + skippedCount++; + continue; + } - ProfilesUpdated(); - } + LoadedProfileFileData.Groups.Add(group); + } + + if (skippedCount > 0) + Log.Warn($"AddGroups skipped {skippedCount} null group(s) in collection."); + ProfilesUpdated(profilesChanged); + } + /// - /// Method to add a to the list. + /// Method to add a to the loaded profile data. /// /// Group as to add. - public static void AddGroup(GroupInfo group) + /// Thrown when group is null. + public static void AddGroup(GroupInfo group, bool profilesChanged = true) { - Groups.Add(group); + ArgumentNullException.ThrowIfNull(group); - ProfilesUpdated(); + LoadedProfileFileData.Groups.Add(group); + + ProfilesUpdated(profilesChanged); } /// @@ -997,9 +1014,19 @@ public static void AddGroup(GroupInfo group) /// /// Name of the group. /// Group as . + /// Thrown when name is null or empty. + /// Thrown when group with specified name is not found. public static GroupInfo GetGroupByName(string name) { - return Groups.First(x => x.Name.Equals(name)); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + + var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(name)); + + if (group == null) + throw new InvalidOperationException($"Group '{name}' not found."); + + return group; } /// @@ -1007,10 +1034,14 @@ public static GroupInfo GetGroupByName(string name) /// /// Old group as . /// New group as . + /// Thrown when oldGroup or newGroup is null. public static void ReplaceGroup(GroupInfo oldGroup, GroupInfo newGroup) { - Groups.Remove(oldGroup); - Groups.Add(newGroup); + ArgumentNullException.ThrowIfNull(oldGroup); + ArgumentNullException.ThrowIfNull(newGroup); + + LoadedProfileFileData.Groups.Remove(oldGroup); + LoadedProfileFileData.Groups.Add(newGroup); ProfilesUpdated(); } @@ -1019,9 +1050,12 @@ public static void ReplaceGroup(GroupInfo oldGroup, GroupInfo newGroup) /// Method to remove a group. /// /// Group as to remove + /// Thrown when group is null. public static void RemoveGroup(GroupInfo group) { - Groups.Remove(group); + ArgumentNullException.ThrowIfNull(group); + + LoadedProfileFileData.Groups.Remove(group); ProfilesUpdated(); } @@ -1032,7 +1066,7 @@ public static void RemoveGroup(GroupInfo group) /// List of group names. public static IReadOnlyCollection GetGroupNames() { - return (from groups in Groups where !groups.IsDynamic select groups.Name).ToList(); + return (from groups in LoadedProfileFileData.Groups where !groups.IsDynamic select groups.Name).ToList(); } /// @@ -1042,7 +1076,7 @@ public static IReadOnlyCollection GetGroupNames() /// True if the profile exists. public static bool GroupExists(string name) { - return Groups.Any(group => group.Name == name); + return LoadedProfileFileData.Groups.Any(group => group.Name == name); } /// @@ -1050,9 +1084,18 @@ public static bool GroupExists(string name) /// /// Name of the group /// True if the group has no profiles. + /// Thrown when name is null or empty. + /// Thrown when group with specified name is not found. public static bool IsGroupEmpty(string name) { - return Groups.FirstOrDefault(x => x.Name == name)!.Profiles.Count == 0; + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name == name); + + if (group == null) + throw new InvalidOperationException($"Group '{name}' not found."); + + return group.Profiles.Count == 0; } #endregion @@ -1063,12 +1106,23 @@ public static bool IsGroupEmpty(string name) /// Method to add a profile to a group. /// /// Profile as to add. + /// Thrown when profile is null. + /// Thrown when profile.Group is null or empty. + /// Thrown when profile's group is not found after creation attempt. public static void AddProfile(ProfileInfo profile) { + ArgumentNullException.ThrowIfNull(profile); + ArgumentException.ThrowIfNullOrWhiteSpace(profile.Group, nameof(profile)); + if (!GroupExists(profile.Group)) AddGroup(new GroupInfo(profile.Group)); - Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Add(profile); + var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group)); + + if (group == null) + throw new InvalidOperationException($"Group '{profile.Group}' not found for profile after creation attempt."); + + group.Profiles.Add(profile); ProfilesUpdated(); } @@ -1078,16 +1132,34 @@ public static void AddProfile(ProfileInfo profile) /// /// Old profile as . /// New profile as . + /// Thrown when oldProfile or newProfile is null. + /// Thrown when profile groups are null or empty. + /// Thrown when old profile's group is not found. public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo newProfile) { - // Remove - Groups.First(x => x.Name.Equals(oldProfile.Group)).Profiles.Remove(oldProfile); + ArgumentNullException.ThrowIfNull(oldProfile); + ArgumentNullException.ThrowIfNull(newProfile); + ArgumentException.ThrowIfNullOrWhiteSpace(oldProfile.Group, nameof(oldProfile)); + ArgumentException.ThrowIfNullOrWhiteSpace(newProfile.Group, nameof(newProfile)); + + // Remove from old group + var oldGroup = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(oldProfile.Group)); + + if (oldGroup == null) + throw new InvalidOperationException($"Group '{oldProfile.Group}' not found for old profile."); + + oldGroup.Profiles.Remove(oldProfile); - // Add + // Add to new group (create if doesn't exist) if (!GroupExists(newProfile.Group)) AddGroup(new GroupInfo(newProfile.Group)); - Groups.First(x => x.Name.Equals(newProfile.Group)).Profiles.Add(newProfile); + var newGroup = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(newProfile.Group)); + + if (newGroup == null) + throw new InvalidOperationException($"Group '{newProfile.Group}' not found for new profile after creation attempt."); + + newGroup.Profiles.Add(newProfile); ProfilesUpdated(); } @@ -1096,9 +1168,20 @@ public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo newProfile /// Method to remove a profile from a group. /// /// Profile as to remove. + /// Thrown when profile is null. + /// Thrown when profile.Group is null or empty. + /// Thrown when profile's group is not found. public static void RemoveProfile(ProfileInfo profile) { - Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Remove(profile); + ArgumentNullException.ThrowIfNull(profile); + ArgumentException.ThrowIfNullOrWhiteSpace(profile.Group, nameof(profile)); + + var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group)); + + if (group == null) + throw new InvalidOperationException($"Group '{profile.Group}' not found."); + + group.Profiles.Remove(profile); ProfilesUpdated(); } @@ -1107,10 +1190,34 @@ public static void RemoveProfile(ProfileInfo profile) /// Method to remove a list of profiles from a group. /// /// List of profiles as to remove. + /// Thrown when profiles collection is null. public static void RemoveProfiles(IEnumerable profiles) { + ArgumentNullException.ThrowIfNull(profiles); + + var skippedCount = 0; foreach (var profile in profiles) - Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Remove(profile); + { + if (profile is null || string.IsNullOrWhiteSpace(profile.Group)) + { + skippedCount++; + continue; + } + + var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group)); + + if (group == null) + { + Log.Warn($"RemoveProfiles: Group '{profile.Group}' not found for profile '{profile.Name ?? ""}'."); + skippedCount++; + continue; + } + + group.Profiles.Remove(profile); + } + + if (skippedCount > 0) + Log.Warn($"RemoveProfiles skipped {skippedCount} null or invalid profile(s) in collection."); ProfilesUpdated(); } @@ -1119,6 +1226,104 @@ public static void RemoveProfiles(IEnumerable profiles) #region Backup + /// + /// Creates a backup of the currently loaded profile file if a backup has not already been created for the current day. + /// + private static void CreateDailyBackupIfNeeded() + { + // Skip if daily backups are disabled + if (!SettingsManager.Current.Profiles_IsDailyBackupEnabled) + { + Log.Info("Daily profile backups are disabled. Skipping backup creation..."); + return; + } + + // Skip if no profile is loaded + if (LoadedProfileFile == null || LoadedProfileFileData == null) + { + Log.Info("No profile file is currently loaded. Skipping backup creation..."); + return; + } + + // Skip if the profile file doesn't exist yet + if (!File.Exists(LoadedProfileFile.Path)) + { + Log.Warn($"Profile file does not exist yet: {LoadedProfileFile.Path}. Skipping backup creation..."); + return; + } + + // Create backup if needed + var currentDate = DateTime.Now.Date; + var lastBackupDate = LoadedProfileFileData.LastBackup?.Date ?? DateTime.MinValue; + var profileFileName = Path.GetFileName(LoadedProfileFile.Path); + + if (lastBackupDate < currentDate) + { + Log.Info($"Creating daily backup for profile: {profileFileName}"); + + // Create backup + Backup(LoadedProfileFile.Path, + GetProfilesBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(profileFileName)); + + // Cleanup old backups + CleanupBackups(GetProfilesBackupFolderLocation(), + profileFileName, + SettingsManager.Current.Profiles_MaximumNumberOfBackups); + + LoadedProfileFileData.LastBackup = currentDate; + } + } + + /// + /// Deletes older backup files in the specified folder to ensure that only the most recent backups, up to the + /// specified maximum, are retained. + /// + /// The full path to the directory containing the backup files to be managed. + /// The profile file name pattern used to identify backup files for cleanup. + /// The maximum number of backup files to retain. Must be greater than zero. + private static void CleanupBackups(string backupFolderPath, string profileFileName, int maxBackupFiles) + { + // Skip if backup directory doesn't exist + if (!Directory.Exists(backupFolderPath)) + { + Log.Error($"Backup directory does not exist: {backupFolderPath}. Cannot cleanup old backups."); + return; + } + + // Extract profile name without extension to match all backup files regardless of extension + // (e.g., "Default" matches "2025-01-19_Default.json", "2025-01-19_Default.encrypted", etc.) + var profileNameWithoutExtension = Path.GetFileNameWithoutExtension(profileFileName); + + // Get all backup files for this specific profile (any extension) sorted by timestamp (newest first) + var backupFiles = Directory.GetFiles(backupFolderPath) + .Where(f => + { + var fileName = Path.GetFileName(f); + + // Check if it's a timestamped backup and contains the profile name + return TimestampHelper.IsTimestampedFilename(fileName) && + fileName.Contains($"_{profileNameWithoutExtension}."); + }) + .OrderByDescending(f => TimestampHelper.ExtractTimestampFromFilename(Path.GetFileName(f))) + .ToList(); + + if (backupFiles.Count > maxBackupFiles) + Log.Info($"Cleaning up old backup files for {profileNameWithoutExtension}... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}."); + + // Delete oldest backups until the maximum number is reached + while (backupFiles.Count > maxBackupFiles) + { + var fileToDelete = backupFiles.Last(); + + File.Delete(fileToDelete); + + backupFiles.RemoveAt(backupFiles.Count - 1); + + Log.Info($"Backup deleted: {fileToDelete}"); + } + } + /// /// Creates a backup of the specified profile file in the given backup folder with the provided backup file name. /// diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs index af7ea9117d..851a5c233c 100644 --- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs +++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs @@ -46,9 +46,6 @@ public static class GlobalStaticConfiguration public static string ZipFileExtensionFilter => "ZIP Archive (*.zip)|*.zip"; public static string XmlFileExtensionFilter => "XML-File (*.xml)|*.xml"; - // Backup settings - public static int Backup_MaximumNumberOfBackups => 10; - #endregion #region Default settings @@ -74,18 +71,23 @@ public static class GlobalStaticConfiguration public static bool Status_ShowWindowOnNetworkChange => true; public static int Status_WindowCloseTime => 10; - // HotKey + // Settings: HotKey public static int HotKey_ShowWindowKey => 79; public static int HotKey_ShowWindowModifier => 3; - // Update + // Settings: Update public static bool Update_CheckForUpdatesAtStartup => true; - public static bool Update_CheckForPreReleases => false; - - // Experimental public static bool Experimental_EnableExperimentalFeatures => false; + // Settings: Profiles + public static bool Profiles_IsDailyBackupEnabled => true; + public static int Profiles_MaximumNumberOfBackups => 10; + + // Settings: Settings + public static bool Settings_IsDailyBackupEnabled => true; + public static int Settings_MaximumNumberOfBackups => 10; + // Application: Dashboard public static string Dashboard_PublicIPv4Address => "1.1.1.1"; public static string Dashboard_PublicIPv6Address => "2606:4700:4700::1111"; diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index 4a41e94680..97834d7515 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -582,6 +582,67 @@ public string Profiles_LastSelected } } + private bool _profiles_IsDailyBackupEnabled = GlobalStaticConfiguration.Profiles_IsDailyBackupEnabled; + + public bool Profiles_IsDailyBackupEnabled + { + get => _profiles_IsDailyBackupEnabled; + set + { + if (value == _profiles_IsDailyBackupEnabled) + return; + + _profiles_IsDailyBackupEnabled = value; + OnPropertyChanged(); + } + } + + private int _profiles_MaximumNumberOfBackups = GlobalStaticConfiguration.Profiles_MaximumNumberOfBackups; + + public int Profiles_MaximumNumberOfBackups + { + get => _profiles_MaximumNumberOfBackups; + set + { + if (value == _profiles_MaximumNumberOfBackups) + return; + + _profiles_MaximumNumberOfBackups = value; + OnPropertyChanged(); + } + } + + // Settings + private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled; + + public bool Settings_IsDailyBackupEnabled + { + get => _settings_IsDailyBackupEnabled; + set + { + if (value == _settings_IsDailyBackupEnabled) + return; + + _settings_IsDailyBackupEnabled = value; + OnPropertyChanged(); + } + } + + private int _settings_MaximumNumberOfBackups = GlobalStaticConfiguration.Settings_MaximumNumberOfBackups; + + public int Settings_MaximumNumberOfBackups + { + get => _settings_MaximumNumberOfBackups; + set + { + if (value == _settings_MaximumNumberOfBackups) + return; + + _settings_MaximumNumberOfBackups = value; + OnPropertyChanged(); + } + } + #endregion #region Others diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 7c0041f063..34ff00a850 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -228,7 +228,7 @@ private static SettingsInfo DeserializeFromXmlFile(string filePath) /// Method to save the currently loaded settings (to a file). /// public static void Save() - { + { // Create the directory if it does not exist Directory.CreateDirectory(GetSettingsFolderLocation()); @@ -265,16 +265,27 @@ private static void SerializeToFile(string filePath) /// called as part of a daily maintenance routine. private static void CreateDailyBackupIfNeeded() { - var currentDate = DateTime.Now.Date; + // Skip if daily backups are disabled + if (!Current.Settings_IsDailyBackupEnabled) + { + Log.Info("Daily backups are disabled. Skipping backup creation..."); + return; + } - if (Current.LastBackup < currentDate) + // Skip if settings file doesn't exist yet + if (!File.Exists(GetSettingsFilePath())) { - // Check if settings file exists - if (!File.Exists(GetSettingsFilePath())) - { - Log.Warn("Settings file does not exist yet. Skipping backup creation..."); - return; - } + Log.Warn("Settings file does not exist yet. Skipping backup creation..."); + return; + } + + // Create backup if needed + var currentDate = DateTime.Now.Date; + var lastBackupDate = Current.LastBackup.Date; + + if (lastBackupDate < currentDate) + { + Log.Info("Creating daily backup of settings..."); // Create backup Backup(GetSettingsFilePath(), @@ -284,7 +295,7 @@ private static void CreateDailyBackupIfNeeded() // Cleanup old backups CleanupBackups(GetSettingsBackupFolderLocation(), GetSettingsFileName(), - GlobalStaticConfiguration.Backup_MaximumNumberOfBackups); + Current.Settings_MaximumNumberOfBackups); Current.LastBackup = currentDate; } @@ -302,14 +313,25 @@ private static void CreateDailyBackupIfNeeded() /// The maximum number of backup files to retain. Must be greater than zero. private static void CleanupBackups(string backupFolderPath, string settingsFileName, int maxBackupFiles) { - // Get all backup files sorted by timestamp (newest first) + // Extract settings name without extension to match all backup files regardless of extension + // (e.g., "Settings" matches "2025-01-19_Settings.json", "2025-01-19_Settings.xml") + var settingsNameWithoutExtension = Path.GetFileNameWithoutExtension(settingsFileName); + + // Get all backup files for settings (any extension) sorted by timestamp (newest first) var backupFiles = Directory.GetFiles(backupFolderPath) - .Where(f => (f.EndsWith(settingsFileName) || f.EndsWith(GetLegacySettingsFileName())) && TimestampHelper.IsTimestampedFilename(Path.GetFileName(f))) + .Where(f => + { + var fileName = Path.GetFileName(f); + + // Check if it's a timestamped backup and contains the settings name + return TimestampHelper.IsTimestampedFilename(fileName) && + fileName.Contains($"_{settingsNameWithoutExtension}."); + }) .OrderByDescending(f => TimestampHelper.ExtractTimestampFromFilename(Path.GetFileName(f))) .ToList(); if (backupFiles.Count > maxBackupFiles) - Log.Info($"Cleaning up old backup files... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}."); + Log.Info($"Cleaning up old backup files for {settingsNameWithoutExtension}... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}."); // Delete oldest backups until the maximum number is reached while (backupFiles.Count > maxBackupFiles) @@ -357,6 +379,11 @@ public static void Upgrade(Version fromVersion, Version toVersion) { Log.Info($"Start settings upgrade from {fromVersion} to {toVersion}..."); + // Create backup + Backup(GetSettingsFilePath(), + GetSettingsBackupFolderLocation(), + TimestampHelper.GetTimestampFilename(GetSettingsFileName())); + // 2023.3.7.0 if (fromVersion < new Version(2023, 3, 7, 0)) UpgradeTo_2023_3_7_0(); diff --git a/Source/NETworkManager.Utilities/TimestampHelper.cs b/Source/NETworkManager.Utilities/TimestampHelper.cs index 0ed4b5b83b..6ee5e83c09 100644 --- a/Source/NETworkManager.Utilities/TimestampHelper.cs +++ b/Source/NETworkManager.Utilities/TimestampHelper.cs @@ -35,7 +35,7 @@ public static bool IsTimestampedFilename(string fileName) if (fileName.Length < 16) return false; - var timestampString = fileName.Substring(0, 14); + var timestampString = fileName[..14]; return DateTime.TryParseExact(timestampString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _); } diff --git a/Source/NETworkManager/App.xaml.cs b/Source/NETworkManager/App.xaml.cs index b3e4cd3f1f..ede9bc5e36 100644 --- a/Source/NETworkManager/App.xaml.cs +++ b/Source/NETworkManager/App.xaml.cs @@ -271,16 +271,25 @@ private void Application_Exit(object sender, ExitEventArgs e) private void Save() { + // Save settings if they have changed if (SettingsManager.Current.SettingsChanged) { Log.Info("Save application settings..."); SettingsManager.Save(); } - if (ProfileManager.ProfilesChanged) + // Save profiles if they have changed + if (ProfileManager.LoadedProfileFile != null && ProfileManager.LoadedProfileFileData != null) { - Log.Info("Save current profiles..."); - ProfileManager.Save(); + if (ProfileManager.LoadedProfileFileData.ProfilesChanged) + { + Log.Info($"Save current profile file \"{ProfileManager.LoadedProfileFile.Name}\"..."); + ProfileManager.Save(); + } + } + else + { + Log.Warn("Cannot save profiles because no profile file is loaded or the profile file is encrypted and not yet unlocked."); } } } diff --git a/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs index ddf627f729..13e1aa6377 100644 --- a/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs +++ b/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs index dcab0e5f5a..a048dfe085 100644 --- a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs +++ b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/AboutViewModel.cs b/Source/NETworkManager/ViewModels/AboutViewModel.cs index 2bd8c56f92..5952bb1fd7 100644 --- a/Source/NETworkManager/ViewModels/AboutViewModel.cs +++ b/Source/NETworkManager/ViewModels/AboutViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Documentation; +using NETworkManager.Documentation; using NETworkManager.Localization.Resources; using NETworkManager.Properties; using NETworkManager.Settings; diff --git a/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs b/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs index 99e89b2bb1..503e53192f 100644 --- a/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NETworkManager.Models.Network; diff --git a/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs b/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs index 5ff6cc9aca..80d9865986 100644 --- a/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs +++ b/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/CommandLineViewModel.cs b/Source/NETworkManager/ViewModels/CommandLineViewModel.cs index 08ba26c985..a840e295b4 100644 --- a/Source/NETworkManager/ViewModels/CommandLineViewModel.cs +++ b/Source/NETworkManager/ViewModels/CommandLineViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Windows.Input; using NETworkManager.Documentation; diff --git a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs index 996707a1f8..f58de949d6 100644 --- a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs +++ b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization; diff --git a/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs index 9d13ed0955..2025a2c7d3 100644 --- a/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs +++ b/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs index e7b0e47069..967221d352 100644 --- a/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs +++ b/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Utilities; +using NETworkManager.Utilities; using System; using System.Security; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs index dbebb8ce62..0a793c49fb 100644 --- a/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs +++ b/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs index 32c047a8cd..2e34b8ea5d 100644 --- a/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs +++ b/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs b/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs index b10e59b342..aee2951ac5 100644 --- a/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs +++ b/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs index 7db27bdec4..b2b195de47 100644 --- a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -684,7 +684,7 @@ public void OnViewHide() /// private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -712,7 +712,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.DNSLookup_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs index 8ab7dcdaf4..12c3dbf8b2 100644 --- a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs @@ -1,4 +1,4 @@ -using DnsClient; +using DnsClient; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Models.Network; diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs index 5e2a38a1fc..047fb86344 100644 --- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs +++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs @@ -1,4 +1,4 @@ -using DnsClient; +using DnsClient; using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; diff --git a/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs index 87b6abefa4..7b06ed3940 100644 --- a/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/DashboardViewModel.cs b/Source/NETworkManager/ViewModels/DashboardViewModel.cs index c9a51810b1..ff21c0f634 100644 --- a/Source/NETworkManager/ViewModels/DashboardViewModel.cs +++ b/Source/NETworkManager/ViewModels/DashboardViewModel.cs @@ -1,4 +1,4 @@ -namespace NETworkManager.ViewModels; +namespace NETworkManager.ViewModels; /// /// ViewModel for the Dashboard view. diff --git a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs index b3a84ac13e..9b37597b7f 100644 --- a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs +++ b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Models.Export; diff --git a/Source/NETworkManager/ViewModels/DropdownViewModel.cs b/Source/NETworkManager/ViewModels/DropdownViewModel.cs index 1cd4370ef6..56dd7fd184 100644 --- a/Source/NETworkManager/ViewModels/DropdownViewModel.cs +++ b/Source/NETworkManager/ViewModels/DropdownViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs index ddd09e332d..d9a3af287e 100644 --- a/Source/NETworkManager/ViewModels/ExportViewModel.cs +++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Windows.Forms; diff --git a/Source/NETworkManager/ViewModels/GroupViewModel.cs b/Source/NETworkManager/ViewModels/GroupViewModel.cs index 9bac8b7e83..b6277d0447 100644 --- a/Source/NETworkManager/ViewModels/GroupViewModel.cs +++ b/Source/NETworkManager/ViewModels/GroupViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Models.Network; +using NETworkManager.Models.Network; using NETworkManager.Models.PowerShell; using NETworkManager.Models.PuTTY; using NETworkManager.Models.RemoteDesktop; diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs index 8603f1ccd6..2ddeb4dc4c 100644 --- a/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs +++ b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Models.HostsFileEditor; +using NETworkManager.Models.HostsFileEditor; using NETworkManager.Utilities; using System; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs index 3734abd491..d3d14ec634 100644 --- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs +++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs b/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs index b43336f7c3..d60463208e 100644 --- a/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/IPAddressViewModel.cs b/Source/NETworkManager/ViewModels/IPAddressViewModel.cs index eaf52d7f75..5df641c3c0 100644 --- a/Source/NETworkManager/ViewModels/IPAddressViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPAddressViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs index 1e7a56743d..1a6a7d5bc7 100644 --- a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using System.Windows.Input; using NETworkManager.Models.IPApi; using NETworkManager.Settings; diff --git a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs index 15ab8359dc..21d1d3cbd0 100644 --- a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using System.Windows.Input; using log4net; using NETworkManager.Models.IPApi; diff --git a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs index 9eb0a0f519..1900bd2f19 100644 --- a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -683,7 +683,7 @@ public void OnViewHide() /// private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -711,7 +711,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.IPGeolocation_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs index ea4a691b6a..c94dfa72ef 100644 --- a/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs index 3e03a4f207..5105db7149 100644 --- a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -692,7 +692,7 @@ public void OnViewHide() /// private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -720,7 +720,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.IPScanner_HostOrIPRange.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs index 7234d9563c..2c8664f60f 100644 --- a/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.SimpleChildWindow; +using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Settings; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 9f14c255ef..79b3811e9c 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/ListenersViewModel.cs b/Source/NETworkManager/ViewModels/ListenersViewModel.cs index a7b4fe06cc..147cd67526 100644 --- a/Source/NETworkManager/ViewModels/ListenersViewModel.cs +++ b/Source/NETworkManager/ViewModels/ListenersViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/LookupHostViewModel.cs b/Source/NETworkManager/ViewModels/LookupHostViewModel.cs index 895700b961..5230198517 100644 --- a/Source/NETworkManager/ViewModels/LookupHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/LookupHostViewModel.cs @@ -1,4 +1,4 @@ -namespace NETworkManager.ViewModels; +namespace NETworkManager.ViewModels; /// /// View model for the lookup host view. diff --git a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs index 3ee09ff332..e841b129c8 100644 --- a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs +++ b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs index 4c93491d56..056b6d6244 100644 --- a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs +++ b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs b/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs index 3472366e86..cbbd1f9b87 100644 --- a/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs +++ b/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Utilities; +using NETworkManager.Utilities; using System; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/MessageViewModel.cs b/Source/NETworkManager/ViewModels/MessageViewModel.cs index 1dc4a20c16..bf7cc88e61 100644 --- a/Source/NETworkManager/ViewModels/MessageViewModel.cs +++ b/Source/NETworkManager/ViewModels/MessageViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Utilities; +using NETworkManager.Utilities; using System; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs index afac966bef..08abc4eace 100644 --- a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs +++ b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Models.Network; +using NETworkManager.Models.Network; using NETworkManager.Settings; using NETworkManager.Utilities; using System; diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs index 08d2a724e5..d374739b8c 100644 --- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs +++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs @@ -1,4 +1,4 @@ -using LiveCharts; +using LiveCharts; using LiveCharts.Configurations; using LiveCharts.Wpf; using log4net; @@ -1679,7 +1679,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -1702,7 +1702,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( // If no tags are selected, show all profiles diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs index 6cc3c66e37..453da98ccb 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.Controls; +using MahApps.Metro.Controls; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -826,7 +826,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -849,7 +849,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.PingMonitor_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs index a8ba9b2ca7..43460623c9 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs index db205fd8b3..3cb9bcb13a 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs @@ -1,4 +1,4 @@ -using LiveCharts; +using LiveCharts; using LiveCharts.Configurations; using LiveCharts.Wpf; using log4net; diff --git a/Source/NETworkManager/ViewModels/PortProfileViewModel.cs b/Source/NETworkManager/ViewModels/PortProfileViewModel.cs index 21c046c319..67ed27b25f 100644 --- a/Source/NETworkManager/ViewModels/PortProfileViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortProfileViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Models.Network; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs b/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs index 967333da4f..37da322862 100644 --- a/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs index 3b0e188816..cb6004a249 100644 --- a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -595,7 +595,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -618,7 +618,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.PortScanner_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs index 5f4ac96980..65ce5b1e58 100644 --- a/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.SimpleChildWindow; +using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Models.Network; using NETworkManager.Settings; diff --git a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs index 2f0ca76e10..83b8a47aae 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs index 64e5ed9e55..0f28f371e2 100644 --- a/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs index 80d632bab7..1752b9f964 100644 --- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using log4net; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; @@ -829,7 +829,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled).SelectMany(x => x.TagsCollection).Distinct().ToList(); + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled).SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -851,7 +851,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.PowerShell_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs index ee1d3a6deb..23c2a90c59 100644 --- a/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; diff --git a/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs index f5a9c24ae3..559b542485 100644 --- a/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs +++ b/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Input; using NETworkManager.Profiles; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/ProfileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileViewModel.cs index 2838d1cec2..153027f21e 100644 --- a/Source/NETworkManager/ViewModels/ProfileViewModel.cs +++ b/Source/NETworkManager/ViewModels/ProfileViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Controls; +using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; using NETworkManager.Models.Network; diff --git a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs index 04d4ca3d90..73c0d13445 100644 --- a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Profiles; +using NETworkManager.Profiles; using NETworkManager.Settings; using NETworkManager.Utilities; using System; @@ -365,7 +365,7 @@ private void SetGroupsView(GroupInfo group = null) Groups = new CollectionViewSource { - Source = ProfileManager.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name) + Source = ProfileManager.LoadedProfileFileData.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name) }.View; // Set to null, so even when the same group is selected, the profiles get refreshed @@ -384,7 +384,7 @@ private void SetGroupsView(GroupInfo group = null) private void CreateTags() { // Get all tags from profiles in the selected group - var tags = ProfileManager.Groups.First(x => x.Name == SelectedGroup.Name).Profiles + var tags = ProfileManager.LoadedProfileFileData.Groups.First(x => x.Name == SelectedGroup.Name).Profiles .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -407,7 +407,7 @@ private void SetProfilesView(ProfileFilterInfo filter, GroupInfo group, ProfileI { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic && ( + Source = ProfileManager.LoadedProfileFileData.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic && ( string.IsNullOrEmpty(Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( // If no tags are selected, show all profiles (!filter.Tags.Any()) || diff --git a/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs index 1c71d7aac1..f552308057 100644 --- a/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Windows.Data; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs index a3a8313eb7..e3b26a5a3d 100644 --- a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using log4net; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; @@ -835,7 +835,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -858,7 +858,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.PuTTY_HostOrSerialLine.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs index 78ced6a3e9..fe1f5ed9f3 100644 --- a/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs index fc445710e0..e1815bc386 100644 --- a/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; using NETworkManager.Utilities; using System; using System.ComponentModel; diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs index 5a9a5b9612..9b2a232621 100644 --- a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; using NETworkManager.Localization.Resources; @@ -721,7 +721,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -744,7 +744,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.RemoteDesktop_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs index 925772e2ec..9f5c050f9f 100644 --- a/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NETworkManager.Models.RemoteDesktop; diff --git a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs index 972d7bfaa0..e17bacebc4 100644 --- a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -505,7 +505,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -528,7 +528,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.SNMP_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs b/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs index 499a12590b..e293f9a243 100644 --- a/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs index 8eb692521d..028de1bb18 100644 --- a/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Windows.Data; diff --git a/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs index 124ebcbbcb..f22d6fa98e 100644 --- a/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs @@ -1,4 +1,4 @@ -using Lextm.SharpSnmpLib.Messaging; +using Lextm.SharpSnmpLib.Messaging; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Models.Network; diff --git a/Source/NETworkManager/ViewModels/SNMPViewModel.cs b/Source/NETworkManager/ViewModels/SNMPViewModel.cs index b255d60b0e..d95b96297c 100644 --- a/Source/NETworkManager/ViewModels/SNMPViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNMPViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs index b41e2c72c5..ceee731192 100644 --- a/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.ObjectModel; using System.Windows.Input; using Dragablz; diff --git a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs index 305dc0e105..b3c916fdda 100644 --- a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.SimpleChildWindow; +using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Models.Network; using NETworkManager.Settings; diff --git a/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs index dd0b8de56c..cddb032164 100644 --- a/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs +++ b/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs index 70069dbb78..a268027722 100644 --- a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs +++ b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs b/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs index cd82cf16af..6b3ca40fcb 100644 --- a/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Models.Appearance; +using NETworkManager.Models.Appearance; using NETworkManager.Settings; using System.ComponentModel; using System.Linq; diff --git a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs index 55dc1a5b1d..9d91a126b1 100644 --- a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using System.Windows; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs b/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs index 817a4a6590..5cfa75f18b 100644 --- a/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Linq; using System.Windows.Data; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs b/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs index aa46309139..363618e622 100644 --- a/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.Controls; +using MahApps.Metro.Controls; using NETworkManager.Settings; using NETworkManager.Utilities; diff --git a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs index ef6213c807..392608bbe5 100644 --- a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Windows.Data; diff --git a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs index d9dcb246fd..87efadeb7e 100644 --- a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs index fd42c76821..e2c58d980d 100644 --- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.SimpleChildWindow; +using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Profiles; using NETworkManager.Settings; @@ -22,6 +22,8 @@ public class SettingsProfilesViewModel : ViewModelBase public Action CloseAction { get; set; } + private readonly bool _isLoading; + private string _location; public string Location @@ -67,12 +69,49 @@ public ProfileFileInfo SelectedProfileFile } } + private bool _isDailyBackupEnabled; + + public bool IsDailyBackupEnabled + { + get => _isDailyBackupEnabled; + set + { + if (value == _isDailyBackupEnabled) + return; + + if (!_isLoading) + SettingsManager.Current.Profiles_IsDailyBackupEnabled = value; + + _isDailyBackupEnabled = value; + OnPropertyChanged(); + } + } + + private int _maximumNumberOfBackups; + + public int MaximumNumberOfBackups + { + get => _maximumNumberOfBackups; + set + { + if (value == _maximumNumberOfBackups) + return; + + if (!_isLoading) + SettingsManager.Current.Profiles_MaximumNumberOfBackups = value; + + _maximumNumberOfBackups = value; + OnPropertyChanged(); + } + } #endregion #region Constructor, LoadSettings public SettingsProfilesViewModel() { + _isLoading = true; + ProfileFiles = new CollectionViewSource { Source = ProfileManager.ProfileFiles }.View; ProfileFiles.SortDescriptions.Add( new SortDescription(nameof(ProfileFileInfo.Name), ListSortDirection.Ascending)); @@ -80,11 +119,15 @@ public SettingsProfilesViewModel() SelectedProfileFile = ProfileFiles.Cast().FirstOrDefault(); LoadSettings(); + + _isLoading = false; } private void LoadSettings() { Location = ProfileManager.GetProfilesFolderLocation(); + IsDailyBackupEnabled = SettingsManager.Current.Profiles_IsDailyBackupEnabled; + MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups; } #endregion diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs index 399ffb1e02..7e15ada0af 100644 --- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs @@ -1,8 +1,6 @@ -using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; using NETworkManager.Settings; using NETworkManager.Utilities; -using NETworkManager.Views; using System; using System.Diagnostics; using System.Threading.Tasks; @@ -16,6 +14,8 @@ public class SettingsSettingsViewModel : ViewModelBase #region Variables public Action CloseAction { get; set; } + private readonly bool _isLoading; + private string _location; public string Location @@ -30,18 +30,61 @@ public string Location OnPropertyChanged(); } } + + private bool _isDailyBackupEnabled; + + public bool IsDailyBackupEnabled + { + get => _isDailyBackupEnabled; + set + { + if (value == _isDailyBackupEnabled) + return; + + if (!_isLoading) + SettingsManager.Current.Settings_IsDailyBackupEnabled = value; + + _isDailyBackupEnabled = value; + OnPropertyChanged(); + } + } + + private int _maximumNumberOfBackups; + + public int MaximumNumberOfBackups + { + get => _maximumNumberOfBackups; + set + { + if (value == _maximumNumberOfBackups) + return; + + if (!_isLoading) + SettingsManager.Current.Settings_MaximumNumberOfBackups = value; + + _maximumNumberOfBackups = value; + OnPropertyChanged(); + } + } + #endregion #region Constructor, LoadSettings public SettingsSettingsViewModel() { + _isLoading = true; + LoadSettings(); + + _isLoading = false; } private void LoadSettings() { Location = SettingsManager.GetSettingsFolderLocation(); + IsDailyBackupEnabled = SettingsManager.Current.Settings_IsDailyBackupEnabled; + MaximumNumberOfBackups = SettingsManager.Current.Settings_MaximumNumberOfBackups; } #endregion diff --git a/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs b/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs index 6cfe5bab66..fbcaf7b473 100644 --- a/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs b/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs index 204ed9f7f0..b760064e6f 100644 --- a/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/SettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsViewModel.cs index a28445fd9d..e7a668933b 100644 --- a/Source/NETworkManager/ViewModels/SettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Text.RegularExpressions; diff --git a/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs b/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs index ce01416562..0e2fb9ef70 100644 --- a/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs index 59b51ebcbc..0a2dc2544a 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Linq; using System.Net; using System.Windows; diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs index eddb327af5..f037c6a6aa 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs @@ -1,4 +1,4 @@ -namespace NETworkManager.ViewModels; +namespace NETworkManager.ViewModels; public class SubnetCalculatorHostViewModel : ViewModelBase { diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs index b1845036f0..67942da2dc 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Localization.Resources; diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs index 59b1ab7d1c..02fbdeff31 100644 --- a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs +++ b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Linq; using System.Net; using System.Windows; diff --git a/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs index b0e70c09ca..4a14b67b72 100644 --- a/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Windows.Data; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs index 5f348ad5bb..bd6e7f1883 100644 --- a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; using NETworkManager.Localization.Resources; @@ -630,7 +630,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -653,7 +653,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.TigerVNC_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs index b2592bcc9b..316ba7e411 100644 --- a/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; diff --git a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs index cd29d858ed..e5a8eea5dc 100644 --- a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -490,7 +490,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -513,7 +513,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.Traceroute_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs index e78f33264c..195871d3f8 100644 --- a/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/TracerouteViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteViewModel.cs index c6801843c6..08993c855b 100644 --- a/Source/NETworkManager/ViewModels/TracerouteViewModel.cs +++ b/Source/NETworkManager/ViewModels/TracerouteViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/UpgradeViewModel.cs b/Source/NETworkManager/ViewModels/UpgradeViewModel.cs index 8eaa9342e0..3c5ea6956a 100644 --- a/Source/NETworkManager/ViewModels/UpgradeViewModel.cs +++ b/Source/NETworkManager/ViewModels/UpgradeViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Documentation; +using NETworkManager.Documentation; using NETworkManager.Settings; using NETworkManager.Utilities; using System; diff --git a/Source/NETworkManager/ViewModels/ViewModelBase.cs b/Source/NETworkManager/ViewModels/ViewModelBase.cs index 705c08e0ec..e1c708d22f 100644 --- a/Source/NETworkManager/ViewModels/ViewModelBase.cs +++ b/Source/NETworkManager/ViewModels/ViewModelBase.cs @@ -1,4 +1,4 @@ -using System.Windows.Input; +using System.Windows.Input; using NETworkManager.Utilities; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs b/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs index 5510300a55..5f33fb1219 100644 --- a/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; namespace NETworkManager.ViewModels; diff --git a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs index e450f3aee9..4e4ed4c4ec 100644 --- a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs +++ b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.Controls; +using MahApps.Metro.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; using NETworkManager.Models.Network; @@ -659,7 +659,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -682,7 +682,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.WakeOnLAN_MACAddress.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs index 52d885b583..45277a9486 100644 --- a/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Windows.Data; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs index 9eecebcbf7..dc04adceb3 100644 --- a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using MahApps.Metro.SimpleChildWindow; using Microsoft.Web.WebView2.Core; using NETworkManager.Controls; @@ -598,7 +598,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -621,7 +621,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.WebConsole_Url.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs index b5f3b9230b..b23100bb4f 100644 --- a/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs @@ -1,4 +1,4 @@ -using MahApps.Metro.SimpleChildWindow; +using MahApps.Metro.SimpleChildWindow; using Microsoft.Web.WebView2.Core; using NETworkManager.Localization.Resources; using NETworkManager.Settings; diff --git a/Source/NETworkManager/ViewModels/WelcomeViewModel.cs b/Source/NETworkManager/ViewModels/WelcomeViewModel.cs index 6c2f9dde56..f2466c31e9 100644 --- a/Source/NETworkManager/ViewModels/WelcomeViewModel.cs +++ b/Source/NETworkManager/ViewModels/WelcomeViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Settings; +using NETworkManager.Settings; using NETworkManager.Utilities; using System; using System.Windows.Input; diff --git a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs index d061001019..66e6c416ea 100644 --- a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs @@ -1,4 +1,4 @@ -using Dragablz; +using Dragablz; using NETworkManager.Controls; using NETworkManager.Localization.Resources; using NETworkManager.Models; @@ -484,7 +484,7 @@ public void OnViewHide() private void CreateTags() { - var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled) + var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled) .SelectMany(x => x.TagsCollection).Distinct().ToList(); var tagSet = new HashSet(tags); @@ -507,7 +507,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul { Profiles = new CollectionViewSource { - Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled && ( + Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled && ( string.IsNullOrEmpty(filter.Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 || x.Whois_Domain.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && ( diff --git a/Source/NETworkManager/ViewModels/WhoisViewModel.cs b/Source/NETworkManager/ViewModels/WhoisViewModel.cs index 8740db96dc..fe4525032c 100644 --- a/Source/NETworkManager/ViewModels/WhoisViewModel.cs +++ b/Source/NETworkManager/ViewModels/WhoisViewModel.cs @@ -1,4 +1,4 @@ -using log4net; +using log4net; using MahApps.Metro.Controls; using MahApps.Metro.SimpleChildWindow; using NETworkManager.Controls; diff --git a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs index c2e7697be2..dda145a34a 100644 --- a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs +++ b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs @@ -1,4 +1,4 @@ -using NETworkManager.Models.Network; +using NETworkManager.Models.Network; using NETworkManager.Utilities; using NETworkManager.Settings; using System; diff --git a/Source/NETworkManager/ViewModels/WiFiViewModel.cs b/Source/NETworkManager/ViewModels/WiFiViewModel.cs index f1c80913f4..0115605412 100644 --- a/Source/NETworkManager/ViewModels/WiFiViewModel.cs +++ b/Source/NETworkManager/ViewModels/WiFiViewModel.cs @@ -1,4 +1,4 @@ -using LiveCharts; +using LiveCharts; using LiveCharts.Wpf; using log4net; using MahApps.Metro.SimpleChildWindow; diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml index d73f2640c8..0989c4a24d 100644 --- a/Source/NETworkManager/Views/SettingsProfilesView.xaml +++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters" xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF" @@ -144,7 +145,8 @@ + + + + + + + +