diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index 6420a2ac6..a4bf8a801 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -19,7 +19,7 @@ - + diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index 56980f27c..205956be5 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -156,7 +156,12 @@ namespace ArchiSteamFarm { try { File.WriteAllText(newFilePath, json); - File.Replace(newFilePath, FilePath, null); + + if (File.Exists(FilePath)) { + File.Replace(newFilePath, FilePath, null); + } else { + File.Move(newFilePath, FilePath); + } } catch (Exception e) { ASF.ArchiLogger.LogGenericException(e); } diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs index 64f2267bc..42c216770 100644 --- a/ArchiSteamFarm/GlobalDatabase.cs +++ b/ArchiSteamFarm/GlobalDatabase.cs @@ -118,7 +118,12 @@ namespace ArchiSteamFarm { try { File.WriteAllText(newFilePath, json); - File.Replace(newFilePath, FilePath, null); + + if (File.Exists(FilePath)) { + File.Replace(newFilePath, FilePath, null); + } else { + File.Move(newFilePath, FilePath); + } } catch (Exception e) { ASF.ArchiLogger.LogGenericException(e); } diff --git a/ArchiSteamFarm/IPC.cs b/ArchiSteamFarm/IPC.cs index c3e1875c7..6c81d77a7 100644 --- a/ArchiSteamFarm/IPC.cs +++ b/ArchiSteamFarm/IPC.cs @@ -50,11 +50,11 @@ namespace ArchiSteamFarm { } internal static void Start() { - if (KeepRunning) { + if (KeepRunning || (HttpListener.Prefixes.Count == 0)) { return; } - ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, HttpListener.Prefixes.FirstOrDefault())); + ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, HttpListener.Prefixes.First())); try { HttpListener.Start(); @@ -84,56 +84,60 @@ namespace ArchiSteamFarm { return; } - if (Program.GlobalConfig.SteamOwnerID == 0) { - ASF.ArchiLogger.LogGenericInfo(Strings.ErrorIPCAccessDenied); - await context.Response.WriteAsync(HttpStatusCode.Forbidden, Strings.ErrorIPCAccessDenied).ConfigureAwait(false); - return; - } + try { + if (Program.GlobalConfig.SteamOwnerID == 0) { + ASF.ArchiLogger.LogGenericWarning(Strings.ErrorIPCAccessDenied); + await context.Response.WriteAsync(HttpStatusCode.Forbidden, Strings.ErrorIPCAccessDenied).ConfigureAwait(false); + return; + } - if (!context.Request.RawUrl.StartsWith("/" + nameof(IPC), StringComparison.Ordinal)) { - await context.Response.WriteAsync(HttpStatusCode.BadRequest, nameof(HttpStatusCode.BadRequest)).ConfigureAwait(false); - return; - } - - switch (context.Request.HttpMethod) { - case WebRequestMethods.Http.Get: - for (int i = 0; i < context.Request.QueryString.Count; i++) { - string key = context.Request.QueryString.GetKey(i); - - switch (key) { - case "command": - string command = context.Request.QueryString.Get(i); - if (string.IsNullOrEmpty(command)) { - break; - } - - Bot bot = Bot.Bots.Values.FirstOrDefault(); - if (bot == null) { - await context.Response.WriteAsync(HttpStatusCode.NotAcceptable, Strings.ErrorNoBotsDefined).ConfigureAwait(false); - return; - } - - if (command[0] != '!') { - command = "!" + command; - } - - string response = await bot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false); - - ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCAnswered, command, response)); - - await context.Response.WriteAsync(HttpStatusCode.OK, response).ConfigureAwait(false); - break; - } - } - - break; - default: + if (!context.Request.RawUrl.StartsWith("/" + nameof(IPC), StringComparison.Ordinal)) { await context.Response.WriteAsync(HttpStatusCode.BadRequest, nameof(HttpStatusCode.BadRequest)).ConfigureAwait(false); return; - } + } - if (context.Response.ContentLength64 == 0) { - await context.Response.WriteAsync(HttpStatusCode.MethodNotAllowed, nameof(HttpStatusCode.MethodNotAllowed)).ConfigureAwait(false); + switch (context.Request.HttpMethod) { + case WebRequestMethods.Http.Get: + for (int i = 0; i < context.Request.QueryString.Count; i++) { + string key = context.Request.QueryString.GetKey(i); + + switch (key) { + case "command": + string command = context.Request.QueryString.Get(i); + if (string.IsNullOrEmpty(command)) { + break; + } + + Bot bot = Bot.Bots.Values.FirstOrDefault(); + if (bot == null) { + await context.Response.WriteAsync(HttpStatusCode.NotAcceptable, Strings.ErrorNoBotsDefined).ConfigureAwait(false); + return; + } + + if (command[0] != '!') { + command = "!" + command; + } + + string response = await bot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false); + + ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCAnswered, command, response)); + + await context.Response.WriteAsync(HttpStatusCode.OK, response).ConfigureAwait(false); + break; + } + } + + break; + default: + await context.Response.WriteAsync(HttpStatusCode.BadRequest, nameof(HttpStatusCode.BadRequest)).ConfigureAwait(false); + return; + } + + if (context.Response.ContentLength64 == 0) { + await context.Response.WriteAsync(HttpStatusCode.MethodNotAllowed, nameof(HttpStatusCode.MethodNotAllowed)).ConfigureAwait(false); + } + } finally { + context.Response.Close(); } } @@ -148,8 +152,7 @@ namespace ArchiSteamFarm { continue; } - await HandleRequest(context).ConfigureAwait(false); - context.Response.Close(); + Utilities.StartBackgroundFunction(() => HandleRequest(context)); } } @@ -164,8 +167,14 @@ namespace ArchiSteamFarm { response.StatusCode = (ushort) statusCode; } - byte[] buffer = Encoding.UTF8.GetBytes(message); + Encoding encoding = Encoding.UTF8; + response.ContentEncoding = encoding; + + string html = "

" + message + "

"; + + byte[] buffer = encoding.GetBytes(html); response.ContentLength64 = buffer.Length; + await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); } catch (Exception e) { response.StatusCode = (ushort) HttpStatusCode.InternalServerError; diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 8f4c836be..71e547b23 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -143,8 +143,9 @@ namespace ArchiSteamFarm { } private static async Task Init(string[] args) { - AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; - TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; // We must register our logging target as soon as possible Target.Register(SteamTarget.TargetName); @@ -183,6 +184,8 @@ namespace ArchiSteamFarm { ParsePostInitArgs(args); } + IPC.Start(); + if (!Debugging.IsDebugBuild) { await ASF.CheckForUpdate().ConfigureAwait(false); } @@ -319,8 +322,6 @@ namespace ArchiSteamFarm { ShutdownSequenceInitialized = true; - IPC.Stop(); - if (Bot.Bots.Count == 0) { return true; } @@ -353,6 +354,31 @@ namespace ArchiSteamFarm { Exit().Wait(); } + private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop(); + + private static async void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) { + if (e?.ExceptionObject == null) { + ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.ExceptionObject)); + return; + } + + ASF.ArchiLogger.LogFatalException((Exception) e.ExceptionObject); + await Exit(1).ConfigureAwait(false); + } + + private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { + if (e?.Exception == null) { + ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.Exception)); + return; + } + + ASF.ArchiLogger.LogFatalException(e.Exception); + + // Normally we should abort the application here, but many tasks are in fact failing in SK2 code which we can't easily fix + // Thanks Valve. + e.SetObserved(); + } + private static void ParsePostInitArgs(IEnumerable args) { if (args == null) { ASF.ArchiLogger.LogNullError(nameof(args)); @@ -407,28 +433,5 @@ namespace ArchiSteamFarm { ShutdownResetEvent.Set(); } - - private static async void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) { - if (e?.ExceptionObject == null) { - ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.ExceptionObject)); - return; - } - - ASF.ArchiLogger.LogFatalException((Exception) e.ExceptionObject); - await Exit(1).ConfigureAwait(false); - } - - private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs e) { - if (e?.Exception == null) { - ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.Exception)); - return; - } - - ASF.ArchiLogger.LogFatalException(e.Exception); - - // Normally we should abort the application here, but many tasks are in fact failing in SK2 code which we can't easily fix - // Thanks Valve. - e.SetObserved(); - } } } \ No newline at end of file diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 27760fd89..2c2b6a9f4 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -95,22 +95,34 @@ namespace ArchiSteamFarm { } } - internal static void StartBackgroundAction(Action action) { + internal static void StartBackgroundAction(Action action, bool longRunning = true) { if (action == null) { ASF.ArchiLogger.LogNullError(nameof(action)); return; } - Task.Factory.StartNew(action, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); + TaskCreationOptions options = TaskCreationOptions.DenyChildAttach; + + if (longRunning) { + options |= TaskCreationOptions.LongRunning; + } + + Task.Factory.StartNew(action, options).Forget(); } - internal static void StartBackgroundFunction(Func function) { + internal static void StartBackgroundFunction(Func function, bool longRunning = true) { if (function == null) { ASF.ArchiLogger.LogNullError(nameof(function)); return; } - Task.Factory.StartNew(function, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); + TaskCreationOptions options = TaskCreationOptions.DenyChildAttach; + + if (longRunning) { + options |= TaskCreationOptions.LongRunning; + } + + Task.Factory.StartNew(function, options).Forget(); } internal static IEnumerable ToEnumerable(this T item) where T : struct {