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