PalacePal/Pal.Client/Plugin.cs

235 lines
8.0 KiB
C#
Raw Normal View History

2023-03-26 13:47:18 +00:00
using System;
2023-02-24 23:55:48 +00:00
using System.Collections.Generic;
2023-02-10 19:48:14 +00:00
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game.Command;
2023-03-26 13:47:18 +00:00
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
2023-10-03 09:08:38 +00:00
using Dalamud.Plugin.Services;
using ECommons;
using ECommons.DalamudServices;
2023-02-15 22:17:19 +00:00
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Commands;
2023-02-15 01:38:04 +00:00
using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
2023-03-26 13:47:18 +00:00
using Pal.Client.Properties;
using Pal.Client.Rendering;
2023-03-30 20:01:43 +00:00
namespace Pal.Client;
/// <summary>
/// With all DI logic elsewhere, this plugin shell really only takes care of a few things around events that
/// need to be sent to different receivers depending on priority or configuration .
/// </summary>
/// <see cref="DependencyInjectionContext"/>
internal sealed class Plugin : IDalamudPlugin
{
2023-03-30 20:01:43 +00:00
private readonly CancellationTokenSource _initCts = new();
private readonly DalamudPluginInterface _pluginInterface;
2023-10-03 09:08:38 +00:00
private readonly ICommandManager _commandManager;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly IFramework _framework;
2023-03-30 20:01:43 +00:00
private readonly TaskCompletionSource<IServiceScope> _rootScopeCompletionSource = new();
private ELoadState _loadState = ELoadState.Initializing;
private DependencyInjectionContext? _dependencyInjectionContext;
private ILogger _logger = DependencyInjectionContext.LoggerProvider.CreateLogger<Plugin>();
private WindowSystem? _windowSystem;
private IServiceScope? _rootScope;
private Action? _loginAction;
public Plugin(
DalamudPluginInterface pluginInterface,
2023-10-03 09:08:38 +00:00
ICommandManager commandManager,
IClientState clientState,
IChatGui chatGui,
IFramework framework)
{
2023-03-30 20:01:43 +00:00
_pluginInterface = pluginInterface;
_commandManager = commandManager;
_clientState = clientState;
_chatGui = chatGui;
_framework = framework;
2023-03-30 20:01:43 +00:00
// set up the current UI language before creating anything
Localization.Culture = new CultureInfo(_pluginInterface.UiLanguage);
2023-03-30 20:01:43 +00:00
_commandManager.AddHandler("/pal", new CommandInfo(OnCommand)
{
HelpMessage = Localization.Command_pal_HelpText
});
2023-03-30 20:01:43 +00:00
// Using TickScheduler requires ECommons to at least be partially initialized
// ECommonsMain.Dispose leaves this untouched.
Svc.Init(pluginInterface);
2023-03-30 20:01:43 +00:00
Task.Run(async () => await CreateDependencyContext());
}
2023-03-30 20:01:43 +00:00
public string Name => Localization.Palace_Pal;
2023-03-30 20:01:43 +00:00
private async Task CreateDependencyContext()
{
try
{
2023-03-30 20:01:43 +00:00
_dependencyInjectionContext = _pluginInterface.Create<DependencyInjectionContext>(this)
?? throw new Exception("Could not create DI root context class");
var serviceProvider = _dependencyInjectionContext.BuildServiceContainer();
_initCts.Token.ThrowIfCancellationRequested();
2023-03-30 20:01:43 +00:00
_logger = serviceProvider.GetRequiredService<ILogger<Plugin>>();
_windowSystem = serviceProvider.GetRequiredService<WindowSystem>();
_rootScope = serviceProvider.CreateScope();
2023-03-30 20:01:43 +00:00
var loader = _rootScope.ServiceProvider.GetRequiredService<DependencyContextInitializer>();
await loader.InitializeAsync(_initCts.Token);
2023-03-30 20:01:43 +00:00
await _framework.RunOnFrameworkThread(() =>
{
2023-03-30 20:01:43 +00:00
_pluginInterface.UiBuilder.Draw += Draw;
_pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
_pluginInterface.LanguageChanged += LanguageChanged;
_clientState.Login += Login;
});
_rootScopeCompletionSource.SetResult(_rootScope);
_loadState = ELoadState.Loaded;
}
2023-03-30 20:01:43 +00:00
catch (Exception e) when (e is ObjectDisposedException
or OperationCanceledException
or RepoVerification.RepoVerificationFailedException
|| (e is FileLoadException && _pluginInterface.IsDev))
{
2023-03-30 20:01:43 +00:00
_rootScopeCompletionSource.SetException(e);
_loadState = ELoadState.Error;
}
2023-03-30 20:01:43 +00:00
catch (Exception e)
{
_rootScopeCompletionSource.SetException(e);
_logger.LogError(e, "Async load failed");
ShowErrorOnLogin(() =>
new Chat(_chatGui).Error(string.Format(Localization.Error_LoadFailed,
$"{e.GetType()} - {e.Message}")));
2023-02-02 16:16:03 +00:00
2023-03-30 20:01:43 +00:00
_loadState = ELoadState.Error;
}
}
private void ShowErrorOnLogin(Action? loginAction)
{
if (_clientState.IsLoggedIn)
{
2023-03-30 20:01:43 +00:00
loginAction?.Invoke();
_loginAction = null;
}
2023-03-30 20:01:43 +00:00
else
_loginAction = loginAction;
}
2023-10-03 09:08:38 +00:00
private void Login()
2023-03-30 20:01:43 +00:00
{
_loginAction?.Invoke();
_loginAction = null;
}
private void OnCommand(string command, string arguments)
{
arguments = arguments.Trim();
Task.Run(async () =>
{
2023-03-30 20:01:43 +00:00
IServiceScope rootScope;
Chat chat;
2023-03-30 20:01:43 +00:00
try
{
rootScope = await _rootScopeCompletionSource.Task;
chat = rootScope.ServiceProvider.GetRequiredService<Chat>();
}
catch (Exception e)
{
2023-03-30 20:01:43 +00:00
_logger.LogError(e, "Could not wait for command root scope");
return;
}
2023-02-24 10:18:03 +00:00
2023-03-30 20:01:43 +00:00
try
{
IPalacePalConfiguration configuration =
rootScope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>();
if (configuration.FirstUse && arguments != "" && arguments != "config")
{
2023-03-30 20:01:43 +00:00
chat.Error(Localization.Error_FirstTimeSetupRequired);
return;
}
2023-03-30 20:01:43 +00:00
Action<string> commandHandler = rootScope.ServiceProvider
.GetRequiredService<IEnumerable<ISubCommand>>()
.SelectMany(cmd => cmd.GetHandlers())
.Where(cmd => cmd.Key == arguments.ToLowerInvariant())
.Select(cmd => cmd.Value)
.SingleOrDefault(missingCommand =>
2023-02-24 10:18:03 +00:00
{
2023-03-30 20:01:43 +00:00
chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, missingCommand,
command));
});
commandHandler.Invoke(arguments);
}
catch (Exception e)
{
_logger.LogError(e, "Could not execute command '{Command}' with arguments '{Arguments}'", command,
arguments);
chat.Error(string.Format(Localization.Error_CommandFailed,
$"{e.GetType()} - {e.Message}"));
}
});
}
2023-03-30 20:01:43 +00:00
private void OpenConfigUi()
=> _rootScope!.ServiceProvider.GetRequiredService<PalConfigCommand>().Execute();
2023-02-08 15:06:43 +00:00
2023-03-30 20:01:43 +00:00
private void LanguageChanged(string languageCode)
{
_logger.LogInformation("Language set to '{Language}'", languageCode);
2023-03-30 20:01:43 +00:00
Localization.Culture = new CultureInfo(languageCode);
_windowSystem!.Windows.OfType<ILanguageChanged>()
.Each(w => w.LanguageChanged());
}
2023-03-30 20:01:43 +00:00
private void Draw()
{
_rootScope!.ServiceProvider.GetRequiredService<RenderAdapter>().DrawLayers();
_windowSystem!.Draw();
}
2023-03-30 20:01:43 +00:00
public void Dispose()
{
_commandManager.RemoveHandler("/pal");
2023-03-30 20:01:43 +00:00
if (_loadState == ELoadState.Loaded)
{
2023-03-30 20:01:43 +00:00
_pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
_pluginInterface.LanguageChanged -= LanguageChanged;
_clientState.Login -= Login;
}
2023-03-30 20:01:43 +00:00
_initCts.Cancel();
_rootScope?.Dispose();
_dependencyInjectionContext?.Dispose();
}
private enum ELoadState
{
Initializing,
Loaded,
Error
}
}