async/dispose code review

This commit is contained in:
JustArchi 2017-08-04 19:26:37 +02:00
parent 6f80ee9faa
commit 6aa5a633e4
9 changed files with 180 additions and 172 deletions

View file

@ -86,6 +86,7 @@ namespace ArchiSteamFarm {
PublicInventorySemaphore.Dispose();
SessionSemaphore.Dispose();
TradeTokenSemaphore.Dispose();
WebBrowser.Dispose();
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {

View file

@ -231,22 +231,24 @@ namespace ArchiSteamFarm {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
ArchiWebHandler.Dispose();
CallbackSemaphore.Dispose();
CardsFarmer.Dispose();
HeartBeatTimer.Dispose();
InitializationSemaphore.Dispose();
LootingSemaphore.Dispose();
PICSSemaphore.Dispose();
Trading.Dispose();
// Those are objects that might be null and the check should be in-place
ArchiWebHandler?.Dispose();
BotDatabase?.Dispose();
CardsFarmer?.Dispose();
CardsFarmerResumeTimer?.Dispose();
ConnectionFailureTimer?.Dispose();
FamilySharingInactivityTimer?.Dispose();
HeartBeatTimer?.Dispose();
PlayingWasBlockedTimer?.Dispose();
SendItemsTimer?.Dispose();
Statistics?.Dispose();
SteamSaleEvent?.Dispose();
Trading?.Dispose();
}
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
@ -803,13 +805,13 @@ namespace ArchiSteamFarm {
return await ResponseBlacklistAdd(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseBlacklistAdd(steamID, args[1]);
return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
case "!BLRM":
if (args.Length > 2) {
return await ResponseBlacklistRemove(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseBlacklistRemove(steamID, args[1]);
return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
case "!FARM":
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
case "!INPUT":
@ -825,13 +827,13 @@ namespace ArchiSteamFarm {
return await ResponseIdleQueueAdd(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseIdleQueueAdd(steamID, args[1]);
return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false);
case "!IQRM":
if (args.Length > 2) {
return await ResponseIdleQueueRemove(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseIdleQueueRemove(steamID, args[1]);
return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false);
case "!LOOT":
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
case "!LOOT^":
@ -1156,7 +1158,7 @@ namespace ArchiSteamFarm {
}
}
private void ImportAuthenticator(string maFilePath) {
private async Task ImportAuthenticator(string maFilePath) {
if (HasMobileAuthenticator || !File.Exists(maFilePath)) {
return;
}
@ -1164,7 +1166,8 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorConverting);
try {
BotDatabase.MobileAuthenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
MobileAuthenticator authenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
await BotDatabase.SetMobileAuthenticator(authenticator).ConfigureAwait(false);
File.Delete(maFilePath);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
@ -1183,14 +1186,14 @@ namespace ArchiSteamFarm {
if (string.IsNullOrEmpty(DeviceID)) {
string deviceID = Program.GetUserInput(ASF.EUserInputType.DeviceID, BotName);
if (string.IsNullOrEmpty(deviceID)) {
BotDatabase.MobileAuthenticator = null;
await BotDatabase.SetMobileAuthenticator().ConfigureAwait(false);
return;
}
SetUserInput(ASF.EUserInputType.DeviceID, deviceID);
}
BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID);
await BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID).ConfigureAwait(false);
}
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished);
@ -1584,7 +1587,7 @@ namespace ArchiSteamFarm {
goto case EResult.RateLimitExceeded;
}
BotDatabase.LoginKey = null;
await BotDatabase.SetLoginKey().ConfigureAwait(false);
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
break;
case EResult.NoConnection:
@ -1821,15 +1824,15 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
}
if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID;
if ((callback.CellID != 0) && (callback.CellID != Program.GlobalDatabase.CellID)) {
await Program.GlobalDatabase.SetCellID(callback.CellID).ConfigureAwait(false);
}
if (!HasMobileAuthenticator) {
// Support and convert 2FA files
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
}
}
@ -1896,7 +1899,7 @@ namespace ArchiSteamFarm {
}
}
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
private async void OnLoginKey(SteamUser.LoginKeyCallback callback) {
if (string.IsNullOrEmpty(callback?.LoginKey)) {
ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
return;
@ -1907,7 +1910,7 @@ namespace ArchiSteamFarm {
loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
}
BotDatabase.LoginKey = loginKey;
await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
SteamUser.AcceptNewLoginKey(callback);
}
@ -2431,7 +2434,7 @@ namespace ArchiSteamFarm {
return null;
}
private string ResponseBlacklistAdd(ulong steamID, string targetsText) {
private async Task<string> ResponseBlacklistAdd(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@ -2456,7 +2459,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
}
BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs);
await BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@ -2471,7 +2474,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistAdd(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@ -2502,7 +2505,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistRemove(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@ -2522,7 +2525,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join("", responses) : null;
}
private string ResponseBlacklistRemove(ulong steamID, string targetsText) {
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@ -2547,7 +2550,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
}
BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs);
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@ -2671,7 +2674,7 @@ namespace ArchiSteamFarm {
return result;
}
private string ResponseIdleQueueAdd(ulong steamID, string targetsText) {
private async Task<string> ResponseIdleQueueAdd(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@ -2696,7 +2699,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
}
BotDatabase.AddIdlingPriorityAppIDs(appIDs);
await BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@ -2711,7 +2714,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueAdd(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueAdd(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@ -2742,7 +2745,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueRemove(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueRemove(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@ -2762,7 +2765,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join("", responses) : null;
}
private string ResponseIdleQueueRemove(ulong steamID, string targetsText) {
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@ -2787,7 +2790,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
}
BotDatabase.RemoveIdlingPriorityAppIDs(appIDs);
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@ -4104,7 +4107,7 @@ namespace ArchiSteamFarm {
if (!HasMobileAuthenticator) {
string maFilePath = BotPath + ".maFile";
if (File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
}
}

View file

@ -26,49 +26,25 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
internal sealed class BotDatabase : IDisposable {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<ulong> BlacklistedFromTradesSteamIDs = new ConcurrentHashSet<ulong>();
private readonly object FileLock = new object();
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
internal string LoginKey {
get => _LoginKey;
[JsonProperty(PropertyName = "_LoginKey")]
internal string LoginKey { get; private set; }
set {
if (_LoginKey == value) {
return;
}
_LoginKey = value;
Save();
}
}
internal MobileAuthenticator MobileAuthenticator {
get => _MobileAuthenticator;
set {
if (_MobileAuthenticator == value) {
return;
}
_MobileAuthenticator = value;
Save();
}
}
[JsonProperty]
private string _LoginKey;
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
[JsonProperty(PropertyName = "_MobileAuthenticator")]
internal MobileAuthenticator MobileAuthenticator { get; private set; }
private string FilePath;
@ -79,43 +55,51 @@ namespace ArchiSteamFarm {
}
FilePath = filePath;
Save();
Save().Wait();
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotDatabase() { }
internal void AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
FileSemaphore.Dispose();
// Those are objects that might be null and the check should be in-place
MobileAuthenticator?.Dispose();
}
internal async Task AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
}
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
internal async Task AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void CorrectMobileAuthenticatorDeviceID(string deviceID) {
internal async Task CorrectMobileAuthenticatorDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID) || (MobileAuthenticator == null)) {
ASF.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(MobileAuthenticator));
return;
}
if (MobileAuthenticator.CorrectDeviceID(deviceID)) {
Save();
await Save().ConfigureAwait(false);
}
}
@ -170,49 +154,69 @@ namespace ArchiSteamFarm {
return botDatabase;
}
internal void RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
internal async Task RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
}
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
internal async Task RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
private void Save() {
internal async Task SetLoginKey(string value = null) {
if (value == LoginKey) {
return;
}
LoginKey = value;
await Save().ConfigureAwait(false);
}
internal async Task SetMobileAuthenticator(MobileAuthenticator value = null) {
if (value == MobileAuthenticator) {
return;
}
MobileAuthenticator = value;
await Save().ConfigureAwait(false);
}
private async Task Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
return;
}
lock (FileLock) {
string newFilePath = FilePath + ".new";
string newFilePath = FilePath + ".new";
try {
File.WriteAllText(newFilePath, json);
await FileSemaphore.WaitAsync().ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
try {
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
} finally {
FileSemaphore.Release();
}
}
}

View file

@ -89,6 +89,7 @@ namespace ArchiSteamFarm {
EventSemaphore.Dispose();
FarmingInitializationSemaphore.Dispose();
FarmingResetSemaphore.Dispose();
GamesToFarm.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();

View file

@ -31,12 +31,12 @@ namespace ArchiSteamFarm {
internal sealed class ConcurrentSortedHashSet<T> : IDisposable, IReadOnlyCollection<T>, ISet<T> {
public int Count {
get {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Count;
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
}
@ -44,159 +44,159 @@ namespace ArchiSteamFarm {
public bool IsReadOnly => false;
private readonly HashSet<T> BackingCollection = new HashSet<T>();
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
public bool Add(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Add(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void Clear() {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.Clear();
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Contains(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Contains(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void CopyTo(T[] array, int arrayIndex) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.CopyTo(array, arrayIndex);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void Dispose() => SemaphoreSlim.Dispose();
public void Dispose() => CollectionSemaphore.Dispose();
public void ExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.ExceptWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, SemaphoreSlim);
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, CollectionSemaphore);
public void IntersectWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.IntersectWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsProperSubsetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsProperSupersetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsSubsetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsSupersetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Overlaps(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Overlaps(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Remove(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Remove(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool SetEquals(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.SetEquals(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void SymmetricExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.SymmetricExceptWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void UnionWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.UnionWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
@ -204,7 +204,7 @@ namespace ArchiSteamFarm {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.Clear();
@ -213,7 +213,7 @@ namespace ArchiSteamFarm {
BackingCollection.Add(item);
}
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
}

View file

@ -42,24 +42,11 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
internal uint CellID {
get => _CellID;
set {
if ((value == 0) || (_CellID == value)) {
return;
}
_CellID = value;
Save();
}
}
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID;
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
internal uint CellID { get; private set; }
private string FilePath;
@ -70,13 +57,20 @@ namespace ArchiSteamFarm {
}
FilePath = filePath;
Save();
Save().Wait();
}
// This constructor is used only by deserializer
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
public void Dispose() => ServerListProvider.ServerListUpdated -= OnServerListUpdated;
public void Dispose() {
// Events we registered
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
// Those are objects that are always being created if constructor doesn't throw exception
FileSemaphore.Dispose();
PackagesRefreshSemaphore.Dispose();
}
internal static GlobalDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@ -136,35 +130,46 @@ namespace ArchiSteamFarm {
}
}
Save();
await Save().ConfigureAwait(false);
} finally {
PackagesRefreshSemaphore.Release();
}
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
internal async Task SetCellID(uint value = 0) {
if (value == CellID) {
return;
}
private void Save() {
CellID = value;
await Save().ConfigureAwait(false);
}
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
private async Task Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
return;
}
lock (FileLock) {
string newFilePath = FilePath + ".new";
string newFilePath = FilePath + ".new";
try {
File.WriteAllText(newFilePath, json);
await FileSemaphore.WaitAsync().ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
try {
File.WriteAllText(newFilePath, json);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
} finally {
FileSemaphore.Release();
}
}
}

View file

@ -38,16 +38,8 @@ namespace ArchiSteamFarm {
internal readonly ProtocolTypes ProtocolTypes;
internal ServerRecordEndPoint(string host, ushort port, ProtocolTypes protocolTypes) {
if (string.IsNullOrEmpty(host)) {
throw new ArgumentNullException(nameof(host));
}
if (port == 0) {
throw new ArgumentNullException(nameof(port));
}
if (protocolTypes == 0) {
throw new ArgumentNullException(nameof(protocolTypes));
if (string.IsNullOrEmpty(host) || (port == 0) || (protocolTypes == 0)) {
throw new ArgumentNullException(nameof(host) + " || " + nameof(port) + " || " + nameof(protocolTypes));
}
Host = host;

View file

@ -40,7 +40,7 @@ namespace ArchiSteamFarm {
private const string URL = "https://" + SharedInfo.StatisticsServer;
private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim RequestsSemaphore = new SemaphoreSlim(1, 1);
private DateTime LastAnnouncementCheck = DateTime.MinValue;
private DateTime LastHeartBeat = DateTime.MinValue;
@ -49,7 +49,7 @@ namespace ArchiSteamFarm {
internal Statistics(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
public void Dispose() => Semaphore.Dispose();
public void Dispose() => RequestsSemaphore.Dispose();
internal async Task OnHeartBeat() {
// Request persona update if needed
@ -62,7 +62,7 @@ namespace ArchiSteamFarm {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
@ -80,7 +80,7 @@ namespace ArchiSteamFarm {
LastHeartBeat = DateTime.UtcNow;
}
} finally {
Semaphore.Release();
RequestsSemaphore.Release();
}
}
@ -114,7 +114,7 @@ namespace ArchiSteamFarm {
}
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
@ -154,7 +154,7 @@ namespace ArchiSteamFarm {
ShouldSendHeartBeats = true;
}
} finally {
Semaphore.Release();
RequestsSemaphore.Release();
}
}
}

View file

@ -34,7 +34,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class WebBrowser {
internal sealed class WebBrowser : IDisposable {
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
@ -63,6 +63,8 @@ namespace ArchiSteamFarm {
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(SharedInfo.AssemblyName + "/" + SharedInfo.Version);
}
public void Dispose() => HttpClient.Dispose();
internal static void Init() {
// Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections;