diff --git a/ArchiSteamFarm/Localization/Strings.Designer.cs b/ArchiSteamFarm/Localization/Strings.Designer.cs index c5b65d113..5e507ddeb 100644 --- a/ArchiSteamFarm/Localization/Strings.Designer.cs +++ b/ArchiSteamFarm/Localization/Strings.Designer.cs @@ -963,6 +963,15 @@ namespace ArchiSteamFarm.Localization { } } + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu ASF process is already running for this working directory, aborting!. + /// + public static string ErrorSingleInstanceRequired { + get { + return ResourceManager.GetString("ErrorSingleInstanceRequired", resourceCulture); + } + } + /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu Could not check latest version!. /// diff --git a/ArchiSteamFarm/Localization/Strings.resx b/ArchiSteamFarm/Localization/Strings.resx index 8f9864968..e13eeb3fc 100644 --- a/ArchiSteamFarm/Localization/Strings.resx +++ b/ArchiSteamFarm/Localization/Strings.resx @@ -717,4 +717,7 @@ StackTrace: Bot has {0} games remaining in its background queue. {0} will be replaced by remaining number of games in BGR's queue + + ASF process is already running for this working directory, aborting! + \ No newline at end of file diff --git a/ArchiSteamFarm/NLog/Logging.cs b/ArchiSteamFarm/NLog/Logging.cs index 4f975c427..2cbdcc333 100644 --- a/ArchiSteamFarm/NLog/Logging.cs +++ b/ArchiSteamFarm/NLog/Logging.cs @@ -139,7 +139,7 @@ namespace ArchiSteamFarm.NLog { return !string.IsNullOrEmpty(result) ? result.Trim() : null; } - internal static void InitCoreLoggers() { + internal static void InitCoreLoggers(bool uniqueInstance) { if (LogManager.Configuration != null) { IsUsingCustomConfiguration = true; InitConsoleLoggers(); @@ -156,14 +156,18 @@ namespace ArchiSteamFarm.NLog { config.AddTarget(coloredConsoleTarget); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, coloredConsoleTarget)); - FileTarget fileTarget = new FileTarget("File") { - DeleteOldFileOnStartup = true, - FileName = SharedInfo.LogFile, - Layout = GeneralLayout - }; + if (uniqueInstance) { + FileTarget fileTarget = new FileTarget("File") { + CleanupFileName = false, + ConcurrentWrites = false, + DeleteOldFileOnStartup = true, + FileName = SharedInfo.LogFile, + Layout = GeneralLayout + }; - config.AddTarget(fileTarget); - config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); + config.AddTarget(fileTarget); + config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); + } LogManager.Configuration = config; InitConsoleLoggers(); diff --git a/ArchiSteamFarm/OS.cs b/ArchiSteamFarm/OS.cs index 6e0beedfe..31de27ba9 100644 --- a/ArchiSteamFarm/OS.cs +++ b/ArchiSteamFarm/OS.cs @@ -22,7 +22,9 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; +using System.Threading; using ArchiSteamFarm.Localization; using JetBrains.Annotations; @@ -33,6 +35,8 @@ namespace ArchiSteamFarm { [NotNull] internal static string Variant => RuntimeInformation.OSDescription.Trim(); + private static Mutex SingleInstance; + internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { DisableQuickEditMode(); @@ -60,6 +64,24 @@ namespace ArchiSteamFarm { } } + internal static bool RegisterProcess() { + if (SingleInstance != null) { + return false; + } + + string uniqueName = "Global\\" + SharedInfo.AssemblyName + "-" + Convert.ToBase64String(Encoding.UTF8.GetBytes(Directory.GetCurrentDirectory())); + + Mutex singleInstance = new Mutex(true, uniqueName, out bool result); + + if (!result) { + return false; + } + + SingleInstance = singleInstance; + + return true; + } + internal static void UnixSetFileAccessExecutable(string path) { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { ASF.ArchiLogger.LogNullError(nameof(path)); @@ -73,6 +95,16 @@ namespace ArchiSteamFarm { } } + internal static void UnregisterProcess() { + if (SingleInstance == null) { + return; + } + + SingleInstance.ReleaseMutex(); + SingleInstance.Dispose(); + SingleInstance = null; + } + private static void DisableQuickEditMode() { if (Console.IsOutputRedirected) { return; diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index cda791977..bfe22f533 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -118,7 +118,12 @@ namespace ArchiSteamFarm { Target.Register(HistoryTarget.TargetName); Target.Register(SteamTarget.TargetName); - InitCore(args); + if (!await InitCore(args).ConfigureAwait(false)) { + await Exit(1).ConfigureAwait(false); + + return; + } + await InitASF(args).ConfigureAwait(false); } @@ -141,7 +146,7 @@ namespace ArchiSteamFarm { await ASF.Init().ConfigureAwait(false); } - private static void InitCore(IReadOnlyCollection args) { + private static async Task InitCore(IReadOnlyCollection args) { Directory.SetCurrentDirectory(SharedInfo.HomeDirectory); // Allow loading configs from source tree if it's a debug build @@ -166,7 +171,17 @@ namespace ArchiSteamFarm { ParsePreInitArgs(args); } - Logging.InitCoreLoggers(); + bool uniqueInstance = OS.RegisterProcess(); + Logging.InitCoreLoggers(uniqueInstance); + + if (!uniqueInstance) { + ASF.ArchiLogger.LogGenericError(Strings.ErrorSingleInstanceRequired); + await Task.Delay(5000).ConfigureAwait(false); + + return false; + } + + return true; } private static async Task InitGlobalConfigAndLanguage() { @@ -320,6 +335,7 @@ namespace ArchiSteamFarm { // Ensure that IPC is stopped before we finalize shutdown sequence await ArchiKestrel.Stop().ConfigureAwait(false); + // Stop all the active bots so they can disconnect cleanly if (Bot.Bots?.Count > 0) { // Stop() function can block due to SK2 sockets, don't forget a maximum delay await Task.WhenAny(Utilities.InParallel(Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(true)))), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false); @@ -328,8 +344,12 @@ namespace ArchiSteamFarm { await Task.Delay(1000).ConfigureAwait(false); } + // Flush all the pending writes to log files LogManager.Flush(); + // Unregister the process from single instancing + OS.UnregisterProcess(); + return true; }