Db: Migrate markers to db
This commit is contained in:
parent
57a5be7938
commit
e624c5b628
@ -28,8 +28,6 @@ namespace Pal.Client.Configuration
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
|
||||||
Migrate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
private string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
||||||
@ -54,7 +52,7 @@ namespace Pal.Client.Configuration
|
|||||||
|
|
||||||
#pragma warning disable CS0612
|
#pragma warning disable CS0612
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
private void Migrate()
|
public void Migrate()
|
||||||
{
|
{
|
||||||
if (_pluginInterface.ConfigFile.Exists)
|
if (_pluginInterface.ConfigFile.Exists)
|
||||||
{
|
{
|
||||||
|
@ -18,18 +18,18 @@ namespace Pal.Client.Configuration.Legacy
|
|||||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true };
|
private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true };
|
||||||
private const int CurrentVersion = 4;
|
private const int CurrentVersion = 4;
|
||||||
|
|
||||||
private static string _pluginConfigDirectory;
|
private static string _pluginConfigDirectory = null!;
|
||||||
private static EMode _mode = EMode.Online; // might not be true, but this is 'less strict filtering' for migrations
|
private static readonly EMode _mode = EMode.Online; // might not be true, but this is 'less strict filtering' for migrations
|
||||||
|
|
||||||
internal static void SetContextProperties(string pluginConfigDirectory)
|
internal static void SetContextProperties(string pluginConfigDirectory)
|
||||||
{
|
{
|
||||||
_pluginConfigDirectory = pluginConfigDirectory;
|
_pluginConfigDirectory = pluginConfigDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint TerritoryType { get; set; }
|
public ushort TerritoryType { get; set; }
|
||||||
public ConcurrentBag<JsonMarker> Markers { get; set; } = new();
|
public ConcurrentBag<JsonMarker> Markers { get; set; } = new();
|
||||||
|
|
||||||
public JsonFloorState(uint territoryType)
|
public JsonFloorState(ushort territoryType)
|
||||||
{
|
{
|
||||||
TerritoryType = territoryType;
|
TerritoryType = territoryType;
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ namespace Pal.Client.Configuration.Legacy
|
|||||||
Markers = new ConcurrentBag<JsonMarker>(Markers.Where(x => x.Seen || !x.WasImported || x.Imports.Count > 0));
|
Markers = new ConcurrentBag<JsonMarker>(Markers.Where(x => x.Seen || !x.WasImported || x.Imports.Count > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonFloorState? Load(uint territoryType)
|
public static JsonFloorState? Load(ushort territoryType)
|
||||||
{
|
{
|
||||||
string path = GetSaveLocation(territoryType);
|
string path = GetSaveLocation(territoryType);
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
@ -136,6 +136,10 @@ namespace Pal.Client.Configuration.Legacy
|
|||||||
{
|
{
|
||||||
foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues())
|
foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues())
|
||||||
{
|
{
|
||||||
|
// we never had markers for eureka orthos, so don't bother
|
||||||
|
if (territory > ETerritoryType.HeavenOnHigh_91_100)
|
||||||
|
break;
|
||||||
|
|
||||||
JsonFloorState? localState = Load((ushort)territory);
|
JsonFloorState? localState = Load((ushort)territory);
|
||||||
if (localState != null)
|
if (localState != null)
|
||||||
action(localState);
|
action(localState);
|
||||||
|
141
Pal.Client/Configuration/Legacy/JsonMigration.cs
Normal file
141
Pal.Client/Configuration/Legacy/JsonMigration.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Pal.Client.Database;
|
||||||
|
using Pal.Common;
|
||||||
|
|
||||||
|
namespace Pal.Client.Configuration.Legacy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Imports legacy territoryType.json files into the database if it exists, and no markers for that territory exist.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class JsonMigration
|
||||||
|
{
|
||||||
|
private readonly ILogger<JsonMigration> _logger;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
|
public JsonMigration(ILogger<JsonMigration> logger, IServiceScopeFactory serviceScopeFactory,
|
||||||
|
DalamudPluginInterface pluginInterface)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS0612
|
||||||
|
public async Task MigrateAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
List<JsonFloorState> floorsToMigrate = new();
|
||||||
|
JsonFloorState.ForEach(floorsToMigrate.Add);
|
||||||
|
|
||||||
|
if (floorsToMigrate.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Found no floors to migrate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
await using var scope = _serviceScopeFactory.CreateAsyncScope();
|
||||||
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||||
|
|
||||||
|
var fileStream = new FileStream(
|
||||||
|
Path.Join(_pluginInterface.GetPluginConfigDirectory(),
|
||||||
|
$"territory-backup-{DateTime.Now:yyyyMMdd-HHmmss}.zip"),
|
||||||
|
FileMode.CreateNew);
|
||||||
|
using (var backup = new ZipArchive(fileStream, ZipArchiveMode.Create, false))
|
||||||
|
{
|
||||||
|
IReadOnlyDictionary<Guid, ImportHistory> imports =
|
||||||
|
await dbContext.Imports.ToDictionaryAsync(import => import.Id, cancellationToken);
|
||||||
|
|
||||||
|
foreach (var floorToMigrate in floorsToMigrate)
|
||||||
|
{
|
||||||
|
backup.CreateEntryFromFile(floorToMigrate.GetSaveLocation(),
|
||||||
|
Path.GetFileName(floorToMigrate.GetSaveLocation()), CompressionLevel.SmallestSize);
|
||||||
|
await MigrateFloor(dbContext, floorToMigrate, imports, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Removing {Count} old json files", floorsToMigrate.Count);
|
||||||
|
foreach (var floorToMigrate in floorsToMigrate)
|
||||||
|
File.Delete(floorToMigrate.GetSaveLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>Whether to archive this file once complete</returns>
|
||||||
|
private async Task MigrateFloor(
|
||||||
|
PalClientContext dbContext,
|
||||||
|
JsonFloorState floorToMigrate,
|
||||||
|
IReadOnlyDictionary<Guid, ImportHistory> imports,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var logScope = _logger.BeginScope($"Import {(ETerritoryType)floorToMigrate.TerritoryType}");
|
||||||
|
if (floorToMigrate.Markers.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Skipping migration, floor has no markers");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await dbContext.Locations.AnyAsync(o => o.TerritoryType == floorToMigrate.TerritoryType,
|
||||||
|
cancellationToken))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Skipping migration, floor already has locations in the database");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Starting migration of {Count} locations", floorToMigrate.Markers.Count);
|
||||||
|
List<ClientLocation> clientLocations = floorToMigrate.Markers
|
||||||
|
.Where(o => o.Type == JsonMarker.EType.Trap || o.Type == JsonMarker.EType.Hoard)
|
||||||
|
.Select(o =>
|
||||||
|
{
|
||||||
|
var clientLocation = new ClientLocation
|
||||||
|
{
|
||||||
|
TerritoryType = floorToMigrate.TerritoryType,
|
||||||
|
Type = MapJsonType(o.Type),
|
||||||
|
X = o.Position.X,
|
||||||
|
Y = o.Position.Y,
|
||||||
|
Z = o.Position.Z,
|
||||||
|
Seen = o.Seen,
|
||||||
|
|
||||||
|
// the SelectMany is misleading here, each import has either 0 or 1 associated db entry with that id
|
||||||
|
ImportedBy = o.Imports
|
||||||
|
.Select(importId =>
|
||||||
|
imports.TryGetValue(importId, out ImportHistory? import) ? import : null)
|
||||||
|
.Where(import => import != null)
|
||||||
|
.Cast<ImportHistory>()
|
||||||
|
.Distinct()
|
||||||
|
.ToList(),
|
||||||
|
};
|
||||||
|
|
||||||
|
clientLocation.RemoteEncounters = o.RemoteSeenOn
|
||||||
|
.Select(accountId => new RemoteEncounter(clientLocation, accountId))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return clientLocation;
|
||||||
|
}).ToList();
|
||||||
|
await dbContext.Locations.AddRangeAsync(clientLocations, cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("Migrated {Count} locations", clientLocations.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientLocation.EType MapJsonType(JsonMarker.EType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
JsonMarker.EType.Trap => ClientLocation.EType.Trap,
|
||||||
|
JsonMarker.EType.Hoard => ClientLocation.EType.Hoard,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0612
|
||||||
|
}
|
||||||
|
}
|
39
Pal.Client/Database/ClientLocation.cs
Normal file
39
Pal.Client/Database/ClientLocation.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Pal.Client.Database
|
||||||
|
{
|
||||||
|
internal sealed class ClientLocation
|
||||||
|
{
|
||||||
|
[Key] public int LocalId { get; set; }
|
||||||
|
public ushort TerritoryType { get; set; }
|
||||||
|
public EType Type { get; set; }
|
||||||
|
public float X { get; set; }
|
||||||
|
public float Y { get; set; }
|
||||||
|
public float Z { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we have encountered the trap/coffer at this location in-game.
|
||||||
|
/// </summary>
|
||||||
|
public bool Seen { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which account ids this marker was seen. This is a list merely to support different remote endpoints
|
||||||
|
/// (where each server would assign you a different id).
|
||||||
|
/// </summary>
|
||||||
|
public List<RemoteEncounter> RemoteEncounters { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To keep track of which markers were imported through a downloaded file, we save the associated import-id.
|
||||||
|
///
|
||||||
|
/// Importing another file for the same remote server will remove the old import-id, and add the new import-id here.
|
||||||
|
/// </summary>
|
||||||
|
public List<ImportHistory> ImportedBy { get; set; } = new();
|
||||||
|
|
||||||
|
public enum EType
|
||||||
|
{
|
||||||
|
Trap = 1,
|
||||||
|
Hoard = 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Pal.Client.Database
|
namespace Pal.Client.Database
|
||||||
{
|
{
|
||||||
@ -8,5 +9,7 @@ namespace Pal.Client.Database
|
|||||||
public string? RemoteUrl { get; set; }
|
public string? RemoteUrl { get; set; }
|
||||||
public DateTime ExportedAt { get; set; }
|
public DateTime ExportedAt { get; set; }
|
||||||
public DateTime ImportedAt { get; set; }
|
public DateTime ImportedAt { get; set; }
|
||||||
|
|
||||||
|
public List<ClientLocation> ImportedLocations { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
136
Pal.Client/Database/Migrations/20230217160342_AddClientLocations.Designer.cs
generated
Normal file
136
Pal.Client/Database/Migrations/20230217160342_AddClientLocations.Designer.cs
generated
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Pal.Client.Database;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Pal.Client.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PalClientContext))]
|
||||||
|
[Migration("20230217160342_AddClientLocations")]
|
||||||
|
partial class AddClientLocations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ImportedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ImportedLocationsLocalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ImportedById", "ImportedLocationsLocalId");
|
||||||
|
|
||||||
|
b.HasIndex("ImportedLocationsLocalId");
|
||||||
|
|
||||||
|
b.ToTable("LocationImports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.ClientLocation", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LocalId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Seen")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ushort>("TerritoryType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<float>("X")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Y")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Z")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("LocalId");
|
||||||
|
|
||||||
|
b.ToTable("Locations");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.ImportHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExportedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ImportedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RemoteUrl")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Imports");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AccountId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(13)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ClientLocationId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClientLocationId");
|
||||||
|
|
||||||
|
b.ToTable("RemoteEncounters");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Pal.Client.Database.ImportHistory", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ImportedById")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Pal.Client.Database.ClientLocation", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ImportedLocationsLocalId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Pal.Client.Database.ClientLocation", "ClientLocation")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientLocationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("ClientLocation");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Pal.Client.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddClientLocations : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Locations",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LocalId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
TerritoryType = table.Column<ushort>(type: "INTEGER", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
X = table.Column<float>(type: "REAL", nullable: false),
|
||||||
|
Y = table.Column<float>(type: "REAL", nullable: false),
|
||||||
|
Z = table.Column<float>(type: "REAL", nullable: false),
|
||||||
|
Seen = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Locations", x => x.LocalId);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LocationImports",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ImportedById = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
ImportedLocationsLocalId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LocationImports", x => new { x.ImportedById, x.ImportedLocationsLocalId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocationImports_Imports_ImportedById",
|
||||||
|
column: x => x.ImportedById,
|
||||||
|
principalTable: "Imports",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocationImports_Locations_ImportedLocationsLocalId",
|
||||||
|
column: x => x.ImportedLocationsLocalId,
|
||||||
|
principalTable: "Locations",
|
||||||
|
principalColumn: "LocalId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RemoteEncounters",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
ClientLocationId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
AccountId = table.Column<string>(type: "TEXT", maxLength: 13, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RemoteEncounters", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_RemoteEncounters_Locations_ClientLocationId",
|
||||||
|
column: x => x.ClientLocationId,
|
||||||
|
principalTable: "Locations",
|
||||||
|
principalColumn: "LocalId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LocationImports_ImportedLocationsLocalId",
|
||||||
|
table: "LocationImports",
|
||||||
|
column: "ImportedLocationsLocalId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_RemoteEncounters_ClientLocationId",
|
||||||
|
table: "RemoteEncounters",
|
||||||
|
column: "ClientLocationId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LocationImports");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RemoteEncounters");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Locations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,50 @@ namespace Pal.Client.Database.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ImportedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ImportedLocationsLocalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ImportedById", "ImportedLocationsLocalId");
|
||||||
|
|
||||||
|
b.HasIndex("ImportedLocationsLocalId");
|
||||||
|
|
||||||
|
b.ToTable("LocationImports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.ClientLocation", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("LocalId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Seen")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ushort>("TerritoryType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<float>("X")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Y")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<float>("Z")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("LocalId");
|
||||||
|
|
||||||
|
b.ToTable("Locations", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Pal.Client.Database.ImportHistory", b =>
|
modelBuilder.Entity("Pal.Client.Database.ImportHistory", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -34,7 +78,54 @@ namespace Pal.Client.Database.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Imports");
|
b.ToTable("Imports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AccountId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(13)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ClientLocationId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClientLocationId");
|
||||||
|
|
||||||
|
b.ToTable("RemoteEncounters", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ClientLocationImportHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Pal.Client.Database.ImportHistory", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ImportedById")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Pal.Client.Database.ClientLocation", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ImportedLocationsLocalId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Pal.Client.Database.ClientLocation", "ClientLocation")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientLocationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("ClientLocation");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,21 @@ namespace Pal.Client.Database
|
|||||||
{
|
{
|
||||||
internal class PalClientContext : DbContext
|
internal class PalClientContext : DbContext
|
||||||
{
|
{
|
||||||
|
public DbSet<ClientLocation> Locations { get; set; } = null!;
|
||||||
public DbSet<ImportHistory> Imports { get; set; } = null!;
|
public DbSet<ImportHistory> Imports { get; set; } = null!;
|
||||||
|
public DbSet<RemoteEncounter> RemoteEncounters { get; set; } = null!;
|
||||||
|
|
||||||
public PalClientContext(DbContextOptions<PalClientContext> options)
|
public PalClientContext(DbContextOptions<PalClientContext> options)
|
||||||
: base(options)
|
: base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<ClientLocation>()
|
||||||
|
.HasMany(o => o.ImportedBy)
|
||||||
|
.WithMany(o => o.ImportedLocations)
|
||||||
|
.UsingEntity(o => o.ToTable("LocationImports"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
Pal.Client/Database/RemoteEncounter.cs
Normal file
41
Pal.Client/Database/RemoteEncounter.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Net;
|
||||||
|
|
||||||
|
namespace Pal.Client.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// To avoid sending too many requests to the server, we cache which locations have been seen
|
||||||
|
/// locally. These never expire, and locations which have been seen with a specific account
|
||||||
|
/// are never sent to the server again.
|
||||||
|
///
|
||||||
|
/// To be marked as seen, it needs to be essentially processed by <see cref="RemoteApi.MarkAsSeen"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RemoteEncounter
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; private set; }
|
||||||
|
|
||||||
|
public int ClientLocationId { get; private set; }
|
||||||
|
public ClientLocation ClientLocation { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Partial account id. This is partially unique - however problems would (in theory)
|
||||||
|
/// only occur once you have two account-ids where the first 13 characters are equal.
|
||||||
|
/// </summary>
|
||||||
|
[MaxLength(13)]
|
||||||
|
public string AccountId { get; private set; }
|
||||||
|
|
||||||
|
private RemoteEncounter(int clientLocationId, string accountId)
|
||||||
|
{
|
||||||
|
ClientLocationId = clientLocationId;
|
||||||
|
AccountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteEncounter(ClientLocation clientLocation, string accountId)
|
||||||
|
{
|
||||||
|
ClientLocation = clientLocation;
|
||||||
|
AccountId = accountId.ToPartialId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,7 @@ namespace Pal.Client
|
|||||||
_sqliteConnectionString =
|
_sqliteConnectionString =
|
||||||
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
||||||
services.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
services.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
||||||
|
services.AddTransient<JsonMigration>();
|
||||||
|
|
||||||
// plugin-specific
|
// plugin-specific
|
||||||
services.AddSingleton<Plugin>();
|
services.AddSingleton<Plugin>();
|
||||||
@ -175,13 +176,19 @@ namespace Pal.Client
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Loading database & running migrations");
|
_logger.LogInformation("Loading database & running migrations");
|
||||||
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||||
await dbContext.Database.MigrateAsync();
|
await dbContext.Database.MigrateAsync(token);
|
||||||
|
|
||||||
_logger.LogInformation("Completed database migrations");
|
_logger.LogInformation("Completed database migrations");
|
||||||
}
|
}
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// v1 migration: config migration for import history, json migration for markers
|
||||||
|
_serviceProvider.GetRequiredService<ConfigurationManager>().Migrate();
|
||||||
|
await _serviceProvider.GetRequiredService<JsonMigration>().MigrateAsync(token);
|
||||||
|
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// windows that have logic to open on startup
|
// windows that have logic to open on startup
|
||||||
_serviceProvider.GetRequiredService<AgreementWindow>();
|
_serviceProvider.GetRequiredService<AgreementWindow>();
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
<PathMap>$(SolutionDir)=X:\</PathMap>
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
<PathMap>$(SolutionDir)=X:\</PathMap>
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user