Migrate from LiteDB to SQLite
This commit is contained in:
parent
3a72715671
commit
6763b47509
1017
RetainerTrack/.editorconfig
Normal file
1017
RetainerTrack/.editorconfig
Normal file
File diff suppressed because it is too large
Load Diff
71
RetainerTrack/Commands/WhoCommand.cs
Normal file
71
RetainerTrack/Commands/WhoCommand.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
60
RetainerTrack/Database/Compiled/PlayerEntityType.cs
Normal file
60
RetainerTrack/Database/Compiled/PlayerEntityType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
92
RetainerTrack/Database/Compiled/RetainerEntityType.cs
Normal file
92
RetainerTrack/Database/Compiled/RetainerEntityType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
47
RetainerTrack/Database/Compiled/RetainerTrackContextModel.cs
Normal file
47
RetainerTrack/Database/Compiled/RetainerTrackContextModel.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
RetainerTrack/Database/Migrations/.editorconfig
Normal file
3
RetainerTrack/Database/Migrations/.editorconfig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[*.cs]
|
||||||
|
# CA1062: Validate arguments of public methods
|
||||||
|
dotnet_diagnostic.CA1062.severity = none
|
62
RetainerTrack/Database/Migrations/20240524200204_InitialCreate.Designer.cs
generated
Normal file
62
RetainerTrack/Database/Migrations/20240524200204_InitialCreate.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.Designer.cs
generated
Normal file
62
RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
62
RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.Designer.cs
generated
Normal file
62
RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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; }
|
|
||||||
public string? Name { get; set; }
|
[MaxLength(20), Required]
|
||||||
}
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
|
||||||
public string? Name { get; set; }
|
[MaxLength(24), Required]
|
||||||
public ushort WorldId { get; set; }
|
public string? Name { get; set; }
|
||||||
public ulong OwnerContentId { get; set; }
|
|
||||||
}
|
[Required]
|
||||||
|
public ushort WorldId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public ulong OwnerLocalContentId { get; set; }
|
||||||
}
|
}
|
||||||
|
14
RetainerTrack/Database/RetainerTrackContext.cs
Normal file
14
RetainerTrack/Database/RetainerTrackContext.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
19
RetainerTrack/Database/RetainerTrackContextFactory.cs
Normal file
19
RetainerTrack/Database/RetainerTrackContextFactory.cs
Normal 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
|
@ -1,8 +1,7 @@
|
|||||||
namespace RetainerTrack.Handlers
|
namespace RetainerTrack.Handlers;
|
||||||
|
|
||||||
|
internal sealed class ContentIdToName
|
||||||
{
|
{
|
||||||
internal sealed class ContentIdToName
|
public ulong ContentId { get; init; }
|
||||||
{
|
public string PlayerName { get; init; } = string.Empty;
|
||||||
public ulong ContentId { get; init; }
|
|
||||||
public string PlayerName { get; init; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,60 +10,94 @@ 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 PersistenceContext _persistenceContext;
|
||||||
private readonly ILogger<GameHooks> _logger;
|
|
||||||
private readonly PersistenceContext _persistenceContext;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the content id to character name packet, seen e.g. when you hover an item to retrieve the
|
/// Processes the content id to character name packet, seen e.g. when you hover an item to retrieve the
|
||||||
/// crafter's signature.
|
/// crafter's signature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private delegate int CharacterNameResultDelegate(nint a1, ulong contentId, char* playerName);
|
private delegate int CharacterNameResultDelegate(nint a1, ulong contentId, char* playerName);
|
||||||
|
|
||||||
private delegate nint SocialListResultDelegate(nint a1, nint dataPtr);
|
private delegate nint SocialListResultDelegate(nint a1, nint dataPtr);
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
[Signature("40 53 48 83 EC 20 48 8B D9 33 C9 45 33 C9", DetourName = nameof(ProcessCharacterNameResult))]
|
[Signature("40 53 48 83 EC 20 48 8B D9 33 C9 45 33 C9", DetourName = nameof(ProcessCharacterNameResult))]
|
||||||
private Hook<CharacterNameResultDelegate> CharacterNameResultHook { get; init; } = null!;
|
private Hook<CharacterNameResultDelegate> CharacterNameResultHook { get; init; } = null!;
|
||||||
|
|
||||||
// Signature adapted from https://github.com/LittleNightmare/UsedName
|
// Signature adapted from https://github.com/LittleNightmare/UsedName
|
||||||
[Signature("48 89 5C 24 10 56 48 83 EC 20 48 ?? ?? ?? ?? ?? ?? 48 8B F2 E8 ?? ?? ?? ?? 48 8B D8",
|
[Signature("48 89 5C 24 10 56 48 83 EC 20 48 ?? ?? ?? ?? ?? ?? 48 8B F2 E8 ?? ?? ?? ?? 48 8B D8",
|
||||||
DetourName = nameof(ProcessSocialListResult))]
|
DetourName = nameof(ProcessSocialListResult))]
|
||||||
private Hook<SocialListResultDelegate> SocialListResultHook { get; init; } = null!;
|
private Hook<SocialListResultDelegate> SocialListResultHook { get; init; } = null!;
|
||||||
|
|
||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
|
|
||||||
public GameHooks(ILogger<GameHooks> logger, PersistenceContext persistenceContext, IGameInteropProvider gameInteropProvider)
|
public GameHooks(ILogger<GameHooks> logger, PersistenceContext persistenceContext, IGameInteropProvider gameInteropProvider)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_persistenceContext = persistenceContext;
|
||||||
|
|
||||||
|
_logger.LogDebug("Initializing game hooks");
|
||||||
|
gameInteropProvider.InitializeFromAttributes(this);
|
||||||
|
CharacterNameResultHook.Enable();
|
||||||
|
SocialListResultHook.Enable();
|
||||||
|
|
||||||
|
_logger.LogDebug("Game hooks initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ProcessCharacterNameResult(nint a1, ulong contentId, char* playerName)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_logger = logger;
|
var mapping = new ContentIdToName
|
||||||
_persistenceContext = persistenceContext;
|
{
|
||||||
|
ContentId = contentId,
|
||||||
|
PlayerName = MemoryHelper.ReadString(new nint(playerName), Encoding.ASCII, 32),
|
||||||
|
};
|
||||||
|
|
||||||
_logger.LogDebug("Initializing game hooks");
|
if (!string.IsNullOrEmpty(mapping.PlayerName))
|
||||||
gameInteropProvider.InitializeFromAttributes(this);
|
{
|
||||||
CharacterNameResultHook.Enable();
|
_logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId,
|
||||||
SocialListResultHook.Enable();
|
mapping.PlayerName);
|
||||||
|
Task.Run(() => _persistenceContext.HandleContentIdMapping(mapping));
|
||||||
_logger.LogDebug("Game hooks initialized");
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Content id {ContentId} didn't resolve to a player name, ignoring",
|
||||||
|
mapping.ContentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Could not process character name result");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ProcessCharacterNameResult(nint a1, ulong contentId, char* playerName)
|
return CharacterNameResultHook.Original(a1, contentId, playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private nint ProcessSocialListResult(nint a1, nint dataPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
var result = Marshal.PtrToStructure<SocialListResultPage>(dataPtr);
|
||||||
|
List<ContentIdToName> mappings = new();
|
||||||
|
foreach (SocialListPlayer player in result.PlayerSpan)
|
||||||
{
|
{
|
||||||
var mapping = new ContentIdToName
|
var mapping = new ContentIdToName
|
||||||
{
|
{
|
||||||
ContentId = contentId,
|
ContentId = player.ContentId,
|
||||||
PlayerName = MemoryHelper.ReadString(new nint(playerName), Encoding.ASCII, 32),
|
PlayerName = MemoryHelper.ReadString(new nint(player.CharacterName), Encoding.ASCII, 32),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(mapping.PlayerName))
|
if (!string.IsNullOrEmpty(mapping.PlayerName))
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId,
|
_logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId,
|
||||||
mapping.PlayerName);
|
mapping.PlayerName);
|
||||||
Task.Run(() => _persistenceContext.HandleContentIdMapping(mapping));
|
mappings.Add(mapping);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -71,92 +105,57 @@ namespace RetainerTrack.Handlers
|
|||||||
mapping.ContentId);
|
mapping.ContentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Could not process character name result");
|
|
||||||
}
|
|
||||||
|
|
||||||
return CharacterNameResultHook.Original(a1, contentId, playerName);
|
if (mappings.Count > 0)
|
||||||
|
Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings));
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
private nint ProcessSocialListResult(nint a1, nint dataPtr)
|
|
||||||
{
|
{
|
||||||
try
|
_logger.LogError(e, "Could not process social list result");
|
||||||
{
|
|
||||||
var result = Marshal.PtrToStructure<SocialListResultPage>(dataPtr);
|
|
||||||
List<ContentIdToName> mappings = new();
|
|
||||||
foreach (SocialListPlayer player in result.PlayerSpan)
|
|
||||||
{
|
|
||||||
var mapping = new ContentIdToName
|
|
||||||
{
|
|
||||||
ContentId = player.ContentId,
|
|
||||||
PlayerName = MemoryHelper.ReadString(new nint(player.CharacterName), Encoding.ASCII, 32),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(mapping.PlayerName))
|
|
||||||
{
|
|
||||||
_logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId,
|
|
||||||
mapping.PlayerName);
|
|
||||||
mappings.Add(mapping);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Content id {ContentId} didn't resolve to a player name, ignoring",
|
|
||||||
mapping.ContentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappings.Count > 0)
|
|
||||||
Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Could not process social list result");
|
|
||||||
}
|
|
||||||
|
|
||||||
return SocialListResultHook.Original(a1, dataPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
return SocialListResultHook.Original(a1, dataPtr);
|
||||||
{
|
}
|
||||||
CharacterNameResultHook.Dispose();
|
|
||||||
SocialListResultHook.Dispose();
|
public void Dispose()
|
||||||
}
|
{
|
||||||
|
CharacterNameResultHook.Dispose();
|
||||||
|
SocialListResultHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There are some caveats here, the social list includes a LOT of things with different types
|
||||||
|
/// (we don't care for the result type in this plugin), see sapphire for which field is the type.
|
||||||
|
///
|
||||||
|
/// 1 = party
|
||||||
|
/// 2 = friend list
|
||||||
|
/// 3 = link shell
|
||||||
|
/// 4 = player search
|
||||||
|
/// 5 = fc short list (first tab, with company board + actions + online members)
|
||||||
|
/// 6 = fc long list (members tab)
|
||||||
|
///
|
||||||
|
/// Both 1 and 2 are sent to you on login, unprompted.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x380)]
|
||||||
|
internal struct SocialListResultPage
|
||||||
|
{
|
||||||
|
[FieldOffset(0x10)] private fixed byte Players[10 * 0x58];
|
||||||
|
|
||||||
|
public Span<SocialListPlayer> PlayerSpan => new(Unsafe.AsPointer(ref Players[0]), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x58)]
|
||||||
|
internal struct SocialListPlayer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If this is set, it means there is a player present in this slot (even if no name can be retrieved),
|
||||||
|
/// 0 if empty.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0x00)] public readonly ulong ContentId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// There are some caveats here, the social list includes a LOT of things with different types
|
/// This *can* be empty, e.g. if you're querying your friend list, the names are ONLY set for characters on the same world.
|
||||||
/// (we don't care for the result type in this plugin), see sapphire for which field is the type.
|
|
||||||
///
|
|
||||||
/// 1 = party
|
|
||||||
/// 2 = friend list
|
|
||||||
/// 3 = link shell
|
|
||||||
/// 4 = player search
|
|
||||||
/// 5 = fc short list (first tab, with company board + actions + online members)
|
|
||||||
/// 6 = fc long list (members tab)
|
|
||||||
///
|
|
||||||
/// Both 1 and 2 are sent to you on login, unprompted.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 0x380)]
|
[FieldOffset(0x31)] public fixed byte CharacterName[32];
|
||||||
internal struct SocialListResultPage
|
|
||||||
{
|
|
||||||
[FieldOffset(0x10)] private fixed byte Players[10 * 0x58];
|
|
||||||
|
|
||||||
public Span<SocialListPlayer> PlayerSpan => new(Unsafe.AsPointer(ref Players[0]), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 0x58)]
|
|
||||||
internal struct SocialListPlayer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// If this is set, it means there is a player present in this slot (even if no name can be retrieved),
|
|
||||||
/// 0 if empty.
|
|
||||||
/// </summary>
|
|
||||||
[FieldOffset(0x00)] public readonly ulong ContentId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This *can* be empty, e.g. if you're querying your friend list, the names are ONLY set for characters on the same world.
|
|
||||||
/// </summary>
|
|
||||||
[FieldOffset(0x31)] public fixed byte CharacterName[32];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,77 +3,71 @@ 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 readonly ILogger<MarketBoardOfferingsHandler> _logger;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly PersistenceContext _persistenceContext;
|
||||||
|
private readonly Hook<MarketBoardOfferings> _marketBoardOfferingsHook;
|
||||||
|
|
||||||
|
public unsafe MarketBoardOfferingsHandler(
|
||||||
|
ILogger<MarketBoardOfferingsHandler> logger,
|
||||||
|
IClientState clientState,
|
||||||
|
IGameInteropProvider gameInteropProvider,
|
||||||
|
PersistenceContext persistenceContext)
|
||||||
{
|
{
|
||||||
private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData);
|
_logger = logger;
|
||||||
|
_clientState = clientState;
|
||||||
|
_persistenceContext = persistenceContext;
|
||||||
|
|
||||||
private readonly ILogger<MarketBoardOfferingsHandler> _logger;
|
_logger.LogDebug("Setting up offerings hook");
|
||||||
private readonly IClientState _clientState;
|
_marketBoardOfferingsHook =
|
||||||
private readonly PersistenceContext _persistenceContext;
|
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",
|
||||||
private readonly Hook<MarketBoardOfferings> _marketBoardOfferingsHook;
|
MarketBoardOfferingsDetour);
|
||||||
|
_marketBoardOfferingsHook.Enable();
|
||||||
|
_logger.LogDebug("Offerings hook enabled successfully");
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe MarketBoardOfferingsHandler(
|
public void Dispose()
|
||||||
ILogger<MarketBoardOfferingsHandler> logger,
|
{
|
||||||
IClientState clientState,
|
_marketBoardOfferingsHook.Dispose();
|
||||||
IGameGui gameGui,
|
}
|
||||||
IGameInteropProvider gameInteropProvider,
|
|
||||||
PersistenceContext persistenceContext)
|
// adapted from https://github.com/tesu/PennyPincher/commit/0f9b3963fd4a6e9b87f585ee491d4de59a93f7a3
|
||||||
|
private unsafe void* MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetData)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_logger = logger;
|
if (packetData != nint.Zero)
|
||||||
_clientState = clientState;
|
|
||||||
_persistenceContext = persistenceContext;
|
|
||||||
|
|
||||||
_logger.LogDebug("Setting up offerings hook");
|
|
||||||
var uiModule = (UIModule*)gameGui.GetUIModule();
|
|
||||||
var infoModule = uiModule->GetInfoModule();
|
|
||||||
var proxy = infoModule->GetInfoProxyById(11);
|
|
||||||
_marketBoardOfferingsHook =
|
|
||||||
gameInteropProvider.HookFromAddress<MarketBoardOfferings>((nint)proxy->vtbl[12],
|
|
||||||
MarketBoardOfferingsDetour);
|
|
||||||
_marketBoardOfferingsHook.Enable();
|
|
||||||
_logger.LogDebug("Offerings hook enabled successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_marketBoardOfferingsHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// adapted from https://github.com/tesu/PennyPincher/commit/0f9b3963fd4a6e9b87f585ee491d4de59a93f7a3
|
|
||||||
private unsafe void* MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetData)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (packetData != nint.Zero)
|
ParseOfferings(packetData);
|
||||||
{
|
|
||||||
ParseOfferings(packetData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Could not parse marketboard offerings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _marketBoardOfferingsHook.Original(a1, packetData);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
private void ParseOfferings(nint dataPtr)
|
|
||||||
{
|
{
|
||||||
ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
_logger.LogError(e, "Could not parse marketboard offerings.");
|
||||||
if (worldId == 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Skipping market board handler, current world unknown");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listings = MarketBoardCurrentOfferings.Read(dataPtr);
|
|
||||||
Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _marketBoardOfferingsHook.Original(a1, packetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseOfferings(nint dataPtr)
|
||||||
|
{
|
||||||
|
ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
|
if (worldId == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Skipping market board handler, current world unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listings = MarketBoardCurrentOfferings.Read(dataPtr);
|
||||||
|
Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +1,78 @@
|
|||||||
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 readonly ILogger<MarketBoardUiHandler> _logger;
|
||||||
|
private readonly PersistenceContext _persistenceContext;
|
||||||
|
private readonly IAddonLifecycle _addonLifecycle;
|
||||||
|
|
||||||
|
public MarketBoardUiHandler(
|
||||||
|
ILogger<MarketBoardUiHandler> logger,
|
||||||
|
PersistenceContext persistenceContext,
|
||||||
|
IAddonLifecycle addonLifecycle)
|
||||||
{
|
{
|
||||||
private const string AddonName = "ItemSearchResult";
|
_logger = logger;
|
||||||
|
_persistenceContext = persistenceContext;
|
||||||
|
_addonLifecycle = addonLifecycle;
|
||||||
|
|
||||||
private readonly ILogger<MarketBoardUiHandler> _logger;
|
_addonLifecycle.RegisterListener(AddonEvent.PreDraw, AddonName, PreDraw);
|
||||||
private readonly PersistenceContext _persistenceContext;
|
}
|
||||||
private readonly IAddonLifecycle _addonLifecycle;
|
|
||||||
|
|
||||||
public MarketBoardUiHandler(
|
private void PreDraw(AddonEvent type, AddonArgs args)
|
||||||
ILogger<MarketBoardUiHandler> logger,
|
{
|
||||||
PersistenceContext persistenceContext,
|
UpdateRetainerNames((AddonItemSearchResult*)args.Addon);
|
||||||
IAddonLifecycle addonLifecycle)
|
}
|
||||||
|
|
||||||
|
private void UpdateRetainerNames(AddonItemSearchResult* addon)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_logger = logger;
|
if (addon == null || !addon->AtkUnitBase.IsVisible)
|
||||||
_persistenceContext = persistenceContext;
|
return;
|
||||||
_addonLifecycle = addonLifecycle;
|
|
||||||
|
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PreDraw, AddonName, PreDraw);
|
var results = addon->Results;
|
||||||
}
|
if (results == null)
|
||||||
|
return;
|
||||||
|
|
||||||
private void PreDraw(AddonEvent type, AddonArgs args)
|
int length = results->ListLength;
|
||||||
{
|
if (length == 0)
|
||||||
UpdateRetainerNames((AddonItemSearchResult*)args.Addon);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateRetainerNames(AddonItemSearchResult* addon)
|
for (int i = 0; i < length; ++i)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (addon == null || !addon->AtkUnitBase.IsVisible)
|
var listItem = results->ItemRendererList[i].AtkComponentListItemRenderer;
|
||||||
return;
|
var uldManager = listItem->AtkComponentButton.AtkComponentBase.UldManager;
|
||||||
|
if (uldManager.NodeListCount < 14)
|
||||||
|
continue;
|
||||||
|
|
||||||
var results = addon->Results;
|
var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5];
|
||||||
if (results == null)
|
string retainerName = retainerNameNode->NodeText.ToString();
|
||||||
return;
|
if (!retainerName.Contains('(', StringComparison.Ordinal))
|
||||||
|
|
||||||
int length = results->ListLength;
|
|
||||||
if (length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < length; ++i)
|
|
||||||
{
|
{
|
||||||
var listItem = results->ItemRendererList[i].AtkComponentListItemRenderer;
|
string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName);
|
||||||
var uldManager = listItem->AtkComponentButton.AtkComponentBase.UldManager;
|
if (!string.IsNullOrEmpty(playerName))
|
||||||
if (uldManager.NodeListCount < 14)
|
retainerNameNode->SetText($"{playerName} ({retainerName})");
|
||||||
continue;
|
|
||||||
|
|
||||||
var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5];
|
|
||||||
string retainerName = retainerNameNode->NodeText.ToString();
|
|
||||||
if (!retainerName.Contains('('))
|
|
||||||
{
|
|
||||||
string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName);
|
|
||||||
if (!string.IsNullOrEmpty(playerName))
|
|
||||||
retainerNameNode->SetText($"{playerName} ({retainerName})");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(e, "Market board draw failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw);
|
_logger.LogInformation(e, "Market board draw failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,65 +5,64 @@ 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 IClientState _clientState;
|
||||||
|
private readonly PersistenceContext _persistenceContext;
|
||||||
|
|
||||||
|
private long _lastUpdate;
|
||||||
|
|
||||||
|
public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext)
|
||||||
{
|
{
|
||||||
private readonly IFramework _framework;
|
_framework = framework;
|
||||||
private readonly IClientState _clientState;
|
_clientState = clientState;
|
||||||
private readonly PersistenceContext _persistenceContext;
|
_persistenceContext = persistenceContext;
|
||||||
|
|
||||||
private long _lastUpdate = 0;
|
_framework.Update += FrameworkUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext)
|
private unsafe void FrameworkUpdate(IFramework _)
|
||||||
|
{
|
||||||
|
long now = Environment.TickCount64;
|
||||||
|
if (!_clientState.IsLoggedIn || _clientState.IsPvPExcludingDen || now - _lastUpdate < 180_000)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastUpdate = now;
|
||||||
|
|
||||||
|
// skip if we're not in an alliance, party members are handled via social list updates
|
||||||
|
var groupManager = GroupManager.Instance();
|
||||||
|
if (groupManager->AllianceFlags == 0x0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<ContentIdToName> mappings = new();
|
||||||
|
foreach (var allianceMember in groupManager->AllianceMembersSpan)
|
||||||
|
HandlePartyMember(allianceMember, mappings);
|
||||||
|
|
||||||
|
if (mappings.Count > 0)
|
||||||
|
Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void HandlePartyMember(PartyMember partyMember, List<ContentIdToName> contentIdToNames)
|
||||||
|
{
|
||||||
|
if (partyMember.ContentID == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string name = MemoryHelper.ReadStringNullTerminated((nint)partyMember.Name);
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
contentIdToNames.Add(new ContentIdToName
|
||||||
{
|
{
|
||||||
_framework = framework;
|
ContentId = (ulong)partyMember.ContentID,
|
||||||
_clientState = clientState;
|
PlayerName = name,
|
||||||
_persistenceContext = persistenceContext;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_framework.Update += FrameworkUpdate;
|
public void Dispose()
|
||||||
}
|
{
|
||||||
|
_framework.Update -= FrameworkUpdate;
|
||||||
private unsafe void FrameworkUpdate(IFramework _)
|
|
||||||
{
|
|
||||||
long now = Environment.TickCount64;
|
|
||||||
if (!_clientState.IsLoggedIn || _clientState.IsPvPExcludingDen || now - _lastUpdate < 180_000)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_lastUpdate = now;
|
|
||||||
|
|
||||||
// skip if we're not in an alliance, party members are handled via social list updates
|
|
||||||
var groupManager = GroupManager.Instance();
|
|
||||||
if (groupManager->AllianceFlags == 0x0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
List<ContentIdToName> mappings = new();
|
|
||||||
foreach (var allianceMember in groupManager->AllianceMembersSpan)
|
|
||||||
HandlePartyMember(allianceMember, mappings);
|
|
||||||
|
|
||||||
if (mappings.Count > 0)
|
|
||||||
Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings));
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void HandlePartyMember(PartyMember partyMember, List<ContentIdToName> contentIdToNames)
|
|
||||||
{
|
|
||||||
if (partyMember.ContentID == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
string name = MemoryHelper.ReadStringNullTerminated((nint)partyMember.Name);
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
contentIdToNames.Add(new ContentIdToName
|
|
||||||
{
|
|
||||||
ContentId = (ulong)partyMember.ContentID,
|
|
||||||
PlayerName = name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_framework.Update -= FrameworkUpdate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,128 +1,171 @@
|
|||||||
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 IClientState _clientState;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ConcurrentDictionary<uint, ConcurrentDictionary<string, ulong>> _worldRetainerCache = new();
|
||||||
|
private readonly ConcurrentDictionary<ulong, string> _playerNameCache = new();
|
||||||
|
|
||||||
|
public PersistenceContext(ILogger<PersistenceContext> logger, IClientState clientState,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
private readonly ILogger<PersistenceContext> _logger;
|
_logger = logger;
|
||||||
private readonly IClientState _clientState;
|
_clientState = clientState;
|
||||||
private readonly LiteDatabase _liteDatabase;
|
_serviceProvider = serviceProvider;
|
||||||
private readonly ConcurrentDictionary<uint, ConcurrentDictionary<string, ulong>> _worldRetainerCache = new();
|
|
||||||
private readonly ConcurrentDictionary<ulong, string> _playerNameCache = new();
|
|
||||||
|
|
||||||
public PersistenceContext(ILogger<PersistenceContext> logger, IClientState clientState,
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
LiteDatabase liteDatabase)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
|
||||||
_clientState = clientState;
|
var retainersByWorld = dbContext.Retainers.GroupBy(retainer => retainer.WorldId);
|
||||||
_liteDatabase = liteDatabase;
|
|
||||||
|
|
||||||
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)
|
||||||
|
{
|
||||||
|
uint currentWorld = _clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
|
if (currentWorld == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var currentWorldCache = _worldRetainerCache.GetOrAdd(currentWorld, _ => new());
|
||||||
|
if (!currentWorldCache.TryGetValue(retainerName, out ulong playerContentId))
|
||||||
|
return 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)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
uint currentWorld = _clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
var updates =
|
||||||
if (currentWorld == 0)
|
listings.ItemListings.DistinctBy(o => o.RetainerId)
|
||||||
return string.Empty;
|
.Where(l => l.RetainerId != 0)
|
||||||
|
.Where(l => l.RetainerOwnerId != 0)
|
||||||
var currentWorldCache = _worldRetainerCache.GetOrAdd(currentWorld, _ => new());
|
.Select(l =>
|
||||||
if (!currentWorldCache.TryGetValue(retainerName, out ulong playerContentId))
|
new Retainer
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
return _playerNameCache.TryGetValue(playerContentId, out string? playerName) ? playerName : string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleMarketBoardPage(MarketBoardCurrentOfferings listings, ushort worldId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var updates =
|
|
||||||
listings.ItemListings.DistinctBy(o => o.RetainerId)
|
|
||||||
.Where(l => l.RetainerId != 0)
|
|
||||||
.Where(l => l.RetainerOwnerId != 0)
|
|
||||||
.Select(l =>
|
|
||||||
new Retainer
|
|
||||||
{
|
|
||||||
Id = l.RetainerId,
|
|
||||||
Name = l.RetainerName,
|
|
||||||
WorldId = worldId,
|
|
||||||
OwnerContentId = l.RetainerOwnerId,
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
_liteDatabase.GetCollection<Retainer>().Upsert(updates);
|
|
||||||
foreach (var retainer in updates)
|
|
||||||
{
|
|
||||||
if (!_playerNameCache.TryGetValue(retainer.OwnerContentId, out string? ownerName))
|
|
||||||
ownerName = retainer.OwnerContentId.ToString();
|
|
||||||
_logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name,
|
|
||||||
ownerName);
|
|
||||||
|
|
||||||
if (retainer.Name != null)
|
|
||||||
{
|
|
||||||
var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new());
|
|
||||||
world[retainer.Name] = retainer.OwnerContentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Could not persist retainer info from market board page");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleContentIdMapping(ContentIdToName mapping)
|
|
||||||
=> HandleContentIdMapping(new List<ContentIdToName> { mapping });
|
|
||||||
|
|
||||||
public void HandleContentIdMapping(IReadOnlyList<ContentIdToName> mappings)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var updates = mappings
|
|
||||||
.Where(mapping => mapping.ContentId != 0 && !string.IsNullOrEmpty(mapping.PlayerName))
|
|
||||||
.Where(mapping =>
|
|
||||||
{
|
|
||||||
if (_playerNameCache.TryGetValue(mapping.ContentId, out string? existingName))
|
|
||||||
return mapping.PlayerName != existingName;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.Select(mapping =>
|
|
||||||
new Player
|
|
||||||
{
|
{
|
||||||
Id = mapping.ContentId,
|
LocalContentId = l.RetainerId,
|
||||||
Name = mapping.PlayerName,
|
Name = l.RetainerName,
|
||||||
|
WorldId = worldId,
|
||||||
|
OwnerLocalContentId = l.RetainerOwnerId,
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
_liteDatabase.GetCollection<Player>().Upsert(updates);
|
|
||||||
foreach (var player in updates)
|
using var scope = _serviceProvider.CreateScope();
|
||||||
_playerNameCache[player.Id] = player.Name ?? string.Empty;
|
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
|
||||||
}
|
|
||||||
catch (Exception e)
|
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)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Could not persist multiple mappings");
|
if (dbRetainers.TryGetValue(retainer.LocalContentId, out var dbRetainer))
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
ownerName);
|
||||||
|
|
||||||
|
if (retainer.Name != null)
|
||||||
|
{
|
||||||
|
var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new());
|
||||||
|
world[retainer.Name] = retainer.OwnerLocalContentId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Could not persist retainer info from market board page");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleContentIdMapping(ContentIdToName mapping)
|
||||||
|
=> HandleContentIdMapping(new List<ContentIdToName> { mapping });
|
||||||
|
|
||||||
|
public void HandleContentIdMapping(IReadOnlyList<ContentIdToName> mappings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var updates = mappings
|
||||||
|
.Where(mapping => mapping.ContentId != 0 && !string.IsNullOrEmpty(mapping.PlayerName))
|
||||||
|
.Where(mapping =>
|
||||||
|
{
|
||||||
|
if (_playerNameCache.TryGetValue(mapping.ContentId, out string? existingName))
|
||||||
|
return mapping.PlayerName != existingName;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.Select(mapping =>
|
||||||
|
new Player
|
||||||
|
{
|
||||||
|
LocalContentId = mapping.ContentId,
|
||||||
|
Name = mapping.PlayerName,
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
using (var scope = _serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
|
||||||
|
dbContext.Players.AddRange(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var player in updates)
|
||||||
|
_playerNameCache[player.LocalContentId] = player.Name ?? string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Could not persist multiple mappings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
RetainerTrack/LegacyDb/LegacyPlayer.cs
Normal file
12
RetainerTrack/LegacyDb/LegacyPlayer.cs
Normal 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; }
|
||||||
|
}
|
14
RetainerTrack/LegacyDb/LegacyRetainer.cs
Normal file
14
RetainerTrack/LegacyDb/LegacyRetainer.cs
Normal 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; }
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -1,72 +1,94 @@
|
|||||||
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
|
||||||
|
internal sealed class RetainerTrackPlugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
// ReSharper disable once UnusedType.Global
|
public const string DatabaseFileName = "retainertrack.data.sqlite3";
|
||||||
internal sealed class RetainerTrackPlugin : IDalamudPlugin
|
private readonly string _sqliteConnectionString;
|
||||||
|
private readonly ServiceProvider? _serviceProvider;
|
||||||
|
|
||||||
|
public RetainerTrackPlugin(
|
||||||
|
DalamudPluginInterface pluginInterface,
|
||||||
|
IFramework framework,
|
||||||
|
IClientState clientState,
|
||||||
|
IGameGui gameGui,
|
||||||
|
IChatGui chatGui,
|
||||||
|
IGameInteropProvider gameInteropProvider,
|
||||||
|
IAddonLifecycle addonLifecycle,
|
||||||
|
ICommandManager commandManager,
|
||||||
|
IDataManager dataManager,
|
||||||
|
IPluginLog pluginLog)
|
||||||
{
|
{
|
||||||
private readonly ServiceProvider? _serviceProvider;
|
ServiceCollection serviceCollection = new();
|
||||||
|
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
|
||||||
|
.ClearProviders()
|
||||||
|
.AddDalamudLogger(pluginLog));
|
||||||
|
serviceCollection.AddSingleton<IDalamudPlugin>(this);
|
||||||
|
serviceCollection.AddSingleton(pluginInterface);
|
||||||
|
serviceCollection.AddSingleton(framework);
|
||||||
|
serviceCollection.AddSingleton(clientState);
|
||||||
|
serviceCollection.AddSingleton(gameGui);
|
||||||
|
serviceCollection.AddSingleton(chatGui);
|
||||||
|
serviceCollection.AddSingleton(gameInteropProvider);
|
||||||
|
serviceCollection.AddSingleton(addonLifecycle);
|
||||||
|
serviceCollection.AddSingleton(commandManager);
|
||||||
|
serviceCollection.AddSingleton(dataManager);
|
||||||
|
|
||||||
public RetainerTrackPlugin(
|
serviceCollection.AddSingleton<PersistenceContext>();
|
||||||
DalamudPluginInterface pluginInterface,
|
serviceCollection.AddSingleton<MarketBoardOfferingsHandler>();
|
||||||
IFramework framework,
|
serviceCollection.AddSingleton<PartyHandler>();
|
||||||
IClientState clientState,
|
serviceCollection.AddSingleton<MarketBoardUiHandler>();
|
||||||
IGameGui gameGui,
|
serviceCollection.AddSingleton<GameHooks>();
|
||||||
IGameInteropProvider gameInteropProvider,
|
serviceCollection.AddSingleton<WhoCommand>();
|
||||||
IAddonLifecycle addonLifecycle,
|
|
||||||
IPluginLog pluginLog)
|
|
||||||
{
|
|
||||||
ServiceCollection serviceCollection = new();
|
|
||||||
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace)
|
|
||||||
.ClearProviders()
|
|
||||||
.AddDalamudLogger(pluginLog));
|
|
||||||
serviceCollection.AddSingleton<IDalamudPlugin>(this);
|
|
||||||
serviceCollection.AddSingleton(pluginInterface);
|
|
||||||
serviceCollection.AddSingleton(framework);
|
|
||||||
serviceCollection.AddSingleton(clientState);
|
|
||||||
serviceCollection.AddSingleton(gameGui);
|
|
||||||
serviceCollection.AddSingleton(gameInteropProvider);
|
|
||||||
serviceCollection.AddSingleton(addonLifecycle);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<LiteDatabase>(_ =>
|
_sqliteConnectionString =
|
||||||
new LiteDatabase(new ConnectionString
|
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), DatabaseFileName)}";
|
||||||
{
|
serviceCollection.AddDbContext<RetainerTrackContext>(o => o
|
||||||
Filename = Path.Join(pluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"),
|
.UseSqlite(_sqliteConnectionString)
|
||||||
Connection = ConnectionType.Direct,
|
.UseModel(RetainerTrackContextModel.Instance));
|
||||||
Upgrade = true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<PersistenceContext>();
|
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
serviceCollection.AddSingleton<MarketBoardOfferingsHandler>();
|
|
||||||
serviceCollection.AddSingleton<PartyHandler>();
|
|
||||||
serviceCollection.AddSingleton<MarketBoardUiHandler>();
|
|
||||||
serviceCollection.AddSingleton<GameHooks>();
|
|
||||||
|
|
||||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
|
||||||
|
|
||||||
LiteDatabase liteDatabase = _serviceProvider.GetRequiredService<LiteDatabase>();
|
RunMigrations(_serviceProvider);
|
||||||
liteDatabase.GetCollection<Retainer>()
|
|
||||||
.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>();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private static void RunMigrations(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_serviceProvider?.Dispose();
|
ImportLegacyData.PluginInterface = serviceProvider.GetRequiredService<DalamudPluginInterface>();
|
||||||
}
|
|
||||||
|
using var scope = serviceProvider.CreateScope();
|
||||||
|
using var dbContext = scope.ServiceProvider.GetRequiredService<RetainerTrackContext>();
|
||||||
|
dbContext.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net7.0-windows7.0/win-x64": {}
|
"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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user