diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 7eb96edee..51613f722 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -23,6 +23,7 @@ */ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -33,7 +34,18 @@ using ArchiSteamFarm.JSON; namespace ArchiSteamFarm { internal static class ASF { + internal sealed class BotConfigEventArgs : EventArgs { + internal readonly BotConfig BotConfig; + + internal BotConfigEventArgs(BotConfig botConfig = null) { + BotConfig = botConfig; + } + } + + private static readonly ConcurrentDictionary LastWriteTimes = new ConcurrentDictionary(); + private static Timer AutoUpdatesTimer; + private static FileSystemWatcher FileSystemWatcher; internal static async Task CheckForUpdate(bool updateOverride = false) { string exeFile = Assembly.GetEntryAssembly().Location; @@ -200,5 +212,154 @@ namespace ArchiSteamFarm { Program.Exit(); } } + + internal static void InitFileWatcher() { + if (FileSystemWatcher != null) { + return; + } + + FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*json") { + NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite + }; + + FileSystemWatcher.Changed += OnChanged; + FileSystemWatcher.Created += OnCreated; + FileSystemWatcher.Deleted += OnDeleted; + FileSystemWatcher.Renamed += OnRenamed; + + FileSystemWatcher.EnableRaisingEvents = true; + } + + private static string GetBotNameFromConfigFileName(string fileName) { + if (string.IsNullOrEmpty(fileName)) { + Logging.LogNullError(nameof(fileName)); + return null; + } + + string botName = Path.GetFileNameWithoutExtension(fileName); + if (!string.IsNullOrEmpty(botName)) { + return !botName.Equals(SharedInfo.ASF) ? botName : null; + } + + Logging.LogNullError(nameof(botName)); + return null; + } + + private static Bot GetBotFromConfigFileName(string fileName) { + if (string.IsNullOrEmpty(fileName)) { + Logging.LogNullError(nameof(fileName)); + return null; + } + + string botName = GetBotNameFromConfigFileName(fileName); + if (string.IsNullOrEmpty(botName)) { + Logging.LogNullError(nameof(botName)); + return null; + } + + Bot bot; + return Bot.Bots.TryGetValue(botName, out bot) ? bot : null; + } + + private static async Task CreateBot(string botName) { + if (string.IsNullOrEmpty(botName)) { + Logging.LogNullError(nameof(botName)); + return; + } + + if (Bot.Bots.ContainsKey(botName)) { + return; + } + + // It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it + await Task.Delay(1000).ConfigureAwait(false); + + if (Bot.Bots.ContainsKey(botName)) { + return; + } + + new Bot(botName).Forget(); + } + + private static async void OnChanged(object sender, FileSystemEventArgs e) { + if ((sender == null) || (e == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(e)); + return; + } + + string botName = GetBotNameFromConfigFileName(e.Name); + if (string.IsNullOrEmpty(botName)) { + return; + } + + Bot bot; + if (!Bot.Bots.TryGetValue(botName, out bot)) { + CreateBot(botName).Forget(); + return; + } + + DateTime lastWriteTime = File.GetLastWriteTime(e.FullPath); + + DateTime savedLastWriteTime; + if (LastWriteTimes.TryGetValue(bot, out savedLastWriteTime)) { + if (savedLastWriteTime >= lastWriteTime) { + return; + } + } + + LastWriteTimes[bot] = lastWriteTime; + + // It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it + await Task.Delay(1000).ConfigureAwait(false); + + // It's also possible that we got some other event in the meantime + if (LastWriteTimes.TryGetValue(bot, out savedLastWriteTime)) { + if (lastWriteTime != savedLastWriteTime) { + return; + } + + if (LastWriteTimes.TryRemove(bot, out savedLastWriteTime)) { + if (lastWriteTime != savedLastWriteTime) { + return; + } + } + } + + bot.OnNewConfigLoaded(new BotConfigEventArgs(BotConfig.Load(e.FullPath))).Forget(); + } + + private static void OnCreated(object sender, FileSystemEventArgs e) { + if ((sender == null) || (e == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(e)); + return; + } + + string botName = GetBotNameFromConfigFileName(e.Name); + if (string.IsNullOrEmpty(botName)) { + return; + } + + CreateBot(botName).Forget(); + } + + private static void OnDeleted(object sender, FileSystemEventArgs e) { + if ((sender == null) || (e == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(e)); + return; + } + + Bot bot = GetBotFromConfigFileName(e.Name); + bot?.OnNewConfigLoaded(new BotConfigEventArgs()).Forget(); + } + + private static void OnRenamed(object sender, RenamedEventArgs e) { + if ((sender == null) || (e == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(e)); + return; + } + + Bot bot = GetBotFromConfigFileName(e.OldName); + bot?.OnNewConfigLoaded(new BotConfigEventArgs()).Forget(); + } } } diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 02abb8a47..5eba970a7 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -256,22 +256,9 @@ namespace ArchiSteamFarm { Initialize().Forget(); } - private async Task Initialize() { - BotConfig.NewConfigLoaded += OnNewConfigLoaded; - BotConfig.InitializeWatcher(); - - if (!BotConfig.Enabled) { - Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", BotName); - return; - } - - // Start - await Start().ConfigureAwait(false); - } - - private async void OnNewConfigLoaded(object sender, BotConfig.BotConfigEventArgs args) { - if ((sender == null) || (args == null)) { - Logging.LogNullError(nameof(sender) + " || " + nameof(args), BotName); + internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) { + if (args == null) { + Logging.LogNullError(nameof(args), BotName); return; } @@ -284,15 +271,12 @@ namespace ArchiSteamFarm { return; } - await InitializationSemaphore.WaitAsync().ConfigureAwait(false); - try { if (args.BotConfig == BotConfig) { return; } Stop(); - BotConfig.NewConfigLoaded -= OnNewConfigLoaded; BotConfig = args.BotConfig; CardsFarmer.Paused = BotConfig.Paused; @@ -341,11 +325,19 @@ namespace ArchiSteamFarm { } } + private async Task Initialize() { + if (!BotConfig.Enabled) { + Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", BotName); + return; + } + + // Start + await Start().ConfigureAwait(false); + } + public void Dispose() { // Those are objects that are always being created if constructor doesn't throw exception ArchiWebHandler.Dispose(); - BotConfig.NewConfigLoaded -= OnNewConfigLoaded; - BotConfig.Dispose(); CardsFarmer.Dispose(); HeartBeatTimer.Dispose(); HandledGifts.Dispose(); @@ -2178,6 +2170,7 @@ namespace ArchiSteamFarm { case EResult.ServiceUnavailable: case EResult.Timeout: case EResult.TryAnotherCM: + case EResult.TwoFactorCodeMismatch: Logging.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult, BotName); break; default: // Unexpected result, shutdown immediately diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 45d4da21c..fbac3bb12 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -28,22 +28,13 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Threading.Tasks; namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ConvertToConstant.Local")] [SuppressMessage("ReSharper", "ConvertToConstant.Global")] - internal sealed class BotConfig : IDisposable { - internal sealed class BotConfigEventArgs : EventArgs { - internal readonly BotConfig BotConfig; - - internal BotConfigEventArgs(BotConfig botConfig = null) { - BotConfig = botConfig; - } - } - + internal sealed class BotConfig { internal enum EFarmingOrder : byte { Unordered, AppIDsAscending, @@ -137,12 +128,6 @@ namespace ArchiSteamFarm { [JsonProperty(Required = Required.DisallowNull)] private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText; - internal event EventHandler NewConfigLoaded; - - private string FilePath; - private FileSystemWatcher FileSystemWatcher; - private DateTime LastWriteTime = DateTime.MinValue; - internal static BotConfig Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { Logging.LogNullError(nameof(filePath)); @@ -167,8 +152,6 @@ namespace ArchiSteamFarm { return null; } - botConfig.FilePath = filePath; - // Support encrypted passwords if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) { // In worst case password will result in null, which will have to be corrected by user during runtime @@ -192,78 +175,5 @@ namespace ArchiSteamFarm { // This constructor is used only by deserializer private BotConfig() { } - - internal void InitializeWatcher() { - if (FileSystemWatcher != null) { - return; - } - - if (string.IsNullOrEmpty(FilePath)) { - Logging.LogNullError(nameof(FilePath)); - return; - } - - string fileDirectory = Path.GetDirectoryName(FilePath); - if (string.IsNullOrEmpty(fileDirectory)) { - Logging.LogNullError(nameof(fileDirectory)); - return; - } - - string fileName = Path.GetFileName(FilePath); - if (string.IsNullOrEmpty(fileName)) { - Logging.LogNullError(nameof(fileName)); - return; - } - - FileSystemWatcher = new FileSystemWatcher(fileDirectory, fileName) { - NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite - }; - - FileSystemWatcher.Changed += OnChanged; - FileSystemWatcher.Deleted += OnDeleted; - FileSystemWatcher.Renamed += OnRenamed; - - FileSystemWatcher.EnableRaisingEvents = true; - } - - private async void OnChanged(object sender, FileSystemEventArgs e) { - if (NewConfigLoaded == null) { - return; - } - - DateTime lastWriteTime; - - lock (FileSystemWatcher) { - lastWriteTime = File.GetLastWriteTime(FilePath); - if (LastWriteTime == lastWriteTime) { - return; - } - - LastWriteTime = lastWriteTime; - } - - // It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it - await Task.Delay(1000).ConfigureAwait(false); - - // It's also possible that we got some other event in the meantime - if (lastWriteTime != LastWriteTime) { - return; - } - - NewConfigLoaded?.Invoke(this, new BotConfigEventArgs(Load(FilePath))); - } - private void OnDeleted(object sender, FileSystemEventArgs e) => NewConfigLoaded?.Invoke(this, new BotConfigEventArgs()); - private void OnRenamed(object sender, RenamedEventArgs e) => NewConfigLoaded?.Invoke(this, new BotConfigEventArgs()); - - public void Dispose() { - if (FileSystemWatcher == null) { - return; - } - - FileSystemWatcher.Changed -= OnChanged; - FileSystemWatcher.Deleted -= OnDeleted; - FileSystemWatcher.Renamed -= OnRenamed; - FileSystemWatcher.Dispose(); - } } } diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index e05b53b2b..28047047c 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -350,6 +350,8 @@ namespace ArchiSteamFarm { new Bot(botName).Forget(); } + + ASF.InitFileWatcher(); } private static void Main(string[] args) {