using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Threading; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using DryIoc; namespace Artemis.UI.Linux; public class ApplicationStateManager { private readonly IWindowService _windowService; // ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released private Mutex? _artemisMutex; public ApplicationStateManager(IContainer container, string[] startupArguments) { _windowService = container.Resolve(); StartupArguments = startupArguments; Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; Core.Utilities.RestartRequested += UtilitiesOnRestartRequested; Core.Utilities.UpdateRequested += UtilitiesOnUpdateRequested; // On OS shutdown dispose the IOC container just so device providers get a chance to clean up if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) controlledApplicationLifetime.Exit += (_, _) => { RunForcedShutdownIfEnabled(); // Dispose plugins before disposing the IOC container because plugins might access services during dispose container.Resolve().Dispose(); container.Dispose(); }; } public string[] StartupArguments { get; } public bool FocusExistingInstance() { _artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535d", out bool createdNew); if (createdNew) return false; return RemoteFocus(); } public void DisplayException(Exception e) { try { _windowService.ShowExceptionDialog("An unhandled exception occured", e); } catch { // ignored, we tried } } private bool RemoteFocus() { // At this point we cannot read the database yet to retrieve the web server port. // Instead use the method external applications should use as well. if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt"))) { KillOtherInstances(); return false; } string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt")); using HttpClient client = new(); try { CancellationTokenSource cts = new(); cts.CancelAfter(2000); HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token); httpResponseMessage.EnsureSuccessStatusCode(); return true; } catch (Exception) { KillOtherInstances(); return false; } } private void KillOtherInstances() { // Kill everything else heh List processes = Process.GetProcessesByName("Artemis.UI").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList(); foreach (Process process in processes) { try { process.Kill(true); } catch (Exception) { // ignored } } } private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e) { List argsList = new(); argsList.AddRange(StartupArguments); if (e.ExtraArgs != null) argsList.AddRange(e.ExtraArgs.Except(argsList)); //TODO: start new instance with correct arguments if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) { RunForcedShutdownIfEnabled(); if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } private void RunForcedShutdownIfEnabled() { if (StartupArguments.Contains("--disable-forced-shutdown")) return; //todo } private void UtilitiesOnUpdateRequested(object? sender, UpdateEventArgs e) { List argsList = new(StartupArguments); if (e.Silent && !argsList.Contains("--minimized")) argsList.Add("--minimized"); // Retain startup arguments after update by providing them to the script string script = Path.Combine(Constants.UpdatingFolder, "installing", "Scripts", "update.sh"); string source = $"\"{Path.Combine(Constants.UpdatingFolder, "installing")}\""; string destination = $"\"{Constants.ApplicationFolder}\""; string args = argsList.Any() ? string.Join(' ', argsList) : ""; RunScriptWithOutputFile(script, $"{source} {destination} {args}", Path.Combine(Constants.DataFolder, "update-log.txt")); // Lets try a graceful shutdown, the script will kill if needed if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } private static void RunScriptWithOutputFile(string script, string arguments, string outputFile) { ProcessStartInfo info = new() { Arguments = $"\"{script}\" {arguments} > \"{outputFile}\"", FileName = "/bin/bash", WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, }; Process.Start(info); } }