diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 897651a6c..7937a848e 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -86,6 +86,7 @@ namespace ArchiSteamFarm { PublicInventorySemaphore.Dispose(); SessionSemaphore.Dispose(); TradeTokenSemaphore.Dispose(); + WebBrowser.Dispose(); } internal async Task AcceptTradeOffer(ulong tradeID) { diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index e49c92548..76246c2d3 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -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 AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet 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(File.ReadAllText(maFilePath)); + MobileAuthenticator authenticator = JsonConvert.DeserializeObject(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 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> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText))); + IEnumerable> tasks = bots.Select(bot => bot.ResponseBlacklistAdd(steamID, targetsText)); ICollection results; switch (Program.GlobalConfig.OptimizationMode) { @@ -2502,7 +2505,7 @@ namespace ArchiSteamFarm { return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; } - IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText))); + IEnumerable> tasks = bots.Select(bot => bot.ResponseBlacklistRemove(steamID, targetsText)); ICollection 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 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 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> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueAdd(steamID, targetsText))); + IEnumerable> tasks = bots.Select(bot => bot.ResponseIdleQueueAdd(steamID, targetsText)); ICollection results; switch (Program.GlobalConfig.OptimizationMode) { @@ -2742,7 +2745,7 @@ namespace ArchiSteamFarm { return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; } - IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueRemove(steamID, targetsText))); + IEnumerable> tasks = bots.Select(bot => bot.ResponseIdleQueueRemove(steamID, targetsText)); ICollection 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 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); } } diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index 81cc6390c..dfcbfb462 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -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 BlacklistedFromTradesSteamIDs = new ConcurrentHashSet(); - private readonly object FileLock = new object(); + private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1); [JsonProperty(Required = Required.DisallowNull)] private readonly ConcurrentHashSet IdlingPriorityAppIDs = new ConcurrentHashSet(); - 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 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 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 appIDs) { + internal async Task AddIdlingPriorityAppIDs(HashSet 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 steamIDs) { + internal async Task RemoveBlacklistedFromTradesSteamIDs(HashSet 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 appIDs) { + internal async Task RemoveIdlingPriorityAppIDs(HashSet 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(); } } } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 929f6e25d..1e8cb22ac 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -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(); diff --git a/ArchiSteamFarm/ConcurrentSortedHashSet.cs b/ArchiSteamFarm/ConcurrentSortedHashSet.cs index fbeb7ad80..7b1ae0c67 100644 --- a/ArchiSteamFarm/ConcurrentSortedHashSet.cs +++ b/ArchiSteamFarm/ConcurrentSortedHashSet.cs @@ -31,12 +31,12 @@ namespace ArchiSteamFarm { internal sealed class ConcurrentSortedHashSet : IDisposable, IReadOnlyCollection, ISet { 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 BackingCollection = new HashSet(); - 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 other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { BackingCollection.ExceptWith(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } - public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, SemaphoreSlim); + public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, CollectionSemaphore); public void IntersectWith(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { BackingCollection.IntersectWith(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public bool IsProperSubsetOf(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { return BackingCollection.IsProperSubsetOf(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public bool IsProperSupersetOf(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { return BackingCollection.IsProperSupersetOf(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public bool IsSubsetOf(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { return BackingCollection.IsSubsetOf(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public bool IsSupersetOf(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { return BackingCollection.IsSupersetOf(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public bool Overlaps(IEnumerable 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 other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { return BackingCollection.SetEquals(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public void SymmetricExceptWith(IEnumerable other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { BackingCollection.SymmetricExceptWith(other); } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } public void UnionWith(IEnumerable 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 other) { - SemaphoreSlim.Wait(); + CollectionSemaphore.Wait(); try { BackingCollection.Clear(); @@ -213,7 +213,7 @@ namespace ArchiSteamFarm { BackingCollection.Add(item); } } finally { - SemaphoreSlim.Release(); + CollectionSemaphore.Release(); } } } diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs index 4987f62e3..0cbaa4dec 100644 --- a/ArchiSteamFarm/GlobalDatabase.cs +++ b/ArchiSteamFarm/GlobalDatabase.cs @@ -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(); } } } diff --git a/ArchiSteamFarm/ServerRecordEndPoint.cs b/ArchiSteamFarm/ServerRecordEndPoint.cs index ec8bddfcf..ca5a6e6a5 100644 --- a/ArchiSteamFarm/ServerRecordEndPoint.cs +++ b/ArchiSteamFarm/ServerRecordEndPoint.cs @@ -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; diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index 1c9a28e06..0515e8187 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -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(); } } } diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index ea7285c5f..d13b19feb 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -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;