/*
* 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.Runtime.CompilerServices;
using ProtonVPN.Client.Contracts.ProcessCommunication;
using ProtonVPN.Client.Logic.Services.Contracts;
using ProtonVPN.Common.Core.Extensions;
using ProtonVPN.Common.Legacy.Abstract;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.AppServiceLogs;
using ProtonVPN.Logging.Contracts.Events.ProcessCommunicationLogs;
using ProtonVPN.ProcessCommunication.Contracts;
using ProtonVPN.ProcessCommunication.Contracts.Controllers;
namespace ProtonVPN.Client.Logic.Services;
public abstract class ServiceCallerBase : IServiceCaller
where TController : IServiceController
{
private static readonly TimeSpan _callTimeout = TimeSpan.FromSeconds(3);
private readonly IGrpcClient _grpcClient;
private readonly Lazy _serviceCommunicationErrorHandler;
private readonly CancellationTokenSource _cancellationTokenSource = new();
protected ILogger Logger { get; }
protected ServiceCallerBase(ILogger logger,
IGrpcClient grpcClient,
Lazy serviceCommunicationErrorHandler)
{
Logger = logger;
_grpcClient = grpcClient;
_serviceCommunicationErrorHandler = serviceCommunicationErrorHandler;
}
protected async Task> InvokeAsync(Func> serviceCall,
[CallerMemberName] string memberName = "")
{
int retryCount = 5;
while (!_cancellationTokenSource.IsCancellationRequested)
{
try
{
TController serviceController =
await _grpcClient.GetServiceControllerOrThrowAsync(TimeSpan.FromSeconds(1));
//CancellationTokenSource cancellationTokenSource = new(_callTimeout);
T result = await serviceCall(serviceController, _cancellationTokenSource.Token);
if (result is Task task)
{
await task;
}
return Result.Ok(result);
}
catch (Exception e)
{
if (!_cancellationTokenSource.IsCancellationRequested)
{
await _serviceCommunicationErrorHandler.Value.HandleAsync();
}
if (_cancellationTokenSource.IsCancellationRequested || retryCount <= 0)
{
LogError(e, memberName, isToRetry: false);
return Result.Fail(e.CombinedMessage());
}
LogError(e, memberName, isToRetry: true);
}
retryCount--;
}
return Result.Fail($"Can't send message because the {GetType().Name} has been stopped.");
}
private void LogError(Exception exception, string callerMemberName, bool isToRetry)
{
Logger.Error(
$"The invocation of '{callerMemberName}' on VPN Service channel returned an exception and will " +
(isToRetry ? string.Empty : "not ") +
$"be retried. Exception message: {exception.Message}");
}
public void Stop()
{
Logger.Info($"The {GetType().Name} has been stopped.");
_cancellationTokenSource.Cancel();
}
}