/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System.Collections.Specialized; using ProtonVPN.Client.EventMessaging.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts.Messages; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents.Locations.Servers; using ProtonVPN.Client.Logic.Profiles.Contracts; using ProtonVPN.Client.Logic.Profiles.Contracts.Messages; using ProtonVPN.Client.Logic.Profiles.Contracts.Models; using ProtonVPN.Client.Logic.Profiles.Files; using ProtonVPN.Client.Logic.Servers.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.AppLogs; namespace ProtonVPN.Client.Logic.Profiles; public class ProfilesManager : IProfilesManager, IEventMessageReceiver { private readonly IEventMessageSender _eventMessageSender; private readonly IProfilesFileReaderWriter _profilesFileReaderWriter; private readonly IFavoriteServersStorage _favoriteServersStorage; private readonly IDefaultProfilesProvider _defaultProfilesProvider; private readonly ILogger _logger; private readonly object _lock = new(); private List _profiles = new(); public ProfilesManager( ILogger logger, IEventMessageSender eventMessageSender, IProfilesFileReaderWriter profilesFileReaderWriter, IDefaultProfilesProvider defaultProfilesProvider, IFavoriteServersStorage favoriteServersStorage) { _logger = logger; _eventMessageSender = eventMessageSender; _profilesFileReaderWriter = profilesFileReaderWriter; _defaultProfilesProvider = defaultProfilesProvider; _favoriteServersStorage = favoriteServersStorage; } public IOrderedEnumerable GetAll() { return _profiles.OrderBy(p => p.CreationDateTimeUtc); } public IConnectionProfile? GetById(Guid profileId) { return _profiles.FirstOrDefault(p => p.Id == profileId); } public void OverrideProfiles(IEnumerable profiles) { lock (_lock) { _logger.Info($"Overriding {_profiles.Count} connection profiles with default profiles and {profiles.Count()} profiles"); _profiles.Clear(); _profiles.AddRange(_defaultProfilesProvider.GetDefaultProfiles()); _profiles.AddRange(profiles.DistinctBy(p => p.Id)); SaveAndBroadcastProfileChanges(NotifyCollectionChangedAction.Reset); } } public void AddOrEditProfile(IConnectionProfile profile) { lock (_lock) { NotifyCollectionChangedAction action = NotifyCollectionChangedAction.Add; List existingProfiles = _profiles.Where(p => p.Id == profile.Id).ToList(); foreach (IConnectionProfile existingProfile in existingProfiles) { _profiles.Remove(existingProfile); action = NotifyCollectionChangedAction.Replace; } _logger.Info($"{(action == NotifyCollectionChangedAction.Add ? "Adding" : "Editing")} connection profile {profile.Name} with Id {profile.Id}"); profile.UpdateDateTimeUtc = DateTime.UtcNow; _profiles.Add(profile); SaveAndBroadcastProfileChanges(action, profile.Id); } } public void DeleteProfile(Guid profileId) { lock (_lock) { List profiles = _profiles.Where(p => p.Id == profileId).ToList(); if (profiles.Count > 0) { foreach (IConnectionProfile profile in profiles) { _logger.Info($"Deleting connection profile {profile.Name} with Id {profile.Id}"); _profiles.Remove(profile); } SaveAndBroadcastProfileChanges(NotifyCollectionChangedAction.Remove, profileId); } } } public void Receive(LoggedInMessage message) { BroadcastProfileChanges(NotifyCollectionChangedAction.Reset); } private void SaveAndBroadcastProfileChanges(NotifyCollectionChangedAction action, Guid? changedProfileId = null) { SetFavoriteServers(); SaveProfiles(); BroadcastProfileChanges(action, changedProfileId); } private void SetFavoriteServers() { List serverIds = _profiles .Where(p => p.Location is SingleServerLocationIntent) .OrderByDescending(p => p.CreationDateTimeUtc) .Select(p => ((SingleServerLocationIntent)p.Location).Server.Id) .ToList(); _favoriteServersStorage.SetProfileServerIds(serverIds); } public void LoadProfiles() { lock (_lock) { _profiles = _profilesFileReaderWriter.Read(out bool doesFileExists); _logger.Info($"Loaded {_profiles.Count} connection profiles from file"); SetFavoriteServers(); // No profiles found and profile file does not exist, create list of default profiles if (!_profiles.Any() && !doesFileExists) { _logger.Info($"No connection profiles found, creating default profiles"); _profiles.AddRange(_defaultProfilesProvider.GetDefaultProfiles()); SaveProfiles(); } } } private void SaveProfiles() { _logger.Info($"Saving {_profiles.Count} connection profiles", stackTraceDepth: 2); _profilesFileReaderWriter.Save(_profiles.ToList()); } private void BroadcastProfileChanges(NotifyCollectionChangedAction action, Guid? changedProfileId = null) { _eventMessageSender.Send(new ProfilesChangedMessage() { Action = action, ChangedProfileId = changedProfileId }); } }