Migrate from LiteDB to SQLite

master v3.0
Liza 2024-05-25 00:12:46 +02:00
parent 3a72715671
commit 6763b47509
Signed by: liza
GPG Key ID: 7199F8D727D55F67
29 changed files with 2788 additions and 483 deletions

1017
RetainerTrack/.editorconfig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using RetainerTrack.Handlers;
namespace RetainerTrack.Commands;
internal sealed class WhoCommand : IDisposable
{
private readonly PersistenceContext _persistenceContext;
private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui;
private readonly IClientState _clientState;
private readonly Dictionary<string, uint> _worlds;
public WhoCommand(PersistenceContext persistenceContext, ICommandManager commandManager, IChatGui chatGui,
IClientState clientState, IDataManager dataManager)
{
_persistenceContext = persistenceContext;
_commandManager = commandManager;
_chatGui = chatGui;
_clientState = clientState;
_worlds = dataManager.GetExcelSheet<World>()!.Where(x => x.IsPublic)
.ToDictionary(x => x.Name.ToString().ToUpperInvariant(), x => x.RowId);
_commandManager.AddHandler("/rwho", new CommandInfo(ProcessCommand)
{
HelpMessage =
"/rwho Character Name@World → Shows all retainers for the character (will use your current world if no world is specified)"
});
}
private void ProcessCommand(string command, string arguments)
{
string[] nameParts = arguments.Split(' ');
if (nameParts.Length != 2)
{
_chatGui.Print($"USAGE: /{command} Character Name@World");
}
else if (nameParts[1].Contains('@', StringComparison.Ordinal))
{
string[] lastNameParts = nameParts[1].Split('@');
if (_worlds.TryGetValue(lastNameParts[1].ToUpperInvariant(), out uint worldId))
ProcessLookup($"{nameParts[0]} {lastNameParts[0]}", worldId);
else
_chatGui.PrintError($"Unknown world: {lastNameParts[1]}");
}
else
ProcessLookup(arguments, _clientState?.LocalPlayer?.CurrentWorld?.Id ?? 0);
}
private void ProcessLookup(string name, uint world)
{
if (world == 0)
return;
_chatGui.Print($"Retainer names for {name}: ");
var retainers = _persistenceContext.GetRetainerNamesForCharacter(name, world);
foreach (var retainerName in retainers)
_chatGui.Print($" - {retainerName}");
if (retainers.Count == 0)
_chatGui.Print(" (No retainers found)");
}
public void Dispose()
{
_commandManager.RemoveHandler("/rwho");
}
}

View File

@ -0,0 +1,60 @@
// <auto-generated />
using System;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
#pragma warning disable 219, 612, 618
#nullable disable
namespace RetainerTrack.Database.Compiled
{
internal partial class PlayerEntityType
{
public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
{
var runtimeEntityType = model.AddEntityType(
"RetainerTrack.Database.Player",
typeof(Player),
baseEntityType);
var localContentId = runtimeEntityType.AddProperty(
"LocalContentId",
typeof(ulong),
propertyInfo: typeof(Player).GetProperty("LocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Player).GetField("<LocalContentId>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
valueGenerated: ValueGenerated.OnAdd,
afterSaveBehavior: PropertySaveBehavior.Throw,
sentinel: 0ul);
localContentId.TypeMapping = SqliteULongTypeMapping.Default;
var name = runtimeEntityType.AddProperty(
"Name",
typeof(string),
propertyInfo: typeof(Player).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Player).GetField("<Name>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
maxLength: 20);
name.TypeMapping = SqliteStringTypeMapping.Default;
var key = runtimeEntityType.AddKey(
new[] { localContentId });
runtimeEntityType.SetPrimaryKey(key);
return runtimeEntityType;
}
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
{
runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
runtimeEntityType.AddAnnotation("Relational:Schema", null);
runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
runtimeEntityType.AddAnnotation("Relational:TableName", "Players");
runtimeEntityType.AddAnnotation("Relational:ViewName", null);
runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);
Customize(runtimeEntityType);
}
static partial void Customize(RuntimeEntityType runtimeEntityType);
}
}

View File

@ -0,0 +1,92 @@
// <auto-generated />
using System;
using System.Reflection;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage;
#pragma warning disable 219, 612, 618
#nullable disable
namespace RetainerTrack.Database.Compiled
{
internal partial class RetainerEntityType
{
public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
{
var runtimeEntityType = model.AddEntityType(
"RetainerTrack.Database.Retainer",
typeof(Retainer),
baseEntityType);
var localContentId = runtimeEntityType.AddProperty(
"LocalContentId",
typeof(ulong),
propertyInfo: typeof(Retainer).GetProperty("LocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Retainer).GetField("<LocalContentId>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
valueGenerated: ValueGenerated.OnAdd,
afterSaveBehavior: PropertySaveBehavior.Throw,
sentinel: 0ul);
localContentId.TypeMapping = SqliteULongTypeMapping.Default;
var name = runtimeEntityType.AddProperty(
"Name",
typeof(string),
propertyInfo: typeof(Retainer).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Retainer).GetField("<Name>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
maxLength: 24);
name.TypeMapping = SqliteStringTypeMapping.Default;
var ownerLocalContentId = runtimeEntityType.AddProperty(
"OwnerLocalContentId",
typeof(ulong),
propertyInfo: typeof(Retainer).GetProperty("OwnerLocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Retainer).GetField("<OwnerLocalContentId>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
sentinel: 0ul);
ownerLocalContentId.TypeMapping = SqliteULongTypeMapping.Default;
var worldId = runtimeEntityType.AddProperty(
"WorldId",
typeof(ushort),
propertyInfo: typeof(Retainer).GetProperty("WorldId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(Retainer).GetField("<WorldId>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
sentinel: (ushort)0);
worldId.TypeMapping = UShortTypeMapping.Default.Clone(
comparer: new ValueComparer<ushort>(
(ushort v1, ushort v2) => v1 == v2,
(ushort v) => (int)v,
(ushort v) => v),
keyComparer: new ValueComparer<ushort>(
(ushort v1, ushort v2) => v1 == v2,
(ushort v) => (int)v,
(ushort v) => v),
providerValueComparer: new ValueComparer<ushort>(
(ushort v1, ushort v2) => v1 == v2,
(ushort v) => (int)v,
(ushort v) => v),
mappingInfo: new RelationalTypeMappingInfo(
storeTypeName: "INTEGER"));
var key = runtimeEntityType.AddKey(
new[] { localContentId });
runtimeEntityType.SetPrimaryKey(key);
return runtimeEntityType;
}
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
{
runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
runtimeEntityType.AddAnnotation("Relational:Schema", null);
runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
runtimeEntityType.AddAnnotation("Relational:TableName", "Retainers");
runtimeEntityType.AddAnnotation("Relational:ViewName", null);
runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);
Customize(runtimeEntityType);
}
static partial void Customize(RuntimeEntityType runtimeEntityType);
}
}

View File

@ -0,0 +1,47 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
#pragma warning disable 219, 612, 618
#nullable disable
namespace RetainerTrack.Database.Compiled
{
[DbContext(typeof(RetainerTrackContext))]
public partial class RetainerTrackContextModel : RuntimeModel
{
private static readonly bool _useOldBehavior31751 =
System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751;
static RetainerTrackContextModel()
{
var model = new RetainerTrackContextModel();
if (_useOldBehavior31751)
{
model.Initialize();
}
else
{
var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024);
thread.Start();
thread.Join();
void RunInitialization()
{
model.Initialize();
}
}
model.Customize();
_instance = model;
}
private static RetainerTrackContextModel _instance;
public static IModel Instance => _instance;
partial void Initialize();
partial void Customize();
}
}

View File

@ -0,0 +1,122 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
#pragma warning disable 219, 612, 618
#nullable disable
namespace RetainerTrack.Database.Compiled
{
public partial class RetainerTrackContextModel
{
partial void Initialize()
{
var player = PlayerEntityType.Create(this);
var retainer = RetainerEntityType.Create(this);
PlayerEntityType.CreateAnnotations(player);
RetainerEntityType.CreateAnnotations(retainer);
AddAnnotation("ProductVersion", "8.0.5");
AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
}
private IRelationalModel CreateRelationalModel()
{
var relationalModel = new RelationalModel(this);
var player = FindEntityType("RetainerTrack.Database.Player")!;
var defaultTableMappings = new List<TableMappingBase<ColumnMappingBase>>();
player.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings);
var retainerTrackDatabasePlayerTableBase = new TableBase("RetainerTrack.Database.Player", null, relationalModel);
var localContentIdColumnBase = new ColumnBase<ColumnMappingBase>("LocalContentId", "INTEGER", retainerTrackDatabasePlayerTableBase);
retainerTrackDatabasePlayerTableBase.Columns.Add("LocalContentId", localContentIdColumnBase);
var nameColumnBase = new ColumnBase<ColumnMappingBase>("Name", "TEXT", retainerTrackDatabasePlayerTableBase);
retainerTrackDatabasePlayerTableBase.Columns.Add("Name", nameColumnBase);
relationalModel.DefaultTables.Add("RetainerTrack.Database.Player", retainerTrackDatabasePlayerTableBase);
var retainerTrackDatabasePlayerMappingBase = new TableMappingBase<ColumnMappingBase>(player, retainerTrackDatabasePlayerTableBase, true);
retainerTrackDatabasePlayerTableBase.AddTypeMapping(retainerTrackDatabasePlayerMappingBase, false);
defaultTableMappings.Add(retainerTrackDatabasePlayerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)localContentIdColumnBase, player.FindProperty("LocalContentId")!, retainerTrackDatabasePlayerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)nameColumnBase, player.FindProperty("Name")!, retainerTrackDatabasePlayerMappingBase);
var tableMappings = new List<TableMapping>();
player.SetRuntimeAnnotation("Relational:TableMappings", tableMappings);
var playersTable = new Table("Players", null, relationalModel);
var localContentIdColumn = new Column("LocalContentId", "INTEGER", playersTable);
playersTable.Columns.Add("LocalContentId", localContentIdColumn);
var nameColumn = new Column("Name", "TEXT", playersTable);
playersTable.Columns.Add("Name", nameColumn);
var pK_Players = new UniqueConstraint("PK_Players", playersTable, new[] { localContentIdColumn });
playersTable.PrimaryKey = pK_Players;
var pK_PlayersUc = RelationalModel.GetKey(this,
"RetainerTrack.Database.Player",
new[] { "LocalContentId" });
pK_Players.MappedKeys.Add(pK_PlayersUc);
RelationalModel.GetOrCreateUniqueConstraints(pK_PlayersUc).Add(pK_Players);
playersTable.UniqueConstraints.Add("PK_Players", pK_Players);
relationalModel.Tables.Add(("Players", null), playersTable);
var playersTableMapping = new TableMapping(player, playersTable, true);
playersTable.AddTypeMapping(playersTableMapping, false);
tableMappings.Add(playersTableMapping);
RelationalModel.CreateColumnMapping(localContentIdColumn, player.FindProperty("LocalContentId")!, playersTableMapping);
RelationalModel.CreateColumnMapping(nameColumn, player.FindProperty("Name")!, playersTableMapping);
var retainer = FindEntityType("RetainerTrack.Database.Retainer")!;
var defaultTableMappings0 = new List<TableMappingBase<ColumnMappingBase>>();
retainer.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings0);
var retainerTrackDatabaseRetainerTableBase = new TableBase("RetainerTrack.Database.Retainer", null, relationalModel);
var localContentIdColumnBase0 = new ColumnBase<ColumnMappingBase>("LocalContentId", "INTEGER", retainerTrackDatabaseRetainerTableBase);
retainerTrackDatabaseRetainerTableBase.Columns.Add("LocalContentId", localContentIdColumnBase0);
var nameColumnBase0 = new ColumnBase<ColumnMappingBase>("Name", "TEXT", retainerTrackDatabaseRetainerTableBase);
retainerTrackDatabaseRetainerTableBase.Columns.Add("Name", nameColumnBase0);
var ownerLocalContentIdColumnBase = new ColumnBase<ColumnMappingBase>("OwnerLocalContentId", "INTEGER", retainerTrackDatabaseRetainerTableBase);
retainerTrackDatabaseRetainerTableBase.Columns.Add("OwnerLocalContentId", ownerLocalContentIdColumnBase);
var worldIdColumnBase = new ColumnBase<ColumnMappingBase>("WorldId", "INTEGER", retainerTrackDatabaseRetainerTableBase);
retainerTrackDatabaseRetainerTableBase.Columns.Add("WorldId", worldIdColumnBase);
relationalModel.DefaultTables.Add("RetainerTrack.Database.Retainer", retainerTrackDatabaseRetainerTableBase);
var retainerTrackDatabaseRetainerMappingBase = new TableMappingBase<ColumnMappingBase>(retainer, retainerTrackDatabaseRetainerTableBase, true);
retainerTrackDatabaseRetainerTableBase.AddTypeMapping(retainerTrackDatabaseRetainerMappingBase, false);
defaultTableMappings0.Add(retainerTrackDatabaseRetainerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)localContentIdColumnBase0, retainer.FindProperty("LocalContentId")!, retainerTrackDatabaseRetainerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)nameColumnBase0, retainer.FindProperty("Name")!, retainerTrackDatabaseRetainerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)ownerLocalContentIdColumnBase, retainer.FindProperty("OwnerLocalContentId")!, retainerTrackDatabaseRetainerMappingBase);
RelationalModel.CreateColumnMapping((ColumnBase<ColumnMappingBase>)worldIdColumnBase, retainer.FindProperty("WorldId")!, retainerTrackDatabaseRetainerMappingBase);
var tableMappings0 = new List<TableMapping>();
retainer.SetRuntimeAnnotation("Relational:TableMappings", tableMappings0);
var retainersTable = new Table("Retainers", null, relationalModel);
var localContentIdColumn0 = new Column("LocalContentId", "INTEGER", retainersTable);
retainersTable.Columns.Add("LocalContentId", localContentIdColumn0);
var nameColumn0 = new Column("Name", "TEXT", retainersTable);
retainersTable.Columns.Add("Name", nameColumn0);
var ownerLocalContentIdColumn = new Column("OwnerLocalContentId", "INTEGER", retainersTable);
retainersTable.Columns.Add("OwnerLocalContentId", ownerLocalContentIdColumn);
var worldIdColumn = new Column("WorldId", "INTEGER", retainersTable);
retainersTable.Columns.Add("WorldId", worldIdColumn);
var pK_Retainers = new UniqueConstraint("PK_Retainers", retainersTable, new[] { localContentIdColumn0 });
retainersTable.PrimaryKey = pK_Retainers;
var pK_RetainersUc = RelationalModel.GetKey(this,
"RetainerTrack.Database.Retainer",
new[] { "LocalContentId" });
pK_Retainers.MappedKeys.Add(pK_RetainersUc);
RelationalModel.GetOrCreateUniqueConstraints(pK_RetainersUc).Add(pK_Retainers);
retainersTable.UniqueConstraints.Add("PK_Retainers", pK_Retainers);
relationalModel.Tables.Add(("Retainers", null), retainersTable);
var retainersTableMapping = new TableMapping(retainer, retainersTable, true);
retainersTable.AddTypeMapping(retainersTableMapping, false);
tableMappings0.Add(retainersTableMapping);
RelationalModel.CreateColumnMapping(localContentIdColumn0, retainer.FindProperty("LocalContentId")!, retainersTableMapping);
RelationalModel.CreateColumnMapping(nameColumn0, retainer.FindProperty("Name")!, retainersTableMapping);
RelationalModel.CreateColumnMapping(ownerLocalContentIdColumn, retainer.FindProperty("OwnerLocalContentId")!, retainersTableMapping);
RelationalModel.CreateColumnMapping(worldIdColumn, retainer.FindProperty("WorldId")!, retainersTableMapping);
return relationalModel.MakeReadOnly();
}
}
}

View File

@ -0,0 +1,3 @@
[*.cs]
# CA1062: Validate arguments of public methods
dotnet_diagnostic.CA1062.severity = none

View File

@ -0,0 +1,62 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RetainerTrack.Database;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
[DbContext(typeof(RetainerTrackContext))]
[Migration("20240524200204_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("RetainerTrack.Database.Player", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.HasKey("LocalContentId");
b.ToTable("Players");
});
modelBuilder.Entity("RetainerTrack.Database.Retainer", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24)
.HasColumnType("TEXT");
b.Property<ulong>("OwnerLocalContentId")
.HasColumnType("INTEGER");
b.Property<ushort>("WorldId")
.HasColumnType("INTEGER");
b.HasKey("LocalContentId");
b.ToTable("Retainers");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Players",
columns: table => new
{
LocalContentId = table.Column<ulong>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Players", x => x.LocalContentId);
});
migrationBuilder.CreateTable(
name: "Retainers",
columns: table => new
{
LocalContentId = table.Column<ulong>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 24, nullable: false),
WorldId = table.Column<ushort>(type: "INTEGER", nullable: false),
OwnerLocalContentId = table.Column<ulong>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Retainers", x => x.LocalContentId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Players");
migrationBuilder.DropTable(
name: "Retainers");
}
}
}

View File

@ -0,0 +1,62 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RetainerTrack.Database;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
[DbContext(typeof(RetainerTrackContext))]
[Migration("20240524201345_ImportLegacyData")]
partial class ImportLegacyData
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("RetainerTrack.Database.Player", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.HasKey("LocalContentId");
b.ToTable("Players");
});
modelBuilder.Entity("RetainerTrack.Database.Retainer", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24)
.HasColumnType("TEXT");
b.Property<ulong>("OwnerLocalContentId")
.HasColumnType("INTEGER");
b.Property<ushort>("WorldId")
.HasColumnType("INTEGER");
b.HasKey("LocalContentId");
b.ToTable("Retainers");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using LiteDB;
using Microsoft.EntityFrameworkCore.Migrations;
using RetainerTrack.LegacyDb;
#nullable disable
#pragma warning disable 0612
namespace RetainerTrack.Database.Migrations
{
/// <inheritdoc />
public partial class ImportLegacyData : Migration
{
public static DalamudPluginInterface PluginInterface { get; set; }
private static readonly string[] PlayerColumns = new[] { "LocalContentId", "Name" };
private static readonly string[] RetainerColumns = new []{ "LocalContentId", "Name", "WorldId", "OwnerLocalContentId" };
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (PluginInterface == null)
return;
string legacyDatabaseFileName = Path.Join(PluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb");
if (!File.Exists(legacyDatabaseFileName))
return;
using var liteDatabase = new LiteDatabase(new ConnectionString
{
Filename = Path.Join(PluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"),
Connection = ConnectionType.Direct,
Upgrade = true,
},
new BsonMapper
{
ResolveCollectionName = (type) =>
{
if (type == typeof(LegacyPlayer))
return LegacyPlayer.CollectionName;
if (type == typeof(LegacyRetainer))
return LegacyRetainer.CollectionName;
throw new ArgumentOutOfRangeException(nameof(type));
}
});
liteDatabase.GetCollection<LegacyRetainer>()
.EnsureIndex(x => x.Id);
liteDatabase.GetCollection<LegacyPlayer>()
.EnsureIndex(x => x.Id);
List<LegacyPlayer> allPlayers = liteDatabase.GetCollection<LegacyPlayer>().FindAll().ToList();
object[,] playersToInsert = To2DArray(
allPlayers.Select(player => new object[] { player.Id, player.Name }).ToList(),
PlayerColumns.Length);
migrationBuilder.InsertData(
table:"Players",
columns: PlayerColumns,
values: playersToInsert);
List<LegacyRetainer> allRetainers = liteDatabase.GetCollection<LegacyRetainer>().FindAll().ToList();
object[,] retainersToInsert = To2DArray(
allRetainers.Select(retainer => new object[] { retainer.Id, retainer.Name, retainer.WorldId, retainer.OwnerContentId }).ToList(),
RetainerColumns.Length);
migrationBuilder.InsertData(
table: "Retainers",
columns: RetainerColumns, values: retainersToInsert);
}
[SuppressMessage("Performance", "CA1814")]
private static object[,] To2DArray(IReadOnlyList<object[]> data, int columnCount)
{
object[,] result = new object[data.Count, columnCount];
for (int i = 0; i < data.Count; i++)
{
for (int j = 0; j < columnCount; j++)
{
result[i, j] = data[i][j];
}
}
return result;
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM Players");
migrationBuilder.Sql("DELETE FROM Retainers");
}
}
}
#pragma warning restore 0612

View File

@ -0,0 +1,62 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RetainerTrack.Database;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
[DbContext(typeof(RetainerTrackContext))]
[Migration("20240524214606_CleanupBrokenPlayerIds")]
partial class CleanupBrokenPlayerIds
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("RetainerTrack.Database.Player", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.HasKey("LocalContentId");
b.ToTable("Players");
});
modelBuilder.Entity("RetainerTrack.Database.Retainer", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24)
.HasColumnType("TEXT");
b.Property<ulong>("OwnerLocalContentId")
.HasColumnType("INTEGER");
b.Property<ushort>("WorldId")
.HasColumnType("INTEGER");
b.HasKey("LocalContentId");
b.ToTable("Retainers");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
/// <inheritdoc />
public partial class CleanupBrokenPlayerIds : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM Players WHERE LocalContentId < 18014398509481984");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -0,0 +1,59 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RetainerTrack.Database;
#nullable disable
namespace RetainerTrack.Database.Migrations
{
[DbContext(typeof(RetainerTrackContext))]
partial class RetainerTrackContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
modelBuilder.Entity("RetainerTrack.Database.Player", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.HasKey("LocalContentId");
b.ToTable("Players");
});
modelBuilder.Entity("RetainerTrack.Database.Retainer", b =>
{
b.Property<ulong>("LocalContentId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24)
.HasColumnType("TEXT");
b.Property<ulong>("OwnerLocalContentId")
.HasColumnType("INTEGER");
b.Property<ushort>("WorldId")
.HasColumnType("INTEGER");
b.HasKey("LocalContentId");
b.ToTable("Retainers");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,8 +1,12 @@
namespace RetainerTrack.Database using System.ComponentModel.DataAnnotations;
namespace RetainerTrack.Database;
public class Player
{ {
internal sealed class Player [Key, Required]
{ public ulong LocalContentId { get; set; }
public ulong Id { get; set; }
[MaxLength(20), Required]
public string? Name { get; set; } public string? Name { get; set; }
} }
}

View File

@ -1,10 +1,18 @@
namespace RetainerTrack.Database using System.ComponentModel.DataAnnotations;
namespace RetainerTrack.Database;
public class Retainer
{ {
internal sealed class Retainer [Key, Required]
{ public ulong LocalContentId { get; set; }
public ulong Id { get; set; }
[MaxLength(24), Required]
public string? Name { get; set; } public string? Name { get; set; }
[Required]
public ushort WorldId { get; set; } public ushort WorldId { get; set; }
public ulong OwnerContentId { get; set; }
} [Required]
public ulong OwnerLocalContentId { get; set; }
} }

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
namespace RetainerTrack.Database;
internal sealed class RetainerTrackContext : DbContext
{
public DbSet<Retainer> Retainers { get; set; }
public DbSet<Player> Players { get; set; }
public RetainerTrackContext(DbContextOptions<RetainerTrackContext> options)
: base(options)
{
}
}

View File

@ -0,0 +1,19 @@
#if EF
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace RetainerTrack.Database;
internal sealed class PalClientContextFactory : IDesignTimeDbContextFactory<RetainerTrackContext>
{
public RetainerTrackContext CreateDbContext(string[] args)
{
var optionsBuilder =
new DbContextOptionsBuilder<RetainerTrackContext>().UseSqlite(
$"Data Source={Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "pluginConfigs", "RetainerTrack", RetainerTrackPlugin.DatabaseFileName)}");
return new RetainerTrackContext(optionsBuilder.Options);
}
}
#endif

View File

@ -1,8 +1,7 @@
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed class ContentIdToName internal sealed class ContentIdToName
{ {
public ulong ContentId { get; init; } public ulong ContentId { get; init; }
public string PlayerName { get; init; } = string.Empty; public string PlayerName { get; init; } = string.Empty;
} }
}

View File

@ -10,8 +10,8 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed unsafe class GameHooks : IDisposable internal sealed unsafe class GameHooks : IDisposable
{ {
private readonly ILogger<GameHooks> _logger; private readonly ILogger<GameHooks> _logger;
@ -159,4 +159,3 @@ namespace RetainerTrack.Handlers
[FieldOffset(0x31)] public fixed byte CharacterName[32]; [FieldOffset(0x31)] public fixed byte CharacterName[32];
} }
} }
}

View File

@ -3,12 +3,11 @@ using System.Threading.Tasks;
using Dalamud.Game.Network.Structures; using Dalamud.Game.Network.Structures;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Info; using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed class MarketBoardOfferingsHandler : IDisposable internal sealed class MarketBoardOfferingsHandler : IDisposable
{ {
private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData); private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData);
@ -21,7 +20,6 @@ namespace RetainerTrack.Handlers
public unsafe MarketBoardOfferingsHandler( public unsafe MarketBoardOfferingsHandler(
ILogger<MarketBoardOfferingsHandler> logger, ILogger<MarketBoardOfferingsHandler> logger,
IClientState clientState, IClientState clientState,
IGameGui gameGui,
IGameInteropProvider gameInteropProvider, IGameInteropProvider gameInteropProvider,
PersistenceContext persistenceContext) PersistenceContext persistenceContext)
{ {
@ -30,11 +28,8 @@ namespace RetainerTrack.Handlers
_persistenceContext = persistenceContext; _persistenceContext = persistenceContext;
_logger.LogDebug("Setting up offerings hook"); _logger.LogDebug("Setting up offerings hook");
var uiModule = (UIModule*)gameGui.GetUIModule();
var infoModule = uiModule->GetInfoModule();
var proxy = infoModule->GetInfoProxyById(11);
_marketBoardOfferingsHook = _marketBoardOfferingsHook =
gameInteropProvider.HookFromAddress<MarketBoardOfferings>((nint)proxy->vtbl[12], gameInteropProvider.HookFromSignature<MarketBoardOfferings>("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54",
MarketBoardOfferingsDetour); MarketBoardOfferingsDetour);
_marketBoardOfferingsHook.Enable(); _marketBoardOfferingsHook.Enable();
_logger.LogDebug("Offerings hook enabled successfully"); _logger.LogDebug("Offerings hook enabled successfully");
@ -76,4 +71,3 @@ namespace RetainerTrack.Handlers
Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId)); Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId));
} }
} }
}

View File

@ -1,14 +1,13 @@
using System; using System;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed unsafe class MarketBoardUiHandler : IDisposable internal sealed unsafe class MarketBoardUiHandler : IDisposable
{ {
private const string AddonName = "ItemSearchResult"; private const string AddonName = "ItemSearchResult";
@ -58,7 +57,7 @@ namespace RetainerTrack.Handlers
var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5]; var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5];
string retainerName = retainerNameNode->NodeText.ToString(); string retainerName = retainerNameNode->NodeText.ToString();
if (!retainerName.Contains('(')) if (!retainerName.Contains('(', StringComparison.Ordinal))
{ {
string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName); string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName);
if (!string.IsNullOrEmpty(playerName)) if (!string.IsNullOrEmpty(playerName))
@ -77,4 +76,3 @@ namespace RetainerTrack.Handlers
_addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw); _addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw);
} }
} }
}

View File

@ -5,15 +5,15 @@ using Dalamud.Memory;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Group; using FFXIVClientStructs.FFXIV.Client.Game.Group;
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed class PartyHandler : IDisposable internal sealed class PartyHandler : IDisposable
{ {
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly PersistenceContext _persistenceContext; private readonly PersistenceContext _persistenceContext;
private long _lastUpdate = 0; private long _lastUpdate;
public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext) public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext)
{ {
@ -45,7 +45,7 @@ namespace RetainerTrack.Handlers
Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings)); Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings));
} }
private unsafe void HandlePartyMember(PartyMember partyMember, List<ContentIdToName> contentIdToNames) private static unsafe void HandlePartyMember(PartyMember partyMember, List<ContentIdToName> contentIdToNames)
{ {
if (partyMember.ContentID == 0) if (partyMember.ContentID == 0)
return; return;
@ -66,4 +66,3 @@ namespace RetainerTrack.Handlers
_framework.Update -= FrameworkUpdate; _framework.Update -= FrameworkUpdate;
} }
} }
}

View File

@ -1,44 +1,49 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using Dalamud.Game.Network.Structures; using Dalamud.Game.Network.Structures;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LiteDB; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using RetainerTrack.Database; using RetainerTrack.Database;
namespace RetainerTrack.Handlers namespace RetainerTrack.Handlers;
{
internal sealed class PersistenceContext internal sealed class PersistenceContext
{ {
private readonly ILogger<PersistenceContext> _logger; private readonly ILogger<PersistenceContext> _logger;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly LiteDatabase _liteDatabase; private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<uint, ConcurrentDictionary<string, ulong>> _worldRetainerCache = new(); private readonly ConcurrentDictionary<uint, ConcurrentDictionary<string, ulong>> _worldRetainerCache = new();
private readonly ConcurrentDictionary<ulong, string> _playerNameCache = new(); private readonly ConcurrentDictionary<ulong, string> _playerNameCache = new();
public PersistenceContext(ILogger<PersistenceContext> logger, IClientState clientState, public PersistenceContext(ILogger<PersistenceContext> logger, IClientState clientState,
LiteDatabase liteDatabase) IServiceProvider serviceProvider)
{ {
_logger = logger; _logger = logger;
_clientState = clientState; _clientState = clientState;
_liteDatabase = liteDatabase; _serviceProvider = serviceProvider;
using (IServiceScope scope = serviceProvider.CreateScope())
{
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
var retainersByWorld = dbContext.Retainers.GroupBy(retainer => retainer.WorldId);
var retainersByWorld = _liteDatabase.GetCollection<Retainer>().FindAll()
.GroupBy(r => r.WorldId);
foreach (var retainers in retainersByWorld) foreach (var retainers in retainersByWorld)
{ {
var world = _worldRetainerCache.GetOrAdd(retainers.Key, _ => new()); var world = _worldRetainerCache.GetOrAdd(retainers.Key, _ => new());
foreach (var retainer in retainers) foreach (var retainer in retainers)
{ {
if (retainer.Name != null) if (retainer.Name != null)
world[retainer.Name] = retainer.OwnerContentId; world[retainer.Name] = retainer.OwnerLocalContentId;
} }
} }
foreach (var player in _liteDatabase.GetCollection<Player>().FindAll()) foreach (var player in dbContext.Players)
_playerNameCache[player.Id] = player.Name ?? string.Empty; _playerNameCache[player.LocalContentId] = player.Name ?? string.Empty;
}
} }
public string GetCharacterNameOnCurrentWorld(string retainerName) public string GetCharacterNameOnCurrentWorld(string retainerName)
@ -54,6 +59,20 @@ namespace RetainerTrack.Handlers
return _playerNameCache.TryGetValue(playerContentId, out string? playerName) ? playerName : string.Empty; return _playerNameCache.TryGetValue(playerContentId, out string? playerName) ? playerName : string.Empty;
} }
public IReadOnlyList<string> GetRetainerNamesForCharacter(string characterName, uint world)
{
using var scope = _serviceProvider.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
return dbContext.Players.Where(p => characterName == p.Name)
.SelectMany(player =>
dbContext.Retainers.Where(x => x.OwnerLocalContentId == player.LocalContentId && x.WorldId == world))
.Select(x => x.Name)
.Where(x => !string.IsNullOrEmpty(x))
.Cast<string>()
.ToList()
.AsReadOnly();
}
public void HandleMarketBoardPage(MarketBoardCurrentOfferings listings, ushort worldId) public void HandleMarketBoardPage(MarketBoardCurrentOfferings listings, ushort worldId)
{ {
try try
@ -65,26 +84,44 @@ namespace RetainerTrack.Handlers
.Select(l => .Select(l =>
new Retainer new Retainer
{ {
Id = l.RetainerId, LocalContentId = l.RetainerId,
Name = l.RetainerName, Name = l.RetainerName,
WorldId = worldId, WorldId = worldId,
OwnerContentId = l.RetainerOwnerId, OwnerLocalContentId = l.RetainerOwnerId,
}) })
.ToList(); .ToList();
_liteDatabase.GetCollection<Retainer>().Upsert(updates);
using var scope = _serviceProvider.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
var ids = updates.Select(x => x.LocalContentId).ToList();
var dbRetainers = dbContext.Retainers.Where(x => ids.Contains(x.LocalContentId))
.ToDictionary(x => x.LocalContentId, x => x);
foreach (var retainer in updates) foreach (var retainer in updates)
{ {
if (!_playerNameCache.TryGetValue(retainer.OwnerContentId, out string? ownerName)) if (dbRetainers.TryGetValue(retainer.LocalContentId, out var dbRetainer))
ownerName = retainer.OwnerContentId.ToString(); {
dbRetainer.Name = retainer.Name;
dbRetainer.WorldId = retainer.WorldId;
dbRetainer.OwnerLocalContentId = retainer.OwnerLocalContentId;
dbContext.Retainers.Update(dbRetainer);
}
else
dbContext.Retainers.Add(retainer);
if (!_playerNameCache.TryGetValue(retainer.OwnerLocalContentId, out string? ownerName))
ownerName = retainer.OwnerLocalContentId.ToString(CultureInfo.InvariantCulture);
_logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name, _logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name,
ownerName); ownerName);
if (retainer.Name != null) if (retainer.Name != null)
{ {
var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new()); var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new());
world[retainer.Name] = retainer.OwnerContentId; world[retainer.Name] = retainer.OwnerLocalContentId;
} }
} }
dbContext.SaveChanges();
} }
catch (Exception e) catch (Exception e)
{ {
@ -111,13 +148,20 @@ namespace RetainerTrack.Handlers
.Select(mapping => .Select(mapping =>
new Player new Player
{ {
Id = mapping.ContentId, LocalContentId = mapping.ContentId,
Name = mapping.PlayerName, Name = mapping.PlayerName,
}) })
.ToList(); .ToList();
_liteDatabase.GetCollection<Player>().Upsert(updates);
using (var scope = _serviceProvider.CreateScope())
{
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
dbContext.Players.AddRange(updates);
}
foreach (var player in updates) foreach (var player in updates)
_playerNameCache[player.Id] = player.Name ?? string.Empty; _playerNameCache[player.LocalContentId] = player.Name ?? string.Empty;
} }
catch (Exception e) catch (Exception e)
{ {
@ -125,4 +169,3 @@ namespace RetainerTrack.Handlers
} }
} }
} }
}

View File

@ -0,0 +1,12 @@
using System;
namespace RetainerTrack.LegacyDb;
[Obsolete]
internal sealed class LegacyPlayer
{
public static string CollectionName => "Player";
public ulong Id { get; set; }
public string? Name { get; set; }
}

View File

@ -0,0 +1,14 @@
using System;
namespace RetainerTrack.LegacyDb;
[Obsolete]
internal sealed class LegacyRetainer
{
public static string CollectionName => "Retainer";
public ulong Id { get; set; }
public string? Name { get; set; }
public ushort WorldId { get; set; }
public ulong OwnerContentId { get; set; }
}

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Version>2.0</Version> <Version>3.0</Version>
<LangVersion>11.0</LangVersion> <LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -25,44 +25,49 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dalamud.Extensions.MicrosoftLogging" Version="2.0.0" /> <PackageReference Include="Dalamud.Extensions.MicrosoftLogging" Version="3.0.0" />
<PackageReference Include="DalamudPackager" Version="2.1.12" /> <PackageReference Include="DalamudPackager" Version="2.1.12" />
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Dalamud"> <Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath> <HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="ImGui.NET"> <Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath> <HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="ImGuiScene"> <Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath> <HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="Lumina"> <Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath> <HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="Lumina.Excel"> <Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath> <HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath> <HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
<Reference Include="FFXIVClientStructs"> <Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath> <HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private> <Private Condition="'$(Configuration)' != 'EF'">false</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin"> <Target Name="RenameLatestZip" AfterTargets="PackagePlugin" Condition="'$(Configuration)' == 'Release'">
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip" /> <Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip" />
</Target> </Target>
</Project> </Project>

View File

@ -1,18 +1,25 @@
using System.IO; using System;
using System.IO;
using Dalamud.Extensions.MicrosoftLogging; using Dalamud.Extensions.MicrosoftLogging;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LiteDB; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using RetainerTrack.Commands;
using RetainerTrack.Database; using RetainerTrack.Database;
using RetainerTrack.Database.Compiled;
using RetainerTrack.Database.Migrations;
using RetainerTrack.Handlers; using RetainerTrack.Handlers;
namespace RetainerTrack namespace RetainerTrack;
{
// ReSharper disable once UnusedType.Global // ReSharper disable once UnusedType.Global
internal sealed class RetainerTrackPlugin : IDalamudPlugin internal sealed class RetainerTrackPlugin : IDalamudPlugin
{ {
public const string DatabaseFileName = "retainertrack.data.sqlite3";
private readonly string _sqliteConnectionString;
private readonly ServiceProvider? _serviceProvider; private readonly ServiceProvider? _serviceProvider;
public RetainerTrackPlugin( public RetainerTrackPlugin(
@ -20,8 +27,11 @@ namespace RetainerTrack
IFramework framework, IFramework framework,
IClientState clientState, IClientState clientState,
IGameGui gameGui, IGameGui gameGui,
IChatGui chatGui,
IGameInteropProvider gameInteropProvider, IGameInteropProvider gameInteropProvider,
IAddonLifecycle addonLifecycle, IAddonLifecycle addonLifecycle,
ICommandManager commandManager,
IDataManager dataManager,
IPluginLog pluginLog) IPluginLog pluginLog)
{ {
ServiceCollection serviceCollection = new(); ServiceCollection serviceCollection = new();
@ -33,40 +43,52 @@ namespace RetainerTrack
serviceCollection.AddSingleton(framework); serviceCollection.AddSingleton(framework);
serviceCollection.AddSingleton(clientState); serviceCollection.AddSingleton(clientState);
serviceCollection.AddSingleton(gameGui); serviceCollection.AddSingleton(gameGui);
serviceCollection.AddSingleton(chatGui);
serviceCollection.AddSingleton(gameInteropProvider); serviceCollection.AddSingleton(gameInteropProvider);
serviceCollection.AddSingleton(addonLifecycle); serviceCollection.AddSingleton(addonLifecycle);
serviceCollection.AddSingleton(commandManager);
serviceCollection.AddSingleton<LiteDatabase>(_ => serviceCollection.AddSingleton(dataManager);
new LiteDatabase(new ConnectionString
{
Filename = Path.Join(pluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"),
Connection = ConnectionType.Direct,
Upgrade = true,
}));
serviceCollection.AddSingleton<PersistenceContext>(); serviceCollection.AddSingleton<PersistenceContext>();
serviceCollection.AddSingleton<MarketBoardOfferingsHandler>(); serviceCollection.AddSingleton<MarketBoardOfferingsHandler>();
serviceCollection.AddSingleton<PartyHandler>(); serviceCollection.AddSingleton<PartyHandler>();
serviceCollection.AddSingleton<MarketBoardUiHandler>(); serviceCollection.AddSingleton<MarketBoardUiHandler>();
serviceCollection.AddSingleton<GameHooks>(); serviceCollection.AddSingleton<GameHooks>();
serviceCollection.AddSingleton<WhoCommand>();
_sqliteConnectionString =
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), DatabaseFileName)}";
serviceCollection.AddDbContext<RetainerTrackContext>(o => o
.UseSqlite(_sqliteConnectionString)
.UseModel(RetainerTrackContextModel.Instance));
_serviceProvider = serviceCollection.BuildServiceProvider(); _serviceProvider = serviceCollection.BuildServiceProvider();
LiteDatabase liteDatabase = _serviceProvider.GetRequiredService<LiteDatabase>();
liteDatabase.GetCollection<Retainer>() RunMigrations(_serviceProvider);
.EnsureIndex(x => x.Id);
liteDatabase.GetCollection<Player>()
.EnsureIndex(x => x.Id);
_serviceProvider.GetRequiredService<PartyHandler>(); _serviceProvider.GetRequiredService<PartyHandler>();
_serviceProvider.GetRequiredService<MarketBoardOfferingsHandler>(); _serviceProvider.GetRequiredService<MarketBoardOfferingsHandler>();
_serviceProvider.GetRequiredService<MarketBoardUiHandler>(); _serviceProvider.GetRequiredService<MarketBoardUiHandler>();
_serviceProvider.GetRequiredService<GameHooks>(); _serviceProvider.GetRequiredService<GameHooks>();
_serviceProvider.GetRequiredService<WhoCommand>();
}
private static void RunMigrations(IServiceProvider serviceProvider)
{
ImportLegacyData.PluginInterface = serviceProvider.GetRequiredService<DalamudPluginInterface>();
using var scope = serviceProvider.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
dbContext.Database.Migrate();
} }
public void Dispose() public void Dispose()
{ {
_serviceProvider?.Dispose(); _serviceProvider?.Dispose();
}
// ensure we're not keeping the file open longer than the plugin is loaded
using (SqliteConnection sqliteConnection = new(_sqliteConnectionString))
SqliteConnection.ClearPool(sqliteConnection);
} }
} }

View File

@ -1,14 +1,14 @@
{ {
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net7.0-windows7.0": { "net8.0-windows7.0": {
"Dalamud.Extensions.MicrosoftLogging": { "Dalamud.Extensions.MicrosoftLogging": {
"type": "Direct", "type": "Direct",
"requested": "[2.0.0, )", "requested": "[3.0.0, )",
"resolved": "2.0.0", "resolved": "3.0.0",
"contentHash": "qp2idn5GuPouUxHHFytMrorbhlcupsgPdO87HjxlBfTY+JID+qoTfPmA5V6HBP1a4DuXGPbk4JtoO/hMmnQrtw==", "contentHash": "jWK3r/cZUXN8H9vHf78gEzeRmMk4YAbCUYzLcTqUAcega8unUiFGwYy+iOjVYJ9urnr9r+hk+vBi1y9wyv+e7Q==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.Logging": "7.0.0" "Microsoft.Extensions.Logging": "8.0.0"
} }
}, },
"DalamudPackager": { "DalamudPackager": {
@ -23,51 +23,395 @@
"resolved": "5.0.17", "resolved": "5.0.17",
"contentHash": "cKPvkdlzIts3ZKu/BzoIc/Y71e4VFKlij4LyioPFATZMot+wB7EAm1FFbZSJez6coJmQUoIg/3yHE1MMU+zOdg==" "contentHash": "cKPvkdlzIts3ZKu/BzoIc/Y71e4VFKlij4LyioPFATZMot+wB7EAm1FFbZSJez6coJmQUoIg/3yHE1MMU+zOdg=="
}, },
"Microsoft.EntityFrameworkCore.Sqlite": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "rBTx2TP+pa+CgXIxWmUbPdO+53WV4Nmq9Njb5Olomh4og/p5qV1jU53wPpqO92gEv+ZR6arwP5Pe11XImYTT+A==",
"dependencies": {
"Microsoft.EntityFrameworkCore.Sqlite.Core": "8.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.6"
}
},
"Microsoft.EntityFrameworkCore.Tools": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "ZG5X2uznVmw+Mk0HVv3YHiTaGcCANDmZg81/9GLvE5zU4B11oxuM1+tndkYCFoM9CSN0/+XfB89TVYViKXYiRA==",
"dependencies": {
"Microsoft.EntityFrameworkCore.Design": "8.0.5"
}
},
"Microsoft.Extensions.DependencyInjection": { "Microsoft.Extensions.DependencyInjection": {
"type": "Direct", "type": "Direct",
"requested": "[7.0.0, )", "requested": "[8.0.0, )",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
"Humanizer.Core": {
"type": "Transitive",
"resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "3.3.3",
"contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ=="
},
"Microsoft.CodeAnalysis.Common": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "lwAbIZNdnY0SUNoDmZHkVUwLO8UyNnyyh1t/4XsbFxi4Ounb3xszIYZaWhyj5ZjyfcwqwmtMbE7fUTVCqQEIdQ==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.3.3",
"System.Collections.Immutable": "6.0.0",
"System.Reflection.Metadata": "6.0.1",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encoding.CodePages": "6.0.0"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "cM59oMKAOxvdv76bdmaKPy5hfj+oR+zxikWoueEB7CwTko7mt9sVKZI8Qxlov0C/LuKEG+WQwifepqL3vuTiBQ==",
"dependencies": {
"Microsoft.CodeAnalysis.Common": "[4.5.0]"
}
},
"Microsoft.CodeAnalysis.CSharp.Workspaces": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "h74wTpmGOp4yS4hj+EvNzEiPgg/KVs2wmSfTZ81upJZOtPkJsVkgfsgtxxqmAeapjT/vLKfmYV0bS8n5MNVP+g==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.CSharp": "[4.5.0]",
"Microsoft.CodeAnalysis.Common": "[4.5.0]",
"Microsoft.CodeAnalysis.Workspaces.Common": "[4.5.0]"
}
},
"Microsoft.CodeAnalysis.Workspaces.Common": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "l4dDRmGELXG72XZaonnOeORyD/T5RpEu5LGHOUIhnv+MmUWDY/m1kWXGwtcgQ5CJ5ynkFiRnIYzTKXYjUs7rbw==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.Bcl.AsyncInterfaces": "6.0.0",
"Microsoft.CodeAnalysis.Common": "[4.5.0]",
"System.Composition": "6.0.0",
"System.IO.Pipelines": "6.0.3",
"System.Threading.Channels": "6.0.0"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "JMGBNGTPsrLM14j5gDG2r5/I1nbbQd1ZdgeUnF7uca8RHYin6wZpFtQNYYqOMUpSxJak55trXE9B8/X2X+pOXw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.6"
}
},
"Microsoft.EntityFrameworkCore": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "sqpDZgfzmTPXy/jCekqTaPDwqRDjtdGmIL+eqFfXtVAoH4AanWjeyxQ1ej3uVnTQO6f23+m9+ggJDVcgyPJxcA==",
"dependencies": {
"Microsoft.EntityFrameworkCore.Abstractions": "8.0.5",
"Microsoft.EntityFrameworkCore.Analyzers": "8.0.5",
"Microsoft.Extensions.Caching.Memory": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0"
}
},
"Microsoft.EntityFrameworkCore.Abstractions": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "qwYdfjFKtmTXX8NIm0MuZxUkon1tcw+aF5huzR7YOVr/tR3s4fqw9DWcvc23l3Jhpo/uGHWZcNPyFlI2CD3Usg=="
},
"Microsoft.EntityFrameworkCore.Analyzers": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "LzoKedC+9A8inF5d3iIzgyv/JDXgKrtpYoGIC3EqGWuHVDm9s/IHHApeTOTbzvnr7yBVV+nmYfyT1nwtzRDp0Q=="
},
"Microsoft.EntityFrameworkCore.Design": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "HWYnbuMwllSCsZjfKj3Vz+HDGOCyGlTMYjI7tZH5pK7AuiGNHOdshCnWlEFEuDV6oAadWfXGTDmkmV53gwTqSQ==",
"dependencies": {
"Humanizer.Core": "2.14.1",
"Microsoft.CodeAnalysis.CSharp.Workspaces": "4.5.0",
"Microsoft.EntityFrameworkCore.Relational": "8.0.5",
"Microsoft.Extensions.DependencyModel": "8.0.0",
"Mono.TextTemplating": "2.2.1"
}
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "x2bdSK3eKKEQkDdYcGxxDU+S7NqhBiz/Fciz01Mafz9P71VRdP3JskKHaZvwK0/sNEAT3hS7BTsDQGUA2F9mAA==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "8.0.5",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
}
},
"Microsoft.EntityFrameworkCore.Sqlite.Core": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "txwDTpgWFeuTLHh4gYxzKnSWx2jtpX3qxRYkMgfLmjZAe5vYxHKPsTNCa7AKR78ZqrUM7iZ5bBiS3s1Q7oZi4g==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "8.0.5",
"Microsoft.EntityFrameworkCore.Relational": "8.0.5",
"Microsoft.Extensions.DependencyModel": "8.0.0"
}
},
"Microsoft.Extensions.Caching.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==",
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
"dependencies": {
"Microsoft.Extensions.Primitives": "8.0.0"
} }
}, },
"Microsoft.Extensions.DependencyInjection.Abstractions": { "Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive", "type": "Transitive",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
},
"Microsoft.Extensions.DependencyModel": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "NSmDw3K0ozNDgShSIpsZcbFIzBX4w28nDag+TfaQujkXGazBm+lid5onlWoCBy4VsLxqnnKjEBbGSJVWJMf43g==",
"dependencies": {
"System.Text.Encodings.Web": "8.0.0",
"System.Text.Json": "8.0.0"
}
}, },
"Microsoft.Extensions.Logging": { "Microsoft.Extensions.Logging": {
"type": "Transitive", "type": "Transitive",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "7.0.0", "Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "7.0.0", "Microsoft.Extensions.Options": "8.0.0"
"Microsoft.Extensions.Options": "7.0.0"
} }
}, },
"Microsoft.Extensions.Logging.Abstractions": { "Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive", "type": "Transitive",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
}, },
"Microsoft.Extensions.Options": { "Microsoft.Extensions.Options": {
"type": "Transitive", "type": "Transitive",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "7.0.0" "Microsoft.Extensions.Primitives": "8.0.0"
} }
}, },
"Microsoft.Extensions.Primitives": { "Microsoft.Extensions.Primitives": {
"type": "Transitive", "type": "Transitive",
"resolved": "7.0.0", "resolved": "8.0.0",
"contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
"Mono.TextTemplating": {
"type": "Transitive",
"resolved": "2.2.1",
"contentHash": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==",
"dependencies": {
"System.CodeDom": "4.4.0"
} }
}, },
"net7.0-windows7.0/win-x64": {} "SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.6",
"contentHash": "BmAf6XWt4TqtowmiWe4/5rRot6GerAeklmOPfviOvwLoF5WwgxcJHAxZtySuyW9r9w+HLILnm8VfJFLCUJYW8A==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.6",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.6"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.6",
"contentHash": "wO6v9GeMx9CUngAet8hbO7xdm+M42p1XeJq47ogyRoYSvNSp0NGLI+MgC0bhrMk9C17MTVFlLiN6ylyExLCc5w==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.6",
"contentHash": "2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.6",
"contentHash": "PQ2Oq3yepLY4P7ll145P3xtx2bX8xF4PzaKPRpw9jZlKvfe4LE/saAV82inND9usn1XRpmxXk7Lal3MTI+6CNg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.6"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Composition": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "d7wMuKQtfsxUa7S13tITC8n1cQzewuhD5iDjZtK2prwFfKVzdYtgrTHgjaV03Zq7feGQ5gkP85tJJntXwInsJA==",
"dependencies": {
"System.Composition.AttributedModel": "6.0.0",
"System.Composition.Convention": "6.0.0",
"System.Composition.Hosting": "6.0.0",
"System.Composition.Runtime": "6.0.0",
"System.Composition.TypedParts": "6.0.0"
}
},
"System.Composition.AttributedModel": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "WK1nSDLByK/4VoC7fkNiFuTVEiperuCN/Hyn+VN30R+W2ijO1d0Z2Qm0ScEl9xkSn1G2MyapJi8xpf4R8WRa/w=="
},
"System.Composition.Convention": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "XYi4lPRdu5bM4JVJ3/UIHAiG6V6lWWUlkhB9ab4IOq0FrRsp0F4wTyV4Dj+Ds+efoXJ3qbLqlvaUozDO7OLeXA==",
"dependencies": {
"System.Composition.AttributedModel": "6.0.0"
}
},
"System.Composition.Hosting": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "w/wXjj7kvxuHPLdzZ0PAUt++qJl03t7lENmb2Oev0n3zbxyNULbWBlnd5J5WUMMv15kg5o+/TCZFb6lSwfaUUQ==",
"dependencies": {
"System.Composition.Runtime": "6.0.0"
}
},
"System.Composition.Runtime": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "qkRH/YBaMPTnzxrS5RDk1juvqed4A6HOD/CwRcDGyPpYps1J27waBddiiq1y93jk2ZZ9wuA/kynM+NO0kb3PKg=="
},
"System.Composition.TypedParts": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "iUR1eHrL8Cwd82neQCJ00MpwNIBs4NZgXzrPqx8NJf/k4+mwBO0XCRmHYJT4OLSwDDqh5nBLJWkz5cROnrGhRA==",
"dependencies": {
"System.Composition.AttributedModel": "6.0.0",
"System.Composition.Hosting": "6.0.0",
"System.Composition.Runtime": "6.0.0"
}
},
"System.IO.Pipelines": {
"type": "Transitive",
"resolved": "6.0.3",
"contentHash": "ryTgF+iFkpGZY1vRQhfCzX0xTdlV3pyaTTqRu2ETbEv+HlV7O6y7hyQURnghNIXvctl5DuZ//Dpks6HdL/Txgw=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "III/lNMSn0ZRBuM9m5Cgbiho5j81u0FAEagFX5ta2DKbljZ3T0IpD8j+BIiHQPeKqJppWS9bGEp6JnKnWKze0g==",
"dependencies": {
"System.Collections.Immutable": "6.0.0"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
},
"System.Text.Json": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==",
"dependencies": {
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "TY8/9+tI0mNaUMgntOxxaq2ndTkdXqLSxvPmas7XEqOlv9lQtB7wLjYGd756lOaO7Dvb5r/WXhluM+0Xe87v5Q=="
}
},
"net8.0-windows7.0/win-x64": {
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.6",
"contentHash": "2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
}
}
} }
} }