diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings
index 674c8d0ba..c2a288a8e 100644
--- a/ArchiSteamFarm.sln.DotSettings
+++ b/ArchiSteamFarm.sln.DotSettings
@@ -183,7 +183,7 @@
ExpressionBody
ExpressionBody
public private protected internal static extern new virtual abstract sealed override readonly unsafe volatile async
- Shift, Bitwise, Conditional
+ Arithmetic, Shift, Bitwise, Conditional
END_OF_LINE
END_OF_LINE
USE_TABS_ONLY
diff --git a/ArchiSteamFarm/ArchiCryptoHelper.cs b/ArchiSteamFarm/ArchiCryptoHelper.cs
index 5cec3c6ee..afd05dd06 100644
--- a/ArchiSteamFarm/ArchiCryptoHelper.cs
+++ b/ArchiSteamFarm/ArchiCryptoHelper.cs
@@ -91,7 +91,7 @@ namespace ArchiSteamFarm {
}
}
- internal static byte[] GenerateSteamParentalHash(byte[] password, byte[] salt, byte hashLength, ESteamParentalAlgorithm steamParentalAlgorithm) {
+ internal static IEnumerable GenerateSteamParentalHash(byte[] password, byte[] salt, byte hashLength, ESteamParentalAlgorithm steamParentalAlgorithm) {
if ((password == null) || (salt == null) || (hashLength == 0) || !Enum.IsDefined(typeof(ESteamParentalAlgorithm), steamParentalAlgorithm)) {
ASF.ArchiLogger.LogNullError(nameof(password) + " || " + nameof(salt) + " || " + nameof(hashLength) + " || " + nameof(steamParentalAlgorithm));
diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs
index d60cd542d..a8ec0b47a 100644
--- a/ArchiSteamFarm/ArchiWebHandler.cs
+++ b/ArchiSteamFarm/ArchiWebHandler.cs
@@ -1976,7 +1976,7 @@ namespace ArchiSteamFarm {
const string request = "/mobileconf/multiajaxop";
// Extra entry for sessionID
- List> data = new List>(8 + (confirmations.Count * 2)) {
+ List> data = new List>(8 + confirmations.Count * 2) {
new KeyValuePair("a", Bot.SteamID.ToString()),
new KeyValuePair("k", confirmationHash),
new KeyValuePair("m", "android"),
diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs
index a30ac1035..05dcd35f4 100755
--- a/ArchiSteamFarm/Bot.cs
+++ b/ArchiSteamFarm/Bot.cs
@@ -466,14 +466,14 @@ namespace ArchiSteamFarm {
}
}
- internal async Task AddGamesToRedeemInBackground(IOrderedDictionary gamesToRedeemInBackground) {
+ internal void AddGamesToRedeemInBackground(IOrderedDictionary gamesToRedeemInBackground) {
if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) {
ArchiLogger.LogNullError(nameof(gamesToRedeemInBackground));
return;
}
- await BotDatabase.AddGamesToRedeemInBackground(gamesToRedeemInBackground).ConfigureAwait(false);
+ BotDatabase.AddGamesToRedeemInBackground(gamesToRedeemInBackground);
if ((GamesRedeemerInBackgroundTimer == null) && BotDatabase.HasGamesToRedeemInBackground && IsConnectedAndLoggedOn) {
Utilities.InBackground(RedeemGamesInBackground);
@@ -880,7 +880,7 @@ namespace ArchiSteamFarm {
IOrderedDictionary validGamesToRedeemInBackground = ValidateGamesToRedeemInBackground(gamesToRedeemInBackground);
if ((validGamesToRedeemInBackground != null) && (validGamesToRedeemInBackground.Count > 0)) {
- await AddGamesToRedeemInBackground(validGamesToRedeemInBackground).ConfigureAwait(false);
+ AddGamesToRedeemInBackground(validGamesToRedeemInBackground);
}
}
@@ -1180,6 +1180,7 @@ namespace ArchiSteamFarm {
int partLength;
bool copyNewline = false;
+ // ReSharper disable ArrangeMissingParentheses - conflict with Roslyn
if (message.Length - i > maxMessageLength) {
int lastNewLine = message.LastIndexOf(Environment.NewLine, i + maxMessageLength - Environment.NewLine.Length, maxMessageLength - Environment.NewLine.Length, StringComparison.Ordinal);
@@ -1199,6 +1200,7 @@ namespace ArchiSteamFarm {
partLength--;
}
+ // ReSharper restore ArrangeMissingParentheses
string messagePart = message.Substring(i, partLength);
messagePart = ASF.GlobalConfig.SteamMessagePrefix + (i > 0 ? "…" : "") + messagePart + (maxMessageLength < message.Length - i ? "…" : "");
@@ -1264,6 +1266,7 @@ namespace ArchiSteamFarm {
int i = 0;
+ // ReSharper disable ArrangeMissingParentheses - conflict with Roslyn
while (i < message.Length) {
int partLength;
bool copyNewline = false;
@@ -1287,6 +1290,7 @@ namespace ArchiSteamFarm {
partLength--;
}
+ // ReSharper restore ArrangeMissingParentheses
string messagePart = message.Substring(i, partLength);
messagePart = ASF.GlobalConfig.SteamMessagePrefix + (i > 0 ? "…" : "") + messagePart + (maxMessageLength < message.Length - i ? "…" : "");
@@ -1716,7 +1720,8 @@ namespace ArchiSteamFarm {
}
authenticator.Init(this);
- await BotDatabase.SetMobileAuthenticator(authenticator).ConfigureAwait(false);
+ BotDatabase.MobileAuthenticator = authenticator;
+
File.Delete(maFilePath);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
@@ -1979,7 +1984,7 @@ namespace ArchiSteamFarm {
}
} else {
// If we're not using login keys, ensure we don't have any saved
- await BotDatabase.SetLoginKey().ConfigureAwait(false);
+ BotDatabase.LoginKey = null;
}
if (!await InitLoginAndPassword(string.IsNullOrEmpty(loginKey)).ConfigureAwait(false)) {
@@ -2070,7 +2075,7 @@ namespace ArchiSteamFarm {
break;
case EResult.InvalidPassword:
- await BotDatabase.SetLoginKey().ConfigureAwait(false);
+ BotDatabase.LoginKey = null;
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
break;
@@ -2405,7 +2410,7 @@ namespace ArchiSteamFarm {
}
if ((callback.CellID != 0) && (callback.CellID != ASF.GlobalDatabase.CellID)) {
- await ASF.GlobalDatabase.SetCellID(callback.CellID).ConfigureAwait(false);
+ ASF.GlobalDatabase.CellID = callback.CellID;
}
// Handle steamID-based maFile
@@ -2530,7 +2535,7 @@ namespace ArchiSteamFarm {
}
}
- private async void OnLoginKey(SteamUser.LoginKeyCallback callback) {
+ private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
if (string.IsNullOrEmpty(callback?.LoginKey)) {
ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
@@ -2547,7 +2552,7 @@ namespace ArchiSteamFarm {
loginKey = ArchiCryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
}
- await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
+ BotDatabase.LoginKey = loginKey;
SteamUser.AcceptNewLoginKey(callback);
}
@@ -2845,7 +2850,7 @@ namespace ArchiSteamFarm {
break;
}
- await BotDatabase.RemoveGameToRedeemInBackground(key).ConfigureAwait(false);
+ BotDatabase.RemoveGameToRedeemInBackground(key);
// If user omitted the name or intentionally provided the same name as key, replace it with the Steam result
if (name.Equals(key) && (result.Items != null) && (result.Items.Count > 0)) {
@@ -2984,7 +2989,7 @@ namespace ArchiSteamFarm {
}
if (i >= steamParentalCode.Length) {
- byte[] passwordHash = ArchiCryptoHelper.GenerateSteamParentalHash(password, settings.salt, (byte) settings.passwordhash.Length, steamParentalAlgorithm);
+ IEnumerable passwordHash = ArchiCryptoHelper.GenerateSteamParentalHash(password, settings.salt, (byte) settings.passwordhash.Length, steamParentalAlgorithm);
if (passwordHash.SequenceEqual(settings.passwordhash)) {
return (true, steamParentalCode);
diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs
index 151819e91..ef23669bd 100644
--- a/ArchiSteamFarm/BotConfig.cs
+++ b/ArchiSteamFarm/BotConfig.cs
@@ -173,6 +173,7 @@ namespace ArchiSteamFarm {
return result;
}
+
set {
if (!string.IsNullOrEmpty(value) && (PasswordFormat != ArchiCryptoHelper.ECryptoMethod.PlainText)) {
value = ArchiCryptoHelper.Encrypt(PasswordFormat, value);
@@ -190,37 +191,37 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal string SteamLogin {
- get => _SteamLogin;
+ get => BackingSteamLogin;
set {
IsSteamLoginSet = true;
- _SteamLogin = value;
+ BackingSteamLogin = value;
}
}
[JsonProperty]
internal string SteamParentalCode {
- get => _SteamParentalCode;
+ get => BackingSteamParentalCode;
set {
IsSteamParentalCodeSet = true;
- _SteamParentalCode = value;
+ BackingSteamParentalCode = value;
}
}
[JsonProperty]
internal string SteamPassword {
- get => _SteamPassword;
+ get => BackingSteamPassword;
set {
IsSteamPasswordSet = true;
- _SteamPassword = value;
+ BackingSteamPassword = value;
}
}
- private string _SteamLogin = DefaultSteamLogin;
- private string _SteamParentalCode = DefaultSteamParentalCode;
- private string _SteamPassword = DefaultSteamPassword;
+ private string BackingSteamLogin = DefaultSteamLogin;
+ private string BackingSteamParentalCode = DefaultSteamParentalCode;
+ private string BackingSteamPassword = DefaultSteamPassword;
private bool ShouldSerializeSensitiveDetails = true;
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamMasterClanID), Required = Required.DisallowNull)]
diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs
index 773568320..c5b048ae7 100644
--- a/ArchiSteamFarm/BotDatabase.cs
+++ b/ArchiSteamFarm/BotDatabase.cs
@@ -24,16 +24,16 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
-using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Collections;
+using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm {
- internal sealed class BotDatabase : IDisposable {
+ internal sealed class BotDatabase : SerializableFile {
internal uint GamesToRedeemInBackgroundCount {
get {
lock (GamesToRedeemInBackground) {
@@ -48,8 +48,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet BlacklistedFromTradesSteamIDs = new ConcurrentHashSet();
- private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
-
[JsonProperty(Required = Required.DisallowNull)]
private readonly OrderedDictionary GamesToRedeemInBackground = new OrderedDictionary();
@@ -59,14 +57,37 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet IdlingPriorityAppIDs = new ConcurrentHashSet();
+ internal string LoginKey {
+ get => BackingLoginKey;
+
+ set {
+ if (BackingLoginKey == value) {
+ return;
+ }
+
+ BackingLoginKey = value;
+ Utilities.InBackground(Save);
+ }
+ }
+
+ internal MobileAuthenticator MobileAuthenticator {
+ get => BackingMobileAuthenticator;
+
+ set {
+ if (BackingMobileAuthenticator == value) {
+ return;
+ }
+
+ BackingMobileAuthenticator = value;
+ Utilities.InBackground(Save);
+ }
+ }
+
[JsonProperty(PropertyName = "_LoginKey")]
- internal string LoginKey { get; private set; }
+ private string BackingLoginKey;
[JsonProperty(PropertyName = "_MobileAuthenticator")]
- internal MobileAuthenticator MobileAuthenticator { get; private set; }
-
- private string FilePath;
- private bool ReadOnly;
+ private MobileAuthenticator BackingMobileAuthenticator;
private BotDatabase([NotNull] string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -79,9 +100,7 @@ namespace ArchiSteamFarm {
[JsonConstructor]
private BotDatabase() { }
- public void Dispose() => FileSemaphore.Dispose();
-
- internal async Task AddBlacklistedFromTradesSteamIDs(IReadOnlyCollection steamIDs) {
+ internal void AddBlacklistedFromTradesSteamIDs(IReadOnlyCollection steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
@@ -89,11 +108,11 @@ namespace ArchiSteamFarm {
}
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
- internal async Task AddGamesToRedeemInBackground(IOrderedDictionary games) {
+ internal void AddGamesToRedeemInBackground(IOrderedDictionary games) {
if ((games == null) || (games.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(games));
@@ -114,11 +133,11 @@ namespace ArchiSteamFarm {
}
if (save) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
- internal async Task AddIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) {
+ internal void AddIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
@@ -126,11 +145,11 @@ namespace ArchiSteamFarm {
}
if (IdlingBlacklistedAppIDs.AddRange(appIDs)) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
- internal async Task AddIdlingPriorityAppIDs(IReadOnlyCollection appIDs) {
+ internal void AddIdlingPriorityAppIDs(IReadOnlyCollection appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
@@ -138,7 +157,7 @@ namespace ArchiSteamFarm {
}
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
@@ -228,25 +247,7 @@ namespace ArchiSteamFarm {
return IdlingPriorityAppIDs.Contains(appID);
}
- internal async Task MakeReadOnly() {
- if (ReadOnly) {
- return;
- }
-
- await FileSemaphore.WaitAsync().ConfigureAwait(false);
-
- try {
- if (ReadOnly) {
- return;
- }
-
- ReadOnly = true;
- } finally {
- FileSemaphore.Release();
- }
- }
-
- internal async Task RemoveBlacklistedFromTradesSteamIDs(IReadOnlyCollection steamIDs) {
+ internal void RemoveBlacklistedFromTradesSteamIDs(IReadOnlyCollection steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
@@ -254,11 +255,11 @@ namespace ArchiSteamFarm {
}
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
- internal async Task RemoveGameToRedeemInBackground(string key) {
+ internal void RemoveGameToRedeemInBackground(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
@@ -273,10 +274,10 @@ namespace ArchiSteamFarm {
GamesToRedeemInBackground.Remove(key);
}
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
- internal async Task RemoveIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) {
+ internal void RemoveIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
@@ -284,11 +285,11 @@ namespace ArchiSteamFarm {
}
if (IdlingBlacklistedAppIDs.RemoveRange(appIDs)) {
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
}
}
- internal async Task RemoveIdlingPriorityAppIDs(IReadOnlyCollection appIDs) {
+ internal void RemoveIdlingPriorityAppIDs(IReadOnlyCollection appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
@@ -296,62 +297,7 @@ namespace ArchiSteamFarm {
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
- await Save().ConfigureAwait(false);
- }
- }
-
- 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() {
- if (ReadOnly) {
- return;
- }
-
- string json = JsonConvert.SerializeObject(this);
-
- if (string.IsNullOrEmpty(json)) {
- ASF.ArchiLogger.LogNullError(nameof(json));
-
- return;
- }
-
- string newFilePath = FilePath + ".new";
-
- await FileSemaphore.WaitAsync().ConfigureAwait(false);
-
- try {
- if (ReadOnly) {
- return;
- }
-
- // We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
- await RuntimeCompatibility.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();
+ Utilities.InBackground(Save);
}
}
diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs
index 10679223b..bd9363c02 100644
--- a/ArchiSteamFarm/Commands.cs
+++ b/ArchiSteamFarm/Commands.cs
@@ -171,11 +171,11 @@ namespace ArchiSteamFarm {
case "BLADD" when args.Length > 2:
return await ResponseBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "BLADD":
- return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
+ return ResponseBlacklistAdd(steamID, args[1]);
case "BLRM" when args.Length > 2:
return await ResponseBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "BLRM":
- return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
+ return ResponseBlacklistRemove(steamID, args[1]);
case "FARM":
return await ResponseFarm(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "INPUT" when args.Length > 3:
@@ -187,21 +187,21 @@ namespace ArchiSteamFarm {
case "IBADD" when args.Length > 2:
return await ResponseIdleBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "IBADD":
- return await ResponseIdleBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
+ return ResponseIdleBlacklistAdd(steamID, args[1]);
case "IBRM" when args.Length > 2:
return await ResponseIdleBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "IBRM":
- return await ResponseIdleBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
+ return ResponseIdleBlacklistRemove(steamID, args[1]);
case "IQ":
return await ResponseIdleQueue(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "IQADD" when args.Length > 2:
return await ResponseIdleQueueAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "IQADD":
- return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false);
+ return ResponseIdleQueueAdd(steamID, args[1]);
case "IQRM" when args.Length > 2:
return await ResponseIdleQueueRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false);
case "IQRM":
- return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false);
+ return ResponseIdleQueueRemove(steamID, args[1]);
case "LEVEL":
return await ResponseLevel(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "LOOT":
@@ -917,7 +917,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseBlacklistAdd(ulong steamID, string targetSteamIDs) {
+ private string ResponseBlacklistAdd(ulong steamID, string targetSteamIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs));
@@ -944,7 +944,7 @@ namespace ArchiSteamFarm {
targetIDs.Add(targetID);
}
- await Bot.BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
+ Bot.BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs);
return FormatBotResponse(Strings.Done);
}
@@ -963,14 +963,14 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseBlacklistAdd(steamID, targetSteamIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseBlacklistAdd(steamID, targetSteamIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseBlacklistRemove(ulong steamID, string targetSteamIDs) {
+ private string ResponseBlacklistRemove(ulong steamID, string targetSteamIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs));
@@ -997,7 +997,7 @@ namespace ArchiSteamFarm {
targetIDs.Add(targetID);
}
- await Bot.BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
+ Bot.BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs);
return FormatBotResponse(Strings.Done);
}
@@ -1016,7 +1016,7 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseBlacklistRemove(steamID, targetSteamIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseBlacklistRemove(steamID, targetSteamIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
@@ -1131,7 +1131,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseIdleBlacklistAdd(ulong steamID, string targetAppIDs) {
+ private string ResponseIdleBlacklistAdd(ulong steamID, string targetAppIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs));
@@ -1158,7 +1158,7 @@ namespace ArchiSteamFarm {
appIDs.Add(appID);
}
- await Bot.BotDatabase.AddIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false);
+ Bot.BotDatabase.AddIdlingBlacklistedAppIDs(appIDs);
return FormatBotResponse(Strings.Done);
}
@@ -1177,14 +1177,14 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseIdleBlacklistAdd(steamID, targetAppIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleBlacklistAdd(steamID, targetAppIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseIdleBlacklistRemove(ulong steamID, string targetAppIDs) {
+ private string ResponseIdleBlacklistRemove(ulong steamID, string targetAppIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs));
@@ -1211,7 +1211,7 @@ namespace ArchiSteamFarm {
appIDs.Add(appID);
}
- await Bot.BotDatabase.RemoveIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false);
+ Bot.BotDatabase.RemoveIdlingBlacklistedAppIDs(appIDs);
return FormatBotResponse(Strings.Done);
}
@@ -1230,7 +1230,7 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseIdleBlacklistRemove(steamID, targetAppIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleBlacklistRemove(steamID, targetAppIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
@@ -1274,7 +1274,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseIdleQueueAdd(ulong steamID, string targetAppIDs) {
+ private string ResponseIdleQueueAdd(ulong steamID, string targetAppIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs));
@@ -1301,7 +1301,7 @@ namespace ArchiSteamFarm {
appIDs.Add(appID);
}
- await Bot.BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
+ Bot.BotDatabase.AddIdlingPriorityAppIDs(appIDs);
return FormatBotResponse(Strings.Done);
}
@@ -1320,14 +1320,14 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseIdleQueueAdd(steamID, targetAppIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleQueueAdd(steamID, targetAppIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
- private async Task ResponseIdleQueueRemove(ulong steamID, string targetAppIDs) {
+ private string ResponseIdleQueueRemove(ulong steamID, string targetAppIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs));
@@ -1354,7 +1354,7 @@ namespace ArchiSteamFarm {
appIDs.Add(appID);
}
- await Bot.BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
+ Bot.BotDatabase.RemoveIdlingPriorityAppIDs(appIDs);
return FormatBotResponse(Strings.Done);
}
@@ -1373,7 +1373,7 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
- IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseIdleQueueRemove(steamID, targetAppIDs))).ConfigureAwait(false);
+ IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleQueueRemove(steamID, targetAppIDs)))).ConfigureAwait(false);
List responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs
index bc8ab4152..cb47ca3cc 100644
--- a/ArchiSteamFarm/GlobalConfig.cs
+++ b/ArchiSteamFarm/GlobalConfig.cs
@@ -145,8 +145,8 @@ namespace ArchiSteamFarm {
internal WebProxy WebProxy {
get {
- if (_WebProxy != null) {
- return _WebProxy;
+ if (BackingWebProxy != null) {
+ return BackingWebProxy;
}
if (string.IsNullOrEmpty(WebProxyText)) {
@@ -163,7 +163,7 @@ namespace ArchiSteamFarm {
return null;
}
- _WebProxy = new WebProxy {
+ BackingWebProxy = new WebProxy {
Address = uri,
BypassProxyOnLocal = true
};
@@ -179,10 +179,10 @@ namespace ArchiSteamFarm {
credentials.Password = WebProxyPassword;
}
- _WebProxy.Credentials = credentials;
+ BackingWebProxy.Credentials = credentials;
}
- return _WebProxy;
+ return BackingWebProxy;
}
}
@@ -201,16 +201,16 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal string WebProxyPassword {
- get => _WebProxyPassword;
+ get => BackingWebProxyPassword;
set {
IsWebProxyPasswordSet = true;
- _WebProxyPassword = value;
+ BackingWebProxyPassword = value;
}
}
- private WebProxy _WebProxy;
- private string _WebProxyPassword = DefaultWebProxyPassword;
+ private WebProxy BackingWebProxy;
+ private string BackingWebProxyPassword = DefaultWebProxyPassword;
private bool ShouldSerializeSensitiveDetails = true;
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamOwnerID), Required = Required.DisallowNull)]
diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs
index dcce4ec25..33018a1d0 100644
--- a/ArchiSteamFarm/GlobalDatabase.cs
+++ b/ArchiSteamFarm/GlobalDatabase.cs
@@ -26,13 +26,14 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.SteamKit2;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
- public sealed class GlobalDatabase : IDisposable {
+ public sealed class GlobalDatabase : SerializableFile {
[JsonProperty(Required = Required.DisallowNull)]
public readonly Guid Guid = Guid.NewGuid();
@@ -42,13 +43,23 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
- private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
- [JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
- internal uint CellID { get; private set; }
+ internal uint CellID {
+ get => BackingCellID;
- private string FilePath;
+ set {
+ if (BackingCellID == value) {
+ return;
+ }
+
+ BackingCellID = value;
+ Utilities.InBackground(Save);
+ }
+ }
+
+ [JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
+ private uint BackingCellID;
private GlobalDatabase([NotNull] string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) {
@@ -61,13 +72,15 @@ namespace ArchiSteamFarm {
[JsonConstructor]
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
- public void Dispose() {
+ public override 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();
+
+ // Base dispose
+ base.Dispose();
}
[ItemCanBeNull]
@@ -159,52 +172,14 @@ namespace ArchiSteamFarm {
PackagesData[packageID] = package;
}
- await Save().ConfigureAwait(false);
+ Utilities.InBackground(Save);
} finally {
PackagesRefreshSemaphore.Release();
}
}
- internal async Task SetCellID(uint value = 0) {
- if (value == CellID) {
- return;
- }
-
- 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;
- }
-
- string newFilePath = FilePath + ".new";
-
- await FileSemaphore.WaitAsync().ConfigureAwait(false);
-
- try {
- // We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
- await RuntimeCompatibility.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();
- }
- }
-
// ReSharper disable UnusedMember.Global
public bool ShouldSerializeCellID() => CellID != 0;
public bool ShouldSerializePackagesData() => PackagesData.Count > 0;
diff --git a/ArchiSteamFarm/Helpers/SerializableFile.cs b/ArchiSteamFarm/Helpers/SerializableFile.cs
new file mode 100644
index 000000000..546eeccc7
--- /dev/null
+++ b/ArchiSteamFarm/Helpers/SerializableFile.cs
@@ -0,0 +1,106 @@
+// _ _ _ ____ _ _____
+// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+// |
+// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
+// Contact: JustArchi@JustArchi.net
+// |
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// |
+// http://www.apache.org/licenses/LICENSE-2.0
+// |
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace ArchiSteamFarm.Helpers {
+ public abstract class SerializableFile : IDisposable {
+ private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
+
+ protected string FilePath { private get; set; }
+
+ private bool ReadOnly;
+ private bool SavingScheduled;
+
+ public virtual void Dispose() => FileSemaphore.Dispose();
+
+ protected async Task Save() {
+ if (ReadOnly || string.IsNullOrEmpty(FilePath)) {
+ return;
+ }
+
+ lock (FileSemaphore) {
+ if (SavingScheduled) {
+ return;
+ }
+
+ SavingScheduled = true;
+ }
+
+ await FileSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ lock (FileSemaphore) {
+ SavingScheduled = false;
+ }
+
+ if (ReadOnly) {
+ return;
+ }
+
+ string json = JsonConvert.SerializeObject(this, Debugging.IsUserDebugging ? Formatting.Indented : Formatting.None);
+
+ if (string.IsNullOrEmpty(json)) {
+ ASF.ArchiLogger.LogNullError(nameof(json));
+
+ return;
+ }
+
+ string newFilePath = FilePath + ".new";
+
+ // We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
+ await RuntimeCompatibility.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();
+ }
+ }
+
+ internal async Task MakeReadOnly() {
+ if (ReadOnly) {
+ return;
+ }
+
+ await FileSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ if (ReadOnly) {
+ return;
+ }
+
+ ReadOnly = true;
+ } finally {
+ FileSemaphore.Release();
+ }
+ }
+ }
+}
diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs
index 994d11be2..79372e1f9 100644
--- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs
+++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs
@@ -233,7 +233,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(validGamesToRedeemInBackground))));
}
- await Utilities.InParallel(bots.Select(bot => bot.AddGamesToRedeemInBackground(validGamesToRedeemInBackground))).ConfigureAwait(false);
+ await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.AddGamesToRedeemInBackground(validGamesToRedeemInBackground)))).ConfigureAwait(false);
Dictionary result = new Dictionary(bots.Count, Bot.BotsComparer);