Files
Nebula-Auth/NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs
T
achiez 8ff960189e 1.5.6 Bug fixes and code clean-ups
- Update: Added "Copy Password" to Mafile context menu (if available)
- Update: "Save Password" now pre-fills the current password in the "Login Again" dialog when encryption is enabled
- Fix: Prevented overlapping confirmation cycles in AutoConfirmer
- Fix: Added a Snackbar notification when a confirmation cycle is skipped due to a too frequent timer interval
- Fix: Fixed a rare bug where the previous proxy was used instead of the current one during session refresh
- Fix: Corrected "Session Permanently Expired" message showing on unrelated errors (e.g. network issues)
- UI-Fix: Fixed visual glitch where proxy input appeared non-empty when switching to a mafile-specific proxy
- Cleanup: Refactored and cleaned up codebase, improved styling and formatting via ReSharper
2025-05-02 23:29:24 +03:00

159 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AchiesUtilities.Extensions;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using NLog;
namespace NebulaAuth.Model.MAAC;
public static class MultiAccountAutoConfirmer
{
private const string LOC_PATH = "MAAC";
private static readonly ReaderWriterLockSlim Lock = new();
public static ObservableCollection<Mafile> Clients { get; }
private static Timer Timer { get; }
static MultiAccountAutoConfirmer()
{
Clients = [];
Timer = new Timer(TimerConfirm);
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
UpdateTimer();
}
private static readonly SemaphoreSlim ExecutionLock = new(1, 1);
// ReSharper disable once AsyncVoidMethod //Already safe
private static async void TimerConfirm(object? state)
{
bool isHeld = false;
try
{
isHeld = await ExecutionLock.WaitAsync(0);
if (!isHeld)
{
SnackbarController.SendSnackbar(GetLocalization("TimerPreventedOverlap"));
return;
}
await TimerConfirmInternal();
}
catch (Exception e)
{
Shell.Logger.Error(e, "Error in MAAC timer");
}
finally
{
if (isHeld)
{
ExecutionLock.Release();
}
}
}
private static async Task TimerConfirmInternal()
{
var clients = Lock.ReadLock(() => Clients.ToArray());
var enabledClients = clients.Where(x => x.LinkedClient is { IsError: false }).ToArray();
enabledClients = DistributeEvenly(enabledClients).ToArray();
var confirmed = 0;
await Task.Run(async () =>
{
foreach (var client in enabledClients)
{
var conf = 0;
try
{
conf = await client.LinkedClient!.Confirm();
}
catch (ObjectDisposedException)
{
//Ignored
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Internal error while confirming {accountName}", client.AccountName);
}
confirmed += conf;
}
}).ConfigureAwait(false);
if (confirmed > 0)
SnackbarController.SendSnackbar(GetLocalization("TimerConfirmed") + confirmed);
return;
//This function helps us to prevent 429 as much as it possible.
//It's not perfect but better than nothing
static IEnumerable<Mafile> DistributeEvenly(IEnumerable<Mafile> input)
{
var elementCounts = input
.GroupBy(x => x.Proxy?.Id ?? -1)
.ToDictionary(g => g.Key, g => new Queue<Mafile>(g));
var result = new List<Mafile>();
bool added;
do
{
added = false;
foreach (var key in elementCounts.Keys.ToList())
{
if (elementCounts[key].Count > 0)
{
result.Add(elementCounts[key].Dequeue());
added = true;
}
}
} while (added);
return result;
}
}
public static bool TryAddToConfirm(Mafile mafile)
{
return Lock.WriteLock(() =>
{
if (Clients.Contains(mafile)) return false;
Clients.Add(mafile);
mafile.LinkedClient = new PortableMaClient(mafile);
return true;
});
}
public static void RemoveFromConfirm(Mafile mafile)
{
Lock.WriteLock(() =>
{
mafile.LinkedClient?.Dispose();
mafile.LinkedClient = null;
Clients.Remove(mafile);
});
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(Settings.TimerSeconds)) return;
UpdateTimer();
}
private static void UpdateTimer()
{
var timerInterval = Settings.Instance.TimerSeconds;
var intervalTimeSpan = TimeSpan.FromSeconds(timerInterval);
Timer.Change(intervalTimeSpan, intervalTimeSpan);
}
private static string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
}