/*
* 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 ProtonVPN.Client.Contracts.ProcessCommunication;
using ProtonVPN.Client.Logic.Connection.Contracts;
using ProtonVPN.Client.Logic.Services.Contracts;
using ProtonVPN.Common.Core.Helpers;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.AppServiceLogs;
using ProtonVPN.OperatingSystems.Services.Contracts;
using ProtonVPN.ProcessCommunication.Contracts;
using ProtonVPN.StatisticalEvents.Contracts.Dimensions;
namespace ProtonVPN.Client.Services.ProcessCommunication;
public class ServiceCommunicationErrorHandler : IServiceCommunicationErrorHandler
{
private readonly TimeSpan _retryTimeoutInterval = TimeSpan.FromSeconds(3);
private readonly ILogger _logger;
private readonly IGrpcClient _grpcClient;
private readonly IServiceManager _serviceManager;
private readonly IConnectionManager _connectionManager;
private readonly SingleFunctionExecutor _singleFunctionExecutor;
public ServiceCommunicationErrorHandler(ILogger logger,
IGrpcClient grpcClient,
IServiceManager serviceManager,
IConnectionManager connectionManager)
{
_logger = logger;
_grpcClient = grpcClient;
_serviceManager = serviceManager;
_connectionManager = connectionManager;
_singleFunctionExecutor = new(ExecuteAsync, retryTimeoutInterval: _retryTimeoutInterval);
}
public async Task HandleAsync()
{
await _singleFunctionExecutor.ExecuteAsync();
}
private async Task ExecuteAsync()
{
await StartServiceIfStoppedAsync();
RecreateGrpcChannelIfPipeNameChanged();
}
private async Task StartServiceIfStoppedAsync()
{
bool isStartCalled = false;
while (true)
{
ServiceStatus? serviceStatus = _serviceManager.GetStatus();
if (serviceStatus is null)
{
_logger.Error("Cannot start the service because it was not found.");
return;
}
switch (serviceStatus)
{
case ServiceStatus.Stopped:
_logger.Info("The service status is Stopped. Starting the service.");
_serviceManager.StartAsync();
isStartCalled = true;
break;
case ServiceStatus.StartPending:
case ServiceStatus.StopPending:
_logger.Debug($"The service status is {serviceStatus}. Waiting.");
break;
case ServiceStatus.Running:
if (isStartCalled)
{
_logger.Info("The service status is Running. Requesting reconnect (if a connection existed).");
_connectionManager.ReconnectAsync(VpnTriggerDimension.Auto);
}
else
{
_logger.Info("The service status is Running. No request to start it was done by this handler.");
}
return; // Service up and running
default:
_logger.Error($"The service status is {serviceStatus}, which is not supported.");
return; // Service returned an invalid status (Such as Pause, which we don't support)
}
await WaitAsync();
}
}
private async Task WaitAsync()
{
await Task.Delay(TimeSpan.FromMilliseconds(300));
}
private void RecreateGrpcChannelIfPipeNameChanged()
{
_grpcClient.CreateIfPipeNameChanged();
}
}