Db: Migrate markers to db
parent
57a5be7938
commit
e624c5b628
|
@ -28,8 +28,6 @@ namespace Pal.Client.Configuration
|
|||
_logger = logger;
|
||||
_pluginInterface = pluginInterface;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
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 CS0618
|
||||
private void Migrate()
|
||||
public void Migrate()
|
||||
{
|
||||
if (_pluginInterface.ConfigFile.Exists)
|
||||
{
|
||||
|
|
|
@ -18,18 +18,18 @@ namespace Pal.Client.Configuration.Legacy
|
|||
private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true };
|
||||
private const int CurrentVersion = 4;
|
||||
|
||||
private static string _pluginConfigDirectory;
|
||||
private static EMode _mode = EMode.Online; // might not be true, but this is 'less strict filtering' for migrations
|
||||
private static string _pluginConfigDirectory = null!;
|
||||
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)
|
||||
{
|
||||
_pluginConfigDirectory = pluginConfigDirectory;
|
||||
}
|
||||
|
||||
public uint TerritoryType { get; set; }
|
||||
public ushort TerritoryType { get; set; }
|
||||
public ConcurrentBag<JsonMarker> Markers { get; set; } = new();
|
||||
|
||||
public JsonFloorState(uint territoryType)
|
||||
public JsonFloorState(ushort 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));
|
||||
}
|
||||
|
||||
public static JsonFloorState? Load(uint territoryType)
|
||||
public static JsonFloorState? Load(ushort territoryType)
|
||||
{
|
||||
string path = GetSaveLocation(territoryType);
|
||||
if (!File.Exists(path))
|
||||
|
@ -136,6 +136,10 @@ namespace Pal.Client.Configuration.Legacy
|
|||
{
|
||||
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);
|
||||
if (localState != null)
|
||||
action(localState);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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.Collections.Generic;
|
||||
|
||||
namespace Pal.Client.Database
|
||||
{
|
||||
|
@ -8,5 +9,7 @@ namespace Pal.Client.Database
|
|||
public string? RemoteUrl { get; set; }
|
||||
public DateTime ExportedAt { 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
|
||||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
@ -34,7 +78,54 @@ namespace Pal.Client.Database.Migrations
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -4,11 +4,21 @@ namespace Pal.Client.Database
|
|||
{
|
||||
internal class PalClientContext : DbContext
|
||||
{
|
||||
public DbSet<ClientLocation> Locations { get; set; } = null!;
|
||||
public DbSet<ImportHistory> Imports { get; set; } = null!;
|
||||
public DbSet<RemoteEncounter> RemoteEncounters { get; set; } = null!;
|
||||
|
||||
public PalClientContext(DbContextOptions<PalClientContext> 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
||||
services.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
||||
services.AddTransient<JsonMigration>();
|
||||
|
||||
// plugin-specific
|
||||
services.AddSingleton<Plugin>();
|
||||
|
@ -175,13 +176,19 @@ namespace Pal.Client
|
|||
{
|
||||
_logger.LogInformation("Loading database & running migrations");
|
||||
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||
await dbContext.Database.MigrateAsync();
|
||||
await dbContext.Database.MigrateAsync(token);
|
||||
|
||||
_logger.LogInformation("Completed database migrations");
|
||||
}
|
||||
|
||||
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
|
||||
_serviceProvider.GetRequiredService<AgreementWindow>();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugType>portable</DebugType>
|
||||
<PathMap>$(SolutionDir)=X:\</PathMap>
|
||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DebugType>portable</DebugType>
|
||||
<PathMap>$(SolutionDir)=X:\</PathMap>
|
||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue