2023-02-16 18:51:54 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-02-15 22:17:19 +00:00
|
|
|
|
using Dalamud.Data;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
using Dalamud.Game;
|
|
|
|
|
using Dalamud.Game.ClientState;
|
|
|
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
|
|
|
using Dalamud.Game.ClientState.Objects;
|
|
|
|
|
using Dalamud.Game.Command;
|
|
|
|
|
using Dalamud.Game.Gui;
|
2023-02-15 22:17:19 +00:00
|
|
|
|
using Dalamud.Interface.Windowing;
|
2023-02-16 18:51:54 +00:00
|
|
|
|
using Dalamud.Logging;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
using Dalamud.Plugin;
|
2023-02-16 18:51:54 +00:00
|
|
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2023-02-16 23:54:23 +00:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2023-02-15 22:17:19 +00:00
|
|
|
|
using Pal.Client.Commands;
|
|
|
|
|
using Pal.Client.Configuration;
|
2023-02-17 15:30:20 +00:00
|
|
|
|
using Pal.Client.Configuration.Legacy;
|
2023-02-16 18:51:54 +00:00
|
|
|
|
using Pal.Client.Database;
|
|
|
|
|
using Pal.Client.DependencyInjection;
|
2023-02-16 23:54:23 +00:00
|
|
|
|
using Pal.Client.DependencyInjection.Logging;
|
2023-02-17 14:51:45 +00:00
|
|
|
|
using Pal.Client.Extensions;
|
2023-02-15 22:17:19 +00:00
|
|
|
|
using Pal.Client.Net;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
using Pal.Client.Properties;
|
2023-02-15 22:17:19 +00:00
|
|
|
|
using Pal.Client.Rendering;
|
|
|
|
|
using Pal.Client.Scheduled;
|
|
|
|
|
using Pal.Client.Windows;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
namespace Pal.Client
|
2023-02-15 13:35:11 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// DI-aware Plugin.
|
|
|
|
|
/// </summary>
|
2023-02-15 22:51:35 +00:00
|
|
|
|
// ReSharper disable once UnusedType.Global
|
|
|
|
|
internal sealed class DependencyInjectionContext : IDalamudPlugin
|
2023-02-15 13:35:11 +00:00
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
public static DalamudLoggerProvider LoggerProvider { get; } = new();
|
|
|
|
|
|
2023-02-17 15:30:20 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialized as temporary logger, will be overriden once context is ready with a logger that supports scopes.
|
|
|
|
|
/// </summary>
|
2023-02-16 23:54:23 +00:00
|
|
|
|
private readonly ILogger _logger = LoggerProvider.CreateLogger<DependencyInjectionContext>();
|
2023-02-17 15:30:20 +00:00
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
private readonly string _sqliteConnectionString;
|
|
|
|
|
private readonly CancellationTokenSource _initCts = new();
|
2023-02-15 13:35:11 +00:00
|
|
|
|
private ServiceProvider? _serviceProvider;
|
2023-02-17 18:12:44 +00:00
|
|
|
|
private Plugin? _plugin;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
|
|
|
|
|
public string Name => Localization.Palace_Pal;
|
|
|
|
|
|
2023-02-15 22:51:35 +00:00
|
|
|
|
public DependencyInjectionContext(DalamudPluginInterface pluginInterface,
|
2023-02-15 13:35:11 +00:00
|
|
|
|
ClientState clientState,
|
|
|
|
|
GameGui gameGui,
|
|
|
|
|
ChatGui chatGui,
|
|
|
|
|
ObjectTable objectTable,
|
|
|
|
|
Framework framework,
|
|
|
|
|
Condition condition,
|
|
|
|
|
CommandManager commandManager,
|
|
|
|
|
DataManager dataManager)
|
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogInformation("Building service container");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
2023-02-17 15:30:20 +00:00
|
|
|
|
// set up legacy services
|
|
|
|
|
#pragma warning disable CS0612
|
|
|
|
|
JsonFloorState.SetContextProperties(pluginInterface.GetPluginConfigDirectory());
|
|
|
|
|
#pragma warning restore CS0612
|
|
|
|
|
|
|
|
|
|
// set up logging
|
2023-02-16 18:51:54 +00:00
|
|
|
|
CancellationToken token = _initCts.Token;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
IServiceCollection services = new ServiceCollection();
|
2023-02-16 23:54:23 +00:00
|
|
|
|
services.AddLogging(builder =>
|
2023-02-17 12:57:12 +00:00
|
|
|
|
builder.AddFilter("Pal", LogLevel.Trace)
|
|
|
|
|
.AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning)
|
2023-02-16 23:54:23 +00:00
|
|
|
|
.AddFilter("Grpc", LogLevel.Debug)
|
|
|
|
|
.ClearProviders()
|
|
|
|
|
.AddProvider(LoggerProvider));
|
2023-02-17 15:30:20 +00:00
|
|
|
|
|
2023-02-15 13:35:11 +00:00
|
|
|
|
// dalamud
|
|
|
|
|
services.AddSingleton<IDalamudPlugin>(this);
|
|
|
|
|
services.AddSingleton(pluginInterface);
|
2023-02-15 22:17:19 +00:00
|
|
|
|
services.AddSingleton(clientState);
|
2023-02-15 13:35:11 +00:00
|
|
|
|
services.AddSingleton(gameGui);
|
|
|
|
|
services.AddSingleton(chatGui);
|
2023-02-17 14:51:45 +00:00
|
|
|
|
services.AddSingleton<Chat>();
|
2023-02-15 13:35:11 +00:00
|
|
|
|
services.AddSingleton(objectTable);
|
|
|
|
|
services.AddSingleton(framework);
|
|
|
|
|
services.AddSingleton(condition);
|
|
|
|
|
services.AddSingleton(commandManager);
|
|
|
|
|
services.AddSingleton(dataManager);
|
2023-02-15 22:51:35 +00:00
|
|
|
|
services.AddSingleton(new WindowSystem(typeof(DependencyInjectionContext).AssemblyQualifiedName));
|
2023-02-15 13:35:11 +00:00
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
// EF core
|
|
|
|
|
_sqliteConnectionString =
|
|
|
|
|
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
|
|
|
|
services.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
2023-02-17 17:36:22 +00:00
|
|
|
|
services.AddTransient<JsonMigration>();
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
2023-02-15 22:17:19 +00:00
|
|
|
|
// plugin-specific
|
2023-02-15 13:35:11 +00:00
|
|
|
|
services.AddSingleton<Plugin>();
|
2023-02-15 22:17:19 +00:00
|
|
|
|
services.AddSingleton<DebugState>();
|
|
|
|
|
services.AddSingleton<Hooks>();
|
|
|
|
|
services.AddSingleton<RemoteApi>();
|
|
|
|
|
services.AddSingleton<ConfigurationManager>();
|
|
|
|
|
services.AddSingleton<IPalacePalConfiguration>(sp => sp.GetRequiredService<ConfigurationManager>().Load());
|
|
|
|
|
services.AddTransient<RepoVerification>();
|
|
|
|
|
services.AddSingleton<PalCommand>();
|
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
// territory & marker related services
|
2023-02-15 22:17:19 +00:00
|
|
|
|
services.AddSingleton<TerritoryState>();
|
|
|
|
|
services.AddSingleton<FrameworkService>();
|
|
|
|
|
services.AddSingleton<ChatService>();
|
|
|
|
|
services.AddSingleton<FloorService>();
|
2023-02-16 18:51:54 +00:00
|
|
|
|
services.AddSingleton<ImportService>();
|
2023-02-15 22:17:19 +00:00
|
|
|
|
|
|
|
|
|
// windows & related services
|
|
|
|
|
services.AddSingleton<AgreementWindow>();
|
|
|
|
|
services.AddSingleton<ConfigWindow>();
|
|
|
|
|
services.AddTransient<StatisticsService>();
|
|
|
|
|
services.AddSingleton<StatisticsWindow>();
|
|
|
|
|
|
|
|
|
|
// these should maybe be scoped
|
2023-02-17 18:12:44 +00:00
|
|
|
|
services.AddScoped<SimpleRenderer>();
|
|
|
|
|
services.AddScoped<SplatoonRenderer>();
|
2023-02-15 22:17:19 +00:00
|
|
|
|
services.AddSingleton<RenderAdapter>();
|
|
|
|
|
|
2023-02-16 09:25:33 +00:00
|
|
|
|
// queue handling
|
|
|
|
|
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedImport>, QueuedImport.Handler>();
|
|
|
|
|
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedUndoImport>, QueuedUndoImport.Handler>();
|
|
|
|
|
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>, QueuedConfigUpdate.Handler>();
|
|
|
|
|
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedSyncResponse>, QueuedSyncResponse.Handler>();
|
|
|
|
|
|
2023-02-15 22:17:19 +00:00
|
|
|
|
// set up the current UI language before creating anything
|
|
|
|
|
Localization.Culture = new CultureInfo(pluginInterface.UiLanguage);
|
2023-02-15 13:35:11 +00:00
|
|
|
|
|
|
|
|
|
// build
|
|
|
|
|
_serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions
|
|
|
|
|
{
|
|
|
|
|
ValidateOnBuild = true,
|
|
|
|
|
ValidateScopes = true,
|
|
|
|
|
});
|
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
2023-02-15 22:17:19 +00:00
|
|
|
|
#if RELEASE
|
|
|
|
|
// You're welcome to remove this code in your fork, but please make sure that:
|
|
|
|
|
// - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and
|
|
|
|
|
// - you host your own server instance
|
|
|
|
|
//
|
|
|
|
|
// This is mainly to avoid this plugin being included in 'mega-repos' that, for whatever reason, decide
|
|
|
|
|
// that collecting all plugins is a good idea (and break half in the process).
|
|
|
|
|
_serviceProvider.GetService<RepoVerification>();
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
// This is not ideal as far as loading the plugin goes, because there's no way to check for errors and
|
|
|
|
|
// tell Dalamud that no, the plugin isn't ready -- so the plugin will count as properly initialized,
|
|
|
|
|
// even if it's not.
|
|
|
|
|
//
|
|
|
|
|
// There's 2-3 seconds of slowdown primarily caused by the sqlite init, but that needs to happen for
|
|
|
|
|
// config stuff.
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger = _serviceProvider.GetRequiredService<ILogger<DependencyInjectionContext>>();
|
|
|
|
|
_logger.LogInformation("Service container built, triggering async init");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
using IDisposable? logScope = _logger.BeginScope("AsyncInit");
|
|
|
|
|
|
2023-02-17 14:51:45 +00:00
|
|
|
|
Chat? chat = null;
|
2023-02-16 18:51:54 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogInformation("Starting async init");
|
2023-02-17 14:51:45 +00:00
|
|
|
|
chat = _serviceProvider.GetService<Chat>();
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
|
|
|
|
// initialize database
|
|
|
|
|
await using (var scope = _serviceProvider.CreateAsyncScope())
|
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogInformation("Loading database & running migrations");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
2023-02-17 17:36:22 +00:00
|
|
|
|
await dbContext.Database.MigrateAsync(token);
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogInformation("Completed database migrations");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
|
|
2023-02-17 17:36:22 +00:00
|
|
|
|
// v1 migration: config migration for import history, json migration for markers
|
|
|
|
|
_serviceProvider.GetRequiredService<ConfigurationManager>().Migrate();
|
|
|
|
|
await _serviceProvider.GetRequiredService<JsonMigration>().MigrateAsync(token);
|
|
|
|
|
|
|
|
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
|
|
2023-02-16 18:51:54 +00:00
|
|
|
|
// windows that have logic to open on startup
|
|
|
|
|
_serviceProvider.GetRequiredService<AgreementWindow>();
|
|
|
|
|
|
|
|
|
|
// initialize components that are mostly self-contained/self-registered
|
|
|
|
|
_serviceProvider.GetRequiredService<Hooks>();
|
|
|
|
|
_serviceProvider.GetRequiredService<PalCommand>();
|
|
|
|
|
_serviceProvider.GetRequiredService<FrameworkService>();
|
|
|
|
|
_serviceProvider.GetRequiredService<ChatService>();
|
|
|
|
|
|
|
|
|
|
token.ThrowIfCancellationRequested();
|
2023-02-17 18:12:44 +00:00
|
|
|
|
_plugin = new Plugin(pluginInterface, _serviceProvider);
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogInformation("Async init complete");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
}
|
2023-02-16 21:09:29 +00:00
|
|
|
|
catch (ObjectDisposedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
2023-02-17 17:36:55 +00:00
|
|
|
|
catch (TaskCanceledException e)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(e, "Task cancelled");
|
|
|
|
|
chat?.Error("Plugin was unloaded before it finished loading.");
|
|
|
|
|
}
|
2023-02-16 18:51:54 +00:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2023-02-16 23:54:23 +00:00
|
|
|
|
_logger.LogError(e, "Async load failed");
|
2023-02-17 14:51:45 +00:00
|
|
|
|
chat?.Error($"Async loading failed: {e.GetType()}: {e.Message}");
|
2023-02-16 18:51:54 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
2023-02-15 13:35:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2023-02-16 18:51:54 +00:00
|
|
|
|
_initCts.Cancel();
|
|
|
|
|
|
2023-02-15 13:35:11 +00:00
|
|
|
|
// ensure we're not calling dispose recursively on ourselves
|
|
|
|
|
if (_serviceProvider != null)
|
|
|
|
|
{
|
2023-02-17 18:12:44 +00:00
|
|
|
|
_logger.LogInformation("Disposing DI Context");
|
|
|
|
|
|
2023-02-15 13:35:11 +00:00
|
|
|
|
ServiceProvider serviceProvider = _serviceProvider;
|
|
|
|
|
_serviceProvider = null;
|
|
|
|
|
|
2023-02-17 18:12:44 +00:00
|
|
|
|
_plugin?.Dispose();
|
|
|
|
|
_plugin = null;
|
2023-02-15 13:35:11 +00:00
|
|
|
|
serviceProvider.Dispose();
|
2023-02-16 18:51:54 +00:00
|
|
|
|
|
|
|
|
|
// ensure we're not keeping the file open longer than the plugin is loaded
|
|
|
|
|
using (SqliteConnection sqliteConnection = new(_sqliteConnectionString))
|
|
|
|
|
SqliteConnection.ClearPool(sqliteConnection);
|
2023-02-15 13:35:11 +00:00
|
|
|
|
}
|
2023-02-17 18:12:44 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_logger.LogDebug("DI context is already disposed");
|
|
|
|
|
}
|
2023-02-15 13:35:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|