Compare commits

...

26 Commits

Author SHA1 Message Date
JustArchi
7b7f1518a6 Translations update 2017-08-04 19:41:51 +02:00
JustArchi
6aa5a633e4 async/dispose code review 2017-08-04 19:26:37 +02:00
JustArchi
6f80ee9faa Misc 2017-08-04 18:27:30 +02:00
JustArchi
99e8df318c Free the main thread
Now possible with C# 7.1
2017-08-04 18:22:49 +02:00
JustArchi
2ea334c62e Improve farming reset logic #586 2017-08-03 12:14:47 +02:00
JustArchi
27d0b7427a Packages update 2017-08-02 23:09:13 +02:00
JustArchi
62c20c331e Bump 2017-08-02 20:53:44 +02:00
JustArchi
51d59f0f66 Fix Windows builds 2017-08-02 20:47:53 +02:00
JustArchi
ab90c9dc68 Bump 2017-08-02 19:59:49 +02:00
JustArchi
f7c8b871b3 Translations update 2017-08-02 19:47:23 +02:00
JustArchi
678b32f318 Remove debug, issue fixed 2017-08-02 19:44:55 +02:00
JustArchi
62221fd6b8 Test fix 2017-08-02 19:36:43 +02:00
JustArchi
f932be1395 Keep original dotnet permissions
Dotnet uses rwx-rw-rw
2017-08-02 19:00:27 +02:00
JustArchi
d8fd1035c3 Handle chmod +x for updates #586 2017-08-02 15:03:22 +02:00
JustArchi
09e8a52811 Packages update + config defaults 2017-08-02 14:32:01 +02:00
JustArchi
0b1032199b Misc 2017-08-01 20:50:41 +02:00
JustArchi
3e5dfb3174 Bump 2017-08-01 19:14:51 +02:00
JustArchi
4ada8595bd Translations update 2017-08-01 19:14:20 +02:00
JustArchi
bc64c43748 Misc 2017-08-01 19:04:55 +02:00
JustArchi
f4f7935d4c Add a workaround for invalid scheme redirections
https://github.com/JustArchi/ArchiSteamFarm/issues/586#issuecomment-319422864
2017-08-01 18:59:10 +02:00
JustArchi
aacc0a1720 Bump 2017-08-01 13:23:57 +02:00
JustArchi
e5ee909b96 Remove debug leftovers 2017-08-01 13:23:19 +02:00
JustArchi
f3f444d0bd Test fix #605 2017-08-01 13:00:54 +02:00
JustArchi
43d18b6d49 Move debug to appropriate place 2017-08-01 12:44:22 +02:00
JustArchi
f24be67c8c Bunch of stuff for #605 2017-08-01 12:41:57 +02:00
JustArchi
6ce4f2941b Bump 2017-07-31 17:51:04 +02:00
18 changed files with 387 additions and 304 deletions

View File

@@ -210,6 +210,13 @@ namespace ArchiSteamFarm {
return;
}
if (IsUnixVersion(version)) {
string executable = Path.Combine(targetDirectory, SharedInfo.AssemblyName);
if (File.Exists(executable)) {
OS.UnixSetFileAccessExecutable(executable);
}
}
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
await RestartOrExit().ConfigureAwait(false);
}
@@ -268,6 +275,22 @@ namespace ArchiSteamFarm {
Bot.RegisterBot(botName);
}
private static bool IsUnixVersion(string version) {
if (string.IsNullOrEmpty(version)) {
ArchiLogger.LogNullError(nameof(version));
return false;
}
switch (version) {
case "linux-arm":
case "linux-x64":
case "osx-x64":
return true;
default:
return false;
}
}
private static bool IsValidBotName(string botName) {
if (string.IsNullOrEmpty(botName)) {
ArchiLogger.LogNullError(nameof(botName));

View File

@@ -3,8 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyVersion>3.0.0.7</AssemblyVersion>
<FileVersion>3.0.0.7</FileVersion>
<AssemblyVersion>3.0.1.2</AssemblyVersion>
<FileVersion>3.0.1.2</FileVersion>
<LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon>
@@ -26,11 +26,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta4" />
<PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0-beta1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NLog" Version="5.0.0-beta09" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha6" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
</ItemGroup>

View File

@@ -58,15 +58,15 @@ namespace ArchiSteamFarm {
private const string SteamStoreURL = "http://" + SteamStoreHost;
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1);
private readonly Bot Bot;
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
private readonly WebBrowser WebBrowser;
private string CachedApiKey;
@@ -86,6 +86,7 @@ namespace ArchiSteamFarm {
PublicInventorySemaphore.Dispose();
SessionSemaphore.Dispose();
TradeTokenSemaphore.Dispose();
WebBrowser.Dispose();
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {

View File

@@ -50,12 +50,11 @@ namespace ArchiSteamFarm {
private const ushort MaxSteamMessageLength = 2048;
private const byte MaxTwoFactorCodeFailures = 3;
private const byte MinHeartBeatTTL = GlobalConfig.DefaultConnectionTimeout; // Assume client is responsive for at least that amount of seconds
private const byte PICSCooldownInMiliseconds = 200; // We might need to tune this further
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
internal readonly ArchiLogger ArchiLogger;
@@ -74,16 +73,16 @@ namespace ArchiSteamFarm {
private readonly BotDatabase BotDatabase;
private readonly string BotName;
private readonly CallbackManager CallbackManager;
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty]
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly Timer HeartBeatTimer;
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1);
private readonly Statistics Statistics;
private readonly SteamApps SteamApps;
private readonly SteamClient SteamClient;
@@ -232,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) {
@@ -361,10 +362,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericException(e);
return (0, DateTime.MinValue);
} finally {
Task.Run(async () => {
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
PICSSemaphore.Release();
}).Forget();
PICSSemaphore.Release();
}
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
@@ -471,10 +469,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericException(e);
return null;
} finally {
Task.Run(async () => {
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
PICSSemaphore.Release();
}).Forget();
PICSSemaphore.Release();
}
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
@@ -810,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":
@@ -832,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^":
@@ -1141,7 +1136,7 @@ namespace ArchiSteamFarm {
try {
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) {
await SteamApps.PICSGetProductInfo(0, null);
await SteamFriends.RequestProfileInfo(SteamClient.SteamID);
}
HeartBeatFailures = 0;
@@ -1163,7 +1158,7 @@ namespace ArchiSteamFarm {
}
}
private void ImportAuthenticator(string maFilePath) {
private async Task ImportAuthenticator(string maFilePath) {
if (HasMobileAuthenticator || !File.Exists(maFilePath)) {
return;
}
@@ -1171,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);
@@ -1190,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);
@@ -1591,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:
@@ -1828,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);
}
}
@@ -1903,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;
@@ -1914,7 +1910,7 @@ namespace ArchiSteamFarm {
loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
}
BotDatabase.LoginKey = loginKey;
await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
SteamUser.AcceptNewLoginKey(callback);
}
@@ -2438,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;
@@ -2463,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);
}
@@ -2478,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) {
@@ -2509,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) {
@@ -2529,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;
@@ -2554,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);
}
@@ -2678,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;
@@ -2703,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);
}
@@ -2718,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) {
@@ -2749,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) {
@@ -2769,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;
@@ -2794,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);
}
@@ -4111,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

@@ -58,9 +58,9 @@ namespace ArchiSteamFarm {
);
private readonly Bot Bot;
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
private readonly Timer IdleFarmingTimer;
[JsonProperty]
@@ -87,8 +87,9 @@ namespace ArchiSteamFarm {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
EventSemaphore.Dispose();
FarmingSemaphore.Dispose();
FarmResetEvent.Dispose();
FarmingInitializationSemaphore.Dispose();
FarmingResetSemaphore.Dispose();
GamesToFarm.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
@@ -140,8 +141,19 @@ namespace ArchiSteamFarm {
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
return;
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (NowFarming) {
if (FarmingResetSemaphore.CurrentCount == 0) {
FarmingResetSemaphore.Release();
}
return;
}
} finally {
FarmingInitializationSemaphore.Release();
}
}
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
@@ -188,7 +200,7 @@ namespace ArchiSteamFarm {
return;
}
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
@@ -231,7 +243,7 @@ namespace ArchiSteamFarm {
KeepFarming = NowFarming = true;
Utilities.StartBackgroundFunction(Farm);
} finally {
FarmingSemaphore.Release();
FarmingInitializationSemaphore.Release();
}
}
@@ -240,7 +252,7 @@ namespace ArchiSteamFarm {
return;
}
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!NowFarming) {
@@ -248,7 +260,10 @@ namespace ArchiSteamFarm {
}
KeepFarming = false;
FarmResetEvent.Set();
if (FarmingResetSemaphore.CurrentCount == 0) {
FarmingResetSemaphore.Release();
}
for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false);
@@ -261,7 +276,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
Bot.OnFarmingStopped();
} finally {
FarmingSemaphore.Release();
FarmingInitializationSemaphore.Release();
}
}
@@ -556,65 +571,68 @@ namespace ArchiSteamFarm {
// If we have restricted card drops, we use complex algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
while (GamesToFarm.Count > 0) {
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
if (await IsPlayableGame(game).ConfigureAwait(false)) {
playableGamesToFarmSolo.Add(game);
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump));
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
}
NowFarming = false;
return;
}
gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
playableGamesToFarmMultiple.Add(game);
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break;
}
}
if (playableGamesToFarmSolo.Count > 0) {
while (playableGamesToFarmSolo.Count > 0) {
Game playableGame = playableGamesToFarmSolo.First();
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
playableGamesToFarmSolo.Remove(playableGame);
} else {
NowFarming = false;
return;
}
}
if (playableGamesToFarmMultiple.Count == 0) {
break;
}
if (await FarmMultiple(playableGamesToFarmMultiple).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else {
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) {
if (await IsPlayableGame(game).ConfigureAwait(false)) {
playableGamesToFarmMultiple.Add(game);
}
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break;
}
}
if (FarmMultiple(playableGamesToFarmMultiple)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else {
NowFarming = false;
return;
}
NowFarming = false;
return;
}
}
} else {
// If we have unrestricted card drops, we use simple algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
while (GamesToFarm.Count > 0) {
Game playableGame = null;
foreach (Game game in GamesToFarm) {
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
playableGame = game;
break;
}
if (playableGame != null) {
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
}
}
NowFarming = false;
return;
NowFarming = false;
return;
}
}
}
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
@@ -646,8 +664,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
success = KeepFarming;
}
@@ -665,7 +682,7 @@ namespace ArchiSteamFarm {
return success;
}
private bool FarmHours(ConcurrentHashSet<Game> games) {
private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
@@ -689,8 +706,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
success = KeepFarming;
}
@@ -711,7 +727,7 @@ namespace ArchiSteamFarm {
return success;
}
private bool FarmMultiple(IEnumerable<Game> games) {
private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
if (games == null) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
@@ -721,7 +737,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))));
bool result = FarmHours(CurrentGamesFarming);
bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false);
CurrentGamesFarming.Clear();
return result;
}

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);
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);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(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

@@ -130,8 +130,7 @@
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
</data>
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
<value>ErrorPropertiConfiginvalid
{0} akan diubah dengan nama properti konfigurasi, {1} akan diubah dengan nilai invalid</value>
<value>Konfigurasi properti {0} tidak valid: {1}</value>
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
@@ -182,14 +181,19 @@
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Tidak dapat memeriksa versi terbaru!</value>
</data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Tidak bisa melanjutkan dengan pembaruan karena tidak ada asset yang berkaitan dengan versi yang berjalan sekarang! Pembaruan otomatis ke versi tersebut tidak mungkin.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Tidak bisa melanjutkan update karena tak ada aset yang termasuk dalam versi tersebut!</value>
</data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Menerima permintaan untuk input pengguna, tetapi proses berjalan dalam mode Headless!</value>
</data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Menolak untuk menangani permintaan karena SteamOwnerID tidak diatur!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve">
<value>Menutup...</value>
</data>
@@ -239,9 +243,12 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Sedang mengecek versi terbaru...</value>
</data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Mengunduh versi baru: {0} ({1} MB)... Sambil menunggu, pertimbangkan untuk mengapresiasi seluruh kerja keras dengan mendonasi! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data>
<data name="UpdateFinished" xml:space="preserve">
<value>Proses update selesai!</value>
<value>Proses pembaruan selesai!</value>
</data>
<data name="UpdateNewVersionAvailable" xml:space="preserve">
<value>Versi terbaru ASF tersedia! Pertimbangkan untuk di-update!</value>
@@ -278,19 +285,32 @@
<value>Masukkan suatu nilai yang tidak terdokumentasi {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Masukkan host WCF Anda: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Menerima nilai yang tidak diketahui untuk {0}, laporkan hal ini: {1}</value>
<value>Menerima nilai yang tidak diketahui untuk {0}, mohon laporkan ini: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
</data>
<data name="WarningTooManyGamesToPlay" xml:space="preserve">
<value>Tidak dapat bermain lebih dari {0} game secara bersamaan, hanya {0} entri pertama dari {1} game yang akan digunakan</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Layanan WCF tidak bisa dimulai karena AddressAccessDeniedException! Jika anda ingin menggunakan layanan WCF yang disediakan ASF, jalankan ASF sebagai Administrator, atau berikan izin yang benar!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Jawaban untuk perintah IPC: {0} dengan: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>Server IPC siap!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Memulai server IPC di {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve">
<value>Bot ini sudah berhenti!</value>
</data>
@@ -317,14 +337,14 @@
<value>Mengecek halaman badge lainnya...</value>
</data>
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
<value>Memilih Algoritma Idling: {0}</value>
<value>Memilih algoritma idling: {0}</value>
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
</data>
<data name="Done" xml:space="preserve">
<value>Selesai!</value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>Kami memiliki total {0} permainan ({1} kartu) meninggalkan ke siaga (~{2} yang tersisa)...</value>
<value>Kita memiliki total {0} permainan ({1} kartu) tersisa untuk idle (~{2} tersisa)...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
@@ -360,7 +380,7 @@
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="PlayingNotAvailable" xml:space="preserve">
<value>Bermain sedang tidak tersedia, kami akan mencoba lagi nanti!</value>
<value>Bermain sedang tidak tersedia, kita akan coba lagi nanti!</value>
</data>
<data name="StillIdling" xml:space="preserve">
<value>Masih idling: {0} ({1})</value>
@@ -460,7 +480,7 @@
<comment>{0} will be replaced by logging off reason (string)</comment>
</data>
<data name="BotLoggedOn" xml:space="preserve">
<value>Berhasil Login!</value>
<value>Berhasil login!</value>
</data>
<data name="BotLoggingIn" xml:space="preserve">
<value>Sedang masuk...</value>
@@ -650,5 +670,8 @@
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Ada {0}/{1} bot yang sudah memiliki semua permainan yang sedang diperiksa.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root>

View File

@@ -218,7 +218,7 @@
<value>无账号正在运行,即将退出...</value>
</data>
<data name="RefreshingOurSession" xml:space="preserve">
<value>刷新会话 </value>
<value>正在刷新会话!</value>
</data>
<data name="RejectingTrade" xml:space="preserve">
<value>拒绝交易︰ {0}</value>
@@ -341,10 +341,10 @@
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
</data>
<data name="Done" xml:space="preserve">
<value>完成</value>
<value>完成</value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>共有{0} 个游戏(共计{1} 张卡) 等待挂卡(~还剩{2})...</value>
<value>共有 {0} 个游戏(共计 {1} 张卡) 等待挂卡(~还剩{2})...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
@@ -363,13 +363,13 @@
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle</comment>
</data>
<data name="IdlingStopped" xml:space="preserve">
<value>挂卡已停止</value>
<value>已停止挂卡!</value>
</data>
<data name="IgnoredStickyPauseEnabled" xml:space="preserve">
<value>请求已忽略,因为强制暂停已开启</value>
</data>
<data name="NothingToIdle" xml:space="preserve">
<value>该账户已经无卡可挂</value>
<value>该账户已经无卡可挂</value>
</data>
<data name="NowIdling" xml:space="preserve">
<value>正在挂卡︰{0} ({1})</value>

View File

@@ -42,14 +42,14 @@ namespace ArchiSteamFarm {
private const byte CodeInterval = 30;
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1);
private static int? SteamTimeDifference;
// "ERROR" is being used by SteamDesktopAuthenticator
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR");
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1);
#pragma warning disable 649
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]

View File

@@ -23,8 +23,10 @@
*/
using System;
using System.IO;
using System.Runtime.InteropServices;
using ArchiSteamFarm.Localization;
using Mono.Unix;
namespace ArchiSteamFarm {
internal static class OS {
@@ -44,6 +46,16 @@ namespace ArchiSteamFarm {
}
}
internal static void UnixSetFileAccessExecutable(string path) {
if (!File.Exists(path) || !UnixFileSystemInfo.TryGetFileSystemEntry(path, out UnixFileSystemInfo entry)) {
return;
}
if (!entry.FileAccessPermissions.HasFlag(FileAccessPermissions.UserExecute)) {
entry.FileAccessPermissions = entry.FileAccessPermissions | FileAccessPermissions.UserExecute;
}
}
private static void DisableQuickEditMode() {
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);

View File

@@ -32,7 +32,6 @@ using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Localization;
using NLog;
@@ -53,7 +52,11 @@ namespace ArchiSteamFarm {
internal static WebBrowser WebBrowser { get; private set; }
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
// We need to keep this one assigned and not calculated on-demand
private static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule.FileName;
private static readonly TaskCompletionSource<bool> ShutdownResetEvent = new TaskCompletionSource<bool>();
private static bool ShutdownSequenceInitialized;
@@ -124,13 +127,11 @@ namespace ArchiSteamFarm {
return;
}
string executable = Process.GetCurrentProcess().MainModule.FileName;
string executableName = Path.GetFileNameWithoutExtension(executable);
string executableName = Path.GetFileNameWithoutExtension(ProcessFileName);
IEnumerable<string> arguments = Environment.GetCommandLineArgs().Skip(executableName.Equals(SharedInfo.AssemblyName) ? 1 : 0);
try {
Process.Start(executable, string.Join(" ", arguments));
Process.Start(ProcessFileName, string.Join(" ", arguments));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
@@ -138,7 +139,7 @@ namespace ArchiSteamFarm {
// Give new process some time to take over the window (if needed)
await Task.Delay(2000).ConfigureAwait(false);
ShutdownResetEvent.Set();
ShutdownResetEvent.TrySetResult(true);
Environment.Exit(0);
}
@@ -155,7 +156,7 @@ namespace ArchiSteamFarm {
}
private static async Task InitASF(string[] args) {
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version + " (" + SharedInfo.ModuleVersion + ")");
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
await InitGlobalDatabaseAndServices().ConfigureAwait(false);
@@ -340,14 +341,12 @@ namespace ArchiSteamFarm {
return true;
}
private static void Main(string[] args) {
Init(args).Wait();
private static async Task Main(string[] args) {
// Initialize
await Init(args).ConfigureAwait(false);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit().Wait();
// Wait for shutdown event
await ShutdownResetEvent.Task.ConfigureAwait(false);
}
private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop();
@@ -427,7 +426,7 @@ namespace ArchiSteamFarm {
return;
}
ShutdownResetEvent.Set();
ShutdownResetEvent.TrySetResult(true);
}
}
}

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

@@ -42,6 +42,7 @@ namespace ArchiSteamFarm {
internal const string UpdateDirectory = "_old";
internal const string VersionFile = AssemblyName + ".version";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
internal static Guid ModuleVersion => Assembly.GetEntryAssembly().ManifestModule.ModuleVersionId;
internal static Version Version => Assembly.GetEntryAssembly().GetName().Version;
}
}

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);
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

@@ -37,7 +37,7 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1);
private bool ParsingScheduled;

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;
@@ -535,11 +537,19 @@ namespace ArchiSteamFarm {
ushort status = (ushort) responseMessage.StatusCode;
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
redirectUri = responseMessage.Headers.Location;
if (!redirectUri.IsAbsoluteUri) {
if (redirectUri.IsAbsoluteUri) {
switch (redirectUri.Scheme) {
case "http":
case "https":
break;
default:
// Invalid ones such as "steammobile"
return null;
}
} else {
redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
}
ASF.ArchiLogger.LogGenericDebug("Asked for <" + requestUri + ">, got unsafely redirected to <" + responseMessage.Headers.Location + ">, resolved URI to: <" + redirectUri + ">");
} else {
if (!Debugging.IsDebugBuild) {
return null;